/*
 * [The "BSD license"]
 *  Copyright (c) 2010 Terence Parr
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 *  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.antlr.analysis;

import org.antlr.grammar.v3.ANTLRParser;
import org.antlr.misc.IntSet;
import org.antlr.misc.IntervalSet;
import org.antlr.tool.Grammar;
import org.antlr.tool.Rule;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Created by IntelliJ IDEA.
 * User: parrt
 * Date: Dec 31, 2007
 * Time: 1:31:16 PM
 * To change this template use File | Settings | File Templates.
 */
public class LL1Analyzer {
	/**	0	if we hit end of rule and invoker should keep going (epsilon) */
	public static final int DETECT_PRED_EOR = 0;
	/**	1	if we found a nonautobacktracking pred */
	public static final int DETECT_PRED_FOUND = 1;
	/**	2	if we didn't find such a pred */
	public static final int DETECT_PRED_NOT_FOUND = 2;

	public Grammar grammar;

	/** Used during LOOK to detect computation cycles */
	protected Set<NFAState> lookBusy = new HashSet<NFAState>();

	public Map<NFAState, LookaheadSet> FIRSTCache = new HashMap<NFAState, LookaheadSet>();
	public Map<Rule, LookaheadSet> FOLLOWCache = new HashMap<Rule, LookaheadSet>();

	public LL1Analyzer(Grammar grammar) {
		this.grammar = grammar;
	}

	/*
	public void computeRuleFIRSTSets() {
		if ( getNumberOfDecisions()==0 ) {
			createNFAs();
		}
		for (Iterator it = getRules().iterator(); it.hasNext();) {
			Rule r = (Rule)it.next();
			if ( r.isSynPred ) {
				continue;
			}
			LookaheadSet s = FIRST(r);
			System.out.println("FIRST("+r.name+")="+s);
		}
	}
	*/

	/*
	public Set<String> getOverriddenRulesWithDifferentFIRST() {
		// walk every rule in this grammar and compare FIRST set with
		// those in imported grammars.
		Set<String> rules = new HashSet();
		for (Iterator it = getRules().iterator(); it.hasNext();) {
			Rule r = (Rule)it.next();
			//System.out.println(r.name+" FIRST="+r.FIRST);
			for (int i = 0; i < delegates.size(); i++) {
				Grammar g = delegates.get(i);
				Rule importedRule = g.getRule(r.name);
				if ( importedRule != null ) { // exists in imported grammar
					// System.out.println(r.name+" exists in imported grammar: FIRST="+importedRule.FIRST);
					if ( !r.FIRST.equals(importedRule.FIRST) ) {
						rules.add(r.name);
					}
				}
			}
		}
		return rules;
	}

	public Set<Rule> getImportedRulesSensitiveToOverriddenRulesDueToLOOK() {
		Set<String> diffFIRSTs = getOverriddenRulesWithDifferentFIRST();
		Set<Rule> rules = new HashSet();
		for (Iterator it = diffFIRSTs.iterator(); it.hasNext();) {
			String r = (String) it.next();
			for (int i = 0; i < delegates.size(); i++) {
				Grammar g = delegates.get(i);
				Set<Rule> callers = g.ruleSensitivity.get(r);
				// somebody invokes rule whose FIRST changed in subgrammar?
				if ( callers!=null ) {
					rules.addAll(callers);
					//System.out.println(g.name+" rules "+callers+" sensitive to "+r+"; dup 'em");
				}
			}
		}
		return rules;
	}
*/

	/*
	public LookaheadSet LOOK(Rule r) {
		if ( r.FIRST==null ) {
			r.FIRST = FIRST(r.startState);
		}
		return r.FIRST;
	}
*/

