/*******************************************************************************
 * Copyright (c) 2007 Exadel, Inc. and Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is made available under the terms of the
 * Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Exadel, Inc. and Red Hat, Inc. - initial API and implementation
 ******************************************************************************/ 
package org.jboss.tools.common.text.ext.util;

import java.io.InputStream;
import java.io.Reader;

import org.eclipse.jface.text.rules.ICharacterScanner;
import org.eclipse.jface.text.rules.IToken;

/**
 * @author Jeremy
 *
 */
public class CSSTextScanner extends TextScanner {
	public static final String CSS_CLASS_NAME = "___css_class_name"; //$NON-NLS-1$
	public static final String CSS_CLASS_NAME_SEPARATOR = "___css_class_name_separator"; //$NON-NLS-1$
	public static final String CSS_CLASS_BODY = "___css_class_body"; //$NON-NLS-1$
	public static final String CSS_CLASS_COMMENT = "___css_class_comment"; //$NON-NLS-1$
	
	private static final int STATE_START			= 0;
	private static final int STATE_NAME				= 1;
	private static final int STATE_NAME_SEPARATOR	= 2;
	private static final int STATE_BODY				= 3;
	private static final int STATE_COMMENT			= 4;
	private static final int STATE_END				= 5;

	private int state;
	private int savedForCommentState;
	
	/**
	 * @param stream
	 */
	public CSSTextScanner(InputStream stream) {
		super(stream);
		this.state = STATE_START;
	}
	/**
	 * @param reader
	 */
	public CSSTextScanner(Reader reader) {
		super(reader);
		this.state = STATE_START;
	}

	private void setState(int newState) {
		if (this.state != STATE_COMMENT)
			this.savedForCommentState = this.state;
		this.state = newState;
	}
	
	/* (non-Javadoc)
	 * @see org.jboss.tools.jsf.text.ext.util.TextScanner#nextToken()
	 */
	public IToken nextToken() {
		offset += length;
		switch (state) {
			case STATE_START:
			case STATE_NAME_SEPARATOR:
				return nextNameSeparatorToken();
			case STATE_NAME:
				return nextNameToken();
			case STATE_BODY:
				return nextBodyToken();
			case STATE_COMMENT:
				return nextCommentToken();
		}
		return nextEndToken();
	}
	
	private IToken nextNameToken() {
		// Check first one char in the stream
		int ch = read();
		if (ch == ICharacterScanner.EOF) {
			return getToken(CSS_CLASS_NAME);
		}
		if (!NMTOKEN_DETECTOR.isWordStart((char)ch)) {
			// Emulate END of text
			clearText();
			return getToken(CSS_CLASS_NAME);
		}

		ch = read();
		int readsCount = 0;
		while (ch != ICharacterScanner.EOF) {
			if (!NMTOKEN_DETECTOR.isWordPart((char)ch)) {
				unread();
				state = STATE_NAME_SEPARATOR;
				return getToken(CSS_CLASS_NAME);
			}
			readsCount++;
			ch = read();
		}
		return getToken(CSS_CLASS_NAME);
	}

	private IToken nextNameSeparatorToken() {
		int count = skipWhitespaceToken();
		if (count > 0) {
			state = STATE_NAME_SEPARATOR;
			return getToken(null);
		}
		int ch = read();
		int readsCount = 0;
		while (ch != ICharacterScanner.EOF) {
			if (NMTOKEN_DETECTOR.isWordStart((char)ch)) {
				unread();
				state = STATE_NAME;
				return (readsCount > 0 ? getToken(CSS_CLASS_NAME_SEPARATOR) : nextNameToken());
			}
			if (ch == '{') {
				unread();
				state = STATE_BODY;
				return (readsCount > 0 ? getToken(CSS_CLASS_NAME_SEPARATOR) : nextBodyToken());
			}
			if (ch == '/') {
				ch = read();
				if (ch == ICharacterScanner.EOF)  {
					break; 
				}
				if (ch == '*') {
					// Comment openning
					unread(); // Unread '*'-char
					unread(); // Unread '/'-char
					setState(STATE_COMMENT);
					if (readsCount > 0) {
						return getToken(CSS_CLASS_NAME_SEPARATOR); 
					} else {
						return nextCommentToken();
					}
				}
				// Broken file ??? Emulate END of text
				clearText();
				return getToken(CSS_CLASS_NAME_SEPARATOR);
			}
			readsCount++;
			ch = read();
		}
		state = STATE_END;
		return getToken(CSS_CLASS_NAME_SEPARATOR);
	}

	private IToken nextBodyToken() {
		int level = 0;
		int ch = read();
		while (ch != ICharacterScanner.EOF) {
			if (ch == '{') {
				level++;
			} else if (ch == '}') {
				level--;
				if (level <= 0) {
					state = STATE_START;
					return getToken(CSS_CLASS_BODY);
				}
			}
			if (ch == '/') {
				ch = read();
				if (ch == ICharacterScanner.EOF) {
					break; 
				}
				if (ch == '*') {
					// Comment openning
					unread(); // Unread '*'-char
					unread(); // Unread '/'-char
					setState(STATE_COMMENT);
					return getToken(CSS_CLASS_BODY);
				}
				// Broken file ??? Emulate END of text
				clearText();
				return getToken(CSS_CLASS_BODY);
			}
			ch = read();
		}
		state = STATE_END;
		return getToken(CSS_CLASS_BODY);
	}
	
	private IToken nextCommentToken() {
		int ch = read();
		while (ch != ICharacterScanner.EOF) {
			if (ch == '*') {
				ch = read();
				if (ch == ICharacterScanner.EOF) break; 
				if (ch == '/') {
					// Comment closing
					setState(savedForCommentState);
					return getToken(CSS_CLASS_COMMENT);
				}
				unread(); // Unread last char 
			}
			ch = read();
		}
		setState(savedForCommentState);
		return getToken(CSS_CLASS_COMMENT);
	}
	
	private IToken nextEndToken () {
		state = STATE_END;
		// Emulate END of text
		clearText();
		return getToken(null);
	}

}
