"use strict";
var __extends = (this && this.__extends) || function (d, b) {
    for (var p in b) /* istanbul ignore next */  if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    /* istanbul ignore next */  d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var rest_1 = require("./rest");
var gast_public_1 = require("./gast_public");
/* tslint:disable:no-use-before-declare */
var utils_1 = require("../../utils/utils");
/* tslint:enable:no-use-before-declare */
var tokens_public_1 = require("../../scan/tokens_public");
var first_1 = require("./first");
var AbstractNextPossibleTokensWalker = (function (_super) {
    __extends(AbstractNextPossibleTokensWalker, _super);
    function AbstractNextPossibleTokensWalker(topProd, path) {
        _super.call(this);
        this.topProd = topProd;
        this.path = path;
        this.possibleTokTypes = [];
        this.nextProductionName = "";
        this.nextProductionOccurrence = 0;
        this.found = false;
        this.isAtEndOfPath = false;
    }
    AbstractNextPossibleTokensWalker.prototype.startWalking = function () {
        this.found = false;
        if (this.path.ruleStack[0] !== this.topProd.name) {
            throw Error("The path does not start with the walker's top Rule!");
        }
        // immutable for the win
        this.ruleStack = (utils_1.cloneArr(this.path.ruleStack)).reverse(); // intelij bug requires assertion
        this.occurrenceStack = (utils_1.cloneArr(this.path.occurrenceStack)).reverse(); // intelij bug requires assertion
        // already verified that the first production is valid, we now seek the 2nd production
        this.ruleStack.pop();
        this.occurrenceStack.pop();
        this.updateExpectedNext();
        this.walk(this.topProd);
        return this.possibleTokTypes;
    };
    AbstractNextPossibleTokensWalker.prototype.walk = function (prod, prevRest) {
        if (prevRest === void 0) { prevRest = []; }
        // stop scanning once we found the path
        if (!this.found) {
            _super.prototype.walk.call(this, prod, prevRest);
        }
    };
    AbstractNextPossibleTokensWalker.prototype.walkProdRef = function (refProd, currRest, prevRest) {
        // found the next production, need to keep walking in it
        if (refProd.referencedRule.name === this.nextProductionName &&
            refProd.occurrenceInParent === this.nextProductionOccurrence) {
            var fullRest = currRest.concat(prevRest);
            this.updateExpectedNext();
            this.walk(refProd.referencedRule, fullRest);
        }
    };
    AbstractNextPossibleTokensWalker.prototype.updateExpectedNext = function () {
        // need to consume the Terminal
        if (utils_1.isEmpty(this.ruleStack)) {
            // must reset nextProductionXXX to avoid walking down another Top Level production while what we are
            // really seeking is the last Terminal...
            this.nextProductionName = "";
            this.nextProductionOccurrence = 0;
            this.isAtEndOfPath = true;
        }
        else {
            this.nextProductionName = this.ruleStack.pop();
            this.nextProductionOccurrence = this.occurrenceStack.pop();
        }
    };
    return AbstractNextPossibleTokensWalker;
}(rest_1.RestWalker));
exports.AbstractNextPossibleTokensWalker = AbstractNextPossibleTokensWalker;
var NextAfterTokenWalker = (function (_super) {
    __extends(NextAfterTokenWalker, _super);
    function NextAfterTokenWalker(topProd, path) {
        _super.call(this, topProd, path);
        this.path = path;
        this.nextTerminalName = "";
        this.nextTerminalOccurrence = 0;
        this.nextTerminalName = tokens_public_1.tokenName(this.path.lastTok);
        this.nextTerminalOccurrence = this.path.lastTokOccurrence;
    }
    NextAfterTokenWalker.prototype.walkTerminal = function (terminal, currRest, prevRest) {
        if (this.isAtEndOfPath && tokens_public_1.tokenName(terminal.terminalType) === this.nextTerminalName &&
            terminal.occurrenceInParent === this.nextTerminalOccurrence && !(this.found)) {
            var fullRest = currRest.concat(prevRest);
            var restProd = new gast_public_1.gast.Flat(fullRest);
            this.possibleTokTypes = first_1.first(restProd);
            this.found = true;
        }
    };
    return NextAfterTokenWalker;
}(AbstractNextPossibleTokensWalker));
exports.NextAfterTokenWalker = NextAfterTokenWalker;
var NextInsideOptionWalker = (function (_super) {
    __extends(NextInsideOptionWalker, _super);
    function NextInsideOptionWalker(topProd, path) {
        _super.call(this, topProd, path);
        this.path = path;
        this.nextOptionOccurrence = 0;
        this.nextOptionOccurrence = this.path.occurrence;
    }
    NextInsideOptionWalker.prototype.walkOption = function (optionProd, currRest, prevRest) {
        if (this.isAtEndOfPath && optionProd.occurrenceInParent === this.nextOptionOccurrence && !(this.found)) {
            var restProd = new gast_public_1.gast.Flat(optionProd.definition);
            this.possibleTokTypes = first_1.first(restProd);
            this.found = true;
        }
        else {
            _super.prototype.walkOption.call(this, optionProd, currRest, prevRest);
        }
    };
    return NextInsideOptionWalker;
}(AbstractNextPossibleTokensWalker));
exports.NextInsideOptionWalker = NextInsideOptionWalker;
var NextInsideManyWalker = (function (_super) {
    __extends(NextInsideManyWalker, _super);
    function NextInsideManyWalker(topProd, path) {
        _super.call(this, topProd, path);
        this.path = path;
        this.nextOccurrence = 0;
        this.nextOccurrence = this.path.occurrence;
    }
    NextInsideManyWalker.prototype.walkMany = function (manyProd, currRest, prevRest) {
        if (this.isAtEndOfPath && manyProd.occurrenceInParent === this.nextOccurrence && !(this.found)) {
            var restProd = new gast_public_1.gast.Flat(manyProd.definition);
            this.possibleTokTypes = first_1.first(restProd);
            this.found = true;
        }
        else {
            _super.prototype.walkMany.call(this, manyProd, currRest, prevRest);
        }
    };
    return NextInsideManyWalker;
}(AbstractNextPossibleTokensWalker));
exports.NextInsideManyWalker = NextInsideManyWalker;
var NextInsideManySepWalker = (function (_super) {
    __extends(NextInsideManySepWalker, _super);
    function NextInsideManySepWalker(topProd, path) {
        _super.call(this, topProd, path);
        this.path = path;
        this.nextOccurrence = 0;
        this.nextOccurrence = this.path.occurrence;
    }
    NextInsideManySepWalker.prototype.walkManySep = function (manySepProd, currRest, prevRest) {
        if (this.isAtEndOfPath && manySepProd.occurrenceInParent === this.nextOccurrence && !(this.found)) {
            var restProd = new gast_public_1.gast.Flat(manySepProd.definition);
            this.possibleTokTypes = first_1.first(restProd);
            this.found = true;
        }
        else {
            _super.prototype.walkManySep.call(this, manySepProd, currRest, prevRest);
        }
    };
    return NextInsideManySepWalker;
}(AbstractNextPossibleTokensWalker));
exports.NextInsideManySepWalker = NextInsideManySepWalker;
var NextInsideAtLeastOneWalker = (function (_super) {
    __extends(NextInsideAtLeastOneWalker, _super);
    function NextInsideAtLeastOneWalker(topProd, path) {
        _super.call(this, topProd, path);
        this.path = path;
        this.nextOccurrence = 0;
        this.nextOccurrence = this.path.occurrence;
    }
    NextInsideAtLeastOneWalker.prototype.walkAtLeastOne = function (atLeastOneProd, currRest, prevRest) {
        if (this.isAtEndOfPath && atLeastOneProd.occurrenceInParent === this.nextOccurrence && !(this.found)) {
            var restProd = new gast_public_1.gast.Flat(atLeastOneProd.definition);
            this.possibleTokTypes = first_1.first(restProd);
            this.found = true;
        }
        else {
            _super.prototype.walkAtLeastOne.call(this, atLeastOneProd, currRest, prevRest);
        }
    };
    return NextInsideAtLeastOneWalker;
}(AbstractNextPossibleTokensWalker));
exports.NextInsideAtLeastOneWalker = NextInsideAtLeastOneWalker;
var NextInsideAtLeastOneSepWalker = (function (_super) {
    __extends(NextInsideAtLeastOneSepWalker, _super);
    function NextInsideAtLeastOneSepWalker(topProd, path) {
        _super.call(this, topProd, path);
        this.path = path;
        this.nextOccurrence = 0;
        this.nextOccurrence = this.path.occurrence;
    }
    NextInsideAtLeastOneSepWalker.prototype.walkAtLeastOneSep = function (atLeastOneSepProd, currRest, prevRest) {
        if (this.isAtEndOfPath && atLeastOneSepProd.occurrenceInParent === this.nextOccurrence && !(this.found)) {
            var restProd = new gast_public_1.gast.Flat(atLeastOneSepProd.definition);
            this.possibleTokTypes = first_1.first(restProd);
            this.found = true;
        }
        else {
            _super.prototype.walkAtLeastOneSep.call(this, atLeastOneSepProd, currRest, prevRest);
        }
    };
    return NextInsideAtLeastOneSepWalker;
}(AbstractNextPossibleTokensWalker));
exports.NextInsideAtLeastOneSepWalker = NextInsideAtLeastOneSepWalker;
var NextInsideOrWalker = (function (_super) {
    __extends(NextInsideOrWalker, _super);
    function NextInsideOrWalker(topRule, occurrence) {
        _super.call(this);
        this.topRule = topRule;
        this.occurrence = occurrence;
        this.result = [];
    }
    NextInsideOrWalker.prototype.startWalking = function () {
        this.walk(this.topRule);
        return this.result;
    };
    NextInsideOrWalker.prototype.walkOr = function (orProd, currRest, prevRest) {
        if (orProd.occurrenceInParent === this.occurrence) {
            this.result = utils_1.map(orProd.definition, function (alt) {
                var altWrapper = new gast_public_1.gast.Flat([alt]);
                return first_1.first(altWrapper);
            });
        }
        else {
            _super.prototype.walkOr.call(this, orProd, currRest, prevRest);
        }
    };
    return NextInsideOrWalker;
}(rest_1.RestWalker));
exports.NextInsideOrWalker = NextInsideOrWalker;
/**
 * This walker only "walks" a single "TOP" level in the Grammar Ast, this means
 * it never "follows" production refs
 */
var AbstractNextTerminalAfterProductionWalker = (function (_super) {
    __extends(AbstractNextTerminalAfterProductionWalker, _super);
    function AbstractNextTerminalAfterProductionWalker(topRule, occurrence) {
        _super.call(this);
        this.topRule = topRule;
        this.occurrence = occurrence;
        this.result = { token: undefined, occurrence: undefined, isEndOfRule: undefined };
    }
    AbstractNextTerminalAfterProductionWalker.prototype.startWalking = function () {
        this.walk(this.topRule);
        return this.result;
    };
    return AbstractNextTerminalAfterProductionWalker;
}(rest_1.RestWalker));
exports.AbstractNextTerminalAfterProductionWalker = AbstractNextTerminalAfterProductionWalker;
var NextTerminalAfterManyWalker = (function (_super) {
    __extends(NextTerminalAfterManyWalker, _super);
    function NextTerminalAfterManyWalker() {
        _super.apply(this, arguments);
    }
    NextTerminalAfterManyWalker.prototype.walkMany = function (manyProd, currRest, prevRest) {
        if (manyProd.occurrenceInParent === this.occurrence) {
            var firstAfterMany = utils_1.first(currRest.concat(prevRest));
            this.result.isEndOfRule = firstAfterMany === undefined;
            if (firstAfterMany instanceof gast_public_1.gast.Terminal) {
                this.result.token = firstAfterMany.terminalType;
                this.result.occurrence = firstAfterMany.occurrenceInParent;
            }
        }
        else {
            _super.prototype.walkMany.call(this, manyProd, currRest, prevRest);
        }
    };
    return NextTerminalAfterManyWalker;
}(AbstractNextTerminalAfterProductionWalker));
exports.NextTerminalAfterManyWalker = NextTerminalAfterManyWalker;
var NextTerminalAfterManySepWalker = (function (_super) {
    __extends(NextTerminalAfterManySepWalker, _super);
    function NextTerminalAfterManySepWalker() {
        _super.apply(this, arguments);
    }
    NextTerminalAfterManySepWalker.prototype.walkManySep = function (manySepProd, currRest, prevRest) {
        if (manySepProd.occurrenceInParent === this.occurrence) {
            var firstAfterManySep = utils_1.first(currRest.concat(prevRest));
            this.result.isEndOfRule = firstAfterManySep === undefined;
            if (firstAfterManySep instanceof gast_public_1.gast.Terminal) {
                this.result.token = firstAfterManySep.terminalType;
                this.result.occurrence = firstAfterManySep.occurrenceInParent;
            }
        }
        else {
            _super.prototype.walkManySep.call(this, manySepProd, currRest, prevRest);
        }
    };
    return NextTerminalAfterManySepWalker;
}(AbstractNextTerminalAfterProductionWalker));
exports.NextTerminalAfterManySepWalker = NextTerminalAfterManySepWalker;
var NextTerminalAfterAtLeastOneWalker = (function (_super) {
    __extends(NextTerminalAfterAtLeastOneWalker, _super);
    function NextTerminalAfterAtLeastOneWalker() {
        _super.apply(this, arguments);
    }
    NextTerminalAfterAtLeastOneWalker.prototype.walkAtLeastOne = function (atLeastOneProd, currRest, prevRest) {
        if (atLeastOneProd.occurrenceInParent === this.occurrence) {
            var firstAfterAtLeastOne = utils_1.first(currRest.concat(prevRest));
            this.result.isEndOfRule = firstAfterAtLeastOne === undefined;
            if (firstAfterAtLeastOne instanceof gast_public_1.gast.Terminal) {
                this.result.token = firstAfterAtLeastOne.terminalType;
                this.result.occurrence = firstAfterAtLeastOne.occurrenceInParent;
            }
        }
        else {
            _super.prototype.walkAtLeastOne.call(this, atLeastOneProd, currRest, prevRest);
        }
    };
    return NextTerminalAfterAtLeastOneWalker;
}(AbstractNextTerminalAfterProductionWalker));
exports.NextTerminalAfterAtLeastOneWalker = NextTerminalAfterAtLeastOneWalker;
// TODO: reduce code duplication in the AfterWalkers
var NextTerminalAfterAtLeastOneSepWalker = (function (_super) {
    __extends(NextTerminalAfterAtLeastOneSepWalker, _super);
    function NextTerminalAfterAtLeastOneSepWalker() {
        _super.apply(this, arguments);
    }
    NextTerminalAfterAtLeastOneSepWalker.prototype.walkAtLeastOneSep = function (atleastOneSepProd, currRest, prevRest) {
        if (atleastOneSepProd.occurrenceInParent === this.occurrence) {
            var firstAfterfirstAfterAtLeastOneSep = utils_1.first(currRest.concat(prevRest));
            this.result.isEndOfRule = firstAfterfirstAfterAtLeastOneSep === undefined;
            if (firstAfterfirstAfterAtLeastOneSep instanceof gast_public_1.gast.Terminal) {
                this.result.token = firstAfterfirstAfterAtLeastOneSep.terminalType;
                this.result.occurrence = firstAfterfirstAfterAtLeastOneSep.occurrenceInParent;
            }
        }
        else {
            _super.prototype.walkAtLeastOneSep.call(this, atleastOneSepProd, currRest, prevRest);
        }
    };
    return NextTerminalAfterAtLeastOneSepWalker;
}(AbstractNextTerminalAfterProductionWalker));
exports.NextTerminalAfterAtLeastOneSepWalker = NextTerminalAfterAtLeastOneSepWalker;
