import { Matrix, Quaternion, Vector3 } from "@babylonjs/core/Maths/math.vector";
import { PmxObject } from "../Loader/Parser/pmxObject";
/**
 * Bone for MMD runtime
 *
 * For mmd runtime, it is necessary to override the bone system because it has a different implementation than the usual matrix update method
 *
 * Which requires the mmd runtime bone, which is the wrapper of the babylon.js bone
 */
export class MmdRuntimeBone {
    /**
     * The Babylon.js bone
     */
    linkedBone;
    /**
     * Name of the bone
     */
    name;
    /**
     * Parent bone
     */
    parentBone;
    /**
     * Child bones
     */
    childBones;
    /**
     * Transform order
     */
    transformOrder;
    /**
     * Bone flag
     *
     * @see PmxObject.Bone.Flag
     */
    flag;
    /**
     * Whether the bone transform is applied after physics
     */
    transformAfterPhysics;
    /**
     * Append transform solver
     *
     * If the bone does not have an append transform solver, it will be null
     */
    appendTransformSolver;
    /**
     * IK solver
     *
     * If the bone does not have an ik solver, it will be null
     */
    ikSolver;
    /**
     * The position offset value to be moved by the bone morph
     *
     * This is a field that is primarily updated by the append transform solver
     */
    morphPositionOffset;
    /**
     * The rotation offset value to be moved by the bone morph
     *
     * This is a field that is primarily updated by the append transform solver
     */
    morphRotationOffset;
    /**
     * The rotation offset value to be moved by the IK solver
     *
     * If this bone is an Ik chain, this value is non-null
     */
    ikRotation;
    /**
     * Local matrix of this bone
     */
    localMatrix;
    /**
     * World matrix of this bone
     *
     * Slice of `MmdModel.worldTransformMatrices` that corresponds to this bone
     */
    worldMatrix;
    /**
     * Gets the position of a local transform with animations and bone morphs applied
     */
    getAnimatedPositionToRef;
    /**
     * Gets the rotation of a local transform with animations and bone morphs applied
     */
    getAnimatedRotationToRef;
    /**
     * Get the position offset of the local transform with animation and bone morph applied
     *
     * Refers to the change from the rest position
     */
    getAnimationPositionOffsetToRef;
    // public getAnimationRotationOffsetToRef: (target: Quaternion) => Quaternion;
    /**
     * Create MMD runtime bone
     * @param linkedBone Linked Babylon.js bone
     * @param boneMetadata Bone metadata
     * @param worldTransformMatrices World transform matrices
     * @param boneIndex Bone index
     */
    constructor(linkedBone, boneMetadata, worldTransformMatrices, boneIndex) {
        this.linkedBone = linkedBone;
        this.name = boneMetadata.name;
        this.parentBone = null;
        this.childBones = [];
        this.transformOrder = boneMetadata.transformOrder;
        this.flag = boneMetadata.flag;
        this.transformAfterPhysics = (boneMetadata.flag & PmxObject.Bone.Flag.TransformAfterPhysics) !== 0;
        this.appendTransformSolver = null;
        this.ikSolver = null;
        this.morphPositionOffset = Vector3.Zero();
        this.morphRotationOffset = Quaternion.Identity();
        this.ikRotation = null;
        this.localMatrix = Matrix.Identity();
        this.worldMatrix = new Float32Array(worldTransformMatrices.buffer, worldTransformMatrices.byteOffset + boneIndex * 16 * 4, 16);
        this.getAnimatedPositionToRef = this._getAnimatedPositionToRef;
        this.getAnimatedRotationToRef = this._getAnimatedRotationToRef;
        this.getAnimationPositionOffsetToRef = this._getAnimationPositionOffsetToRef;
        // this.getAnimationRotationOffsetToRef = this._getAnimationRotationOffsetToRef;
    }
    _getAnimatedPositionWithMorphToRef(target) {
        target.copyFrom(this.linkedBone.position);
        return target.addInPlace(this.morphPositionOffset);
    }
    _getAnimatedPositionToRef(target) {
        target.copyFrom(this.linkedBone.position);
        return target;
    }
    _getAnimatedRotationToRef(target) {
        return target.copyFrom(this.linkedBone.rotationQuaternion);
    }
    _getAnimatedRotationWithMorphToRef(target) {
        target.copyFrom(this.linkedBone.rotationQuaternion);
        return target.multiplyInPlace(this.morphRotationOffset);
    }
    static _TempVector3 = new Vector3();
    _getAnimationPositionOffsetToRef(target) {
        target.copyFrom(this.linkedBone.position);
        this.linkedBone.getRestMatrix().getTranslationToRef(MmdRuntimeBone._TempVector3);
        return target.subtractInPlace(MmdRuntimeBone._TempVector3);
    }
    _getAnimationPositionOffsetWithMorphToRef(target) {
        target.copyFrom(this.linkedBone.position);
        target.addInPlace(this.morphPositionOffset);
        this.linkedBone.getRestMatrix().getTranslationToRef(MmdRuntimeBone._TempVector3);
        return target.subtractInPlace(MmdRuntimeBone._TempVector3);
    }
    // a: rest quaternion
    // b: animation quaternion
    // c: animated quaternion
    // a * b = c
    // to get b from a and c:
    // a^-1 * c = b
    // private static readonly _TempQuaternion = new Quaternion();
    // private _getAnimationRotationOffsetToRef(target: Quaternion): Quaternion {
    //     target.copyFrom(this.linkedBone.rotationQuaternion);
    //     Quaternion.FromRotationMatrixToRef(this.linkedBone.getRestMatrix(), MmdRuntimeBone._TempQuaternion).invertInPlace();
    //     return MmdRuntimeBone._TempQuaternion.multiplyInPlace(target);
    // }
    // private _getAnimationRotationOffsetWithMorphToRef(target: Quaternion): Quaternion {
    //     target.copyFrom(this.linkedBone.rotationQuaternion);
    //     target.multiplyInPlace(this.morphRotationOffset);
    //     Quaternion.FromRotationMatrixToRef(this.linkedBone.getRestMatrix(), MmdRuntimeBone._TempQuaternion).invertInPlace();
    //     return MmdRuntimeBone._TempQuaternion.multiplyInPlace(target);
    // }
    /**
     * Allows the animation of this bone to be affected by the `morphPositionOffset` and `morphRotationOffset` fields
     */
    enableMorph() {
        this.getAnimatedPositionToRef = this._getAnimatedPositionWithMorphToRef;
        this.getAnimatedRotationToRef = this._getAnimatedRotationWithMorphToRef;
        this.getAnimationPositionOffsetToRef = this._getAnimationPositionOffsetWithMorphToRef;
        // this.getAnimationRotationOffsetToRef = this._getAnimationRotationOffsetWithMorphToRef;
    }
    /**
     * Disables the animation of this bone to be affected by the `morphPositionOffset` and `morphRotationOffset` fields
     */
    disableMorph() {
        this.getAnimatedPositionToRef = this._getAnimatedPositionToRef;
        this.getAnimatedRotationToRef = this._getAnimatedRotationToRef;
        this.getAnimationPositionOffsetToRef = this._getAnimationPositionOffsetToRef;
        // this.getAnimationRotationOffsetToRef = this._getAnimationRotationOffsetToRef;
    }
    static _TempRotation = Quaternion.Identity();
    static _TempPosition = Vector3.Zero();
    /**
     * Update the local matrix of this bone
     */
    updateLocalMatrix() {
        const rotation = this.getAnimatedRotationToRef(MmdRuntimeBone._TempRotation);
        if (this.ikRotation !== null) {
            this.ikRotation.multiplyToRef(rotation, rotation);
        }
        const position = this.getAnimatedPositionToRef(MmdRuntimeBone._TempPosition);
        if (this.appendTransformSolver !== null) {
            if (this.appendTransformSolver.affectRotation) {
                rotation.multiplyInPlace(this.appendTransformSolver.appendRotationOffset);
            }
            if (this.appendTransformSolver.affectPosition) {
                position.addInPlace(this.appendTransformSolver.appendPositionOffset);
            }
        }
        Matrix.ComposeToRef(this.linkedBone.scaling, rotation, position, this.localMatrix);
    }
    static _Stack = [];
    static _ParentWorldMatrix = Matrix.Identity();
    /**
     * Update the world matrix of this bone and its children bones recursively
     */
    updateWorldMatrix() {
        const stack = MmdRuntimeBone._Stack;
        stack.length = 0;
        stack.push(this);
        const parentWorldMatrix = MmdRuntimeBone._ParentWorldMatrix;
        while (stack.length > 0) {
            const bone = stack.pop();
            const parentBone = bone.parentBone;
            if (parentBone !== null) {
                bone.localMatrix.multiplyToArray(Matrix.FromArrayToRef(parentBone.worldMatrix, 0, parentWorldMatrix), bone.worldMatrix, 0);
            }
            else {
                bone.localMatrix.copyToArray(bone.worldMatrix);
            }
            const childrenBones = bone.childBones;
            for (let i = 0, l = childrenBones.length; i < l; ++i) {
                stack.push(childrenBones[i]);
            }
        }
    }
    /**
     * Get the world matrix of this bone
     *
     * The result of this method is not same as `linkedBone.getFinalMatrix()`
     *
     * `linkedBone.getFinalMatrix()` updated at the end of the mmd runtime update, so it may not be the latest value
     * @param target target matrix
     * @returns target matrix
     */
    getWorldMatrixToRef(target) {
        return Matrix.FromArrayToRef(this.worldMatrix, 0, target);
    }
    /**
     * Get the world translation of this bone
     * @param target target vector
     * @returns target vector
     */
    getWorldTranslationToRef(target) {
        return Vector3.FromArrayToRef(this.worldMatrix, 12, target);
    }
    /**
     * Set the world translation of this bone
     * @param source source vector
     */
    setWorldTranslationFromRef(source) {
        source.toArray(this.worldMatrix, 12);
    }
    /**
     * Get ik solver index
     *
     * If the bone does not have an ik solver, it will return -1
     */
    get ikSolverIndex() {
        return this.ikSolver !== null ? this.ikSolver.index : -1;
    }
}
