/* InstantVR Movements
 * author: Pascal Serrarnes
 * email: support@passervr.com
 * version: 3.0.8
 * date: June 26, 2015
 * 
 */

using UnityEngine;

namespace Passer.Humanoid {

    public abstract class Movements {
        public Movements() { }

        public virtual void Start(HumanoidControl _humanoid, HumanoidTarget _target) {
        }

        private static float maxAngularSpeed = 360;

        protected Quaternion LimitRotationSpeed(Quaternion oldRotation, Quaternion newRotation, float thisMaxAngularSpeed) {
            newRotation = Quaternion.RotateTowards(oldRotation, newRotation, maxAngularSpeed * Time.deltaTime);
            return newRotation;
        }

        //float last;
        protected Quaternion MeasureRotationSpeed(Quaternion oldRotation, Quaternion newRotation) {
            //Debug.Log(maxAngularSpeed * Time.deltaTime + ": "+ Quaternion.Angle(oldRotation, newRotation) + " T " + newRotation.eulerAngles);
            //last = Time.time;
            return newRotation;
        }
        protected static Quaternion LimitRotationSpeed(Quaternion oldRotation, Quaternion newRotation) {
            newRotation = Quaternion.RotateTowards(oldRotation, newRotation, maxAngularSpeed * Time.deltaTime);
            return newRotation;
        }

        protected static Quaternion LimitAngle(HumanoidTarget.TargetedBone targetedBone, ref Quaternion lastLocalBoneRotation, Quaternion boneRotation) {
            if (targetedBone.parent == null || targetedBone.parent.bone.transform == null)
                return boneRotation;

            Quaternion parentRotation = targetedBone.parent.bone.targetRotation;
            Quaternion localRotation = Quaternion.Inverse(parentRotation) * boneRotation;

            float lastAngle = Quaternion.Angle(Quaternion.identity, lastLocalBoneRotation);
            float angle = Quaternion.Angle(parentRotation, boneRotation);
            if (angle < lastAngle) {
                // Decreasing angle

                // No damping or limit

                lastLocalBoneRotation = localRotation;
                return boneRotation;

            } else {
                // Increasing angle
                Quaternion deltaLocalRotation = Quaternion.Inverse(lastLocalBoneRotation) * localRotation;

                // Damping tension based limit
                float tension = targetedBone.GetTension();
                float f = Mathf.Clamp01(1 - tension * tension);
                deltaLocalRotation = Quaternion.Slerp(Quaternion.identity, deltaLocalRotation, f);
                lastLocalBoneRotation = lastLocalBoneRotation * deltaLocalRotation;

                // Hard limit
                lastLocalBoneRotation = Quaternion.RotateTowards(Quaternion.identity, lastLocalBoneRotation, targetedBone.bone.maxAngle);

                return parentRotation * lastLocalBoneRotation;
            }
        }
        protected Vector3 LimitAngles(HumanoidTarget.TargetedBone bone, Vector3 angles) {
            angles = UnityAngles.Clamp(angles, bone.bone.minAngles, bone.bone.maxAngles);
            return angles;
        }

        public static void ClampRotation(Transform bone, Quaternion referenceRotation, Vector3 ROMnegative, Vector3 ROMpositive) {
            Quaternion localRotation = Quaternion.Inverse(referenceRotation) * bone.rotation;
            Vector3 localEuler = localRotation.eulerAngles;

            localRotation = Quaternion.Euler(
                Mathf.Clamp(Angle180(localEuler.x), ROMnegative.x, ROMpositive.x),
                Mathf.Clamp(Angle180(localEuler.y), ROMnegative.y, ROMpositive.y),
                Mathf.Clamp(Angle180(localEuler.z), ROMnegative.z, ROMpositive.z));

            bone.rotation = referenceRotation * localRotation;
        }

        public static Vector3 MuscleTension(Transform bone, Quaternion referenceRotation, Vector3 ROMnegative, Vector3 ROMpositive) {
            Quaternion localRotation = Quaternion.Inverse(referenceRotation) * bone.rotation;
            Vector3 localEuler = localRotation.eulerAngles;

            Vector3 tension = new Vector3(
                AxisTension(Angle180(localEuler.x), ROMnegative.x, ROMpositive.x),
                AxisTension(Angle180(localEuler.y), ROMnegative.y, ROMpositive.y),
                AxisTension(Angle180(localEuler.z), ROMnegative.z, ROMpositive.z));

            return tension;
        }

        private static float AxisTension(float angle, float ROMnegative, float ROMpositive) {
            return AxisTension(angle, ROMnegative, ROMpositive, 1);
        }

        private static float AxisTension(float angle, float ROMnegative, float ROMpositive, float MaxTension) {
            float f;
            if (angle < 0)
                f = angle / ROMnegative;
            else {
                f = angle / ROMpositive;
            }

            return (f * f) * MaxTension;
        }

        public static Vector3 Euler180(Vector3 eulerAngles) {
            return new Vector3(
                Angle180(eulerAngles.x),
                Angle180(eulerAngles.y),
                Angle180(eulerAngles.z));
        }

        public static float Angle180(float _angle) {
            float angle = _angle;
            while (angle > 180)
                angle -= 360;
            while (angle < -180)
                angle += 360;
            return angle;
        }

        public static float AngleDifference(float a, float b) {
            float r = b - a;
            while (r < -180)
                r += 360;
            while (r > 180)
                r -= 360;
            return r;
        }

    }
}