/*******************************************************************************
 * Copyright (c) 2006 Oracle Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are 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:
 *     Oracle Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.bpel.ui.editors.xpath;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.eclipse.bpel.ui.editors.xpath.rules.AxisRule;
import org.eclipse.bpel.ui.editors.xpath.rules.FloatRule;
import org.eclipse.bpel.ui.editors.xpath.rules.FunctionRule;
import org.eclipse.bpel.ui.editors.xpath.rules.ITokenContext;
import org.eclipse.bpel.ui.editors.xpath.rules.SingleCharRule;
import org.eclipse.bpel.ui.editors.xpath.rules.SingleOperatorRule;
import org.eclipse.bpel.ui.editors.xpath.rules.StringRule;
import org.eclipse.bpel.ui.editors.xpath.rules.WordRule;
import org.eclipse.bpel.ui.preferences.PreferenceConstants;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.rules.BufferedRuleBasedScanner;
import org.eclipse.jface.text.rules.ICharacterScanner;
import org.eclipse.jface.text.rules.IRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.IWordDetector;
import org.eclipse.jface.text.rules.SingleLineRule;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.jface.text.rules.WhitespaceRule;
import org.eclipse.swt.SWT;

/**
 * @author Michal Chmielewski (michal.chmielewski@oracle.com)
 * @date Oct 25, 2006
 *
 */


@SuppressWarnings("nls")

public class XPathSourceScanner extends BufferedRuleBasedScanner {
	
	XPathWhitespaceDetector fWhiteSpaceDetector = new XPathWhitespaceDetector ();
	
	IWordDetector fNCNameDetector = new XPathWordDetector.NCNameWordDetector ();
	
	IWordDetector fWordDetector = new XPathWordDetector();
	
	IWordDetector fVariableNameDetector = new XPathWordDetector.VariableDetector();
	
	IWordDetector fQNameDetector = new XPathWordDetector.QNameDetector ();
	
	
	LinkedList<IToken> tokenWindow = new LinkedList<IToken>();
	
	/**
	 * The scanner for the XPath source editor, which provides
	 * syntax coloring based on the default damager and repairer.
	 * 
	 * @param manager
	 */
	
	@SuppressWarnings("nls")
	public XPathSourceScanner( ColorManager manager ) {

		IToken defToken = new Token ( 
					new TextAttribute (
						manager.getColor( PreferenceConstants.DEFAULT  )												
					) );
		
		final IToken operatorToken = new Token ( 
				new TextAttribute (
						manager.getColor( PreferenceConstants.OPERAND  ),
						null,
						SWT.BOLD						
				) );
		
		IToken number = new Token (
				new TextAttribute (
						manager.getColor( PreferenceConstants.NUMBER  ),
						null,
						SWT.BOLD						
				) );

					
		IToken string = new Token ( 
				new TextAttribute (  
						manager.getColor( PreferenceConstants.STRING ) ) ) ;
		
		
		IToken brackets = new Token (
				new TextAttribute (  
						manager.getColor( PreferenceConstants.BRACKET ),
						null,
						SWT.BOLD ) 						
				);


		IToken axis = new Token (
				new TextAttribute (  
						manager.getColor( PreferenceConstants.AXIS ),
						null,
						SWT.ITALIC ) 						
				);

		IToken pathSep = new Token (
					new TextAttribute (  
							manager.getColor( PreferenceConstants.PATH_SEPARATOR ),
							null,
							SWT.BOLD ) 						
					);					
		
		IToken functionsDefault = new Token (
				new TextAttribute (  
						manager.getColor( PreferenceConstants.FUNCTIONS_XPATH ),
						null,
						SWT.ITALIC ) 											
				);
		
		IToken functions = new Token (
				new TextAttribute (  
						manager.getColor( PreferenceConstants.FUNCTIONS_XPATH ),
						null,
						SWT.BOLD)
				) ;
		
		final IToken variableToken = new Token ( 
				new TextAttribute( 
						manager.getColor( PreferenceConstants.VARIABLES ),
						null,
						SWT.BOLD
				));
				
		IToken partToken = new Token ( 
				new TextAttribute( 
						manager.getColor( PreferenceConstants.VARIABLE_PART ),
						null,
						SWT.BOLD
				));
						
		
		// The list of rules for this scanner.
		List<IRule> rules = new ArrayList<IRule>(24);
		
				
		// Add rule for double quotes string
		rules.add( new SingleLineRule("\"", "\"", string , '\\') ); //$NON-NLS-1$ //$NON-NLS-2$
		// Add a rule for single quotes string
		rules.add( new SingleLineRule("'", "'", string , '\\') ); //$NON-NLS-1$ //$NON-NLS-2$
	
		// Add function calls ...
		
		// Add generic whitespace rule.
		rules.add( new WhitespaceRule(new XPathWhitespaceDetector()) );
		
		// numbers
		rules.add ( new FloatRule ( number )) ;

		WordRule wordRule;
		
		// variable rule
		wordRule = new WordRule ( fVariableNameDetector );
		wordRule.addWord ( WordRule.ANY , variableToken );
		rules.add (wordRule);
		
		// Variable part rule
		wordRule = new WordRule ( new XPathWordDetector.MessagePartDetector() );
		wordRule.addWord ( WordRule.ANY , partToken );
		wordRule.setTokenContextCheck( new TokenContext () {
			@Override
			public boolean checkSeenTokens(XPathSourceScanner scanner) {
				return (scanner.lastToken(0) == variableToken);
			}			
		});
		rules.add (wordRule);
		
		
		// Some operators.
		rules.add ( new SingleOperatorRule ( operatorToken, "+-*=|/<>" ) );
		
		// Operators of sorts ...
		rules.add ( new StringRule ( operatorToken, "!=") );
		rules.add ( new StringRule ( operatorToken, ">=") );
		rules.add ( new StringRule ( operatorToken, "<=") );
		rules.add ( new StringRule ( operatorToken, ">=") );
		
		rules.add ( new SingleCharRule ( brackets, "[]().@," ) );			
		
		rules.add ( new StringRule ( operatorToken, "//") );	
		
		rules.add ( new StringRule ( pathSep, "::") );	
		
		
		// rule for operators ...
		wordRule = new WordRule( new XPathWordDetector () );
		
		wordRule.addWord ("mod",operatorToken);
		wordRule.addWord ("div",operatorToken);
		wordRule.addWord ("and",operatorToken);
		wordRule.addWord ("or",operatorToken);
		
		wordRule.setTokenContextCheck( new TokenContext() {
			@Override
			public boolean checkSeenTokens(XPathSourceScanner scanner) {
				int idx = (scanner.lastToken(0) == Token.WHITESPACE ? 1 : 0);								
				return scanner.lastToken(idx) != operatorToken;
			}			
		});
		
		
		rules.add( wordRule );

		AxisRule axisRule = new AxisRule ( fNCNameDetector );
		axisRule.addWords(AXIS, axis);
		rules.add(axisRule);
		
				
		// The basic XPath functions
		FunctionRule functionRule = new FunctionRule ( fQNameDetector );
		functionRule.addWords(XPATH_FUNCTIONS,functionsDefault);
		rules.add(functionRule);
		
		// All other functions
		functionRule = new FunctionRule ( fQNameDetector );
		functionRule.addWord( WordRule.ANY,functions);
		rules.add(functionRule);
				
		
		wordRule = new WordRule( fWordDetector );
		wordRule.addWord ( WordRule.ANY, defToken );
		rules.add( wordRule );
		
		
		setDefaultReturnToken( defToken ) ;
		
		setRules ( rules.toArray(new IRule[]{} ));		
	}

	
	
