"use strict";
var gast_public_1 = require("./gast_public");
var first_1 = require("./first");
var interpreter_1 = require("./interpreter");
var utils_1 = require("../../utils/utils");
var tokens_public_1 = require("../../scan/tokens_public");
function buildLookaheadForTopLevel(rule) {
    var restProd = new gast_public_1.gast.Flat(rule.definition);
    var possibleTokTypes = first_1.first(restProd);
    return getSimpleLookahead(possibleTokTypes);
}
exports.buildLookaheadForTopLevel = buildLookaheadForTopLevel;
function buildLookaheadForOption(optionOccurrence, ruleGrammar) {
    return buildLookAheadForGrammarProd(interpreter_1.NextInsideOptionWalker, optionOccurrence, ruleGrammar);
}
exports.buildLookaheadForOption = buildLookaheadForOption;
function buildLookaheadForMany(manyOccurrence, ruleGrammar) {
    return buildLookAheadForGrammarProd(interpreter_1.NextInsideManyWalker, manyOccurrence, ruleGrammar);
}
exports.buildLookaheadForMany = buildLookaheadForMany;
function buildLookaheadForManySep(manyOccurrence, ruleGrammar) {
    return buildLookAheadForGrammarProd(interpreter_1.NextInsideManySepWalker, manyOccurrence, ruleGrammar);
}
exports.buildLookaheadForManySep = buildLookaheadForManySep;
function buildLookaheadForAtLeastOne(manyOccurrence, ruleGrammar) {
    return buildLookAheadForGrammarProd(interpreter_1.NextInsideAtLeastOneWalker, manyOccurrence, ruleGrammar);
}
exports.buildLookaheadForAtLeastOne = buildLookaheadForAtLeastOne;
function buildLookaheadForAtLeastOneSep(manyOccurrence, ruleGrammar) {
    return buildLookAheadForGrammarProd(interpreter_1.NextInsideAtLeastOneSepWalker, manyOccurrence, ruleGrammar);
}
exports.buildLookaheadForAtLeastOneSep = buildLookaheadForAtLeastOneSep;
function buildLookaheadForOr(orOccurrence, ruleGrammar, ignoreAmbiguities) {
    if (ignoreAmbiguities === void 0) { ignoreAmbiguities = false; }
    var alternativesTokens = new interpreter_1.NextInsideOrWalker(ruleGrammar, orOccurrence).startWalking();
    if (!ignoreAmbiguities) {
        checkForOrAmbiguities(alternativesTokens, orOccurrence, ruleGrammar);
    }
    var hasLastAnEmptyAlt = utils_1.isEmpty(utils_1.last(alternativesTokens));
    if (hasLastAnEmptyAlt) {
        var lastIdx_1 = alternativesTokens.length - 1;
        /**
         * This will return the Index of the alternative to take or the <lastidx> if only the empty alternative matched
         */
        return function chooseAlternativeWithEmptyAlt() {
            var nextToken = this.NEXT_TOKEN();
            // checking only until length - 1 because there is nothing to check in an empty alternative, it is always valid
            for (var i = 0; i < lastIdx_1; i++) {
                var currAltTokens = alternativesTokens[i];
                // 'for' loop for performance reasons.
                for (var j = 0; j < currAltTokens.length; j++) {
                    if (nextToken instanceof currAltTokens[j]) {
                        return i;
                    }
                }
            }
            // an OR(alternation) with an empty alternative will always match
            return lastIdx_1;
        };
    }
    else {
        /**
         * This will return the Index of the alternative to take or -1 if none of the alternatives match
         */
        return function chooseAlternative() {
            var nextToken = this.NEXT_TOKEN();
            for (var i = 0; i < alternativesTokens.length; i++) {
                var currAltTokens = alternativesTokens[i];
                // 'for' loop for performance reasons.
                for (var j = 0; j < currAltTokens.length; j++) {
                    if (nextToken instanceof currAltTokens[j]) {
                        return i;
                    }
                }
            }
            return -1;
        };
    }
}
exports.buildLookaheadForOr = buildLookaheadForOr;
function checkForOrAmbiguities(alternativesTokens, orOccurrence, ruleGrammar) {
    var altsAmbiguityErrors = checkAlternativesAmbiguities(alternativesTokens);
    if (!utils_1.isEmpty(altsAmbiguityErrors)) {
        var errorMessages = utils_1.map(altsAmbiguityErrors, function (currAmbiguity) {
            return ("Ambiguous alternatives: <" + currAmbiguity.alts.join(" ,") + "> in <OR" + orOccurrence + "> inside <" + ruleGrammar.name + "> ") +
                ("Rule, <" + tokens_public_1.tokenName(currAmbiguity.token) + "> may appears as the first Terminal in all these alternatives.\n");
        });
        throw new Error(errorMessages.join("\n ---------------- \n") +
            "To Resolve this, either: \n" +
            "1. refactor your grammar to be LL(1)\n" +
            "2. provide explicit lookahead functions in the form {WHEN:laFunc, THEN_DO:...}\n" +
            "3. Add ignore arg to this OR Production:\n" +
            "OR([], 'msg', recognizer.IGNORE_AMBIGUITIES)\n" +
            "In that case the parser will always pick the first alternative that" +
            " matches and ignore all the others");
    }
}
exports.checkForOrAmbiguities = checkForOrAmbiguities;
function checkAlternativesAmbiguities(alternativesTokens) {
    var allTokensFlat = utils_1.flatten(alternativesTokens);
    var uniqueTokensFlat = utils_1.uniq(allTokensFlat);
    var tokensToAltsIndicesItAppearsIn = utils_1.map(uniqueTokensFlat, function (seekToken) {
        var altsCurrTokenAppearsIn = utils_1.pick(alternativesTokens, function (altToLookIn) {
            return utils_1.find(altToLookIn, function (currToken) {
                return currToken === seekToken;
            });
        });
        var altsIndicesTokenAppearsIn = utils_1.map(utils_1.keys(altsCurrTokenAppearsIn), function (index) {
            return parseInt(index, 10) + 1;
        });
        return { token: seekToken, alts: altsIndicesTokenAppearsIn };
    });
    var tokensToAltsIndicesWithAmbiguity = utils_1.filter(tokensToAltsIndicesItAppearsIn, function (tokAndAltsItAppearsIn) {
        return tokAndAltsItAppearsIn.alts.length > 1;
    });
    return tokensToAltsIndicesWithAmbiguity;
}
exports.checkAlternativesAmbiguities = checkAlternativesAmbiguities;
function buildLookAheadForGrammarProd(prodWalkerConstructor, ruleOccurrence, ruleGrammar) {
    var path = {
        ruleStack: [ruleGrammar.name],
        occurrenceStack: [1],
        occurrence: ruleOccurrence
    };
    var walker = new prodWalkerConstructor(ruleGrammar, path);
    var possibleNextTokTypes = walker.startWalking();
    return getSimpleLookahead(possibleNextTokTypes);
}
function getSimpleLookahead(possibleNextTokTypes) {
    return function () {
        var nextToken = this.NEXT_TOKEN();
        for (var j = 0; j < possibleNextTokTypes.length; j++) {
            if (nextToken instanceof possibleNextTokTypes[j]) {
                return true;
            }
        }
        return false;
    };
}
