/* 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 {Interval} = require('./../IntervalSet');
const INVALID_INTERVAL = new Interval(-1, -2);

/**
 * The basic notion of a tree has a parent, a payload, and a list of children.
 * It is the most abstract interface for all the trees used by ANTLR.
 */
class Tree {}

class SyntaxTree extends Tree {
	constructor() {
		super();
	}
}

class ParseTree extends SyntaxTree {
	constructor() {
		super();
	}
}

class RuleNode extends ParseTree {
	constructor() {
		super();
	}

	getRuleContext(){
		throw new Error("missing interface implementation")
	}
}

class TerminalNode extends ParseTree {
	constructor() {
		super();
	}
}

class ErrorNode extends TerminalNode {
	constructor() {
		super();
	}
}

class ParseTreeVisitor {
	visit(ctx) {
		 if (Array.isArray(ctx)) {
			return ctx.map(function(child) {
				return child.accept(this);
			}, this);
		} else {
			return ctx.accept(this);
		}
	}

	visitChildren(ctx) {
		if (ctx.children) {
			return this.visit(ctx.children);
		} else {
			return null;
		}
	}

	visitTerminal(node) {
	}

	visitErrorNode(node) {
	}
}

class ParseTreeListener {
	visitTerminal(node) {
	}

	visitErrorNode(node) {
	}

	enterEveryRule(node) {
	}

	exitEveryRule(node) {
	}
}

class TerminalNodeImpl extends TerminalNode {
	constructor(symbol) {
		super();
		this.parentCtx = null;
		this.symbol = symbol;
	}

	getChild(i) {
		return null;
	}

	getSymbol() {
		return this.symbol;
	}

	getParent() {
		return this.parentCtx;
	}

	getPayload() {
		return this.symbol;
	}

	getSourceInterval() {
		if (this.symbol === null) {
			return INVALID_INTERVAL;
		}
		const tokenIndex = this.symbol.tokenIndex;
		return new Interval(tokenIndex, tokenIndex);
	}

	getChildCount() {
		return 0;
	}

	accept(visitor) {
		return visitor.visitTerminal(this);
	}

	getText() {
		return this.symbol.text;
	}

	toString() {
		if (this.symbol.type === Token.EOF) {
			return "<EOF>";
		} else {
			return this.symbol.text;
		}
	}
}


/**
 * Represents a token that was consumed during resynchronization
 * rather than during a valid match operation. For example,
 * we will create this kind of a node during single token insertion
 * and deletion as well as during "consume until error recovery set"
 * upon no viable alternative exceptions.
 */
class ErrorNodeImpl extends TerminalNodeImpl {
	constructor(token) {
		super(token);
	}

	isErrorNode() {
		return true;
	}

	accept(visitor) {
		return visitor.visitErrorNode(this);
	}
}

class ParseTreeWalker {

	/**
	 * Performs a walk on the given parse tree starting at the root and going down recursively
	 * with depth-first search. On each node, {@link ParseTreeWalker//enterRule} is called before
	 * recursively walking down into child nodes, then
	 * {@link ParseTreeWalker//exitRule} is called after the recursive call to wind up.
	 * @param listener The listener used by the walker to process grammar rules
	 * @param t The parse tree to be walked on
	 */
	walk(listener, t) {
		const errorNode = t instanceof ErrorNode ||
				(t.isErrorNode !== undefined && t.isErrorNode());
		if (errorNode) {
			listener.visitErrorNode(t);
		} else if (t instanceof TerminalNode) {
			listener.visitTerminal(t);
		} else {
			this.enterRule(listener, t);
			for (let i = 0; i < t.getChildCount(); i++) {
				const child = t.getChild(i);
				this.walk(listener, child);
			}
			this.exitRule(listener, t);
		}
	}

	/**
	 * Enters a grammar rule by first triggering the generic event {@link ParseTreeListener//enterEveryRule}
	 * then by triggering the event specific to the given parse tree node
	 * @param listener The listener responding to the trigger events
	 * @param r The grammar rule containing the rule context
	 */
	enterRule(listener, r) {
		const ctx = r.getRuleContext();
		listener.enterEveryRule(ctx);
		ctx.enterRule(listener);
	}

	/**
	 * Exits a grammar rule by first triggering the event specific to the given parse tree node
	 * then by triggering the generic event {@link ParseTreeListener//exitEveryRule}
	 * @param listener The listener responding to the trigger events
	 * @param r The grammar rule containing the rule context
	 */
	exitRule(listener, r) {
		const ctx = r.getRuleContext();
		ctx.exitRule(listener);
		listener.exitEveryRule(ctx);
	}
}

ParseTreeWalker.DEFAULT = new ParseTreeWalker();

module.exports = {
	RuleNode,
	ErrorNode,
	TerminalNode,
	ErrorNodeImpl,
	TerminalNodeImpl,
	ParseTreeListener,
	ParseTreeVisitor,
	ParseTreeWalker,
	INVALID_INTERVAL
}
