"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Property = void 0;
const vscode_languageserver_types_1 = require("vscode-languageserver-types");
const util_1 = require("./util");
class Property {
    constructor(document, escapeChar, arg, arg2) {
        this.assignmentOperatorRange = null;
        this.assignmentOperator = null;
        this.valueRange = null;
        this.value = null;
        this.document = document;
        this.escapeChar = escapeChar;
        this.nameRange = Property.getNameRange(document, arg);
        let value = document.getText().substring(document.offsetAt(this.nameRange.start), document.offsetAt(this.nameRange.end));
        this.name = Property.getValue(value, escapeChar);
        if (arg2) {
            this.valueRange = arg2.getRange();
            value = document.getText().substring(document.offsetAt(this.valueRange.start), document.offsetAt(this.valueRange.end));
            this.value = Property.getValue(value, escapeChar);
            this.range = vscode_languageserver_types_1.Range.create(this.nameRange.start, this.valueRange.end);
        }
        else {
            let argRange = arg.getRange();
            if (this.nameRange.start.line === argRange.start.line
                && this.nameRange.start.character === argRange.start.character
                && this.nameRange.end.line === argRange.end.line
                && this.nameRange.end.character === argRange.end.character) {
            }
            else {
                this.valueRange = Property.getValueRange(document, arg);
                value = document.getText().substring(document.offsetAt(this.valueRange.start), document.offsetAt(this.valueRange.end));
                this.value = Property.getValue(value, escapeChar);
                this.assignmentOperatorRange = vscode_languageserver_types_1.Range.create(this.nameRange.end, this.valueRange.start);
                this.assignmentOperator = "=";
            }
            this.range = argRange;
        }
    }
    getRange() {
        return this.range;
    }
    getName() {
        return this.name;
    }
    getNameRange() {
        return this.nameRange;
    }
    getValue() {
        return this.value;
    }
    getValueRange() {
        return this.valueRange;
    }
    /**
     * Retrieves the operator used for delimiting between the name and
     * value of this property. This will either be the "=" character
     * or null if a character was not used or if this property has no
     * value defined.
     */
    getAssignmentOperator() {
        return this.assignmentOperator;
    }
    getAssignmentOperatorRange() {
        return this.assignmentOperatorRange;
    }
    /**
     * Returns the value of this property including any enclosing
     * single or double quotes and relevant escape characters.
     * Escaped newlines and its associated contiguous whitespace
     * characters however will not be returned as they are deemed to
     * be uninteresting to clients trying to return a Dockerfile.
     *
     * @return the unescaped value of this property or null if this
     *         property has no associated value
     */
    getUnescapedValue() {
        if (this.valueRange === null) {
            return null;
        }
        let escaped = false;
        let rawValue = "";
        let value = this.document.getText().substring(this.document.offsetAt(this.valueRange.start), this.document.offsetAt(this.valueRange.end));
        rawLoop: for (let i = 0; i < value.length; i++) {
            let char = value.charAt(i);
            switch (char) {
                case this.escapeChar:
                    for (let j = i + 1; j < value.length; j++) {
                        switch (value.charAt(j)) {
                            case '\r':
                                j++;
                            case '\n':
                                escaped = true;
                                i = j;
                                continue rawLoop;
                            case ' ':
                            case '\t':
                                break;
                            default:
                                rawValue = rawValue + char;
                                continue rawLoop;
                        }
                    }
                    // this happens if there's only whitespace after the escape character
                    rawValue = rawValue + char;
                    break;
                case '\r':
                case '\n':
                    break;
                case ' ':
                case '\t':
                    if (!escaped) {
                        rawValue = rawValue + char;
                    }
                    break;
                case '#':
                    if (escaped) {
                        for (let j = i + 1; j < value.length; j++) {
                            switch (value.charAt(j)) {
                                case '\r':
                                    j++;
                                case '\n':
                                    i = j;
                                    continue rawLoop;
                            }
                        }
                    }
                    else {
                        rawValue = rawValue + char;
                    }
                    break;
                default:
                    rawValue = rawValue + char;
                    escaped = false;
                    break;
            }
        }
        return rawValue;
    }
    static getNameRange(document, arg) {
        let value = arg.getValue();
        let index = value.indexOf('=');
        if (index !== -1) {
            let initial = value.charAt(0);
            let before = value.charAt(index - 1);
            // check if content before the equals sign are in quotes
            // "var"=value
            // 'var'=value
            // otherwise, just assume it's a standard definition
            // var=value
            if ((initial === '"' && before === '"') || (initial === '\'' && before === '\'') || (initial !== '"' && initial !== '\'')) {
                return vscode_languageserver_types_1.Range.create(arg.getRange().start, document.positionAt(document.offsetAt(arg.getRange().start) + index));
            }
        }
        // no '=' found, just defined the property's name
        return arg.getRange();
    }
    static getValueRange(document, arg) {
        return vscode_languageserver_types_1.Range.create(document.positionAt(document.offsetAt(arg.getRange().start) + arg.getValue().indexOf('=') + 1), document.positionAt(document.offsetAt(arg.getRange().end)));
    }
    /**
     * Returns the actual value of this key-value pair. The value will
     * have its escape characters removed if applicable. If the value
     * spans multiple lines and there are comments nested within the
     * lines, they too will be removed.
     *
     * @return the value that this key-value pair will actually be, may
     *         be null if no value is defined, may be the empty string
     *         if the value only consists of whitespace
     */
    static getValue(value, escapeChar) {
        let escaped = false;
        const skip = util_1.Util.findLeadingNonWhitespace(value, escapeChar);
        if (skip !== 0 && value.charAt(skip) === '#') {
            // need to skip over comments
            escaped = true;
        }
        value = value.substring(skip);
        let first = value.charAt(0);
        let last = value.charAt(value.length - 1);
        let literal = first === '\'' || first === '"';
        let inSingle = (first === '\'' && last === '\'');
        let inDouble = false;
        if (first === '"') {
            for (let i = 1; i < value.length; i++) {
                if (value.charAt(i) === escapeChar) {
                    i++;
                }
                else if (value.charAt(i) === '"' && i === value.length - 1) {
                    inDouble = true;
                }
            }
        }
        if (inSingle || inDouble) {
            value = value.substring(1, value.length - 1);
        }
        let commentCheck = -1;
        let escapedValue = "";
        let start = 0;
        parseValue: for (let i = 0; i < value.length; i++) {
            let char = value.charAt(i);
            switch (char) {
                case escapeChar:
                    if (i + 1 === value.length) {
                        escapedValue = escapedValue + escapeChar;
                        break parseValue;
                    }
                    char = value.charAt(i + 1);
                    if (char === ' ' || char === '\t') {
                        whitespaceCheck: for (let j = i + 2; j < value.length; j++) {
                            let char2 = value.charAt(j);
                            switch (char2) {
                                case ' ':
                                case '\t':
                                    break;
                                case '\r':
                                    j++;
                                case '\n':
                                    escaped = true;
                                    i = j;
                                    continue parseValue;
                                default:
                                    if (!inDouble && !inSingle && !literal) {
                                        if (char2 === escapeChar) {
                                            // add the escaped character
                                            escapedValue = escapedValue + char;
                                            // now start parsing from the next escape character
                                            i = i + 1;
                                        }
                                        else {
                                            // the expectation is that this j = i + 2 here
                                            escapedValue = escapedValue + char + char2;
                                            i = j;
                                        }
                                        continue parseValue;
                                    }
                                    break whitespaceCheck;
                            }
                        }
                    }
                    if (inDouble) {
                        if (char === '\r') {
                            escaped = true;
                            i = i + 2;
                        }
                        else if (char === '\n') {
                            escaped = true;
                            i++;
                        }
                        else if (char !== '"') {
                            if (char === escapeChar) {
                                i++;
                            }
                            escapedValue = escapedValue + escapeChar;
                        }
                        continue parseValue;
                    }
                    else if (inSingle || literal) {
                        if (char === '\r') {
                            escaped = true;
                            i = i + 2;
                        }
                        else if (char === '\n') {
                            escaped = true;
                            i++;
                        }
                        else {
                            escapedValue = escapedValue + escapeChar;
                        }
                        continue parseValue;
                    }
                    else if (char === escapeChar) {
                        // double escape, append one and move on
                        escapedValue = escapedValue + escapeChar;
                        i++;
                    }
                    else if (char === '\r') {
                        escaped = true;
                        // offset one more for \r\n
                        i = i + 2;
                    }
                    else if (char === '\n') {
                        escaped = true;
                        i++;
                        start = i;
                    }
                    else {
                        // any other escapes are simply ignored
                        escapedValue = escapedValue + char;
                        i++;
                    }
                    break;
                case ' ':
                case '\t':
                    if (escaped && commentCheck === -1) {
                        commentCheck = i;
                    }
                    escapedValue = escapedValue + char;
                    break;
                case '\r':
                    i++;
                case '\n':
                    if (escaped && commentCheck !== -1) {
                        // rollback and remove the whitespace that was previously appended
                        escapedValue = escapedValue.substring(0, escapedValue.length - (i - commentCheck - 1));
                        commentCheck = -1;
                    }
                    break;
                case '#':
                    // a newline was escaped and now there's a comment
                    if (escaped) {
                        if (commentCheck !== -1) {
                            // rollback and remove the whitespace that was previously appended
                            escapedValue = escapedValue.substring(0, escapedValue.length - (i - commentCheck));
                            commentCheck = -1;
                        }
                        newlineCheck: for (let j = i + 1; j < value.length; j++) {
                            switch (value.charAt(j)) {
                                case '\r':
                                    j++;
                                case '\n':
                                    i = j;
                                    break newlineCheck;
                            }
                        }
                        continue parseValue;
                    }
                default:
                    if (escaped) {
                        escaped = false;
                        commentCheck = -1;
                    }
                    escapedValue = escapedValue + char;
                    break;
            }
        }
        return escapedValue;
    }
}
exports.Property = Property;
