"use strict";
var utils_1 = require("../utils/utils");
var tokens_public_1 = require("./tokens_public");
var lang_extensions_1 = require("../lang/lang_extensions");
function fillUpLineToOffset(lineToOffset, text) {
    var currLine = 0;
    var currOffset = 0;
    // line 1 (idx 0 in the array) always starts at offset 0
    lineToOffset.push(0);
    while (currOffset < text.length) {
        var c = text.charCodeAt(currOffset);
        if (c === 10) {
            currLine++;
            // +1 because the next line starts only AFTER the "\n"
            lineToOffset.push(currOffset + 1);
        }
        else if (c === 13) {
            if (currOffset !== text.length - 1 &&
                text.charCodeAt(currOffset + 1) === 10) {
                // +2 because the next line starts only AFTER the "\r\n"
                lineToOffset.push(currOffset + 2);
                // "consume" two chars
                currOffset++;
            }
            else {
                currLine++;
                // +1 because the next line starts only AFTER the "\r"
                lineToOffset.push(currOffset + 1);
            }
        }
        currOffset++;
    }
    // to make the data structure consistent
    lineToOffset.push(Infinity);
}
exports.fillUpLineToOffset = fillUpLineToOffset;
function getStartLineFromLineToOffset(startOffset, lineToOffset) {
    return findLineOfOffset(startOffset, lineToOffset);
}
exports.getStartLineFromLineToOffset = getStartLineFromLineToOffset;
function getEndLineFromLineToOffset(endOffset, lineToOffset) {
    return findLineOfOffset(endOffset, lineToOffset);
}
exports.getEndLineFromLineToOffset = getEndLineFromLineToOffset;
function getStartColumnFromLineToOffset(startOffset, lineToOffset) {
    return findColumnOfOffset(startOffset, lineToOffset);
}
exports.getStartColumnFromLineToOffset = getStartColumnFromLineToOffset;
function getEndColumnFromLineToOffset(endOffset, lineToOffset) {
    // none inclusive
    return findColumnOfOffset(endOffset, lineToOffset);
}
exports.getEndColumnFromLineToOffset = getEndColumnFromLineToOffset;
/**
 *  Modification of a binary search to seek
 */
