import Signal from "../events/signal/Signal.js";
import { inverseLerp } from "../math/inverseLerp.js";
import { computeHashFloat } from "../primitives/numbers/computeHashFloat.js";

class BoundedValue {
    /**
     *
     * @param {Number} currentValue
     * @param {Number} upperLimit
     * @param {Number} [lowerLimit=0]
     * @constructor
     */
    constructor(currentValue, upperLimit, lowerLimit) {
        /**
         * @property {Number}
         * @private
         */
        this.__value = currentValue !== undefined ? currentValue : 0;
        /**
         * @property {Number}
         * @private
         */
        this.__limitUpper = upperLimit !== undefined ? upperLimit : 0;

        /**
         * @type {Number}
         * @private
         */
        this.__limitLower = lowerLimit !== undefined ? lowerLimit : 0;

        this.onChanged = new Signal();
        this.onOverflow = new Signal();
        this.onFilled = new Signal();

        this.on = {
            changed: this.onChanged,
            overflow: this.onOverflow
        };
    }

    /**
     *
     * @param {Number} v
     */
    setUpperLimit(v) {
        const oldValue = this.__limitUpper;
        if (v === oldValue) {
            //no change
            return;
        }
        this.__limitUpper = v;
        this.onChanged.send4(this.__value, this.__limitUpper, this.__value, oldValue);
    }

    /**
     *
     * @returns {Number}
     */
    getUpperLimit() {
        return this.__limitUpper;
    }

    /**
     *
     * @returns {Number}
     */
    getLowerLimit() {
        return this.__limitLower;
    }

    /**
     *
     * @param {Number} v
     */
    setLowerLimit(v) {
        const old = this.__limitLower;

        if (v === old) {
            //no change
            return;
        }

        this.__limitLower = v;

        //TODO change signal signature to include lower limit
        this.onChanged.dispatch();
    }

    /**
     *
     * @param {Number} v
     */
    setValue(v) {
        const oldValue = this.__value;
        if (oldValue === v) {
            //no change
            return;
        }

        const spill = v - this.__limitUpper;
        this.__value = v;
        //dispatch change
        this.onChanged.send4(this.__value, this.__limitUpper, oldValue, this.__limitUpper);
        if (spill > 0) {
            this.onOverflow.send1(spill);
        }
        if (spill >= 0) {
            this.onFilled.send0();
        }
    }

    /**
     *
     * @returns {Number}
     */
    getValue() {
        return this.__value;
    }

    /**
     *
     * @returns {number}
     */
    getFraction() {
        return inverseLerp(this.__limitLower, this.__limitUpper, this.__value);
    }

    /**
     *
     */
    setValueToLimit() {
        this.setValue(this.__limitUpper);
    }

    /**
     *
     * @return {boolean}
     */
    isValueAtLimit() {
        return this.__value === this.__limitUpper;
    }

    /**
     *
     * @param {Number} val
     */
    addValue(val) {
        this.setValue(this.__value + val);
    }

    /**
     *
     * @param {BoundedValue} other
     * @returns {BoundedValue}
     */
    copy(other) {
        this.setUpperLimit(other.getUpperLimit());
        this.setValue(other.getValue());
        return this;
    }

    /**
     *
     * @param {BoundedValue} other
     * @returns {boolean}
     */
    equals(other) {
        return this.__value === other.__value && this.__limitLower === other.__limitLower && this.__limitUpper === other.__limitUpper;
    }

    /**
     *
     * @returns {number}
     */
    hash() {
        const v0 = computeHashFloat(this.__value);
        const v1 = computeHashFloat(this.__limitLower);
        const v2 = computeHashFloat(this.__limitUpper);

        const h0 = (v0 << 5) - v0 + v1;

        return (h0 << 5) - h0 + v2;
    }

    toJSON() {
        return {
            value: this.__value,
            limit: this.__limitUpper
        };
    }

    fromJSON(json) {
        this.setUpperLimit(json.limit);
        this.setValue(json.value);
    }

    /**
     *
     * @param {BinaryBuffer} buffer
     */
    toBinaryBuffer(buffer) {
        buffer.writeFloat64(this.__value);
        buffer.writeFloat64(this.__limitUpper);
    }

    /**
     *
     * @param {BinaryBuffer} buffer
     */
    fromBinaryBuffer(buffer) {
        const value = buffer.readFloat64();
        const upperLimit = buffer.readFloat64();

        this.setUpperLimit(upperLimit);
        this.setValue(value);
    }
}


export default BoundedValue;
