using Passer.Humanoid.Tracking;
using UnityEngine;

namespace Passer.Humanoid {

    [System.Serializable]
    public class BonePose {

        public BoneType boneType = BoneType.AllBones;

        public BoneReference boneRef = new BoneReference();
        public BoneReference referenceBoneRef = new BoneReference();

        public bool setTranslation;
        public bool setRotation;
        public bool setScale;

        public Vector3 translation;
        public Quaternion rotation;
        public Vector3 scale;

        public void Reset(HumanoidControl humanoid) {
            rotation = Quaternion.identity;
            translation = Vector3.zero;

            HumanoidTarget.TargetedBone targetedBone = humanoid.GetBone(boneRef.boneId);
            targetedBone.target.transform.localRotation = rotation;
            targetedBone.target.transform.localPosition = translation;
        }

        public void ShowPose(HumanoidControl humanoid, float value) {
            HumanoidTarget.TargetedBone targetedBone = humanoid.GetBone(boneRef.boneId);
            if (targetedBone == null || targetedBone.target.transform == null)
                return;

            Vector3 referencePosition = Vector3.zero;
            Quaternion referenceRotation = Quaternion.identity;
            Vector3 referenceScale = Vector3.zero;

            HumanoidTarget.TargetedBone referenceBone = humanoid.GetBone(referenceBoneRef.boneId);
            if (referenceBoneRef.boneId != Bone.None && referenceBone.target.transform != null) {
                referencePosition = referenceBone.target.transform.position;
                referenceRotation = referenceBone.target.transform.rotation; //referenceBone.bone.targetRotation;
                referenceScale = referenceBone.target.transform.lossyScale;
            }
            else {
                referencePosition = targetedBone.TargetBasePosition(); // humanoid.transform.position;
                referenceRotation = humanoid.transform.rotation;
                referenceScale = humanoid.transform.lossyScale;
            }

            if (setTranslation)
                //targetedBone.target.transform.position = targetedBone.TargetBasePosition() + Vector3.Lerp(Vector3.zero, referenceRotation * translation, value);
                targetedBone.target.transform.position = referencePosition + Vector3.Lerp(Vector3.zero, referenceRotation * translation, value);
            if (setRotation)
                targetedBone.target.transform.rotation = Quaternion.Slerp(targetedBone.TargetBaseRotation(), referenceRotation * rotation, value);
            if (setScale)
                targetedBone.target.transform.localScale = Vector3.Scale(referenceScale, Vector3.Lerp(Vector3.one, referenceRotation * scale, value));
        }

        public void ShowPose(HumanoidControl humanoid, Side showSide, float value) {
            HumanoidTarget.TargetedBone targetedBone = humanoid.GetBone(showSide, boneRef.sideBoneId);
            if (targetedBone == null || targetedBone.target.transform == null)
                return;

            Vector3 referencePosition = Vector3.zero;
            Quaternion referenceRotation = Quaternion.identity;
            Vector3 referenceScale = Vector3.zero;

            HumanoidTarget.TargetedBone referenceBone = humanoid.GetBone(showSide, referenceBoneRef.sideBoneId);

            if (referenceBone != null && referenceBone.bone.transform != null) {
                referencePosition = referenceBone.target.transform.position;
                referenceRotation = referenceBone.target.transform.rotation;
                referenceScale = referenceBone.target.transform.lossyScale;
            }
            else {
                referencePosition = humanoid.transform.position;
                referenceRotation = humanoid.transform.rotation;
                referenceScale = humanoid.transform.lossyScale;
            }

            if (boneRef.type == BoneType.SideBones && boneRef.side == Side.AnySide && showSide == Side.Right) {
                // We need to convert the left-orientated anyside to a right side
                // For this, we mirror the translation/rotation along the YZ-plane (not scale!)
                if (setTranslation) {
                    Vector3 mirroredTranslation = new Vector3(-translation.x, translation.y, translation.z);
                    targetedBone.target.transform.position = targetedBone.TargetBasePosition() + Vector3.Lerp(Vector3.zero, referenceRotation * mirroredTranslation, value);
                }
                if (setRotation) {
                    Quaternion mirroredRotation = new Quaternion(rotation.x, -rotation.y, -rotation.z, rotation.w);
                    targetedBone.target.transform.rotation = Quaternion.Slerp(targetedBone.TargetBaseRotation(), referenceRotation * mirroredRotation, value);
                }
                if (setScale)
                    targetedBone.target.transform.localScale = Vector3.Scale(referenceScale, Vector3.Lerp(Vector3.one, referenceRotation * scale, value));
            }
            else {
                if (setTranslation)
                    targetedBone.target.transform.position = targetedBone.TargetBasePosition() + Vector3.Lerp(Vector3.zero, referenceRotation * translation, value);
                if (setRotation)
                    targetedBone.target.transform.rotation = Quaternion.Slerp(targetedBone.TargetBaseRotation(), referenceRotation * rotation, value);
                if (setScale)
                    targetedBone.target.transform.localScale = Vector3.Scale(referenceScale, Vector3.Lerp(Vector3.one, referenceRotation * scale, value));
            }
        }

