/*
 * [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.tool;

import org.antlr.Tool;
import org.antlr.analysis.Label;
import org.antlr.analysis.NFAState;
import org.antlr.analysis.RuleClosureTransition;
import org.antlr.analysis.Transition;
import org.antlr.misc.IntervalSet;
import org.antlr.misc.Utils;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.Stack;

/** Generate a random phrase given a grammar.
 *  Usage:
 *     java org.antlr.tool.RandomPhrase grammarFile.g startRule [seed]
 *
 *  For example:
 *     java org.antlr.tool.RandomPhrase simple.g program 342
 *
 *  The seed acts like a unique identifier so you can get the same random
 *  phrase back during unit testing, for example.
 *
 *  If you do not specify a seed then the current time in milliseconds is used
 *  guaranteeing that you'll never see that seed again.
 *
 *  NOTE: this does not work well for large grammars...it tends to recurse
 *  too much and build really long strings.  I need throttle control; later.
 */
public class RandomPhrase {
	public static final boolean debug = false;

	protected static Random random;

	/** an experimental method to generate random phrases for a given
	 *  grammar given a start rule.  Return a list of token types.
	 */
	protected static void randomPhrase(Grammar g, List<Integer> tokenTypes, String startRule) {
		NFAState state = g.getRuleStartState(startRule);
		NFAState stopState = g.getRuleStopState(startRule);

		Stack<NFAState> ruleInvocationStack = new Stack<NFAState>();
		while ( true ) {
			if ( state==stopState && ruleInvocationStack.isEmpty() ) {
				break;
			}
			if ( debug ) System.out.println("state "+state);
			if ( state.getNumberOfTransitions()==0 ) {
				if ( debug ) System.out.println("dangling state: "+state);
				return;
			}
			// end of rule node
			if ( state.isAcceptState() ) {
				NFAState invokingState = ruleInvocationStack.pop();
				if ( debug ) System.out.println("pop invoking state "+invokingState);
				//System.out.println("leave "+state.enclosingRule.name);
				RuleClosureTransition invokingTransition =
					(RuleClosureTransition)invokingState.transition[0];
				// move to node after state that invoked this rule
				state = invokingTransition.followState;
				continue;
			}
			if ( state.getNumberOfTransitions()==1 ) {
				// no branching, just take this path
				Transition t0 = state.transition[0];
				if ( t0 instanceof RuleClosureTransition ) {
					ruleInvocationStack.push(state);
					if ( debug ) System.out.println("push state "+state);
					//System.out.println("call "+((RuleClosureTransition)t0).rule.name);
					//System.out.println("stack depth="+ruleInvocationStack.size());
				}
				else if ( t0.label.isSet() || t0.label.isAtom() ) {
					tokenTypes.add( getTokenType(t0.label) );
				}
				state = (NFAState)t0.target;
				continue;
			}

			int decisionNumber = state.getDecisionNumber();
			if ( decisionNumber==0 ) {
				System.out.println("weird: no decision number but a choice node");
				continue;
			}
			// decision point, pick ith alternative randomly
			int n = g.getNumberOfAltsForDecisionNFA(state);
			int randomAlt = random.nextInt(n) + 1;
			if ( debug ) System.out.println("randomAlt="+randomAlt);
			NFAState altStartState =
				g.getNFAStateForAltOfDecision(state, randomAlt);
			Transition t = altStartState.transition[0];
			state = (NFAState)t.target;
		}
	}

	protected static Integer getTokenType(Label label) {
		if ( label.isSet() ) {
			// pick random element of set
			IntervalSet typeSet = (IntervalSet)label.getSet();
			int randomIndex = random.nextInt(typeSet.size());
			return typeSet.get(randomIndex);
		}
		else {
			return Utils.integer(label.getAtom());
		}
		//System.out.println(t0.label.toString(g));
	}

	/** Used to generate random strings */
	public static void main(String[] args) {
		if ( args.length < 2 ) {
			System.err.println("usage: java org.antlr.tool.RandomPhrase grammarfile startrule");
			return;
		}
		String grammarFileName = args[0];
		String startRule = args[1];
		long seed = System.currentTimeMillis(); // use random seed unless spec.
		if ( args.length==3 ) {
			String seedStr = args[2];
			seed = Long.parseLong(seedStr);
		}
		try {
			random = new Random(seed);

			CompositeGrammar composite = new CompositeGrammar();
			Tool tool = new Tool();
			Grammar parser = new Grammar(tool, grammarFileName, composite);
			composite.setDelegationRoot(parser);

			FileReader fr = new FileReader(grammarFileName);
			BufferedReader br = new BufferedReader(fr);
			parser.parseAndBuildAST(br);
			br.close();

			parser.composite.assignTokenTypes();
			parser.composite.defineGrammarSymbols();
			parser.composite.createNFAs();

			List<? extends Collection<? extends Rule>> leftRecursiveRules = parser.checkAllRulesForLeftRecursion();
			if ( leftRecursiveRules.size()>0 ) {
				return;
			}

			if ( parser.getRule(startRule)==null ) {
				System.out.println("undefined start rule "+startRule);
				return;
			}

			String lexerGrammarText = parser.getLexerGrammar();
			Grammar lexer = new Grammar(tool);
			lexer.importTokenVocabulary(parser);
			lexer.fileName = grammarFileName;
			if ( lexerGrammarText!=null ) {
				lexer.setGrammarContent(lexerGrammarText);
			}
			else {
				System.err.println("no lexer grammar found in "+grammarFileName);
			}
			lexer.buildNFA();
			leftRecursiveRules = lexer.checkAllRulesForLeftRecursion();
			if ( leftRecursiveRules.size()>0 ) {
				return;
			}
			//System.out.println("lexer:\n"+lexer);

			List<Integer> tokenTypes = new ArrayList<Integer>(100);
			randomPhrase(parser, tokenTypes, startRule);
			System.out.println("token types="+tokenTypes);
			for (int i = 0; i < tokenTypes.size(); i++) {
				Integer ttypeI = tokenTypes.get(i);
				int ttype = ttypeI;
				String ttypeDisplayName = parser.getTokenDisplayName(ttype);
				if ( Character.isUpperCase(ttypeDisplayName.charAt(0)) ) {
					List<Integer> charsInToken = new ArrayList<Integer>(10);
					randomPhrase(lexer, charsInToken, ttypeDisplayName);
					System.out.print(" ");
					for (int j = 0; j < charsInToken.size(); j++) {
						Integer cI = charsInToken.get(j);
						System.out.print((char)cI.intValue());
					}
				}
				else { // it's a literal
					String literal =
						ttypeDisplayName.substring(1,ttypeDisplayName.length()-1);
					System.out.print(" "+literal);
				}
			}
			System.out.println();
		}
		catch (Error er) {
			System.err.println("Error walking "+grammarFileName+" rule "+startRule+" seed "+seed);
			er.printStackTrace(System.err);
		}
		catch (Exception e) {
			System.err.println("Exception walking "+grammarFileName+" rule "+startRule+" seed "+seed);
			e.printStackTrace(System.err);
		}
	}
}