function findLineOfOffset(targetOffset, lineToOffset) {
    var lowIdx = 0;
    var highIdx = lineToOffset.length - 1;
    var found = false;
    var line = -1;
    while (!found) {
        var middleIdx = Math.floor((highIdx + lowIdx) / 2);
        var middleOffset = lineToOffset[middleIdx];
        var middleNextOffset = lineToOffset[middleIdx + 1];
        if (middleOffset <= targetOffset &&
            middleNextOffset > targetOffset) {
            found = true;
            line = middleIdx;
        }
        else if (middleOffset > targetOffset) {
            highIdx = middleIdx;
        }
        else if (middleNextOffset < targetOffset) {
            lowIdx = middleIdx;
        }/* istanbul ignore else */ 
        else if (middleNextOffset === targetOffset) {
            found = true;
            line = middleIdx + 1;
        }
        else {
            /* istanbul ignore next */ throw Error("non exhaustive match");
        }
    }
    // +1 because lines are counted from 1 while array indices are zero based.
    return line + 1;
}
function findColumnOfOffset(offset, lineToOffset) {
    var line = findLineOfOffset(offset, lineToOffset);
    // +1 because columns always start at 1
    return offset - lineToOffset[line - 1] + 1;
}
function tokenStructuredMatcher(tokInstance, tokConstructor) {
    if (tokInstance.tokenType === tokConstructor.tokenType) {
        return true;
    }
    else if (tokConstructor.extendingTokenTypes.length > 0) {
        var extendingTokenTypes = tokConstructor.extendingTokenTypes;
        var extendingTokenTypesLength = extendingTokenTypes.length;
        for (var i = 0; i < extendingTokenTypesLength; i++) {
            if (extendingTokenTypes[i] === tokInstance.tokenType) {
                return true;
            }
        }
        return false;
    }
    else {
        return false;
    }
}
exports.tokenStructuredMatcher = tokenStructuredMatcher;
function tokenInstanceofMatcher(tokInstance, tokConstructor) {
    return tokInstance instanceof tokConstructor;
}
exports.tokenInstanceofMatcher = tokenInstanceofMatcher;
function tokenClassIdentity(tokenConstructor) {
    // return tokenName(tokenConstructor)
    return tokenConstructor.tokenType;
}
exports.tokenClassIdentity = tokenClassIdentity;
function tokenInstanceIdentity(tokenInstance) {
    return tokenInstance.constructor.tokenType;
}
exports.tokenInstanceIdentity = tokenInstanceIdentity;
function tokenStructuredIdentity(token) {
    return token.tokenType;
}
exports.tokenStructuredIdentity = tokenStructuredIdentity;
function isBaseTokenOrObject(tokClass) {
    return isBaseTokenClass(tokClass) || tokClass === Object;
}
exports.isBaseTokenOrObject = isBaseTokenOrObject;
function isBaseTokenClass(tokClass) {
    return tokClass === tokens_public_1.Token || tokClass === tokens_public_1.LazyToken || tokClass === tokens_public_1.SimpleLazyToken;
}
exports.isBaseTokenClass = isBaseTokenClass;
exports.tokenShortNameIdx = 1;
exports.tokenIdxToClass = new lang_extensions_1.HashTable();
function augmentTokenClasses(tokenClasses) {
    // 1. collect the parent Token classes as well.
    var tokenClassesAndParents = expandTokenHierarchy(tokenClasses);
    // 2. add required tokenType and extendingTokenTypes properties
    assignTokenDefaultProps(tokenClassesAndParents);
    // 3. fill up the extendingTokenTypes
    assignExtendingTokensProp(tokenClassesAndParents);
}
exports.augmentTokenClasses = augmentTokenClasses;
function expandTokenHierarchy(tokenClasses) {
    var tokenClassesAndParents = utils_1.cloneArr(tokenClasses);
    utils_1.forEach(tokenClasses, function (currTokClass) {
        var currParentClass = utils_1.getSuperClass(currTokClass);
        while (!isBaseTokenOrObject(currParentClass)) {
            if (!utils_1.contains(tokenClassesAndParents, currParentClass)) {
                tokenClassesAndParents.push(currParentClass);
            }
            currParentClass = utils_1.getSuperClass(currParentClass);
        }
    });
    return tokenClassesAndParents;
}
exports.expandTokenHierarchy = expandTokenHierarchy;
function assignTokenDefaultProps(tokenClasses) {
    utils_1.forEach(tokenClasses, function (currTokClass) {
        if (!hasShortKeyProperty(currTokClass)) {
            exports.tokenIdxToClass.put(exports.tokenShortNameIdx, currTokClass);
            currTokClass.tokenType = exports.tokenShortNameIdx++;
        }
        if (!hasExtendingTokensTypesProperty(currTokClass)) {
            currTokClass.extendingTokenTypes = [];
        }
    });
}
exports.assignTokenDefaultProps = assignTokenDefaultProps;
function assignExtendingTokensProp(tokenClasses) {
    utils_1.forEach(tokenClasses, function (currTokClass) {
        var currSubClassesExtendingTypes = [currTokClass.tokenType];
        var currParentClass = utils_1.getSuperClass(currTokClass);
        while (!isBaseTokenClass(currParentClass) && currParentClass !== Object) {
            var newExtendingTypes = utils_1.difference(currSubClassesExtendingTypes, currParentClass.extendingTokenTypes);
            currParentClass.extendingTokenTypes = currParentClass.extendingTokenTypes.concat(newExtendingTypes);
            currSubClassesExtendingTypes.push(currParentClass.tokenType);
            currParentClass = utils_1.getSuperClass(currParentClass);
        }
    });
}
exports.assignExtendingTokensProp = assignExtendingTokensProp;
function hasShortKeyProperty(tokClass) {
    return utils_1.has(tokClass, "tokenType");
}
exports.hasShortKeyProperty = hasShortKeyProperty;
function hasExtendingTokensTypesProperty(tokClass) {
    return utils_1.has(tokClass, "extendingTokenTypes");
}
exports.hasExtendingTokensTypesProperty = hasExtendingTokensTypesProperty;
function createSimpleLazyToken(startOffset, endOffset, tokClass, cacheData) {
    return {
        startOffset: startOffset,
        endOffset: endOffset,
        tokenType: tokClass.tokenType,
        cacheData: cacheData
    };
}
exports.createSimpleLazyToken = createSimpleLazyToken;
function createLazyTokenInstance(startOffset, endOffset, tokClass, cacheData) {
    return new tokClass(startOffset, endOffset, cacheData);
}
exports.createLazyTokenInstance = createLazyTokenInstance;
function isInheritanceBasedToken(token) {
    return token instanceof tokens_public_1.Token || token instanceof tokens_public_1.LazyToken;
}
exports.isInheritanceBasedToken = isInheritanceBasedToken;
function getImageFromLazyToken(lazyToken) {
    if (lazyToken.isInsertedInRecovery) {
        return "";
    }
    return lazyToken.cacheData.orgText.substring(lazyToken.startOffset, lazyToken.endOffset + 1);
}
exports.getImageFromLazyToken = getImageFromLazyToken;
function getStartLineFromLazyToken(lazyToken) {
    if (lazyToken.isInsertedInRecovery) {
        return NaN;
    }
    ensureLineDataProcessing(lazyToken.cacheData);
    return getStartLineFromLineToOffset(lazyToken.startOffset, lazyToken.cacheData.lineToOffset);
}
exports.getStartLineFromLazyToken = getStartLineFromLazyToken;
function getStartColumnFromLazyToken(lazyToken) {
    if (lazyToken.isInsertedInRecovery) {
        return NaN;
    }
    ensureLineDataProcessing(lazyToken.cacheData);
    return getStartColumnFromLineToOffset(lazyToken.startOffset, lazyToken.cacheData.lineToOffset);
}
exports.getStartColumnFromLazyToken = getStartColumnFromLazyToken;
function getEndLineFromLazyToken(lazyToken) {
    if (lazyToken.isInsertedInRecovery) {
        return NaN;
    }
    ensureLineDataProcessing(lazyToken.cacheData);
    return getEndLineFromLineToOffset(lazyToken.endOffset, lazyToken.cacheData.lineToOffset);
}
exports.getEndLineFromLazyToken = getEndLineFromLazyToken;
function getEndColumnFromLazyToken(lazyToken) {
    if (lazyToken.isInsertedInRecovery) {
        return NaN;
    }
    ensureLineDataProcessing(lazyToken.cacheData);
    return getEndColumnFromLineToOffset(lazyToken.endOffset, lazyToken.cacheData.lineToOffset);
}
exports.getEndColumnFromLazyToken = getEndColumnFromLazyToken;
function ensureLineDataProcessing(cacheData) {
    if (utils_1.isEmpty(cacheData.lineToOffset)) {
        fillUpLineToOffset(cacheData.lineToOffset, cacheData.orgText);
    }
}
exports.ensureLineDataProcessing = ensureLineDataProcessing;
function isLazyTokenType(tokType) {
    return tokens_public_1.LazyToken.prototype.isPrototypeOf(tokType.prototype) ||
        tokens_public_1.SimpleLazyToken.prototype.isPrototypeOf(tokType.prototype);
}
exports.isLazyTokenType = isLazyTokenType;
function isSimpleTokenType(tokType) {
    return tokens_public_1.SimpleLazyToken.prototype.isPrototypeOf(tokType.prototype);
}
exports.isSimpleTokenType = isSimpleTokenType;