        public void ShowPoseAdditive(HumanoidControl humanoid, Side showSide, float value) {
            HumanoidTarget.TargetedBone targetedBone = humanoid.GetBone(showSide, boneRef.sideBoneId);
            if (targetedBone == null || targetedBone.target.transform == null)
                return;

            Vector3 referencePosition = Vector3.zero;
            Quaternion referenceRotation = Quaternion.identity;
            Vector3 referenceScale = Vector3.zero;

            HumanoidTarget.TargetedBone referenceBone = humanoid.GetBone(showSide, referenceBoneRef.sideBoneId);

            if (referenceBone != null && referenceBone.bone.transform != null) {
                referencePosition = referenceBone.target.transform.position;
                referenceRotation = referenceBone.target.transform.rotation;
                referenceScale = referenceBone.target.transform.lossyScale;
            }
            else {
                referencePosition = humanoid.transform.position;
                referenceRotation = humanoid.transform.rotation;
                referenceScale = humanoid.transform.lossyScale;
            }

            if (boneRef.type == BoneType.SideBones && boneRef.side == Side.AnySide && showSide == Side.Right) {
                // We need to convert the left-orientated anyside to a right side
                // For this, we mirror the translation/rotation along the YZ-plane (not scale!)
                if (setTranslation) {
                    Vector3 mirroredTranslation = new Vector3(-translation.x, translation.y, translation.z);
                    targetedBone.target.transform.position += Vector3.Lerp(Vector3.zero, referenceRotation * mirroredTranslation, value);
                }
                if (setRotation) {
                    Quaternion mirroredRotation = new Quaternion(rotation.x, -rotation.y, -rotation.z, rotation.w);
                    targetedBone.target.transform.rotation = Quaternion.Slerp(targetedBone.target.transform.rotation, referenceRotation * mirroredRotation, value);
                }
                if (setScale)
                    targetedBone.target.transform.localScale = Vector3.Scale(targetedBone.target.transform.localScale, Vector3.Lerp(Vector3.one, referenceRotation * scale, value));
            }
            else {
                if (setTranslation)
                    targetedBone.target.transform.position += Vector3.Lerp(Vector3.zero, referenceRotation * translation, value);
                if (setRotation)
                    targetedBone.target.transform.rotation = Quaternion.Slerp(targetedBone.target.transform.rotation, referenceRotation * rotation, value);
                if (setScale)
                    targetedBone.target.transform.localScale = Vector3.Scale(targetedBone.target.transform.localScale, Vector3.Lerp(Vector3.one, referenceRotation * scale, value));
            }
        }
        public void UpdateTranslation(HumanoidControl humanoid, Side side = Side.AnySide) {
            if (side == Side.AnySide) {
                if (boneRef.boneId < 0 || boneRef.boneId > Bone.Count || boneRef.boneId == Bone.None)
                    return;

                HumanoidTarget.TargetedBone targetedBone = humanoid.GetBone(boneRef.boneId);
                if (targetedBone.target.transform == null)
                    return;

                setTranslation = true;
                Vector3 targetBasePosition = targetedBone.TargetBasePosition();
                if (referenceBoneRef.boneId == Bone.None) {
                    translation = Quaternion.Inverse(humanoid.transform.rotation) * (targetedBone.target.transform.position - targetBasePosition);
                    return;
                }

                HumanoidTarget.TargetedBone referenceBone = humanoid.GetBone(referenceBoneRef.boneId);
                if (referenceBone.target.transform != null)
                    translation = Quaternion.Inverse(referenceBone.bone.targetRotation) * (targetedBone.target.transform.position - targetBasePosition);
                else
                    translation = Quaternion.Inverse(humanoid.transform.rotation) * (targetedBone.target.transform.position - targetBasePosition);

                setTranslation = (translation.sqrMagnitude > 0.001F);
            }
            else {
                if (boneRef.sideBoneId < 0 || boneRef.sideBoneId > SideBone.Count)
                    return;

                HumanoidTarget.TargetedBone targetedBone = humanoid.GetBone(side, boneRef.sideBoneId);
                if (targetedBone.target.transform == null)
                    return;

                setTranslation = true;
                Vector3 targetBasePosition = targetedBone.TargetBasePosition();
                if (referenceBoneRef.sideBoneId == SideBone.None) {
                    translation = Quaternion.Inverse(humanoid.transform.rotation) * (targetedBone.target.transform.position - targetBasePosition);
                    return;
                }

                HumanoidTarget.TargetedBone referenceBone = humanoid.GetBone(side, referenceBoneRef.sideBoneId);
                if (referenceBone.target.transform != null)
                    translation = Quaternion.Inverse(referenceBone.bone.targetRotation) * (targetedBone.target.transform.position - targetBasePosition);
                else
                    translation = Quaternion.Inverse(humanoid.transform.rotation) * (targetedBone.target.transform.position - targetBasePosition);

                setTranslation = (translation.sqrMagnitude > 0.001F);
            }
        }

