/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.guessIndentation = void 0;
    class SpacesDiffResult {
        constructor() {
            this.spacesDiff = 0;
            this.looksLikeAlignment = false;
        }
    }
    /**
     * Compute the diff in spaces between two line's indentation.
     */
    function spacesDiff(a, aLength, b, bLength, result) {
        result.spacesDiff = 0;
        result.looksLikeAlignment = false;
        // This can go both ways (e.g.):
        //  - a: "\t"
        //  - b: "\t    "
        //  => This should count 1 tab and 4 spaces
        let i;
        for (i = 0; i < aLength && i < bLength; i++) {
            const aCharCode = a.charCodeAt(i);
            const bCharCode = b.charCodeAt(i);
            if (aCharCode !== bCharCode) {
                break;
            }
        }
        let aSpacesCnt = 0, aTabsCount = 0;
        for (let j = i; j < aLength; j++) {
            const aCharCode = a.charCodeAt(j);
            if (aCharCode === 32 /* CharCode.Space */) {
                aSpacesCnt++;
            }
            else {
                aTabsCount++;
            }
        }
        let bSpacesCnt = 0, bTabsCount = 0;
        for (let j = i; j < bLength; j++) {
            const bCharCode = b.charCodeAt(j);
            if (bCharCode === 32 /* CharCode.Space */) {
                bSpacesCnt++;
            }
            else {
                bTabsCount++;
            }
        }
        if (aSpacesCnt > 0 && aTabsCount > 0) {
            return;
        }
        if (bSpacesCnt > 0 && bTabsCount > 0) {
            return;
        }
        const tabsDiff = Math.abs(aTabsCount - bTabsCount);
        const spacesDiff = Math.abs(aSpacesCnt - bSpacesCnt);
        if (tabsDiff === 0) {
            // check if the indentation difference might be caused by alignment reasons
            // sometime folks like to align their code, but this should not be used as a hint
            result.spacesDiff = spacesDiff;
            if (spacesDiff > 0 && 0 <= bSpacesCnt - 1 && bSpacesCnt - 1 < a.length && bSpacesCnt < b.length) {
                if (b.charCodeAt(bSpacesCnt) !== 32 /* CharCode.Space */ && a.charCodeAt(bSpacesCnt - 1) === 32 /* CharCode.Space */) {
                    if (a.charCodeAt(a.length - 1) === 44 /* CharCode.Comma */) {
                        // This looks like an alignment desire: e.g.
                        // const a = b + c,
                        //       d = b - c;
                        result.looksLikeAlignment = true;
                    }
                }
            }
            return;
        }
        if (spacesDiff % tabsDiff === 0) {
            result.spacesDiff = spacesDiff / tabsDiff;
            return;
        }
    }
    function guessIndentation(source, defaultTabSize, defaultInsertSpaces) {
        // Look at most at the first 10k lines
        const linesCount = Math.min(source.getLineCount(), 10000);
        let linesIndentedWithTabsCount = 0; // number of lines that contain at least one tab in indentation
        let linesIndentedWithSpacesCount = 0; // number of lines that contain only spaces in indentation
        let previousLineText = ''; // content of latest line that contained non-whitespace chars
        let previousLineIndentation = 0; // index at which latest line contained the first non-whitespace char
        const ALLOWED_TAB_SIZE_GUESSES = [2, 4, 6, 8, 3, 5, 7]; // prefer even guesses for `tabSize`, limit to [2, 8].
        const MAX_ALLOWED_TAB_SIZE_GUESS = 8; // max(ALLOWED_TAB_SIZE_GUESSES) = 8
        const spacesDiffCount = [0, 0, 0, 0, 0, 0, 0, 0, 0]; // `tabSize` scores
        const tmp = new SpacesDiffResult();
        for (let lineNumber = 1; lineNumber <= linesCount; lineNumber++) {
            const currentLineLength = source.getLineLength(lineNumber);
            const currentLineText = source.getLineContent(lineNumber);
            // if the text buffer is chunk based, so long lines are cons-string, v8 will flattern the string when we check charCode.
            // checking charCode on chunks directly is cheaper.
            const useCurrentLineText = currentLineLength <= 65536;
            let currentLineHasContent = false; // does `currentLineText` contain non-whitespace chars
            let currentLineIndentation = 0; // index at which `currentLineText` contains the first non-whitespace char
            let currentLineSpacesCount = 0; // count of spaces found in `currentLineText` indentation
            let currentLineTabsCount = 0; // count of tabs found in `currentLineText` indentation
            for (let j = 0, lenJ = currentLineLength; j < lenJ; j++) {
                const charCode = useCurrentLineText ? currentLineText.charCodeAt(j) : source.getLineCharCode(lineNumber, j);
                if (charCode === 9 /* CharCode.Tab */) {
                    currentLineTabsCount++;
                }
                else if (charCode === 32 /* CharCode.Space */) {
                    currentLineSpacesCount++;
                }
                else {
                    // Hit non whitespace character on this line
                    currentLineHasContent = true;
                    currentLineIndentation = j;
                    break;
                }
            }
            // Ignore empty or only whitespace lines
            if (!currentLineHasContent) {
                continue;
            }
            if (currentLineTabsCount > 0) {
                linesIndentedWithTabsCount++;
            }
            else if (currentLineSpacesCount > 1) {
                linesIndentedWithSpacesCount++;
            }
            spacesDiff(previousLineText, previousLineIndentation, currentLineText, currentLineIndentation, tmp);
            if (tmp.looksLikeAlignment) {
                // if defaultInsertSpaces === true && the spaces count == tabSize, we may want to count it as valid indentation
                //
                // - item1
                //   - item2
                //
                // otherwise skip this line entirely
                //
                // const a = 1,
                //       b = 2;
                if (!(defaultInsertSpaces && defaultTabSize === tmp.spacesDiff)) {
                    continue;
                }
            }
            const currentSpacesDiff = tmp.spacesDiff;
            if (currentSpacesDiff <= MAX_ALLOWED_TAB_SIZE_GUESS) {
                spacesDiffCount[currentSpacesDiff]++;
            }
            previousLineText = currentLineText;
            previousLineIndentation = currentLineIndentation;
        }
        let insertSpaces = defaultInsertSpaces;
        if (linesIndentedWithTabsCount !== linesIndentedWithSpacesCount) {
            insertSpaces = linesIndentedWithTabsCount < linesIndentedWithSpacesCount;
        }
        let tabSize = defaultTabSize;
        // Guess tabSize only if inserting spaces...
        if (insertSpaces) {
            let tabSizeScore = insertSpaces ? 0 : 0.1 * linesCount;
            // console.log("score threshold: " + tabSizeScore);
            ALLOWED_TAB_SIZE_GUESSES.forEach((possibleTabSize) => {
                const possibleTabSizeScore = spacesDiffCount[possibleTabSize];
                if (possibleTabSizeScore > tabSizeScore) {
                    tabSizeScore = possibleTabSizeScore;
                    tabSize = possibleTabSize;
                }
            });
            // Let a tabSize of 2 win even if it is not the maximum
            // (only in case 4 was guessed)
            if (tabSize === 4 && spacesDiffCount[4] > 0 && spacesDiffCount[2] > 0 && spacesDiffCount[2] >= spacesDiffCount[4] / 2) {
                tabSize = 2;
            }
        }
        // console.log('--------------------------');
        // console.log('linesIndentedWithTabsCount: ' + linesIndentedWithTabsCount + ', linesIndentedWithSpacesCount: ' + linesIndentedWithSpacesCount);
        // console.log('spacesDiffCount: ' + spacesDiffCount);
        // console.log('tabSize: ' + tabSize + ', tabSizeScore: ' + tabSizeScore);
        return {
            insertSpaces: insertSpaces,
            tabSize: tabSize,
        };
    }
    exports.guessIndentation = guessIndentation;
});
//# sourceMappingURL=indentationGuesser.js.map