import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material";
import { Inject, Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { I18nService } from "../../services/internationalisation/internationalisation.service";
import { NgParameter } from "../../formulaire/ngparam";
import { ParamValueMode } from "jalhyd";
import { sprintf } from "sprintf-js";

@Component({
    selector: "dialog-edit-param-values",
    templateUrl: "dialog-edit-param-values.component.html",
    styleUrls: ["dialog-edit-param-values.component.scss"]
})
export class DialogEditParamValuesComponent implements OnInit {

    /** the related parameter to change the "variable" value of */
    public param: NgParameter;

    /** available value modes (min / max, list) */
    public valueModes: { value: ParamValueMode; label: string; }[];

    /** available decimal separators */
    public decimalSeparators: { label: string; value: string; }[];

    /** current decimal separator */
    public decimalSeparator: string;

    public valuesListForm: FormGroup;

    constructor(
        public dialogRef: MatDialogRef<DialogEditParamValuesComponent>,
        private intlService: I18nService,
        private fb: FormBuilder,
        @Inject(MAT_DIALOG_DATA) public data: any
    ) {
        this.param = data.param;
        // an explicit ReactiveForm is required for file input component
        const initialValue = (this.param.valueMode === ParamValueMode.LISTE ? this.valuesList : "");
        this.valuesListForm = this.fb.group({
            file: [""],
            valuesList: [ initialValue,
                [
                    Validators.required
                    // Validators.pattern(new RegExp(this.valuesListPattern)) // behaves weirdly
                ]
            ]
        });

        // available options for select controls
        this.valueModes = [
            {
                value: ParamValueMode.MINMAX,
                label: this.intlService.localizeText("INFO_PARAMMODE_MINMAX")
            },
            {
                value: ParamValueMode.LISTE,
                label: this.intlService.localizeText("INFO_PARAMMODE_LIST")
            }
        ];
        this.decimalSeparators = [
            {
                label: this.intlService.localizeText("INFO_PARAMFIELD_PARAMVARIER_SEPARATEUR_POINT"),
                value: "."
            },
            {
                label: this.intlService.localizeText("INFO_PARAMFIELD_PARAMVARIER_SEPARATEUR_VIRGULE"),
                value: ","
            }
        ];
        this.decimalSeparator = this.decimalSeparators[0].value;
    }

    /**
     * regular expression pattern for values list validation (depends on decimal separator)
     */
    public get valuesListPattern() {
        // standard pattern for decimal separator "." : ^-?([0-9]*\.)?([0-9]+[Ee]-?)?[0-9]+$
        const escapedDecimalSeparator = (this.decimalSeparator === "." ? "\\." : this.decimalSeparator);
        const numberSubPattern = `-?([0-9]*${escapedDecimalSeparator})?([0-9]+[Ee]-?)?[0-9]+`;
        const re = `^${numberSubPattern}(${this.separatorPattern}${numberSubPattern})*$`;
        return re;
    }

    /**
     * accepted separator: everything but [numbers, E, +, -, decimal separator], any length
     */
    public get separatorPattern() {
        return "[^0-9-+Ee" + this.decimalSeparator  + "]+";
    }

    public get selectedValueMode() {
        return this.param.valueMode;
    }

    public set selectedValueMode(v) {
        this.param.valueMode = v;
    }

    public get isMinMax() {
        return this.param.valueMode === ParamValueMode.MINMAX;
    }

    public get isListe() {
        return this.param.valueMode === ParamValueMode.LISTE;
    }

    /**
     * renders model's numbers list as text values list (semicolon separated)
     */
    public get valuesList() {
        return (this.param.valueList || []).join(";");
    }

    /**
     * injects text values list into model's numbers list
     */
    public set valuesList(list: string) {
        const vals = [];
        const separatorRE = new RegExp(this.separatorPattern);
        const parts = list.trim().split(separatorRE);
        parts.forEach((e) => {
            if (e.length > 0) {
                // ensure decimal separator is "." for Number()
                if (this.decimalSeparator !== ".") {
                    const re = new RegExp(this.decimalSeparator, "g"); // @TODO remove "g" ?
                    e = e.replace(re, ".");
                }
                vals.push(Number(e));
            }
        });
        this.param.valueList = vals;
    }

    public onValidate() {
        const status = this.validateValuesListString(this.valuesListForm.controls.valuesList.value);

        if (status.ok) {
            this.valuesListForm.controls.valuesList.setErrors(null);
            this.valuesList = this.valuesListForm.controls.valuesList.value;
            this.dialogRef.close();
        } else {
            this.valuesListForm.controls.valuesList.setErrors({ "model": status.message });
        }
    }

    /**
     * Returns { ok: true } if every element of list is a valid Number, { ok: false, message: "reason" } otherwise
     * @param list a string containing a list of numbers separated by this.separatorPattern
     */
    private validateValuesListString(list: string) {
        let message: string;
        // 1. validate against general pattern
        let ok = new RegExp(this.valuesListPattern).test(list);

        if (ok) {
            // 2. validate each value
            const separatorRE = new RegExp(this.separatorPattern);
            const parts = list.trim().split(separatorRE);
            for (let i = 0; i < parts.length && ok; i++) {
                let e = parts[i];
                if (e.length > 0) { // should always be true as separator might be several characters long
                    // ensure decimal separator is "." for Number()
                    if (this.decimalSeparator !== ".") {
                        const re = new RegExp(this.decimalSeparator, "g"); // @TODO remove "g" ?
                        e = e.replace(re, ".");
                    }
                    // 2.1 check it is a valid Number
                    const n = (Number(e));
                    // 2.2 validate against model
                    let modelIsHappy = true;
                    try {
                        this.param.checkValue(n);
                    } catch (e) {
                        modelIsHappy = false;
                        message = sprintf(this.intlService.localizeText("ERROR_INVALID_AT_POSITION"), i + 1)
                            + " " + this.intlService.localizeMessage(e);
                    }
                    // synthesis
                    ok = (
                        ok
                        && !isNaN(n)
                        && isFinite(n)
                        && modelIsHappy
                    );
                }
            }
        } else {
            message = this.uitextMustBeListOfNumbers;
        }

        return { ok, message };
    }

    public onFileSelected(event: any) {
        if (event.target.files && event.target.files.length) {
            const fr = new FileReader();
            fr.onload = () => {
                this.valuesListForm.controls.valuesList.setErrors(null);
                // this.valuesList = String(fr.result);
                this.valuesListForm.controls.valuesList.setValue(String(fr.result));
            };
            fr.onerror = () => {
                fr.abort();
                throw new Error("Erreur de lecture du fichier");
            };
            fr.readAsText(event.target.files[0]);
        }
    }

    public onValueModeChange(event) {
        this.initVariableValues();
    }

    private initVariableValues() {
        // init min / max / step
        if (this.isMinMax) {
            if (this.param.minValue === undefined) {
                this.param.minValue = this.param.getValue() / 2;
            }
            if (this.param.maxValue === undefined) {
                this.param.maxValue = this.param.getValue() * 2;
            }
            let step = this.param.stepValue;
            if (step === undefined) {
                step = (this.param.maxValue - this.param.minValue) / 20;
            }
            this.param.stepValue = step;
        }
        // init values list
        if (this.isListe) {
            if (this.param.valueList === undefined) {
                if (this.param.isDefined) {
                    this.param.valueList = [ this.param.getValue() ];
                } else {
                    this.param.valueList = [];
                }
                // set form control initial value
                this.valuesListForm.controls.valuesList.setValue(this.valuesList);
            }
        }
    }

    public get uiTextModeSelection() {
        return this.intlService.localizeText("INFO_PARAMFIELD_PARAMVARIER_MODE");
    }

    public get uitextValeurMini() {
        return this.intlService.localizeText("INFO_PARAMFIELD_VALEURMINI");
    }

    public get uitextValeurMaxi() {
        return this.intlService.localizeText("INFO_PARAMFIELD_VALEURMAXI");
    }

    public get uitextPasVariation() {
        return this.intlService.localizeText("INFO_PARAMFIELD_PASVARIATION");
    }

    public get uitextClose() {
        return this.intlService.localizeText("INFO_OPTION_CLOSE");
    }

    public get uitextCancel() {
        return this.intlService.localizeText("INFO_OPTION_CANCEL");
    }

    public get uitextValidate() {
        return this.intlService.localizeText("INFO_OPTION_VALIDATE");
    }

    public get uitextEditParamVariableValues() {
        return this.intlService.localizeText("INFO_PARAMFIELD_PARAMVARIER_TITLE");
    }

    public get uitextListeValeurs() {
        return this.intlService.localizeText("INFO_PARAMFIELD_PARAMVARIER_VALUES_FORMAT");
    }

    public get uitextMustBeANumber(): string {
        return this.intlService.localizeText("ERROR_PARAM_MUST_BE_A_NUMBER");
    }

    public get uitextMustBeListOfNumbers() {
        return sprintf(this.intlService.localizeText("INFO_PARAMFIELD_PARAMVARIER_VALUES_FORMAT_ERROR"), this.separatorPattern);
    }

    public get uitextDecimalSeparator() {
        return this.intlService.localizeText("INFO_PARAMFIELD_PARAMVARIER_SEPARATEUR_DECIMAL");
    }

    public get uitextImportFile() {
        return this.intlService.localizeText("INFO_PARAMFIELD_PARAMVARIER_IMPORT_FICHIER");
    }

    public ngOnInit() {
        this.initVariableValues();
    }
}