	/** From an NFA state, s, find the set of all labels reachable from s.
	 *  Used to compute follow sets for error recovery.  Never computes
	 *  a FOLLOW operation.  FIRST stops at end of rules, returning EOR, unless
	 *  invoked from another rule.  I.e., routine properly handles
	 *
	 *     a : b A ;
	 *
	 *  where b is nullable.
	 *
	 *  We record with EOR_TOKEN_TYPE if we hit the end of a rule so we can
	 *  know at runtime (when these sets are used) to start walking up the
	 *  follow chain to compute the real, correct follow set (as opposed to
	 *  the FOLLOW, which is a superset).
	 *
	 *  This routine will only be used on parser and tree parser grammars.
	 */
	public LookaheadSet FIRST(NFAState s) {
		//System.out.println("> FIRST("+s.enclosingRule.name+") in rule "+s.enclosingRule);
		lookBusy.clear();
		LookaheadSet look = _FIRST(s, false);
		//System.out.println("< FIRST("+s.enclosingRule.name+") in rule "+s.enclosingRule+"="+look.toString(this.grammar));
		return look;
	}

	public LookaheadSet FOLLOW(Rule r) {
        //System.out.println("> FOLLOW("+r.name+") in rule "+r.startState.enclosingRule);
		LookaheadSet f = FOLLOWCache.get(r);
		if ( f!=null ) {
			return f;
		}
		f = _FIRST(r.stopState, true);
		FOLLOWCache.put(r, f);
        //System.out.println("< FOLLOW("+r+") in rule "+r.startState.enclosingRule+"="+f.toString(this.grammar));
		return f;
	}

	public LookaheadSet LOOK(NFAState s) {
		if ( NFAToDFAConverter.debug ) {
			System.out.println("> LOOK("+s+")");
		}
		lookBusy.clear();
		LookaheadSet look = _FIRST(s, true);
		// FOLLOW makes no sense (at the moment!) for lexical rules.
		if ( grammar.type!=Grammar.LEXER && look.member(Label.EOR_TOKEN_TYPE) ) {
			// avoid altering FIRST reset as it is cached
			LookaheadSet f = FOLLOW(s.enclosingRule);
			f.orInPlace(look);
			f.remove(Label.EOR_TOKEN_TYPE);
			look = f;
			//look.orInPlace(FOLLOW(s.enclosingRule));
		}
		else if ( grammar.type==Grammar.LEXER && look.member(Label.EOT) ) {
			// if this has EOT, lookahead is all char (all char can follow rule)
			//look = new LookaheadSet(Label.EOT);
			look = new LookaheadSet(IntervalSet.COMPLETE_SET);
		}
		if ( NFAToDFAConverter.debug ) {
			System.out.println("< LOOK("+s+")="+look.toString(grammar));
		}
		return look;
	}

