/* Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
 * Use of this file is governed by the BSD 3-clause license that
 * can be found in the LICENSE.txt file in the project root.
 */

const {Token} = require('./Token');
const {ParseTreeListener, TerminalNode, ErrorNode} = require('./tree/Tree');
const Recognizer = require('./Recognizer');
const {DefaultErrorStrategy} = require('./error/ErrorStrategy');
const ATNDeserializer = require('./atn/ATNDeserializer');
const ATNDeserializationOptions = require('./atn/ATNDeserializationOptions');
const Lexer = require('./Lexer');

class TraceListener extends ParseTreeListener {
	constructor(parser) {
		super();
		this.parser = parser;
	}

	enterEveryRule(ctx) {
		console.log("enter   " + this.parser.ruleNames[ctx.ruleIndex] + ", LT(1)=" + this.parser._input.LT(1).text);
	}

	visitTerminal(node) {
		console.log("consume " + node.symbol + " rule " + this.parser.ruleNames[this.parser._ctx.ruleIndex]);
	}

	exitEveryRule(ctx) {
		console.log("exit    " + this.parser.ruleNames[ctx.ruleIndex] + ", LT(1)=" + this.parser._input.LT(1).text);
	}
}

class Parser extends Recognizer {
	/**
	 * this is all the parsing support code essentially; most of it is error
	 * recovery stuff.
	 */
	constructor(input) {
		super();
		// The input stream.
		this._input = null;
		/**
		 * The error handling strategy for the parser. The default value is a new
		 * instance of {@link DefaultErrorStrategy}.
		 */
		this._errHandler = new DefaultErrorStrategy();
		this._precedenceStack = [];
		this._precedenceStack.push(0);
		/**
		 * The {@link ParserRuleContext} object for the currently executing rule.
		 * this is always non-null during the parsing process.
		 */
		this._ctx = null;
		/**
		 * Specifies whether or not the parser should construct a parse tree during
		 * the parsing process. The default value is {@code true}.
		 */
		this.buildParseTrees = true;
		/**
		 * When {@link //setTrace}{@code (true)} is called, a reference to the
		 * {@link TraceListener} is stored here so it can be easily removed in a
		 * later call to {@link //setTrace}{@code (false)}. The listener itself is
		 * implemented as a parser listener so this field is not directly used by
		 * other parser methods.
		 */
		this._tracer = null;
		/**
		 * The list of {@link ParseTreeListener} listeners registered to receive
		 * events during the parse.
		 */
		this._parseListeners = null;
		/**
		 * The number of syntax errors reported during parsing. this value is
		 * incremented each time {@link //notifyErrorListeners} is called.
		 */
		this._syntaxErrors = 0;
		this.setInputStream(input);
	}

	// reset the parser's state
	reset() {
		if (this._input !== null) {
			this._input.seek(0);
		}
		this._errHandler.reset(this);
		this._ctx = null;
		this._syntaxErrors = 0;
		this.setTrace(false);
		this._precedenceStack = [];
		this._precedenceStack.push(0);
		if (this._interp !== null) {
			this._interp.reset();
		}
	}

	/**
	 * Match current input symbol against {@code ttype}. If the symbol type
	 * matches, {@link ANTLRErrorStrategy//reportMatch} and {@link //consume} are
	 * called to complete the match process.
	 *
	 * <p>If the symbol type does not match,
	 * {@link ANTLRErrorStrategy//recoverInline} is called on the current error
	 * strategy to attempt recovery. If {@link //getBuildParseTree} is
	 * {@code true} and the token index of the symbol returned by
	 * {@link ANTLRErrorStrategy//recoverInline} is -1, the symbol is added to
	 * the parse tree by calling {@link ParserRuleContext//addErrorNode}.</p>
	 *
	 * @param ttype the token type to match
	 * @return the matched symbol
	 * @throws RecognitionException if the current input symbol did not match
	 * {@code ttype} and the error strategy could not recover from the
	 * mismatched symbol
	 */
	match(ttype) {
		let t = this.getCurrentToken();
		if (t.type === ttype) {
			this._errHandler.reportMatch(this);
			this.consume();
		} else {
			t = this._errHandler.recoverInline(this);
			if (this.buildParseTrees && t.tokenIndex === -1) {
				// we must have conjured up a new token during single token
				// insertion
				// if it's not the current symbol
				this._ctx.addErrorNode(t);
			}
		}
		return t;
	}

