import { min2 } from "../../../core/math/min2.js";
import { ResourceAccessKind } from "../../../core/model/ResourceAccessKind.js";
import { ResourceAccessSpecification } from "../../../core/model/ResourceAccessSpecification.js";
import { System } from "../System.js";
import { Transform } from "../transform/Transform.js";
import { TransformAttachment, TransformAttachmentFlags } from "./TransformAttachment.js";

/**
 * @readonly
 * @type {number}
 */
const QUEUE_ITERATION_COUNT = 32;

class UpdateContext {
    constructor() {
        /**
         *
         * @type {TransformAttachment|null}
         */
        this.attachment = null;

        /**
         *
         * @type {Transform|null}
         */
        this.transform = null;

        /**
         *
         * @type {number}
         */
        this.entity = -1;

        /**
         *
         * @type {Transform|null}
         */
        this.parent_transform = null;

        /**
         *
         * @type {EntityComponentDataset|null}
         */
        this.ecd = null;
    }

    toString() {
        return `UpdateContext{ attachment:${this.attachment}, entity:${this.entity} }`;
    }

    update() {
        this.transform.multiplyTransforms(
            this.parent_transform,
            this.attachment.transform
        );
    }

    /**
     *
     * @returns {boolean}
     */
    bind_parent() {

        const parent_transform = this.ecd.getComponent(this.attachment.parent, Transform);

        if (parent_transform === undefined) {
            return false;
        }

        this.parent_transform = parent_transform;

        return true;
    }

    link() {
        const t_parent = this.parent_transform;

        t_parent.subscribe(this.update, this);

        const t_attachment = this.attachment.transform;

        t_attachment.subscribe(this.update, this);
    }

    unlink() {
        const transform = this.parent_transform;

        transform.unsubscribe(this.update, this);

        const t_attachment = this.attachment.transform;

        t_attachment.unsubscribe(this.update, this);
    }
}

export class TransformAttachmentSystem extends System {

    /**
     *
     * @type {UpdateContext[]}
     * @private
     */
    __contexts = [];

    /**
     *
     * @type {UpdateContext[]}
     * @private
     */
    __queue = [];
    __queue_size = 0;
    __queue_cursor = 0;

    constructor() {
        super();

        this.dependencies = [TransformAttachment, Transform];

        this.components_used = [
            ResourceAccessSpecification.from(TransformAttachment, ResourceAccessKind.Read),
            ResourceAccessSpecification.from(Transform, ResourceAccessKind.Read | ResourceAccessKind.Write),
        ];
    }

    /**
     *
     * @param {UpdateContext} ctx
     * @private
     */
    __finalize_link(ctx) {

        ctx.link();

        this.__contexts[ctx.entity] = ctx;

        if ((ctx.attachment.flags & TransformAttachmentFlags.Immediate) !== 0) {
            ctx.update();
        }
    }

    /**
     *
     * @param {UpdateContext} ctx
     * @private
     */
    __enqueue(ctx) {
        this.__queue[this.__queue_size++] = ctx;
    }

    /**
     *
     * @param {number} entity
     * @returns {boolean}
     * @private
     */
    __dequeue_entity(entity) {
        for (let i = 0; i < this.__queue_size; i++) {
            const ctx = this.__queue[i];

            if (ctx.entity === entity) {
                this.__queue.splice(i, 1);
                this.__queue_size--;
                return true;
            }
        }

        return false;
    }

    /**
     *
     * @param {TransformAttachment} attachment
     * @param {Transform} transform
     * @param {number} entity
     */
    link(attachment, transform, entity) {
        const ctx = new UpdateContext();

        ctx.attachment = attachment;
        ctx.transform = transform;
        ctx.entity = entity;


        ctx.ecd = this.entityManager.dataset;

        if (ctx.bind_parent()) {
            this.__finalize_link(ctx);
        } else {
            // failed to bind parent, queue up instead
            this.__enqueue(ctx);
        }

    }

    /**
     *
     * @param {TransformAttachment} attachment
     * @param {Transform} transform
     * @param {number} entity
     */
    unlink(attachment, transform, entity) {
        const ctx = this.__contexts[entity];

        if (ctx !== undefined) {

            delete this.__contexts[entity];

            ctx.unlink();

        } else {
            // no context found, check the queue
            this.__dequeue_entity(entity);
        }

    }

    update(timeDelta) {
        const step_count = min2(this.__queue_size, QUEUE_ITERATION_COUNT);

        for (let i = 0; i < step_count; i++) {
            const index = this.__queue_cursor % this.__queue_size;

            const ctx = this.__queue[index];

            if (ctx.bind_parent()) {
                // parent obtained
                this.__finalize_link(ctx);

                this.__queue.splice(index, 1);
                this.__queue_size--;

            } else {
                this.__queue_cursor++;
            }
        }

    }
}