	protected LookaheadSet _FIRST(NFAState s, boolean chaseFollowTransitions) {
		/*
		System.out.println("_LOOK("+s+") in rule "+s.enclosingRule);
		if ( s.transition[0] instanceof RuleClosureTransition ) {
			System.out.println("go to rule "+((NFAState)s.transition[0].target).enclosingRule);
		}
		*/
		if ( !chaseFollowTransitions && s.isAcceptState() ) {
			if ( grammar.type==Grammar.LEXER ) {
				// FOLLOW makes no sense (at the moment!) for lexical rules.
				// assume all char can follow
				return new LookaheadSet(IntervalSet.COMPLETE_SET);
			}
			return new LookaheadSet(Label.EOR_TOKEN_TYPE);
		}

		if ( lookBusy.contains(s) ) {
			// return a copy of an empty set; we may modify set inline
			return new LookaheadSet();
		}
		lookBusy.add(s);

		Transition transition0 = s.transition[0];
		if ( transition0==null ) {
			return null;
		}

		if ( transition0.label.isAtom() ) {
			int atom = transition0.label.getAtom();
			return new LookaheadSet(atom);
		}
		if ( transition0.label.isSet() ) {
			IntSet sl = transition0.label.getSet();
			return new LookaheadSet(sl);
		}

		// compute FIRST of transition 0
		LookaheadSet tset = null;
		// if transition 0 is a rule call and we don't want FOLLOW, check cache
		if ( !chaseFollowTransitions && transition0 instanceof RuleClosureTransition ) {
			tset = FIRSTCache.get((NFAState)transition0.target);
		}

		// if not in cache, must compute
		if ( tset==null ) {
			tset = _FIRST((NFAState)transition0.target, chaseFollowTransitions);
			// save FIRST cache for transition 0 if rule call
			if ( !chaseFollowTransitions && transition0 instanceof RuleClosureTransition ) {
				FIRSTCache.put((NFAState)transition0.target, tset);
			}
		}

        LookaheadSet tsetCached = tset; // tset is stored in cache. We can't return the same instance

		// did we fall off the end?
		if ( grammar.type!=Grammar.LEXER && tset.member(Label.EOR_TOKEN_TYPE) ) {
			if ( transition0 instanceof RuleClosureTransition ) {
				// we called a rule that found the end of the rule.
				// That means the rule is nullable and we need to
				// keep looking at what follows the rule ref.  E.g.,
				// a : b A ; where b is nullable means that LOOK(a)
				// should include A.
				RuleClosureTransition ruleInvocationTrans =
					(RuleClosureTransition)transition0;
				// remove the EOR and get what follows
				//tset.remove(Label.EOR_TOKEN_TYPE);
				NFAState following = ruleInvocationTrans.followState;
				LookaheadSet fset =	_FIRST(following, chaseFollowTransitions);
				fset.orInPlace(tset); // tset cached; or into new set
				fset.remove(Label.EOR_TOKEN_TYPE);
				tset = fset;
			}
		}

		Transition transition1 = s.transition[1];
		if ( transition1!=null ) {
			LookaheadSet tset1 =
				_FIRST((NFAState)transition1.target, chaseFollowTransitions);
			tset1.orInPlace(tset);
			tset = tset1;
		}

		// never return a cached set; clone
		return tset==tsetCached ? new LookaheadSet(tset) : tset;
	}

	/** Is there a non-syn-pred predicate visible from s that is not in
	 *  the rule enclosing s?  This accounts for most predicate situations
	 *  and lets ANTLR do a simple LL(1)+pred computation.
	 *
	 *  TODO: what about gated vs regular preds?
	 */
	public boolean detectConfoundingPredicates(NFAState s) {
		lookBusy.clear();
		Rule r = s.enclosingRule;
		return _detectConfoundingPredicates(s, r, false) == DETECT_PRED_FOUND;
	}