	/**
	 * Match current input symbol as a wildcard. If the symbol type matches
	 * (i.e. has a value greater than 0), {@link ANTLRErrorStrategy//reportMatch}
	 * and {@link //consume} are called to complete the match process.
	 *
	 * <p>If the symbol type does not match,
	 * {@link ANTLRErrorStrategy//recoverInline} is called on the current error
	 * strategy to attempt recovery. If {@link //getBuildParseTree} is
	 * {@code true} and the token index of the symbol returned by
	 * {@link ANTLRErrorStrategy//recoverInline} is -1, the symbol is added to
	 * the parse tree by calling {@link ParserRuleContext//addErrorNode}.</p>
	 *
	 * @return the matched symbol
	 * @throws RecognitionException if the current input symbol did not match
	 * a wildcard and the error strategy could not recover from the mismatched
	 * symbol
	 */
	matchWildcard() {
		let t = this.getCurrentToken();
		if (t.type > 0) {
			this._errHandler.reportMatch(this);
			this.consume();
		} else {
			t = this._errHandler.recoverInline(this);
			if (this._buildParseTrees && t.tokenIndex === -1) {
				// we must have conjured up a new token during single token
				// insertion
				// if it's not the current symbol
				this._ctx.addErrorNode(t);
			}
		}
		return t;
	}

	getParseListeners() {
		return this._parseListeners || [];
	}

	/**
	 * Registers {@code listener} to receive events during the parsing process.
	 *
	 * <p>To support output-preserving grammar transformations (including but not
	 * limited to left-recursion removal, automated left-factoring, and
	 * optimized code generation), calls to listener methods during the parse
	 * may differ substantially from calls made by
	 * {@link ParseTreeWalker//DEFAULT} used after the parse is complete. In
	 * particular, rule entry and exit events may occur in a different order
	 * during the parse than after the parser. In addition, calls to certain
	 * rule entry methods may be omitted.</p>
	 *
	 * <p>With the following specific exceptions, calls to listener events are
	 * <em>deterministic</em>, i.e. for identical input the calls to listener
	 * methods will be the same.</p>
	 *
	 * <ul>
	 * <li>Alterations to the grammar used to generate code may change the
	 * behavior of the listener calls.</li>
	 * <li>Alterations to the command line options passed to ANTLR 4 when
	 * generating the parser may change the behavior of the listener calls.</li>
	 * <li>Changing the version of the ANTLR Tool used to generate the parser
	 * may change the behavior of the listener calls.</li>
	 * </ul>
	 *
	 * @param listener the listener to add
	 *
	 * @throws NullPointerException if {@code} listener is {@code null}
	 */
	addParseListener(listener) {
		if (listener === null) {
			throw "listener";
		}
		if (this._parseListeners === null) {
			this._parseListeners = [];
		}
		this._parseListeners.push(listener);
	}

	/**
	 * Remove {@code listener} from the list of parse listeners.
	 *
	 * <p>If {@code listener} is {@code null} or has not been added as a parse
	 * listener, this method does nothing.</p>
	 * @param listener the listener to remove
	 */
	removeParseListener(listener) {
		if (this._parseListeners !== null) {
			const idx = this._parseListeners.indexOf(listener);
			if (idx >= 0) {
				this._parseListeners.splice(idx, 1);
			}
			if (this._parseListeners.length === 0) {
				this._parseListeners = null;
			}
		}
	}

// Remove all parse listeners.
	removeParseListeners() {
		this._parseListeners = null;
	}

// Notify any parse listeners of an enter rule event.
	triggerEnterRuleEvent() {
		if (this._parseListeners !== null) {
			const ctx = this._ctx;
			this._parseListeners.map(function(listener) {
				listener.enterEveryRule(ctx);
				ctx.enterRule(listener);
			});
		}
	}

	/**
	 * Notify any parse listeners of an exit rule event.
	 * @see //addParseListener
	 */
	triggerExitRuleEvent() {
		if (this._parseListeners !== null) {
			// reverse order walk of listeners
			const ctx = this._ctx;
			this._parseListeners.slice(0).reverse().map(function(listener) {
				ctx.exitRule(listener);
				listener.exitEveryRule(ctx);
			});
		}
	}

	getTokenFactory() {
		return this._input.tokenSource._factory;
	}

