// cf. https://blog.thoughtram.io/angular/2016/07/27/custom-form-controls-in-angular-2.html

import { Component, Input, forwardRef, OnInit, DoCheck, ChangeDetectorRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS, FormControl } from '@angular/forms';

import { ComputeNodeType, ParamDefinition, NumericalString, Message } from 'jalhyd';

import { ParamService } from '../../services/param/param.service';
import { InternationalisationService, LanguageCode } from '../../services/internationalisation/internationalisation.service';
import { NgParameter } from '../../calculators/generic/ngparam';

@Component({
    selector: 'param-input',
    /* OK
    <input placeholder="{{_paramDef.symbol}}" [ngModel]="_paramDef.v" (ngModelChange)="setValue($event)"/>
    <p *ngIf="_message">{{_message}}</p>
    */
    templateUrl: "./param-input.component.html",
    providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => ParamInputComponent),
            multi: true
        },
        {
            provide: NG_VALIDATORS,
            useExisting: forwardRef(() => ParamInputComponent),
            multi: true
        }
    ]
})
export class ParamInputComponent implements ControlValueAccessor, OnInit, DoCheck {
    /**
     * type de noeud de calcul
     */
    private _computeNode: ComputeNodeType;

    @Input()
    private set computeNodeType(t: ComputeNodeType) {
        this._computeNode = t;
    }

    /**
     * Parameter symbol (Q, Ks, B, ...) attribute
     */
    @Input('symbol')
    private _paramSymbol: string;

    /**
     * enable/disable input field
     */
    @Input('inputDisabled')
    private _inputDisabled: boolean;

    /**
     * managed parameter
     */
    private _paramDef: NgParameter;

    private _message: string;

    /**
     * flag d'affichage du titre
     */
    public displayTitle: boolean = false;

    /**
     * true si la modification du paramètre géré vient de l'interface utilisateur
     *
     * false si la modification du paramètre géré vient d'un appel du code
     */
    private _fromUI: boolean;

    /**
     * valeur dans le contrôle (saisie par l'utilisateur)
     */
    private _uiValue: NumericalString;

    constructor(private paramService: ParamService, private changeDetector: ChangeDetectorRef, private intlService: InternationalisationService) {
        this._uiValue = new NumericalString();
    }

    hasError(): boolean {
        let res = (this._message != undefined);
        // if (res)
        //     this.log("hasError : true " + this._message);
        // else
        //     this.log("hasError : false");
        return res;
    }

    private getSfromUI(): string {
        return this._fromUI ? " fromUI " : " fromMODEL";
    }

    private getSParam(): string {
        return " " + this._paramDef.symbol + "=" + this._paramDef.toString()
    }

    private getSUIvalue(v: NumericalString = undefined): string {
        if (v == undefined)
            return "  uiValue=" + this._uiValue.toString() + "";

        return "  uiValue=" + v.toString() + "";
    }

    ngOnInit() {
        // retrieve parameter from symbol
        this._paramDef = this.paramService.getParameter(this._computeNode, this._paramSymbol);
    }

    /**
     * fonction appelée lorsque l'utilisateur fait une saisie
     * @param event valeur du contrôle
     */
    private setValue(event: any) {
        this._fromUI = true;
        this._uiValue.value = event;
        // this.log(this._uiValue.toString());
        return this.validateUIValue();
    }

    /**
     * fonction appelée lors d'un rafraîchissement de l'UI
     */
    ngDoCheck(): void {
        // this.log("ngDoCheck start : " + this.getSParam() + this.getSUIvalue() + this.getSfromUI());

        if (this._fromUI)
            this.updateMessage(this._uiValue);
        else {
            if (this._paramDef.isDefined) {
                this.updateMessage(new NumericalString(this._paramDef.getValue()));
                this._uiValue.value = String(this._paramDef.getValue());
            }
            else
                this.updateMessage(this._uiValue);
        }

        // this.log("ngDoCheck end : " + this.getSParam() + this.getSUIvalue());

        this._fromUI = false;
    }

    private updateMessage(v: NumericalString) {
        // this.log("updateMessage start :" + this.getSParam() + this.getSfromUI() + this.getSUIvalue(v) + "  message=" + this._message);

        if (v.isNumerical) {
            this._message = undefined;

            try {
                this._paramDef.checkValue(v.numericalValue);
            }
            catch (e) {
                if (e instanceof Message)
                    this._message = this.intlService.localizeMessage(e);
                else
                    this._message = "invalid value";
            }
        }
        else {
            switch (this.intlService.currentLanguage.code) {
                case LanguageCode.FRENCH:
                    this._message = "Veuillez entrer une valeur numérique";
                    break;

                default:
                    this._message = "Please enter a numerical value";
            }
        }

        // this.log("updateMessage end :" + this.getSParam() + this.getSfromUI() + this.getSUIvalue(v) + "  message=" + this._message);
    }

    private validateUIValue() {
        // this.log("");
        // this.log("validateValue start : val '" + this._uiValue.toString() + "'" + this.getSParam() + this.getSfromUI());

        let ok: boolean = this._uiValue.isNumerical;
        if (ok) {
            try {
                if (!this._paramDef.isDefined || this._paramDef.getValue() != this._uiValue.numericalValue) {
                    this._paramDef.setValue(this._uiValue.numericalValue);
                    this.changeDetector.detectChanges();  // provoque une détection des changements dans les contrôles
                }
            }
            catch (e) {
                ok = false;
            }
        }

        if (!ok) {
            // this.log("validateValue end : " + this.getSParam());

            let err = {
                rangeError: {
                    // given: val,
                    given: this._uiValue.toString(),
                    max: 4,
                    min: 0
                }
            };
            return err;
        }

        // this.log("validateValue end : " + this.getSParam());
        return null;
    }

    // private log(m: string) {
    //     console.log("ParamInputComponent(" + this._id + ") : " + m);
    // }

    // ControlValueAccessor interface

    propagateChange = (_: any) => { };

    /*
    //From ControlValueAccessor interface
    writeValue(value: any) {
        if (value !== this.innerValue) {
            this.innerValue = value;
        }
    }
    */
    writeValue(value: any) {
        // this.log("writeValue " + value);
    }

    registerOnChange(fn: any) {
        this.propagateChange = fn;
    }

    registerOnTouched() { }
}