import Signal from "../../../../../../../core/events/signal/Signal.js";
import {
    DataType2TypedArrayConstructorMapping
} from "../../../../../../../core/binary/type/DataType2TypedArrayConstructorMapping.js";
import { array_copy } from "../../../../../../../core/collection/array/array_copy.js";
import { min2 } from "../../../../../../../core/math/min2.js";
import { assert } from "../../../../../../../core/assert.js";
import { ParameterLookupTable } from "../../parameter/ParameterLookupTable.js";

export class AttributeValue {
    constructor() {
        /**
         * Attribute spec
         * @type {AttributeSpec}
         */
        this.__spec = null;

        /**
         *
         * @type {number[]}
         */
        this.__value = [];

        /**
         *
         * @type {ParameterLookupTable}
         * @private
         */
        this.__lut = null;

        /**
         *
         * @type {Signal}
         */
        this.onValueChange = new Signal();
    }

    /**
     * @param {AttributeSpec} s
     */
    set spec(s) {
        this.__spec = s;
        this.init();
    }

    /**
     *
     * @returns {AttributeSpec}
     */
    get spec() {
        return this.__spec;
    }

    /**
     * Will generate actual typed value storage
     */
    init() {
        const spec = this.__spec;

        const TAC = DataType2TypedArrayConstructorMapping[spec.type];

        const new_storage = new TAC(spec.itemSize);

        array_copy(this.__value, 0, new_storage, 0, min2(this.__value.length, new_storage.length));

        this.__value = new_storage;
    }

    /**
     * @param {ParameterLookupTable} v
     */
    set lookup(v) {
        this.__lut = v;
    }

    /**
     *
     * @returns {ParameterLookupTable}
     */
    get lookup() {
        return this.__lut;
    }

    set lookup_value(v) {
        this.write_lookup_value(v);
    }

    /**
     *
     * @param {number} v
     */
    write_lookup_value(v) {
        assert.isNumber(v, 'v');

        if (this.__lut === null) {
            throw new Error('No lookup table attached');
        }

        const r = [];

        this.__lut.sample(v, r);

        this.write_value(r);
    }

    /**
     *
     * @param {number[]} v
     */
    write_value_silent(v) {
        this.onValueChange.mute();
        this.write_value(v);
        this.onValueChange.unmute();
    }

    /**
     *
     * @param {number[]} v
     */
    write_value(v) {
        let change_flag = false;

        const itemSize = this.__spec.itemSize;

        for (let i = 0; i < itemSize; i++) {
            const old = this.__value[i];

            this.__value[i] = v[i];

            if (this.__value[i] !== old) {
                // stored value has changed
                change_flag = true;
            }
        }

        if (change_flag) {
            this.onValueChange.send0();
        }
    }

    /**
     *
     * @param {number[]} destination
     */
    read_value(destination) {
        array_copy(this.__value, 0, destination, 0, this.spec.itemSize);
    }

    /**
     *
     * @param {number[]} [value]
     * @param {Object} [lookup]
     * @param {number} [lookup_value]
     */
    fromJSON({
                 value,
                 lookup,
                 lookup_value
             }) {

        if (value !== undefined) {
            if (Array.isArray(value)) {

                if (lookup !== undefined) {
                    console.warn("Both 'lookup' and 'value' were specified, expected one or the other. Ignoring the value attribute.");
                } else {
                    this.write_value(value);
                }

            } else {
                throw new Error(`Expected 'value' to be an array, instead got something else (=${value})`);
            }
        } else {
            // retain current value
        }

        if (lookup !== undefined) {
            const lut = new ParameterLookupTable();

            lut.fromJSON(lookup);

            this.lookup = lut;
        } else {
            this.lookup = null;
        }

        if (lookup_value !== undefined) {
            assert.isNumber(lookup_value, 'lookup_value');
            assert.isFinite(lookup_value, 'lookup_value');

            if (lookup === undefined) {
                console.warn("found 'lookup_value' attribute, but no 'lookup' table. Ignoring lookup value");
            } else {
                this.lookup_value = lookup_value;
            }
        } else if (lookup !== undefined) {
            // supply a default lookup value
            this.lookup_value = 0;
        }
    }
}
