import { System } from "../System.js";
import { InverseKinematics } from "./InverseKinematics.js";
import Mesh, { MeshFlags } from "../../graphics/ecs/mesh/Mesh.js";
import { TwoBoneInverseKinematicsSolver } from "./TwoBoneInverseKinematicsSolver.js";
import { extractSkeletonFromMeshComponent } from "../../graphics/ecs/mesh/SkeletonUtils.js";
import { OneBoneSurfaceAlignmentSolver } from "./OneBoneSurfaceAlignmentSolver.js";
import { IKProblem } from "./IKProblem.js";
import { obtainTerrain } from "../terrain/util/obtainTerrain.js";
import { ResourceAccessSpecification } from "../../../core/model/ResourceAccessSpecification.js";
import { ResourceAccessKind } from "../../../core/model/ResourceAccessKind.js";


export class InverseKinematicsSystem extends System {

    dependencies = [InverseKinematics, Mesh];

    components_used = [
        ResourceAccessSpecification.from(Mesh, ResourceAccessKind.Read | ResourceAccessSpecification.Write)
    ];

    /**
     *
     * @type {{"2BIK": TwoBoneInverseKinematicsSolver}}
     */
    solvers = {
        "2BIK": new TwoBoneInverseKinematicsSolver(),
        "1BSA": new OneBoneSurfaceAlignmentSolver()
    };

    problems = {};

    /**
     *
     * @type {Terrain}
     */
    __terrain = null;


    get componentClass() {
        return InverseKinematics;
    }

    /**
     *
     * @param {InverseKinematics} ik
     * @param {Mesh} mesh
     */
    recordEntityProblems(ik, mesh) {

        if (!mesh.getFlag(MeshFlags.InView)) {
            //not visible
            return;
        }

        const skeleton = extractSkeletonFromMeshComponent(mesh);

        if (skeleton === undefined) {
            return;
        }

        const ikConstraints = ik.constraints;

        const n = ikConstraints.length;

        for (let i = 0; i < n; i++) {
            /**
             *
             * @type {IKConstraint}
             */
            const c = ikConstraints[i];

            const solverID = c.solver;

            //define problem
            const problem = IKProblem.pool.create();
            problem.constraint = c;
            problem.skeleton = skeleton;
            problem.terrain = this.__terrain;

            const problemSet = this.problems[solverID];

            if (problemSet === undefined) {
                this.problems[solverID] = [problem];
            } else {
                problemSet.push(problem);
            }
        }

    }

    update(timeDelta) {


        const em = this.entityManager;

        const ecd = em.dataset;

        if (ecd == null) {
            return;
        }

        this.__terrain = obtainTerrain(ecd);

        if (this.__terrain === null) {
            return;
        }


        ecd.traverseEntities([InverseKinematics, Mesh], this.recordEntityProblems, this);

        //solve problems
        for (const solverID in this.solvers) {
            const problemSet = this.problems[solverID];

            if (problemSet === undefined) {
                continue;
            }

            const n = problemSet.length;

            if (n === 0) {
                continue;
            }

            const solver = this.solvers[solverID];

            for (let i = 0; i < n; i++) {
                const problem = problemSet[i];

                try {
                    solver.solve(problem);
                } catch (e) {
                    console.error("Solution failed:", e);
                }

                IKProblem.pool.release(problem);
            }
        }

        //clear problem set
        for (const solverID in this.problems) {
            const problemSet = this.problems[solverID];

            problemSet.splice(0, problemSet.length);
        }
    }
}