        public void UpdateRotation(HumanoidControl humanoid, Side side = Side.AnySide) {
            if (side == Side.AnySide) {
                if (boneRef.boneId < 0 || boneRef.boneId > Bone.Count || boneRef.boneId == Bone.None)
                    return;

                HumanoidTarget.TargetedBone targetedBone = humanoid.GetBone(boneRef.boneId);
                if (targetedBone.target.transform == null)
                    return;

                setRotation = true;
                if (referenceBoneRef.boneId == Bone.None) {
                    rotation = Quaternion.Inverse(humanoid.transform.rotation) * targetedBone.target.transform.rotation;
                    return;
                }

                HumanoidTarget.TargetedBone referenceBone = humanoid.GetBone(referenceBoneRef.boneId);
                if (referenceBone.target.transform == null)
                    rotation = Quaternion.Inverse(humanoid.transform.rotation) * targetedBone.target.transform.rotation;
                else
                    rotation = Quaternion.Inverse(referenceBone.target.transform.rotation) * targetedBone.target.transform.rotation;

            }
            else {
                if (boneRef.sideBoneId < 0 || boneRef.sideBoneId > SideBone.Count)
                    return;

                HumanoidTarget.TargetedBone targetedBone = humanoid.GetBone(side, boneRef.sideBoneId);
                if (targetedBone.target.transform == null)
                    return;

                setRotation = true;
                if (referenceBoneRef.sideBoneId == SideBone.None) {
                    rotation = Quaternion.Inverse(humanoid.transform.rotation) * targetedBone.target.transform.rotation;
                    return;
                }

                HumanoidTarget.TargetedBone referenceBone = humanoid.GetBone(side, referenceBoneRef.sideBoneId);
                if (referenceBone.target.transform == null)
                    rotation = Quaternion.Inverse(humanoid.transform.rotation) * targetedBone.target.transform.rotation;
                else {
                    rotation = Quaternion.Inverse(referenceBone.target.transform.rotation) * targetedBone.target.transform.rotation;
                }
                if (side == Side.Right) {
                    // Sidebones need to be stored like left bones
                    rotation = Quaternion.Inverse(rotation);
                }
            }
        }

        public void SetReferenceRoot() {
            //old_referenceBoneId = HumanBodyBones.LastBone;
            referenceBoneRef.boneId = Humanoid.Tracking.Bone.None;
        }

        public void SetReferenceLocal(HumanoidControl humanoid, Side side = Side.AnySide) {
            if (side == Side.AnySide) {
                if (boneRef.boneId < 0 || boneRef.boneId > Bone.Count)
                    return;

                HumanoidTarget.TargetedBone targetedBone = humanoid.GetBone(boneRef.boneId);
                if (targetedBone.parent == null)
                    SetReferenceRoot();
                else {
                    referenceBoneRef.type = BoneType.AllBones;
                    referenceBoneRef.boneId = targetedBone.parent.boneId;
                }
            }
            else {
                if (boneRef.sideBoneId < 0 || boneRef.sideBoneId > SideBone.Count)
                    return;

                HumanoidTarget.TargetedBone targetedBone = humanoid.GetBone(side, boneRef.sideBoneId);
                if (targetedBone.parent == null)
                    SetReferenceRoot();
                else {
                    referenceBoneRef.type = BoneType.SideBones;
                    SideBone parentSideBoneId = BoneReference.HumanoidSideBone(targetedBone.parent.boneId);
                    referenceBoneRef.sideBoneId = parentSideBoneId;
                }
            }
        }

        public void SetReference(Bone referenceBoneId) {
            referenceBoneRef.boneId = referenceBoneId;
        }

        public float GetScore(HumanoidControl humanoid, Side side) {
            float score = 0;
            HumanoidTarget.TargetedBone targetedBone = humanoid.GetBone(side, boneRef.sideBoneId);
            if (targetedBone == null)
                return score;

            Vector3 referencePosition = Vector3.zero;
            Quaternion referenceRotation = Quaternion.identity;
            Vector3 referenceScale = Vector3.zero;

            HumanoidTarget.TargetedBone referenceBone = humanoid.GetBone(side, referenceBoneRef.sideBoneId);
            if (referenceBone != null && referenceBone.bone.transform != null) {
                referencePosition = referenceBone.target.transform.position;
                referenceRotation = referenceBone.bone.targetRotation;
                referenceScale = referenceBone.target.transform.lossyScale;                
            }
            else {
                referencePosition = humanoid.transform.position;
                referenceRotation = humanoid.transform.rotation;
                referenceScale = humanoid.transform.lossyScale;
            }

            if (setRotation) {
                Quaternion poseRotation = rotation;
                if (boneRef.type == BoneType.SideBones && boneRef.side == Side.AnySide && side == Side.Right) {
                    // We need to convert the left-orientated anyside to a right side
                    // For this, we mirror the translation/rotation along the YZ-plane
                    poseRotation = new Quaternion(rotation.x, -rotation.y, -rotation.z, rotation.w);
                }

                float angle = Quaternion.Angle(targetedBone.bone.targetRotation, referenceRotation * poseRotation);
                score = Mathf.Clamp01((90 - angle) / 90);
                score *= score;
            }
            return score;
        }
    }
}