	protected int _detectConfoundingPredicates(NFAState s,
											   Rule enclosingRule,
											   boolean chaseFollowTransitions)
	{
		//System.out.println("_detectNonAutobacktrackPredicates("+s+")");
		if ( !chaseFollowTransitions && s.isAcceptState() ) {
			if ( grammar.type==Grammar.LEXER ) {
				// FOLLOW makes no sense (at the moment!) for lexical rules.
				// assume all char can follow
				return DETECT_PRED_NOT_FOUND;
			}
			return DETECT_PRED_EOR;
		}

		if ( lookBusy.contains(s) ) {
			// return a copy of an empty set; we may modify set inline
			return DETECT_PRED_NOT_FOUND;
		}
		lookBusy.add(s);

		Transition transition0 = s.transition[0];
		if ( transition0==null ) {
			return DETECT_PRED_NOT_FOUND;
		}

		if ( !(transition0.label.isSemanticPredicate()||
			   transition0.label.isEpsilon()) ) {
			return DETECT_PRED_NOT_FOUND;
		}

		if ( transition0.label.isSemanticPredicate() ) {
			//System.out.println("pred "+transition0.label);
			SemanticContext ctx = transition0.label.getSemanticContext();
			SemanticContext.Predicate p = (SemanticContext.Predicate)ctx;
			if ( p.predicateAST.getType() != ANTLRParser.BACKTRACK_SEMPRED ) {
				return DETECT_PRED_FOUND;
			}
		}
		
		/*
		if ( transition0.label.isSemanticPredicate() ) {
			System.out.println("pred "+transition0.label);
			SemanticContext ctx = transition0.label.getSemanticContext();
			SemanticContext.Predicate p = (SemanticContext.Predicate)ctx;
			// if a non-syn-pred found not in enclosingRule, say we found one
			if ( p.predicateAST.getType() != ANTLRParser.BACKTRACK_SEMPRED &&
				 !p.predicateAST.enclosingRuleName.equals(enclosingRule.name) )
			{
				System.out.println("found pred "+p+" not in "+enclosingRule.name);
				return DETECT_PRED_FOUND;
			}
		}
		*/

		int result = _detectConfoundingPredicates((NFAState)transition0.target,
												  enclosingRule,
												  chaseFollowTransitions);
		if ( result == DETECT_PRED_FOUND ) {
			return DETECT_PRED_FOUND;
		}

		if ( result == DETECT_PRED_EOR ) {
			if ( transition0 instanceof RuleClosureTransition ) {
				// we called a rule that found the end of the rule.
				// That means the rule is nullable and we need to
				// keep looking at what follows the rule ref.  E.g.,
				// a : b A ; where b is nullable means that LOOK(a)
				// should include A.
				RuleClosureTransition ruleInvocationTrans =
					(RuleClosureTransition)transition0;
				NFAState following = ruleInvocationTrans.followState;
				int afterRuleResult =
					_detectConfoundingPredicates(following,
												 enclosingRule,
												 chaseFollowTransitions);
				if ( afterRuleResult == DETECT_PRED_FOUND ) {
					return DETECT_PRED_FOUND;
				}
			}
		}

		Transition transition1 = s.transition[1];
		if ( transition1!=null ) {
			int t1Result =
				_detectConfoundingPredicates((NFAState)transition1.target,
											 enclosingRule,
											 chaseFollowTransitions);
			if ( t1Result == DETECT_PRED_FOUND ) {
				return DETECT_PRED_FOUND;
			}
		}

		return DETECT_PRED_NOT_FOUND;
	}

	/** Return predicate expression found via epsilon edges from s.  Do
	 *  not look into other rules for now.  Do something simple.  Include
	 *  backtracking synpreds.
	 */
	public SemanticContext getPredicates(NFAState altStartState) {
		lookBusy.clear();
		return _getPredicates(altStartState, altStartState);
	}

	protected SemanticContext _getPredicates(NFAState s, NFAState altStartState) {
		//System.out.println("_getPredicates("+s+")");
		if ( s.isAcceptState() ) {
			return null;
		}

		// avoid infinite loops from (..)* etc...
		if ( lookBusy.contains(s) ) {
			return null;
		}
		lookBusy.add(s);

		Transition transition0 = s.transition[0];
		// no transitions
		if ( transition0==null ) {
			return null;
		}

		// not a predicate and not even an epsilon
		if ( !(transition0.label.isSemanticPredicate()||
			   transition0.label.isEpsilon()) ) {
			return null;
		}

		SemanticContext p = null;
		SemanticContext p0;
		SemanticContext p1 = null;
		if ( transition0.label.isSemanticPredicate() ) {
			//System.out.println("pred "+transition0.label);
			p = transition0.label.getSemanticContext();
			// ignore backtracking preds not on left edge for this decision
			if ( ((SemanticContext.Predicate)p).predicateAST.getType() ==
				  ANTLRParser.BACKTRACK_SEMPRED  &&
				 s == altStartState.transition[0].target )
			{
				p = null; // don't count
			}
		}

		// get preds from beyond this state
		p0 = _getPredicates((NFAState)transition0.target, altStartState);

		// get preds from other transition
		Transition transition1 = s.transition[1];
		if ( transition1!=null ) {
			p1 = _getPredicates((NFAState)transition1.target, altStartState);
		}

		// join this&following-right|following-down
		return SemanticContext.and(p,SemanticContext.or(p0,p1));
	}
}
