import { assert } from "../../../core/assert.js";
import { combine_hash } from "../../../core/collection/array/combine_hash.js";
import Vector2 from '../../../core/geom/Vector2.js';
import { objectDeepEquals } from "../../../core/model/object/objectDeepEquals.js";
import ObservedBoolean from "../../../core/model/ObservedBoolean.js";
import { computeStringHash } from "../../../core/primitives/strings/computeStringHash.js";

/**
 *
 * @enum {number}
 */
export const GUIElementFlag = {
    /**
     * If this is set to false GUI element's view will not be managed by the system
     */
    Managed: 1,
    /**
     * Whether or not underlying view has been initialized
     */
    Initialized: 2
};

class GUIElement {
    /**
     *
     * @type {View}
     */
    view = null;

    /**
     *
     * @type {String}
     */
    klass = null;

    /**
     *
     * @type {Object}
     */
    parameters = {};

    /**
     * ranges from 0..1 in both X and Y, controls anchor point of element positioning
     * @type {Vector2}
     */
    anchor = new Vector2(0, 0);

    /**
     * Used for visual grouping of elements, system will create and manage named containers to group elements together
     * @readonly
     * @type {String|null}
     */
    group = null;

    /**
     * @private
     * @type {number}
     */
    flags = GUIElementFlag.Managed;


    /**
     *
     * @type {ObservedBoolean}
     */
    visible = new ObservedBoolean(true);


    /**
     *
     * @param {number|GUIElementFlag} flag
     * @returns {void}
     */
    setFlag(flag) {
        this.flags |= flag;
    }

    /**
     *
     * @param {number|GUIElementFlag} flag
     * @returns {void}
     */
    clearFlag(flag) {
        this.flags &= ~flag;
    }

    /**
     *
     * @param {number|GUIElementFlag} flag
     * @param {boolean} value
     */
    writeFlag(flag, value) {
        if (value) {
            this.setFlag(flag);
        } else {
            this.clearFlag(flag);
        }
    }

    /**
     *
     * @param {number|GUIElementFlag} flag
     * @returns {boolean}
     */
    getFlag(flag) {
        return (this.flags & flag) === flag;
    }

    /**
     *
     * @param {GUIElement} other
     * @returns {boolean}
     */
    equals(other) {
        if (this.flags !== other.flags) {
            return false;
        }

        if (this.klass !== other.klass) {
            return false;
        }

        if (!this.getFlag(GUIElementFlag.Managed) && this.view !== other.view) {
            return false;
        }

        if (!this.anchor.equals(other.anchor)) {
            return false;
        }

        if (this.group !== other.group) {
            return false;
        }

        if (!this.visible.equals(other.visible)) {
            return false;
        }

        if (!objectDeepEquals(this.parameters, other.parameters)) {
            return false;
        }

        return true;
    }

    /**
     *
     * @return {number}
     */
    hash() {
        return combine_hash(
            this.flags,
            computeStringHash(this.klass),
            this.anchor.hash(),
            computeStringHash(this.group),
            this.visible.hash()
        )
    }

    /**
     *
     * @param {View} view
     * @returns {GUIElement}
     */
    static fromView(view) {
        assert.defined(view, 'view');
        assert.notNull(view, 'view');

        const r = new GUIElement();

        r.clearFlag(GUIElementFlag.Managed);
        r.view = view;

        return r;
    }

    /**
     *
     * @param j
     * @return {GUIElement}
     */
    static fromJSON(j) {
        const r = new GUIElement();

        r.fromJSON(j);

        return r;
    }

    fromJSON({ klass, parameters = {}, anchor = Vector2.zero, group = null, visible = true }) {
        if (group !== null && typeof group !== "string") {
            throw new Error(`Expected group to be null or string, instead was '${typeof group}'`);
        }

        this.klass = klass;
        this.parameters = parameters;
        this.anchor.fromJSON(anchor);
        this.group = group;
        this.visible.set(visible);
    }

    toJSON() {
        return {
            klass: this.klass,
            parameters: this.parameters,
            anchor: this.anchor.toJSON(),
            group: this.group,
            visible: this.visible.toJSON()
        };
    }

}

GUIElement.typeName = "GUIElement";
GUIElement.serializable = true;

export default GUIElement;


