/* --------------------------------------------------------------------------------------------
 * Copyright (c) Remy Suen. All rights reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.
 * ------------------------------------------------------------------------------------------ */
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.DockerFormatter = void 0;
const vscode_languageserver_types_1 = require("vscode-languageserver-types");
const dockerfile_ast_1 = require("dockerfile-ast");
class DockerFormatter {
    getIndentation(formattingOptions) {
        let indentation = "\t";
        if (formattingOptions && formattingOptions.insertSpaces) {
            indentation = "";
            for (let i = 0; i < formattingOptions.tabSize; i++) {
                indentation = indentation + " ";
            }
        }
        return indentation;
    }
    /**
     * Creates a TextEdit for formatting the given document.
     *
     * @param document the document being formatted
     * @param start the start offset of the document's content to be replaced
     * @param end the end offset of the document's content to be replaced
     * @param indent true if this block should be replaced with an indentation, false otherwise
     * @param indentation the string to use for an indentation
     */
    createFormattingEdit(document, start, end, indent, indentation) {
        if (indent) {
            return vscode_languageserver_types_1.TextEdit.replace({
                start: document.positionAt(start),
                end: document.positionAt(end)
            }, indentation);
        }
        else {
            return vscode_languageserver_types_1.TextEdit.del({
                start: document.positionAt(start),
                end: document.positionAt(end)
            });
        }
    }
    formatOnType(document, position, ch, options) {
        const dockerfile = dockerfile_ast_1.DockerfileParser.parse(document.getText());
        // check that the inserted character is the escape character
        if (dockerfile.getEscapeCharacter() === ch) {
            for (let comment of dockerfile.getComments()) {
                // ignore if we're in a comment
                if (comment.getRange().start.line === position.line) {
                    return [];
                }
            }
            const directive = dockerfile.getDirective();
            // ignore if we're in the parser directive
            if (directive && position.line === 0) {
                return [];
            }
            const content = document.getText();
            validityCheck: for (let i = document.offsetAt(position); i < content.length; i++) {
                switch (content.charAt(i)) {
                    case ' ':
                    case '\t':
                        break;
                    case '\r':
                    case '\n':
                        break validityCheck;
                    default:
                        // not escaping a newline, no need to format the next line
                        return [];
                }
            }
            const line = position.line + 1;
            const indentedLines = [];
            const skippedLines = [];
            indentedLines[line] = true;
            skippedLines[line] = true;
            const heredocLines = [];
            if (this.inHeredoc(dockerfile, line)) {
                heredocLines.push(line);
            }
            return this.formatLines(document, document.getText(), [line], indentedLines, skippedLines, heredocLines, options);
        }
        return [];
    }
    formatRange(document, range, options) {
        const lines = [];
        for (let i = range.start.line; i <= range.end.line; i++) {
            lines.push(i);
        }
        return this.format(document, lines, options);
    }
    formatDocument(document, options) {
        const lines = [];
        for (let i = 0; i < document.lineCount; i++) {
            lines.push(i);
        }
        return this.format(document, lines, options);
    }
    inHeredoc(dockerfile, line) {
        for (const instruction of dockerfile.getInstructions()) {
            if (instruction instanceof dockerfile_ast_1.Copy || instruction instanceof dockerfile_ast_1.Run) {
                const lines = this.getHeredocLines(instruction.getHeredocs());
                if (lines.indexOf(line) !== -1) {
                    return true;
                }
            }
        }
        return false;
    }
    getHeredocLines(heredocs) {
        let start = -1;
        for (let i = 0; i < heredocs.length; i++) {
            // if there's content, use the first line of the content
            const contentRange = heredocs[i].getContentRange();
            if (contentRange !== null) {
                start = contentRange.start.line;
                break;
            }
            // there may be a delimiter even if there's no content
            const delimiterRange = heredocs[i].getDelimiterRange();
            if (delimiterRange !== null) {
                start = delimiterRange.start.line;
                break;
            }
        }
        if (start === -1) {
            return [];
        }
        let end = -1;
        for (let i = heredocs.length - 1; i >= 0; i--) {
            // there may be a delimiter even if there's no content
            const delimiterRange = heredocs[i].getDelimiterRange();
            if (delimiterRange !== null) {
                end = delimiterRange.end.line;
                break;
            }
            // if there's content, use the first line of the content
            const contentRange = heredocs[i].getContentRange();
            if (contentRange !== null) {
                end = contentRange.end.line;
                break;
            }
        }
        let heredocLines = [];
        for (let i = start; i <= end; i++) {
            heredocLines.push(i);
        }
        return heredocLines;
    }
    /**
     * Formats the specified lines of the given document based on the
     * provided formatting options.
     *
     * @param document the text document to format
     * @param lines the lines to format
     * @param options the formatting options to use to perform the format
     * @return the text edits to apply to format the lines of the document
     */
    format(document, lines, options) {
        let content = document.getText();
        let dockerfile = dockerfile_ast_1.DockerfileParser.parse(content);
        const indentedLines = [];
        const skippedLines = [];
        const heredocLines = [];
        for (let i = 0; i < document.lineCount; i++) {
            indentedLines[i] = false;
            skippedLines[i] = false;
        }
        for (let instruction of dockerfile.getInstructions()) {
            let range = instruction.getRange();
            if (range.start.line !== range.end.line) {
                for (let i = range.start.line + 1; i <= range.end.line; i++) {
                    skippedLines[i] = true;
                }
            }
            if (instruction instanceof dockerfile_ast_1.Copy || instruction instanceof dockerfile_ast_1.Run) {
                const heredocs = instruction.getHeredocs();
                if (heredocs.length > 0) {
                    heredocLines.push(...this.getHeredocLines(heredocs));
                }
            }
            indentedLines[range.start.line] = false;
            for (let i = range.start.line + 1; i <= range.end.line; i++) {
                indentedLines[i] = true;
            }
        }
        return this.formatLines(document, content, lines, indentedLines, skippedLines, heredocLines, options);
    }
    formatLines(document, content, lines, indentedLines, skippedLines, heredocLines, options) {
        const indentation = this.getIndentation(options);
        const edits = [];
        lineCheck: for (let i = 0; i < lines.length; i++) {
            if (options && options.ignoreMultilineInstructions && skippedLines[lines[i]]) {
                continue;
            }
            else if (heredocLines.indexOf(lines[i]) !== -1) {
                continue;
            }
            let startOffset = document.offsetAt(vscode_languageserver_types_1.Position.create(lines[i], 0));
            for (let j = startOffset; j < content.length; j++) {
                switch (content.charAt(j)) {
                    case ' ':
                    case '\t':
                        break;
                    case '\r':
                    case '\n':
                        if (j !== startOffset) {
                            // only whitespace on this line, trim it
                            let edit = vscode_languageserver_types_1.TextEdit.del({
                                start: document.positionAt(startOffset),
                                end: document.positionAt(j)
                            });
                            edits.push(edit);
                        }
                        // process the next line
                        continue lineCheck;
                    default:
                        // found a line that should be indented
                        if (indentedLines[lines[i]]) {
                            const originalIndentation = document.getText().substring(startOffset, j);
                            // change the indentation if it's not what we expect
                            if (originalIndentation !== indentation) {
                                const edit = this.createFormattingEdit(document, startOffset, j, indentedLines[lines[i]], indentation);
                                edits.push(edit);
                            }
                        }
                        else if (j !== startOffset) {
                            // non-whitespace character encountered, realign
                            const edit = this.createFormattingEdit(document, startOffset, j, indentedLines[lines[i]], indentation);
                            edits.push(edit);
                        }
                        // process the next line
                        continue lineCheck;
                }
            }
            if (startOffset < content.length) {
                // only whitespace on the last line, trim it
                let edit = vscode_languageserver_types_1.TextEdit.del({
                    start: document.positionAt(startOffset),
                    end: document.positionAt(content.length)
                });
                edits.push(edit);
            }
        }
        return edits;
    }
}
exports.DockerFormatter = DockerFormatter;
