import Signal from "../../../../../core/events/signal/Signal.js";
import { ResourceAccessKind } from "../../../../../core/model/ResourceAccessKind.js";
import { ResourceAccessSpecification } from "../../../../../core/model/ResourceAccessSpecification.js";
import { convert_three_clip } from "../../../../animation/clip/ecd_bind_animation_curve.js";
import { System } from "../../../../ecs/System.js";
import { assetTypeByPath } from "../../mesh/assetTypeByPath.js";
import { SGMesh } from "./SGMesh.js";
import { SGMeshAnimationController } from "./SGMeshAnimationController.js";

export class SGMeshAnimationControllerSystem extends System {
    /**
     * Current time delta
     * @type {number}
     */
    #time_delta = 0;

    /**
     * Entity queue
     * @type {number[]}
     */
    #queue = [];

    /**
     * Fires when one or more animation clips are currently playing and are updated
     * @readonly
     * @type {Signal}
     */
    onWorkDone = new Signal()

    /**
     * How many clips were updated during last tick
     * @type {number}
     */
    #cycle_clip_advance_count = 0;

    /**
     *
     * @param {Engine} engine
     */
    constructor(engine) {
        super();

        this.engine = engine;

        this.dependencies = [
            SGMeshAnimationController,
            SGMesh
        ];

        this.components_used = [
            ResourceAccessSpecification.from(SGMesh, ResourceAccessKind.Read | ResourceAccessKind.Write),
            ResourceAccessSpecification.from(SGMeshAnimationController, ResourceAccessKind.Read)
        ];

    }

    async #process(entity) {
        const em = this.entityManager;

        const ecd = em.dataset;

        const mesh = ecd.getComponent(entity, SGMesh);

        if (mesh.node === null) {
            // requeue
            this.#enqueue(entity);
            return;
        }

        const assetType = assetTypeByPath(mesh.url);

        const asset = await this.engine.assetManager.promise(mesh.url, assetType);

        const three_animations = asset.animations ?? [];

        const animations = three_animations.map(three_animation => {

            return convert_three_clip(mesh.node, three_animation)
        });

        const controller = ecd.getComponent(entity, SGMeshAnimationController);

        controller.bound = animations;
    }

    /**
     *
     * @param {number} entity
     */
    #enqueue(entity) {
        this.#queue.push(entity);
    }

    /**
     *
     * @param {SGMeshAnimationController} anim
     * @param {SGMesh} mesh
     * @param {number} entity
     */
    async link(anim, mesh, entity) {

        if (mesh.node === null) {
            this.#enqueue(entity);
            return;
        }

        await this.#process(entity);
    }

    /**
     *
     * @param {SGMeshAnimationController} controller
     */
    #visit_entity(controller) {

        const active = controller.active;

        let active_count = active.length;

        for (let i = 0; i < active_count; i++) {
            const playback = active[i];

            playback.time += this.#time_delta;

            const clip = controller.getBoundByName(playback.clip_name);

            if (clip === undefined) {
                // clip doesn't exist
                continue;
            }

            let t = playback.time;

            if (t > clip.duration) {
                if (playback.loop) {
                    t = t % clip.duration;
                } else {
                    t = clip.duration;

                    // playback finished, remove active
                    active.splice(i, 1);

                    i--;
                    active_count--;
                }
            }

            clip.writeAt(t);

            // mark clip update
            this.#cycle_clip_advance_count++;
        }
    }

    update(td) {
        this.#cycle_clip_advance_count = 0;
        this.#time_delta = td;

        for (let i = 0; i < 1000; i++) {
            if (this.#queue.length === 0) {
                break;
            }

            const entity = this.#queue.pop();

            this.#process(entity);
        }

        const ecd = this.entityManager.dataset;

        if (ecd === null) {
            return;
        }

        ecd.traverseComponents(SGMeshAnimationController, this.#visit_entity, this);

        if (this.#cycle_clip_advance_count > 0) {
            this.onWorkDone.send0();
        }
    }
}