import { Pair } from "../util/pair"

/**
 * mode de génération des valeurs
 */
export enum ParamValueMode {
    /**
     * valeur unique
     */
    SINGLE,

    /**
     * min, max, pas
     */
    MINMAX,

    /**
     * liste de valeurs discrètes
     */
    LISTE
}

/**
 * itérateur sur les (ou la) valeurs prises par le paramètre
 */
export class ParamValueIterator implements IterableIterator<number> {
    /**
     * paramètre à itérer
     */
    private _param: ParamValues;

    /**
     * true si les valeurs sont fournies de max à min (ParamValueMode.MINMAX)
     */
    private _reverse: boolean;

    private _index: number;

    /**
     * cas de figure :
     * 0 : valeur fixée
     * 1 : min/max/pas
     * 2 : liste de valeurs
     */
    private _config: number;

    constructor(prm: ParamValues, reverse: boolean = false) {
        this._param = prm;
        this.reset(reverse)
    }

    public reset(reverse: boolean) {
        this._reverse = reverse;

        switch (this._param.valueMode) {
            case ParamValueMode.SINGLE:
                this._config = 0;
                this._index = 0;
                break;

            case ParamValueMode.MINMAX:
                this._config = 1;
                if (reverse)
                    this._index = this._param.max;
                else
                    this._index = this._param.min;
                break;

            case ParamValueMode.LISTE:
                this._config = 2;
                this._index = 0;
                break;

            default:
                throw new Error(`mode de génération de valeurs ${ParamValueMode[this._param.valueMode]}`);
        }
    }

    public next(): IteratorResult<number> {
        switch (this._config) {
            // valeur fixée
            case 0:
                if (this._index == 0)
                    return {
                        done: false,
                        value: this._param.singleValue
                    };
                else
                    return {
                        done: true,
                        value: undefined
                    };

            // min/max
            case 1:
                const res = this._index;
                const end = this._reverse ? this._index < this._param.min : this._index > this._param.max;
                if (!end) {
                    if (this._reverse)
                        this._index -= this._param.step;
                    else
                        this._index += this._param.step;
                    return {
                        done: false,
                        value: res
                    };
                } else {
                    return {
                        done: true,
                        value: undefined
                    };
                }

            // liste
            case 2:
                const i = this._index;
                if (this._index < this._param.valueList.length) {
                    const res = this._param.valueList[this._index++];
                    return {
                        done: false,
                        value: res
                    };
                } else {
                    return {
                        done: true,
                        value: undefined
                    };
                }

            default:
                throw new Error(`ParamValueIterator.next() : erreur interne`);
        }
    }

    // public get current(): number {
    //     if (this._config == 1)
    //         return this._index;
    //     throw new Error(`appel ParamValueIterator.current() invalide`)
    // }

    // interface IterableIterator

    public [Symbol.iterator](): IterableIterator<number> {
        return this;
    }
}

export class ParamValues {
    /**
     * mode de génération des valeurs : min/max, liste, ...
     */
    private _valueMode: ParamValueMode = ParamValueMode.MINMAX;

    /**
     * valeur dans le cas ParamValueMode.SINGLE
     */
    private _singleValue: number;

    /**
     * valeur min dans le cas ParamValueMode.MINMAX
     */
    private _minValue: number = undefined;

    /**
     * valeur max dans le cas ParamValueMode.MINMAX
     */
    private _maxValue: number = undefined;

    /**
     * pas de progression dans le cas ParamValueMode.MINMAX
     */
    private _stepValue: number = undefined;

    /**
     * liste de valeurs dans le cas ParamValueMode.LISTE
     */
    private _valueList: number[];

    public setValues(o: number | any, max?: number, step?: number) {
        if (typeof (o) === "number") {
            if (max == undefined) {
                this._valueMode = ParamValueMode.SINGLE;
                this._singleValue = o as number;
            } else {
                this._valueMode = ParamValueMode.MINMAX;
                this._minValue = o as number;
                this._maxValue = max;
                this._stepValue = step;
            }
        }
        else if (Array.isArray(o))
            this._valueList = o;
        else
            throw new Error(`ParamValues.setValues() :  appel invalide`);
    }

    public get valueMode() {
        return this._valueMode;
    }

    public set valueMode(m: ParamValueMode) {
        this._valueMode = m;
    }

    public get singleValue() {
        return this._singleValue;
    }

    public set singleValue(v: number) {
        this._singleValue = v;
    }

    public get min() {
        return this._minValue;
    }

    public set min(v: number) {
        this._minValue = v;
    }

    public get max() {
        return this._maxValue;
    }

    public set max(v: number) {
        this._maxValue = v;
    }

    public get stepRefValue(): Pair {
        return new Pair(1e-9, this._maxValue - this._minValue);
    }

    public get step() {
        return this._stepValue;
    }

    public set step(v: number) {
        this._stepValue = v;
    }

    public get valueList() {
        return this._valueList;
    }

    public set valueList(l: number[]) {
        this._valueList = l;
    }

    public getValuesIterator(reverse: boolean = false): ParamValueIterator {
        return new ParamValueIterator(this, reverse);
    }

    /**
     * copie des membres
     */
    public copyMembers(n: ParamValues) {
        n._valueMode = this.valueMode;
        n._minValue = this._minValue;
        n._maxValue = this._maxValue;
        n._stepValue = this._stepValue;
        if (this._valueList != undefined)
            n._valueList = this._valueList.slice(0); // copie
    }
}