export default class PresetParser {
    constructor(settings) {
        this._settings = settings;
    }

    readPresetProperties(preset, strings) {
        const propertiesToRead = [
            "description",
            "discussion",
            "warning",
            "disclaimer",
            "include_warning",
            "include_disclaimer",
            "discussion",
            "force_options_review",
            "parser",
        ];
        const propertiesMetadata = {};
        preset.options = [];

        propertiesToRead.forEach((propertyName) => {
            // metadata of each property, name, type, optional true/false; example:
            // keywords: {type: MetadataTypes.WORDS_ARRAY, optional: true}
            propertiesMetadata[propertyName] = this._settings.presetsFileMetadata[propertyName];
            preset[propertyName] = undefined;
        });

        preset._currentOptionGroup = undefined;

        for (const line of strings) {
            if (line.startsWith(this._settings.MetapropertyDirective)) {
                this._parseAttributeLine(preset, line, propertiesMetadata);
            }
        }

        delete preset._currentOptionGroup;
    }

    _parseAttributeLine(preset, line, propertiesMetadata) {
        line = line.slice(this._settings.MetapropertyDirective.length).trim(); // (#$ DESCRIPTION: foo) -> (DESCRIPTION: foo)
        const lowCaseLine = line.toLowerCase();
        let isProperty = false;

        for (const propertyName in propertiesMetadata) {
            const lineBeginning = `${propertyName.toLowerCase()}:`; // "description:"

            if (lowCaseLine.startsWith(lineBeginning)) {
                line = line.slice(lineBeginning.length).trim(); // (Title: foo) -> (foo)
                this._parseProperty(preset, line, propertyName);
                isProperty = true;
            }
        }

        if (!isProperty && lowCaseLine.startsWith(this._settings.OptionsDirectives.OPTION_DIRECTIVE)) {
            this._parseOptionDirective(preset, line);
        }
    }

    _parseOptionDirective(preset, line) {
        const lowCaseLine = line.toLowerCase();

        if (lowCaseLine.startsWith(this._settings.OptionsDirectives.BEGIN_OPTION_DIRECTIVE)) {
            const option = this._getOption(line);
            if (!preset._currentOptionGroup) {
                preset.options.push(option);
            } else {
                preset._currentOptionGroup.childs.push(option);
            }
        } else if (lowCaseLine.startsWith(this._settings.OptionsDirectives.BEGIN_OPTION_GROUP_DIRECTIVE)) {
            const optionGroup = this._getOptionGroup(line);
            preset._currentOptionGroup = optionGroup;
            preset.options.push(optionGroup);
        } else if (lowCaseLine.startsWith(this._settings.OptionsDirectives.END_OPTION_GROUP_DIRECTIVE)) {
            preset._currentOptionGroup = undefined;
        }
    }

    _getOption(line) {
        const directiveRemoved = line.slice(this._settings.OptionsDirectives.BEGIN_OPTION_DIRECTIVE.length).trim();
        const directiveRemovedLowCase = directiveRemoved.toLowerCase();
        const OptionChecked = this._isOptionChecked(directiveRemovedLowCase);

        const regExpRemoveChecked = new RegExp(
            this._escapeRegex(this._settings.OptionsDirectives.OPTION_CHECKED),
            "gi",
        );
        const regExpRemoveUnchecked = new RegExp(
            this._escapeRegex(this._settings.OptionsDirectives.OPTION_UNCHECKED),
            "gi",
        );
        let optionName = directiveRemoved.replace(regExpRemoveChecked, "");
        optionName = optionName.replace(regExpRemoveUnchecked, "").trim();

        return {
            name: optionName.slice(1).trim(),
            checked: OptionChecked,
        };
    }

    _getOptionGroup(line) {
        const lowercaseLine = line.toLowerCase();
        const exlusiveDirective = this._settings.OptionsDirectives.EXCLUSIVE_OPTION_GROUP;
        const isExclusiveGroup = lowercaseLine.includes(exlusiveDirective);

        let directiveRemoved;
        if (isExclusiveGroup) {
            const exclusiveDirectiveLength = exlusiveDirective.length;
            directiveRemoved = line
                .slice(lowercaseLine.lastIndexOf(exlusiveDirective) + exclusiveDirectiveLength - 1)
                .trim();
        } else {
            directiveRemoved = line.slice(this._settings.OptionsDirectives.BEGIN_OPTION_GROUP_DIRECTIVE.length).trim();
        }

        return {
            name: directiveRemoved.slice(1).trim(),
            childs: [],
            isExclusive: isExclusiveGroup,
        };
    }

    _isOptionChecked(lowCaseLine) {
        return lowCaseLine.includes(this._settings.OptionsDirectives.OPTION_CHECKED);
    }