	// Tell our token source and error strategy about a new way to create tokens.
	setTokenFactory(factory) {
		this._input.tokenSource._factory = factory;
	}

	/**
	 * The ATN with bypass alternatives is expensive to create so we create it
	 * lazily.
	 *
	 * @throws UnsupportedOperationException if the current parser does not
	 * implement the {@link //getSerializedATN()} method.
	 */
	getATNWithBypassAlts() {
		const serializedAtn = this.getSerializedATN();
		if (serializedAtn === null) {
			throw "The current parser does not support an ATN with bypass alternatives.";
		}
		let result = this.bypassAltsAtnCache[serializedAtn];
		if (result === null) {
			const deserializationOptions = new ATNDeserializationOptions();
			deserializationOptions.generateRuleBypassTransitions = true;
			result = new ATNDeserializer(deserializationOptions)
					.deserialize(serializedAtn);
			this.bypassAltsAtnCache[serializedAtn] = result;
		}
		return result;
	}

	/**
	 * The preferred method of getting a tree pattern. For example, here's a
	 * sample use:
	 *
	 * <pre>
	 * ParseTree t = parser.expr();
	 * ParseTreePattern p = parser.compileParseTreePattern("&lt;ID&gt;+0",
	 * MyParser.RULE_expr);
	 * ParseTreeMatch m = p.match(t);
	 * String id = m.get("ID");
	 * </pre>
	 */
	compileParseTreePattern(pattern, patternRuleIndex, lexer) {
		lexer = lexer || null;
		if (lexer === null) {
			if (this.getTokenStream() !== null) {
				const tokenSource = this.getTokenStream().tokenSource;
				if (tokenSource instanceof Lexer) {
					lexer = tokenSource;
				}
			}
		}
		if (lexer === null) {
			throw "Parser can't discover a lexer to use";
		}
		const m = new ParseTreePatternMatcher(lexer, this);
		return m.compile(pattern, patternRuleIndex);
	}

	getInputStream() {
		return this.getTokenStream();
	}

	setInputStream(input) {
		this.setTokenStream(input);
	}

	getTokenStream() {
		return this._input;
	}

	// Set the token stream and reset the parser.
	setTokenStream(input) {
		this._input = null;
		this.reset();
		this._input = input;
	}

	/**
	 * Match needs to return the current input symbol, which gets put
	 * into the label for the associated token ref; e.g., x=ID.
	 */
	getCurrentToken() {
		return this._input.LT(1);
	}

	notifyErrorListeners(msg, offendingToken, err) {
		offendingToken = offendingToken || null;
		err = err || null;
		if (offendingToken === null) {
			offendingToken = this.getCurrentToken();
		}
		this._syntaxErrors += 1;
		const line = offendingToken.line;
		const column = offendingToken.column;
		const listener = this.getErrorListenerDispatch();
		listener.syntaxError(this, offendingToken, line, column, msg, err);
	}

	/**
	 * Consume and return the {@linkplain //getCurrentToken current symbol}.
	 *
	 * <p>E.g., given the following input with {@code A} being the current
	 * lookahead symbol, this function moves the cursor to {@code B} and returns
	 * {@code A}.</p>
	 *
	 * <pre>
	 * A B
	 * ^
	 * </pre>
	 *
	 * If the parser is not in error recovery mode, the consumed symbol is added
	 * to the parse tree using {@link ParserRuleContext//addChild(Token)}, and
	 * {@link ParseTreeListener//visitTerminal} is called on any parse listeners.
	 * If the parser <em>is</em> in error recovery mode, the consumed symbol is
	 * added to the parse tree using
	 * {@link ParserRuleContext//addErrorNode(Token)}, and
	 * {@link ParseTreeListener//visitErrorNode} is called on any parse
	 * listeners.
	 */
	consume() {
		const o = this.getCurrentToken();
		if (o.type !== Token.EOF) {
			this.getInputStream().consume();
		}
		const hasListener = this._parseListeners !== null && this._parseListeners.length > 0;
		if (this.buildParseTrees || hasListener) {
			let node;
			if (this._errHandler.inErrorRecoveryMode(this)) {
				node = this._ctx.addErrorNode(o);
			} else {
				node = this._ctx.addTokenNode(o);
			}
			node.invokingState = this.state;
			if (hasListener) {
				this._parseListeners.map(function(listener) {
					if (node instanceof ErrorNode || (node.isErrorNode !== undefined && node.isErrorNode())) {
						listener.visitErrorNode(node);
					} else if (node instanceof TerminalNode) {
						listener.visitTerminal(node);
					}
				});
			}
		}
		return o;
	}

