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";
import { ApplicationSetupService } from "../../services/app-setup/app-setup.service";

@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;

    /** when true, shows the values chart instead of the edit form */
    public viewChart = false;
    // chart config
    public chartData = {};
    public chartOptions;

    constructor(
        public dialogRef: MatDialogRef<DialogEditParamValuesComponent>,
        private intlService: I18nService,
        private appSetupService: ApplicationSetupService,
        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;

        // chart configuration
        const nDigits = this.appSetupService.displayDigits;
        this.chartOptions = {
            responsive: true,
            maintainAspectRatio: true,
            animation: {
                duration: 0
            },
            legend: {
                display: false
            },
            scales: {
                xAxes: [{
                    type: "linear",
                    position: "bottom",
                    ticks: {
                        precision: nDigits
                    }
                }],
                yAxes: [{
                    type: "linear",
                    position: "left",
                    ticks: {
                        precision: nDigits
                    }
                }]
            },
            tooltips: {
                callbacks: {
                    label: function(tooltipItem) {
                        return  Number(tooltipItem.yLabel).toFixed(nDigits);
                    }
                }
            }
        };
    }

    // proxy to model values

    public get minValue() {
        return this.param.minValue;
    }

    public set minValue(v) {
        this.param.setMinValue(this, v);
    }

    public get maxValue() {
        return this.param.maxValue;
    }

    public set maxValue(v) {
        this.param.setMaxValue(this, v);
    }

    public get stepValue() {
        return this.param.stepValue;
    }

    public set stepValue(v) {
        this.param.setStepValue(this, v);
    }

    /**
     * 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.setValueList(this, vals);
    }

    public toggleViewChart() {
        // validate list values before switching views ?
        if (! this.viewChart && this.param.valueMode === ParamValueMode.LISTE) {
            if (this.onValidate(false)) {
                // toggle
                this.viewChart = ! this.viewChart;
            }
        } else {
            // toggle
            this.viewChart = ! this.viewChart;
        }
        // refresh chart when displaying it only
        if (this.viewChart) {
            this.drawChart();
        }
    }

    public onValidate(close = true) {
        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;
            if (close) {
                this.dialogRef.close();
            }
            return true;
        } else {
            this.valuesListForm.controls.valuesList.setErrors({ "model": status.message });
            return false;
        }
    }

    /**
     * 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.setMinValue(this, this.param.getValue() / 2);
            }
            if (this.param.maxValue === undefined) {
                this.param.setMaxValue(this, this.param.getValue() * 2);
            }
            let step = this.param.stepValue;
            if (step === undefined) {
                step = (this.param.maxValue - this.param.minValue) / 20;
            }
            this.param.setStepValue(this, step);
        }
        // init values list
        if (this.isListe) {
            if (this.param.valueList === undefined) {
                if (this.param.isDefined) {
                    this.param.setValueList(this, [ this.param.getValue() ]);
                } else {
                    this.param.setValueList(this, []);
                }
                // set form control initial value
                this.valuesListForm.controls.valuesList.setValue(this.valuesList);
            }
        }
    }

    /**
     * (re)Génère le graphique d'évolution des valeurs
     */
    private drawChart() {
        const data = [];
        let i = 0;
        for (const v of this.param.valuesIterator) {
            data.push({
                x: i,
                y: v
            });
            i++;
        }
        this.chartData = {
            datasets: [{
                label: "",
                data: data,
                borderColor: "#808080", // couleur de la ligne
                backgroundColor: "rgba(0,0,0,0)",  // couleur de remplissage sous la courbe : transparent
                showLine: "true"
            }]
        };
    }

    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();
    }
}