    _parseProperty(preset, line, propertyName) {
        switch (this._settings.presetsFileMetadata[propertyName].type) {
            case this._settings.MetadataTypes.STRING_ARRAY:
                this._processArrayProperty(preset, line, propertyName);
                break;
            case this._settings.MetadataTypes.STRING:
                this._processStringProperty(preset, line, propertyName);
                break;
            case this._settings.MetadataTypes.FILE_PATH:
                this._processStringProperty(preset, line, propertyName);
                break;
            case this._settings.MetadataTypes.FILE_PATH_ARRAY:
                this._processArrayProperty(preset, line, propertyName);
                break;
            case this._settings.MetadataTypes.BOOLEAN:
                this._processBooleanProperty(preset, line, propertyName);
                break;
            case this._settings.MetadataTypes.PARSER:
                this._processParserProperty(preset, line, propertyName);
                break;
            default:
                this.console.err(
                    `Parcing preset: unknown property type '${this._settings.presetsFileMetadata[propertyName].type}' for the property '${propertyName}'`,
                );
        }
    }

    _processParserProperty(preset, line, propertyName) {
        preset[propertyName] = line;
    }

    _processBooleanProperty(preset, line, propertyName) {
        const trueValues = ["true", "yes"];

        const lineLowCase = line.toLowerCase();
        let result = false;

        if (trueValues.includes(lineLowCase)) {
            result = true;
        }

        preset[propertyName] = result;
    }

    _processArrayProperty(preset, line, propertyName) {
        if (!preset[propertyName]) {
            preset[propertyName] = [];
        }

        preset[propertyName].push(line);
    }

    _processStringProperty(preset, line, propertyName) {
        preset[propertyName] = line;
    }

    _getOptionName(line) {
        const directiveRemoved = line.slice(this._settings.OptionsDirectives.BEGIN_OPTION_DIRECTIVE.length).trim();
        const regExpRemoveChecked = new RegExp(
            this._escapeRegex(`${this._settings.OptionsDirectives.OPTION_CHECKED}:`),
            "gi",
        );
        const regExpRemoveUnchecked = new RegExp(
            this._escapeRegex(`${this._settings.OptionsDirectives.OPTION_UNCHECKED}:`),
            "gi",
        );
        let optionName = directiveRemoved.replace(regExpRemoveChecked, "");
        optionName = optionName.replace(regExpRemoveUnchecked, "").trim();
        return optionName;
    }

    _escapeRegex(string) {
        return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    }

    removeUncheckedOptions(strings, checkedOptions) {
        let resultStrings = [];
        let isCurrentOptionExcluded = false;
        const lowerCasedCheckedOptions = checkedOptions.map((optionName) => optionName.toLowerCase());

        strings.forEach((str) => {
            if (this._isLineAttribute(str)) {
                const line = this._removeAttributeDirective(str);

                if (this._isOptionBegin(line)) {
                    const optionNameLowCase = this._getOptionName(line).toLowerCase();

                    if (!lowerCasedCheckedOptions.includes(optionNameLowCase)) {
                        isCurrentOptionExcluded = true;
                    }
                } else if (this._isOptionEnd(line)) {
                    isCurrentOptionExcluded = false;
                }
            } else if (!isCurrentOptionExcluded) {
                resultStrings.push(str);
            }
        });

        resultStrings = this._removeExcessiveEmptyLines(resultStrings);

        return resultStrings;
    }

    _removeExcessiveEmptyLines(strings) {
        // removes empty lines if there are two or more in a row leaving just one empty line
        const result = [];
        let lastStringEmpty = false;

        strings.forEach((str) => {
            if ("" !== str || !lastStringEmpty) {
                result.push(str);
            }

            if ("" === str) {
                lastStringEmpty = true;
            } else {
                lastStringEmpty = false;
            }
        });

        return result;
    }

    _isLineAttribute(line) {
        return line.trim().startsWith(this._settings.MetapropertyDirective);
    }

    _isOptionBegin(line) {
        const lowCaseLine = line.toLowerCase();
        return lowCaseLine.startsWith(this._settings.OptionsDirectives.BEGIN_OPTION_DIRECTIVE);
    }

    _isOptionEnd(line) {
        const lowCaseLine = line.toLowerCase();
        return lowCaseLine.startsWith(this._settings.OptionsDirectives.END_OPTION_DIRECTIVE);
    }

    _removeAttributeDirective(line) {
        return line.trim().slice(this._settings.MetapropertyDirective.length).trim();
    }

    isIncludeFound(strings) {
        for (const str of strings) {
            const match = PresetParser._sRegExpInclude.exec(str);

            if (match !== null) {
                return true;
            }
        }

        return false;
    }
}

// Reg exp extracts file/path.txt from # include: file/path.txt
PresetParser._sRegExpInclude = /^#\$[ ]+?INCLUDE:[ ]+?(?<filePath>\S+$)/;
