"use strict";
var tokens_public_1 = require("./tokens_public");
var lexer_public_1 = require("./lexer_public");
var utils_1 = require("../utils/utils");
var PATTERN = "PATTERN";
function analyzeTokenClasses(tokenClasses) {
    var onlyRelevantClasses = utils_1.reject(tokenClasses, function (currClass) {
        return currClass[PATTERN] === lexer_public_1.Lexer.NA;
    });
    var allTransformedPatterns = utils_1.map(onlyRelevantClasses, function (currClass) {
        return addStartOfInput(currClass[PATTERN]);
    });
    var allPatternsToClass = utils_1.zipObject(allTransformedPatterns, onlyRelevantClasses);
    var patternIdxToClass = utils_1.map(allTransformedPatterns, function (pattern) {
        return allPatternsToClass[pattern.toString()];
    });
    var patternIdxToGroup = utils_1.map(onlyRelevantClasses, function (clazz) {
        var groupName = clazz.GROUP;
        if (groupName === lexer_public_1.Lexer.SKIPPED) {
            return undefined;
        }
        else if (utils_1.isString(groupName)) {
            return groupName;
        }/* istanbul ignore else */ 
        else if (utils_1.isUndefined(groupName)) {
            return "default";
        }
        else {
            /* istanbul ignore next */ throw Error("non exhaustive match");
        }
    });
    var patternIdxToLongerAltIdx = utils_1.map(onlyRelevantClasses, function (clazz) {
        var longerAltClass = clazz.LONGER_ALT;
        if (longerAltClass) {
            var longerAltIdx = utils_1.indexOf(onlyRelevantClasses, longerAltClass);
            return longerAltIdx;
        }
    });
    var patternIdxToPushMode = utils_1.map(onlyRelevantClasses, function (clazz) { return clazz.PUSH_MODE; });
    var patternIdxToPopMode = utils_1.map(onlyRelevantClasses, function (clazz) { return utils_1.has(clazz, "POP_MODE"); });
    var patternIdxToCanLineTerminator = utils_1.map(allTransformedPatterns, function (pattern) {
        // TODO: unicode escapes of line terminators too?
        return /\\n|\\r|\\s/g.test(pattern.source);
    });
    var emptyGroups = utils_1.reduce(onlyRelevantClasses, function (acc, clazz) {
        var groupName = clazz.GROUP;
        if (utils_1.isString(groupName)) {
            acc[groupName] = [];
        }
        return acc;
    }, {});
    return {
        allPatterns: allTransformedPatterns,
        patternIdxToClass: patternIdxToClass,
        patternIdxToGroup: patternIdxToGroup,
        patternIdxToLongerAltIdx: patternIdxToLongerAltIdx,
        patternIdxToCanLineTerminator: patternIdxToCanLineTerminator,
        patternIdxToPushMode: patternIdxToPushMode,
        patternIdxToPopMode: patternIdxToPopMode,
        emptyGroups: emptyGroups
    };
}
exports.analyzeTokenClasses = analyzeTokenClasses;
function validatePatterns(tokenClasses, validModesNames) {
    var errors = [];
    var missingResult = findMissingPatterns(tokenClasses);
    var validTokenClasses = missingResult.valid;
    errors = errors.concat(missingResult.errors);
    var invalidResult = findInvalidPatterns(validTokenClasses);
    validTokenClasses = invalidResult.valid;
    errors = errors.concat(invalidResult.errors);
    errors = errors.concat(findEndOfInputAnchor(validTokenClasses));
    errors = errors.concat(findUnsupportedFlags(validTokenClasses));
    errors = errors.concat(findDuplicatePatterns(validTokenClasses));
    errors = errors.concat(findInvalidGroupType(validTokenClasses));
    errors = errors.concat(findModesThatDoNotExist(validTokenClasses, validModesNames));
    return errors;
}
exports.validatePatterns = validatePatterns;
function findMissingPatterns(tokenClasses) {
    var tokenClassesWithMissingPattern = utils_1.filter(tokenClasses, function (currClass) {
        return !utils_1.has(currClass, PATTERN);
    });
    var errors = utils_1.map(tokenClassesWithMissingPattern, function (currClass) {
        return {
            message: "Token class: ->" + tokens_public_1.tokenName(currClass) + "<- missing static 'PATTERN' property",
            type: lexer_public_1.LexerDefinitionErrorType.MISSING_PATTERN,
            tokenClasses: [currClass]
        };
    });
    var valid = utils_1.difference(tokenClasses, tokenClassesWithMissingPattern);
    return { errors: errors, valid: valid };
}
exports.findMissingPatterns = findMissingPatterns;
function findInvalidPatterns(tokenClasses) {
    var tokenClassesWithInvalidPattern = utils_1.filter(tokenClasses, function (currClass) {
        var pattern = currClass[PATTERN];
        return !utils_1.isRegExp(pattern);
    });
    var errors = utils_1.map(tokenClassesWithInvalidPattern, function (currClass) {
        return {
            message: "Token class: ->" + tokens_public_1.tokenName(currClass) + "<- static 'PATTERN' can only be a RegExp",
            type: lexer_public_1.LexerDefinitionErrorType.INVALID_PATTERN,
            tokenClasses: [currClass]
        };
    });
    var valid = utils_1.difference(tokenClasses, tokenClassesWithInvalidPattern);
    return { errors: errors, valid: valid };
}
exports.findInvalidPatterns = findInvalidPatterns;
var end_of_input = /[^\\][\$]/;
function findEndOfInputAnchor(tokenClasses) {
    var invalidRegex = utils_1.filter(tokenClasses, function (currClass) {
        var pattern = currClass[PATTERN];
        return end_of_input.test(pattern.source);
    });
    var errors = utils_1.map(invalidRegex, function (currClass) {
        return {
            message: "Token class: ->" + tokens_public_1.tokenName(currClass) + "<- static 'PATTERN' cannot contain end of input anchor '$'",
            type: lexer_public_1.LexerDefinitionErrorType.EOI_ANCHOR_FOUND,
            tokenClasses: [currClass]
        };
    });
    return errors;
}
exports.findEndOfInputAnchor = findEndOfInputAnchor;
function findUnsupportedFlags(tokenClasses) {
    var invalidFlags = utils_1.filter(tokenClasses, function (currClass) {
        var pattern = currClass[PATTERN];
        return pattern instanceof RegExp && (pattern.multiline || pattern.global);
    });
    var errors = utils_1.map(invalidFlags, function (currClass) {
        return {
            message: "Token class: ->" + tokens_public_1.tokenName(currClass) +
                "<- static 'PATTERN' may NOT contain global('g') or multiline('m')",
            type: lexer_public_1.LexerDefinitionErrorType.UNSUPPORTED_FLAGS_FOUND,
            tokenClasses: [currClass]
        };
    });
    return errors;
}
exports.findUnsupportedFlags = findUnsupportedFlags;
// This can only test for identical duplicate RegExps, not semantically equivalent ones.
function findDuplicatePatterns(tokenClasses) {
    var found = [];
    var identicalPatterns = utils_1.map(tokenClasses, function (outerClass) {
        return utils_1.reduce(tokenClasses, function (result, innerClass) {
            if ((outerClass.PATTERN.source === innerClass.PATTERN.source) && !utils_1.contains(found, innerClass) &&
                innerClass.PATTERN !== lexer_public_1.Lexer.NA) {
                // this avoids duplicates in the result, each class may only appear in one "set"
                // in essence we are creating Equivalence classes on equality relation.
                found.push(innerClass);
                result.push(innerClass);
                return result;
            }
            return result;
        }, []);
    });
    identicalPatterns = utils_1.compact(identicalPatterns);
    var duplicatePatterns = utils_1.filter(identicalPatterns, function (currIdenticalSet) {
        return currIdenticalSet.length > 1;
    });
    var errors = utils_1.map(duplicatePatterns, function (setOfIdentical) {
        var classNames = utils_1.map(setOfIdentical, function (currClass) {
            return tokens_public_1.tokenName(currClass);
        });
        var dupPatternSrc = utils_1.first(setOfIdentical).PATTERN;
        return {
            message: ("The same RegExp pattern ->" + dupPatternSrc + "<-") +
                ("has been used in all the following classes: " + classNames.join(", ") + " <-"),
            type: lexer_public_1.LexerDefinitionErrorType.DUPLICATE_PATTERNS_FOUND,
            tokenClasses: setOfIdentical
        };
    });
    return errors;
}
exports.findDuplicatePatterns = findDuplicatePatterns;
function findInvalidGroupType(tokenClasses) {
    var invalidTypes = utils_1.filter(tokenClasses, function (clazz) {
        if (!utils_1.has(clazz, "GROUP")) {
            return false;
        }
        var group = clazz.GROUP;
        return group !== lexer_public_1.Lexer.SKIPPED &&
            group !== lexer_public_1.Lexer.NA && !utils_1.isString(group);
    });
    var errors = utils_1.map(invalidTypes, function (currClass) {
        return {
            message: "Token class: ->" + tokens_public_1.tokenName(currClass) + "<- static 'GROUP' can only be Lexer.SKIPPED/Lexer.NA/A String",
            type: lexer_public_1.LexerDefinitionErrorType.INVALID_GROUP_TYPE_FOUND,
            tokenClasses: [currClass]
        };
    });
    return errors;
}
exports.findInvalidGroupType = findInvalidGroupType;
function findModesThatDoNotExist(tokenClasses, validModes) {
    var invalidModes = utils_1.filter(tokenClasses, function (clazz) {
        return clazz.PUSH_MODE !== undefined && !utils_1.contains(validModes, clazz.PUSH_MODE);
    });
    var errors = utils_1.map(invalidModes, function (clazz) {
        var msg = ("Token class: ->" + tokens_public_1.tokenName(clazz) + "<- static 'PUSH_MODE' value cannot refer to a Lexer Mode ->" + clazz.PUSH_MODE + "<-") +
            "which does not exist";
        return {
            message: msg,
            type: lexer_public_1.LexerDefinitionErrorType.PUSH_MODE_DOES_NOT_EXIST,
            tokenClasses: [clazz]
        };
    });
    return errors;
}
exports.findModesThatDoNotExist = findModesThatDoNotExist;
function addStartOfInput(pattern) {
    var flags = pattern.ignoreCase ? "i" : "";
    // always wrapping in a none capturing group preceded by '^' to make sure matching can only work on start of input.
    // duplicate/redundant start of input markers have no meaning (/^^^^A/ === /^A/)
    return new RegExp("^(?:" + pattern.source + ")", flags);
}
exports.addStartOfInput = addStartOfInput;
function countLineTerminators(text) {
    var lineTerminators = 0;
    var currOffset = 0;
    while (currOffset < text.length) {
        var c = text.charCodeAt(currOffset);
        if (c === 10) {
            lineTerminators++;
        }
        else if (c === 13) {
            if (currOffset !== text.length - 1 &&
                text.charCodeAt(currOffset + 1) === 10) {
            }
            else {
                lineTerminators++;
            }
        }
        currOffset++;
    }
    return lineTerminators;
}
exports.countLineTerminators = countLineTerminators;
