package com.quantum.editors;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.rules.EndOfLineRule;
import org.eclipse.jface.text.rules.ICharacterScanner;
import org.eclipse.jface.text.rules.IPredicateRule;
import org.eclipse.jface.text.rules.IRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.IWhitespaceDetector;
import org.eclipse.jface.text.rules.MultiLineRule;
import org.eclipse.jface.text.rules.RuleBasedPartitionScanner;
import org.eclipse.jface.text.rules.SingleLineRule;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.jface.text.rules.WhitespaceRule;

public class SQLPartitionScanner extends RuleBasedPartitionScanner {
	public final static String SQL_COMMENT = "__sql_comment"; //$NON-NLS-1$
	public final static String SQL_IDENTIFIER = "__sql_word"; //$NON-NLS-1$
	public final static String SQL_STRING = "__sql_string"; //$NON-NLS-1$
	public final static String SQL_KEYWORD = "__sql_keyword"; //$NON-NLS-1$
	public final static String SQL_SYMBOL = "__sql_symbol"; //$NON-NLS-1$
	public final static String SQL_SEPARATOR = "__sql_separator"; //$NON-NLS-1$
	public final static String SQL_NUMERIC = "__sql_numeric"; //$NON-NLS-1$
	
	private final static String[] KEYWORDS = {
		"ALTER", //$NON-NLS-1$
		"AND", //$NON-NLS-1$
		"BY", //$NON-NLS-1$
		"COLUMN", //$NON-NLS-1$
		"CREATE", //$NON-NLS-1$
		"DELETE", //$NON-NLS-1$
		"DROP", //$NON-NLS-1$
		"FROM", //$NON-NLS-1$
		"GROUP",  //$NON-NLS-1$
		"INSERT", //$NON-NLS-1$
		"INTO", //$NON-NLS-1$
		"NOT", //$NON-NLS-1$
		"NULL", //$NON-NLS-1$
		"OR", //$NON-NLS-1$
		"ORDER", //$NON-NLS-1$
		"SELECT", //$NON-NLS-1$
		"SEQUENCE", //$NON-NLS-1$
		"SET",  //$NON-NLS-1$
		"TABLE", //$NON-NLS-1$
		"UNION", //$NON-NLS-1$
		"UNIQUE", //$NON-NLS-1$
		"UPDATE", //$NON-NLS-1$
		"USING", //$NON-NLS-1$
		"VALUES", //$NON-NLS-1$
		"VIEW", //$NON-NLS-1$
		"WHEN", //$NON-NLS-1$
		"WHERE" //$NON-NLS-1$
	};

	public SQLPartitionScanner() {

		List rules = new ArrayList();

		IToken comment = new Token(SQL_COMMENT);
		IToken string = new Token(SQL_STRING);
		IToken identifier = new Token(SQL_IDENTIFIER);
		IToken keyword = new Token(SQL_KEYWORD);
		IToken separator = new Token(SQL_SEPARATOR);
		IToken symbol = new Token(SQL_SYMBOL);
		IToken whitespace = new Token(IDocument.DEFAULT_CONTENT_TYPE);
		IToken numeric = new Token(SQL_NUMERIC);
		
		rules.add(new PredicateRuleAdapter(new WhitespaceRule(new WhitespaceDetector()), whitespace));
		rules.add(new MultiLineRule("/*", "*/", comment)); //$NON-NLS-1$ //$NON-NLS-2$
		rules.add(new EndOfLineRule("--", comment)); //$NON-NLS-1$
		rules.add(new SingleLineRule("'", "'", string)); //$NON-NLS-1$ //$NON-NLS-2$
		rules.add(new PredicateRuleAdapter(new SQLNumberRule(numeric), numeric));
		SQLWordRule wordRule = new SQLWordRule(identifier);
		for (int i = 0; i < KEYWORDS.length; i++) {
			wordRule.addKeyword(KEYWORDS[i], keyword);
		}
		rules.add(new PredicateRuleAdapter(wordRule, keyword));
		rules.add(new PredicateRuleAdapter(wordRule, identifier));
		rules.add(new PredicateRuleAdapter(new SQLSeparatorRule(separator), separator));
		rules.add(new PredicateRuleAdapter(new SymbolRule(symbol), symbol));
		
		IPredicateRule[] result= new IPredicateRule[rules.size()];
		rules.toArray(result);
		setPredicateRules(result);
	}
}

class PredicateRuleAdapter implements IPredicateRule {
	IRule rule;
	IToken token;
	public PredicateRuleAdapter(IRule rule, IToken token) {
		this.rule = rule;
		this.token = token;
	}
	
	public IToken evaluate(ICharacterScanner scanner, boolean resume) {
		return rule.evaluate(scanner);
	}

	public IToken getSuccessToken() {
		return token;
	}

	public IToken evaluate(ICharacterScanner scanner) {
		return rule.evaluate(scanner);
	}

}

class SQLSeparatorRule implements IRule {
	IToken token;
	public SQLSeparatorRule(IToken token) {
		this.token = token;
	}
	public IToken evaluate(ICharacterScanner scanner) {
		char c = (char) scanner.read();
		if (c == ';') {
			return token;
		}
		scanner.unread();
		return Token.UNDEFINED;
	}

}

class SymbolRule implements IRule {
	IToken token;
	public SymbolRule(IToken token) {
		this.token = token;
	}
	public IToken evaluate(ICharacterScanner scanner) {
		int val = scanner.read();
		if (val != ICharacterScanner.EOF) {
			char c = (char) val;
			if (!Character.isWhitespace(c) && !Character.isLetterOrDigit(c) && c != '_') {
				return token;
			}
		}
		scanner.unread();
		return Token.UNDEFINED;
	}

}

class WhitespaceDetector implements IWhitespaceDetector {

	public boolean isWhitespace(char c) {
		return Character.isWhitespace(c);
	}
}

class SQLNumberRule implements IRule {
	private IToken token;
	
	public SQLNumberRule(IToken token) {
		this.token = token;
	}

	public IToken evaluate(ICharacterScanner scanner) {
		char c = (char) scanner.read();
		if (Character.isDigit(c)) {
			// postive numbers and zero
			do {
				c= (char) scanner.read();
			} while (Character.isDigit(c) || c == '.');
			scanner.unread();
			return token;
		} else if (c == '-') {
			// negative numbers
			c = (char) scanner.read();
			if (Character.isDigit(c)) {
				do {
					c= (char) scanner.read();
				} while (Character.isDigit(c) || c == '.');
				scanner.unread();
				return token;
			} else {
				scanner.unread();
				scanner.unread();
				return Token.UNDEFINED;
			}
		} else {
			scanner.unread();
			return Token.UNDEFINED;
		}
	}
}

class SQLWordRule implements IRule {
	private IToken token;
	private HashMap keywords = new HashMap();
	
	public SQLWordRule(IToken token) {
		this.token = token;
	}

	public void addKeyword(String word, IToken token) {
		keywords.put(word.toUpperCase(), token);
	}

	public IToken evaluate(ICharacterScanner scanner) {
		char c = (char) scanner.read();
		if (Character.isLetter(c) || c == '_') {
			StringBuffer value = new StringBuffer();
			do {
				value.append(c);
				c= (char) scanner.read();
			} while (Character.isLetterOrDigit(c) || c == '_');
			scanner.unread();
			IToken retVal = (IToken) keywords.get(value.toString().toUpperCase());
			if (retVal != null) {
				return retVal;
			} else {
				return token;
			}
		} else {
			scanner.unread();
			return Token.UNDEFINED;
		}
	}
}