import Signal from "../events/signal/Signal.js";

class ObservedValue {
    /**
     *
     * @template T
     * @param {T} v
     * @constructor
     */
    constructor(v) {
        this.onChanged = new Signal();
        this.__value = v;
        /**
         *
         * @type {(function(): T)}
         */
        this.getValue = ObservedValue.prototype.get;
    }

    /**
     *
     * @param {T} value
     */
    set(value) {
        if (this.__value !== value) {
            const oldValue = this.__value;
            this.__value = value;
            this.onChanged.send2(value, oldValue);
        }
    }

    /**
     * Set value without triggering {@link #onChanged} signal
     * @param {T} value
     * @see #set
     */
    setSilent(value) {
        this.__value = value;
    }

    /**
     *
     * @returns {T}
     */
    get() {
        return this.__value;
    }

    /**
     *
     * @param {ObservedValue} other
     */
    copy(other) {
        this.set(other.__value);
    }

    /**
     *
     * @param {ObservedValue} other
     * @returns {boolean}
     */
    equals(other) {
        return (typeof other === 'object') && this.__value === other.__value;
    }

    /**
     *
     * @returns {ObservedValue.<T>}
     */
    clone() {
        return new ObservedValue(this.__value);
    }

    /**
     * Convenience method, invoked given function with current value and registers onChanged callback
     * @param {function} processor
     * @param {*} [thisArg]
     * @returns {ObservedValue.<T>}
     */
    process(processor, thisArg) {
        this.onChanged.add(processor, thisArg);

        const v = this.__value;

        processor.call(thisArg, v, v);

        return this;
    }

    toString() {
        return JSON.stringify({
            value: this.__value
        });
    }

    toJSON() {
        return this.get();
    }

    fromJSON(value) {
        this.set(value);
    }
}

export default ObservedValue;
