import { assert } from "../../core/assert.js";
import Signal from "../../core/events/signal/Signal.js";
import { noop } from "../../core/function/noop.js";
import { OptionAbstract } from "./OptionAbstract.js";

export class Option extends OptionAbstract {

    on = {
        written: new Signal(),
        writeFailed: new Signal(),
        settingsUpdated: new Signal()
    };

    /**
     *
     * @param {string} id
     * @param {function} read
     * @param {function} [write]
     * @param {object} [settings]
     * @constructor
     */
    constructor(id, read, write = noop, settings = {
        transient: false
    }) {
        super(id);

        assert.isFunction(read, 'read');

        const self = this;

        function wrappedWrite(v) {
            let result;

            let exception_thrown = false;

            try {
                result = write(v);
            } catch (e) {
                exception_thrown = true;

                self.on.writeFailed.send2(v, e);
            }

            if (!exception_thrown) {
                if (result !== undefined && typeof result.then === "function") {
                    result.then(
                        () => self.on.written.send1(v),
                        (e) => self.on.writeFailed.send2(v, e)
                    );
                } else {
                    self.on.written.send1(v);
                }
            }

            return result;
        }

        this.read = read;
        this.write = wrappedWrite;
        this.settings = settings;

        /**
         * Controls serialization. Transient options are not serialized
         * @type {boolean}
         */
        this.isTransient = (settings.transient === true);

    }

    /**
     *
     * @param {string} key
     * @param {number|boolean|string} value
     */
    setSetting(key, value) {
        this.settings[key] = value;

        this.on.settingsUpdated.send2(key, value);
    }

    toJSON() {
        let v = this.read();
        switch (typeof v) {
            case "number":
            case "boolean":
            case "string":
                return v;
            default:
                return null;
        }
    }

    fromJSON(json) {
        this.write(json);
    }
}