	addContextToParseTree() {
		// add current context to parent if we have a parent
		if (this._ctx.parentCtx !== null) {
			this._ctx.parentCtx.addChild(this._ctx);
		}
	}

	/**
	 * Always called by generated parsers upon entry to a rule. Access field
	 * {@link //_ctx} get the current context.
	 */
	enterRule(localctx, state, ruleIndex) {
		this.state = state;
		this._ctx = localctx;
		this._ctx.start = this._input.LT(1);
		if (this.buildParseTrees) {
			this.addContextToParseTree();
		}
		if (this._parseListeners !== null) {
			this.triggerEnterRuleEvent();
		}
	}

	exitRule() {
		this._ctx.stop = this._input.LT(-1);
		// trigger event on _ctx, before it reverts to parent
		if (this._parseListeners !== null) {
			this.triggerExitRuleEvent();
		}
		this.state = this._ctx.invokingState;
		this._ctx = this._ctx.parentCtx;
	}

	enterOuterAlt(localctx, altNum) {
		localctx.setAltNumber(altNum);
		// if we have new localctx, make sure we replace existing ctx
		// that is previous child of parse tree
		if (this.buildParseTrees && this._ctx !== localctx) {
			if (this._ctx.parentCtx !== null) {
				this._ctx.parentCtx.removeLastChild();
				this._ctx.parentCtx.addChild(localctx);
			}
		}
		this._ctx = localctx;
	}

	/**
	 * Get the precedence level for the top-most precedence rule.
	 *
	 * @return The precedence level for the top-most precedence rule, or -1 if
	 * the parser context is not nested within a precedence rule.
	 */
	getPrecedence() {
		if (this._precedenceStack.length === 0) {
			return -1;
		} else {
			return this._precedenceStack[this._precedenceStack.length-1];
		}
	}

	enterRecursionRule(localctx, state, ruleIndex, precedence) {
	   this.state = state;
	   this._precedenceStack.push(precedence);
	   this._ctx = localctx;
	   this._ctx.start = this._input.LT(1);
	   if (this._parseListeners !== null) {
		   this.triggerEnterRuleEvent(); // simulates rule entry for
		   									// left-recursive rules
	   }
   }

	// Like {@link //enterRule} but for recursive rules.
	pushNewRecursionContext(localctx, state, ruleIndex) {
		const previous = this._ctx;
		previous.parentCtx = localctx;
		previous.invokingState = state;
		previous.stop = this._input.LT(-1);

		this._ctx = localctx;
		this._ctx.start = previous.start;
		if (this.buildParseTrees) {
			this._ctx.addChild(previous);
		}
		if (this._parseListeners !== null) {
			this.triggerEnterRuleEvent(); // simulates rule entry for
											// left-recursive rules
		}
	}

	unrollRecursionContexts(parentCtx) {
		this._precedenceStack.pop();
		this._ctx.stop = this._input.LT(-1);
		const retCtx = this._ctx; // save current ctx (return value)
		// unroll so _ctx is as it was before call to recursive method
		if (this._parseListeners !== null) {
			while (this._ctx !== parentCtx) {
				this.triggerExitRuleEvent();
				this._ctx = this._ctx.parentCtx;
			}
		} else {
			this._ctx = parentCtx;
		}
		// hook into tree
		retCtx.parentCtx = parentCtx;
		if (this.buildParseTrees && parentCtx !== null) {
			// add return ctx into invoking rule's tree
			parentCtx.addChild(retCtx);
		}
	}

	getInvokingContext(ruleIndex) {
		let ctx = this._ctx;
		while (ctx !== null) {
			if (ctx.ruleIndex === ruleIndex) {
				return ctx;
			}
			ctx = ctx.parentCtx;
		}
		return null;
	}

	precpred(localctx, precedence) {
		return precedence >= this._precedenceStack[this._precedenceStack.length-1];
	}

	inContext(context) {
		// TODO: useful in parser?
		return false;
	}