	/**
	 * 
	 * @see org.eclipse.jface.text.rules.RuleBasedScanner#nextToken()
	 */
	
	@Override
	public IToken nextToken() {
		
		IToken next = super.nextToken();
		
		tokenWindow.addFirst(next);
		if (tokenWindow.size() > 4) {
			tokenWindow.removeLast();
		}
		
		return next;
	}


	/**
	 * Returns the last token with the index of offset. Index 0 means the last token seen,
	 * 1 means the one before the last token seen.
	 * 
	 * @param offset
	 * @return the token requested or undefined.
	 */
	
	public IToken lastToken ( int offset ) {
		try {
			return tokenWindow.get(offset);
		} catch (Throwable t) {
			return Token.UNDEFINED;
		}		
	}
	

	static private final String[] XPATH_FUNCTIONS = {
		
		"last","position","count","id","local-name","namespace-uri","name",
		"string","concat","starts-with","contains","substring-before","substring-after",
		"substring","string-length","normalize-space","translate",
		"boolean","not","true","false","lang",
		"number","sum","floor","ceiling","round"
	};
	
	
	static private final String[] AXIS = {
		"ancestor",
		"ancestor-or-self",
		"attribute",
		"child",
		"descendant",
		"descendant-or-self",
		"following",
		"following-sibling",
		"namespace",
		"parent",
		"preceding",
		"preceding-sibling",
		"self"		
	};
	
	
	/**
	 * The TokenContext class allows us to see what tokens we have seen
	 * so far. In some syntax coloring constructs we need to have a memory
	 * (albeit a simple one) of where we have been.
	 *  
	 * @author Michal Chmielewski (michal.chmielewski@oracle.com)
	 * @date Nov 27, 2006
	 */
	
	
	abstract class TokenContext implements ITokenContext {

		/** (non-Javadoc)
		 * @see org.eclipse.bpel.ui.editors.xpath.rules.ITokenContext#check(org.eclipse.jface.text.rules.ICharacterScanner)
		 */
		public boolean check (ICharacterScanner scanner) {
			if (scanner instanceof XPathSourceScanner) {
				return checkSeenTokens ( (XPathSourceScanner) scanner); 
			}
			return false;
		}
		
		/**
		 * 
		 * @param scanner
		 * @return true if the right tokens have been seen so far, false
		 * otherwise.
		 * 
		 */
		
		public abstract boolean checkSeenTokens ( XPathSourceScanner scanner ) ;
	}
	
}
