import { AABB3 } from "../../../../../core/geom/3d/aabb/AABB3.js";
import { computeStringHash } from "../../../../../core/primitives/strings/computeStringHash.js";
import { entity_node_compute_bounding_box } from "../../../../ecs/parent/entity_node_compute_bounding_box.js";
import { ShadedGeometry } from "../ShadedGeometry.js";

export const SGMeshFlags = {
    CastShadow: 1,
    ReceiveShadow: 2,
    Loaded: 4
};

/**
 * @readonly
 * @type {number}
 */
const DEFAULT_FLAGS = SGMeshFlags.CastShadow
    | SGMeshFlags.ReceiveShadow
;

export class SGMesh {
    /**
     * Allows settings a new material to all parts of the mesh
     * @type {Material|null}
     */
    #material_override = null;

    /**
     *
     * @type {number}
     */
    flags = DEFAULT_FLAGS;

    /**
     *
     * @type {string|null}
     */
    __url = null;

    /**
     *
     * @type {EntityNode|null}
     * @private
     */
    __node = null;

    /**
     *
     * @type {AABB3}
     * @private
     */
    __initial_bounds = new AABB3();

    /**
     * Contents are generated by the system
     * Do not modify contents unless you know exactly what you're doing
     * @returns {EntityNode|null}
     */
    get node() {
        return this.__node;
    }

    /**
     *
     * @param {SGMesh} other
     * @returns {boolean}
     */
    equals(other) {
        if (other === undefined || other === null) {
            return false;
        }

        return this.__url === other.__url
            && this.flags === other.flags
            && this.materialOverride === other.materialOverride;
    }

    hash() {
        return computeStringHash(this.__url);
    }

    /**
     *
     * @param {SGMesh} other
     */
    copy(other) {
        this.__url = other.__url;
        this.__node = null;
        this.__initial_bounds.copy(other.__initial_bounds);
        this.#material_override = other.#material_override;
    }

    /**
     *
     * @returns {SGMesh}
     */
    clone() {
        const r = new SGMesh();

        r.copy(this);

        return r;
    }

    /**
     *
     * @param {Material} v
     */
    set materialOverride(v) {
        if (v === this.#material_override) {
            return;
        }

        if (v === null) {
            throw new Error(`Once material override is set it can not be cleared, create a new SGMesh instance if you would like to clear override`);
        }

        this.#material_override = v;

        this.applyMaterialOverride();
    }

    get materialOverride() {
        return this.#material_override;
    }

    applyMaterialOverride() {
        if (this.__node === null) {
            return;
        }

        this.__node.traverse(node => {
            const sg = node.entity.getComponent(ShadedGeometry);

            if (sg !== null) {
                sg.material = this.#material_override;
            }
        });
    }

    /**
     *
     * @returns {string|null}
     */
    get url() {
        return this.__url;
    }

    /**
     *
     * @param {string|null} v
     */
    set url(v) {
        if (v === this.__url) {
            // no change
            return;
        }

        this.__url = v;
    }

    /**
     *
     * @param {AABB3} destination
     * @returns {boolean}
     */
    getBoundingBox(destination) {
        if (this.__node === null) {
            return false;
        }

        entity_node_compute_bounding_box(destination, this.__node);

        return true;
    }

    /**
     *
     * @param {AABB3} destination
     */
    getUntransformedBoundingBox(destination) {
        destination.copy(this.__initial_bounds);
    }

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

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

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

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

    /**
     *
     * @return {boolean}
     */
    get castShadow() {
        return this.getFlag(SGMeshFlags.CastShadow);
    }

    /**
     *
     * @param {boolean} v
     */
    set castShadow(v) {
        this.writeFlag(SGMeshFlags.CastShadow, v);
    }

    /**
     *
     * @return {boolean}
     */
    get receiveShadow() {
        return this.getFlag(SGMeshFlags.ReceiveShadow);
    }

    /**
     *
     * @param {boolean} v
     */
    set receiveShadow(v) {
        this.writeFlag(SGMeshFlags.ReceiveShadow, v);
    }

    /**
     *
     * @param {string} url
     * @returns {SGMesh}
     */
    static fromURL(url) {
        const r = new SGMesh();

        r.url = url;
        r.castShadow = true;
        r.receiveShadow = true;

        return r;
    }

    toJSON() {
        return {
            url: this.__url,
            receiveShadow: this.receiveShadow,
            castShadow: this.castShadow,
        };
    }

    fromJSON({ url, receiveShadow = true, castShadow = true }) {
        this.clearFlag(SGMeshFlags.Loaded);

        this.__url = url;
        this.receiveShadow = receiveShadow;
        this.castShadow = castShadow;
    }
}

SGMesh.typeName = "SGMesh";