	/**
	 * Checks whether or not {@code symbol} can follow the current state in the
	 * ATN. The behavior of this method is equivalent to the following, but is
	 * implemented such that the complete context-sensitive follow set does not
	 * need to be explicitly constructed.
	 *
	 * <pre>
	 * return getExpectedTokens().contains(symbol);
	 * </pre>
	 *
	 * @param symbol the symbol type to check
	 * @return {@code true} if {@code symbol} can follow the current state in
	 * the ATN, otherwise {@code false}.
	 */
	isExpectedToken(symbol) {
		const atn = this._interp.atn;
		let ctx = this._ctx;
		const s = atn.states[this.state];
		let following = atn.nextTokens(s);
		if (following.contains(symbol)) {
			return true;
		}
		if (!following.contains(Token.EPSILON)) {
			return false;
		}
		while (ctx !== null && ctx.invokingState >= 0 && following.contains(Token.EPSILON)) {
			const invokingState = atn.states[ctx.invokingState];
			const rt = invokingState.transitions[0];
			following = atn.nextTokens(rt.followState);
			if (following.contains(symbol)) {
				return true;
			}
			ctx = ctx.parentCtx;
		}
		if (following.contains(Token.EPSILON) && symbol === Token.EOF) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Computes the set of input symbols which could follow the current parser
	 * state and context, as given by {@link //getState} and {@link //getContext},
	 * respectively.
	 *
	 * @see ATN//getExpectedTokens(int, RuleContext)
	 */
	getExpectedTokens() {
		return this._interp.atn.getExpectedTokens(this.state, this._ctx);
	}

	getExpectedTokensWithinCurrentRule() {
		const atn = this._interp.atn;
		const s = atn.states[this.state];
		return atn.nextTokens(s);
	}

	// Get a rule's index (i.e., {@code RULE_ruleName} field) or -1 if not found.
	getRuleIndex(ruleName) {
		const ruleIndex = this.getRuleIndexMap()[ruleName];
		if (ruleIndex !== null) {
			return ruleIndex;
		} else {
			return -1;
		}
	}

	/**
	 * Return List&lt;String&gt; of the rule names in your parser instance
	 * leading up to a call to the current rule. You could override if
	 * you want more details such as the file/line info of where
	 * in the ATN a rule is invoked.
	 *
	 * this is very useful for error messages.
	 */
	getRuleInvocationStack(p) {
		p = p || null;
		if (p === null) {
			p = this._ctx;
		}
		const stack = [];
		while (p !== null) {
			// compute what follows who invoked us
			const ruleIndex = p.ruleIndex;
			if (ruleIndex < 0) {
				stack.push("n/a");
			} else {
				stack.push(this.ruleNames[ruleIndex]);
			}
			p = p.parentCtx;
		}
		return stack;
	}

	// For debugging and other purposes.
	getDFAStrings() {
		return this._interp.decisionToDFA.toString();
	}

	// For debugging and other purposes.
	dumpDFA() {
		let seenOne = false;
		for (let i = 0; i < this._interp.decisionToDFA.length; i++) {
			const dfa = this._interp.decisionToDFA[i];
			if (dfa.states.length > 0) {
				if (seenOne) {
					console.log();
				}
				this.printer.println("Decision " + dfa.decision + ":");
				this.printer.print(dfa.toString(this.literalNames, this.symbolicNames));
				seenOne = true;
			}
		}
	}

	/*
		"			printer = function() {\r\n" +
		"				this.println = function(s) { document.getElementById('output') += s + '\\n'; }\r\n" +
		"				this.print = function(s) { document.getElementById('output') += s; }\r\n" +
		"			};\r\n" +
		*/
	getSourceName() {
		return this._input.sourceName;
	}

	/**
	 * During a parse is sometimes useful to listen in on the rule entry and exit
	 * events as well as token matches. this is for quick and dirty debugging.
	 */
	setTrace(trace) {
		if (!trace) {
			this.removeParseListener(this._tracer);
			this._tracer = null;
		} else {
			if (this._tracer !== null) {
				this.removeParseListener(this._tracer);
			}
			this._tracer = new TraceListener(this);
			this.addParseListener(this._tracer);
		}
	}
}

/**
 * this field maps from the serialized ATN string to the deserialized {@link
 * ATN} with
 * bypass alternatives.
 *
 * @see ATNDeserializationOptions//isGenerateRuleBypassTransitions()
 */
Parser.bypassAltsAtnCache = {};

module.exports = Parser;
