import is from '@pilotlab/is';
import { IAnimationEaseFunction } from '@pilotlab/animation';
import RangeScaleBase from './rangeScaleBase';


export class RangeScale extends RangeScaleBase {
    constructor(
        inputMin:number = 0, inputMax:number = 1,
        outputMin:number = 0, outputMax:number = 1,
        ease?:IAnimationEaseFunction
    ) {
        super(inputMin, inputMax, ease);

        let isInvert:boolean = outputMin > outputMax;
        let outputMinFinal:number = isInvert ? outputMax : outputMin;
        let outputMaxFinal:number = isInvert ? outputMin : outputMax;

        if (is.notEmpty(outputMinFinal)) this._outputMin = outputMinFinal;
        if (is.notEmpty(outputMaxFinal)) this._outputMax = outputMaxFinal;

        this._isInvert = isInvert;
    }


    /*--------------------------------------------------------------------*
     START: Static Methods
     *--------------------------------------------------------------------*/
    /**
     * Returns the relative position of the input value
     * within the min/max range as a value between 0.0 and 1.0.
     */
    static normalize(value:number, min:number, max:number, isConstrain:boolean = true, isInvert:boolean = false):number {
        let p:number = (value - min) / (max - min);

        if (isConstrain) {
            if (p < 0) p = 0;
            if (p > 1) p = 1;
        }

        if (isInvert) p = 1.0 - p;
        return p;
    }


    /**
     * Accepts a value between 0.0 and 1.0 and returns the input value at the corresponding
     * progress point within the min/max input range.
     */
    static unnormalize(valueNormalized:number, min:number, max:number, isConstrain:boolean = true, isInvert:boolean = false):number {
        let p:number = valueNormalized;

        if (isConstrain) {
            if (p < 0) p = 0;
            if (p > 1) p = 1;
        }

        if (isInvert) p = 1.0 - p;
        return (min + (p * (max - min)));
    }


    static outputEase(value:number, min:number, max:number, ease:IAnimationEaseFunction):number {
        if (min > max) return ease(value - min, max, min - max, max - min);
        else return ease(value - min, min, max - min, max - min);
    }


    static easeNormalized(value:number, ease:IAnimationEaseFunction, isInvert:boolean = false):number {
        if (isInvert) value = 1.0 - value;
        return ease(value, 0, 1, 1);
    }


    /*--------------------------------------------------------------------*
     START: Properties
     *--------------------------------------------------------------------*/
    get outputMin():number { return this._outputMin; }
    set outputMin(value:number) { this._outputMin = value; }
    private _outputMin:number = 0.0;


    get outputMax():number { return this._outputMax; }
    set outputMax(value:number) { this._outputMax = value; }
    private _outputMax:number = 0.0;


    get isInvert():boolean { return this._isInvert; }
    set isInvert(value:boolean) { this._isInvert = value; }
    private _isInvert:boolean = false;


    get value():number { return this._value; }
    private _value:number;


    /*--------------------------------------------------------------------*
     START: Public Methods
     *--------------------------------------------------------------------*/
    output(
        inputValue:number,
        isValueNormalized:boolean = false,
        isConstrain:boolean = true,
        isNormalizeReturnValue:boolean = false,
        isInvertReturnValue:boolean = false
    ):number {
        //----- Make sure we have a raw (not normalized or constrained) input value to work with.
        if (isValueNormalized) inputValue = this.unnormalizeInput(inputValue, false);
        if (inputValue === this.p_lastInputRaw) return this._value;
        this.p_lastInputRaw = inputValue;
        this.p_checkInputPreviousRaw(inputValue);

        //----- Now get the raw, unconstrained output value.
        let p:number = this.normalizeInput(inputValue, false);
        let value:number = this._outputFromNormalized(p);

        //----- Find the return value.
        value = this.normalizeOutput(value, isConstrain, isInvertReturnValue);
        if (is.notEmpty(this.p_ease)) value = RangeScale.easeNormalized(value, this.p_ease);
        if (this._isInvert) value = 1.0 - value;
        this.p_lastInputNormalized = value;
        if (!isNormalizeReturnValue) value = this.unnormalizeOutput(value);

        //----- Store the value for possible later retrieval.
        this._value = value;

        return value
    }


    /*--------------------------------------------------------------------*
     START: Public Methods
     *--------------------------------------------------------------------*/
    /**
     * Returns the relative position of the output value
     * within the min/max range as a value between 0.0 and 1.0.
     */
    normalizeOutput(outputUnnormalized:number, isConstrain:boolean = true, isInvert:boolean = false):number {
        return RangeScale.normalize(outputUnnormalized, this._outputMin, this._outputMax, isConstrain, isInvert);
    }


    /**
     * Accepts a value between 0.0 and 1.0 and returns the output value
     * at the corresponding progress point within the min/max input range.
     */
    unnormalizeOutput(outputNormalized:number, isConstrain:boolean = true, isInvert:boolean = false):number {
        return RangeScale.unnormalize(outputNormalized, this._outputMin, this._outputMax, isConstrain, isInvert);
    }


    /*--------------------------------------------------------------------*
     START: Private Methods
     *--------------------------------------------------------------------*/
    private _outputFromNormalized(inputNormalized:number):number {
        let p:number = inputNormalized;
        return (this._outputMin + (p * (this._outputMax - this._outputMin)));
    }
} // End class


export default RangeScale;
