using System;
using System.Collections.Generic;
using UnityEngine;


namespace UniHumanoid
{
    public static class BvhAnimation
    {
        class CurveSet
        {
            BvhNode Node;
            Func<float, float, float, Quaternion> EulerToRotation;
            public CurveSet(BvhNode node)
            {
                Node = node;
            }

            public BvhChannelCurve PositionX;
            public BvhChannelCurve PositionY;
            public BvhChannelCurve PositionZ;
            public Vector3 GetPosition(int i)
            {
                return new Vector3(
                    PositionX.Keys[i],
                    PositionY.Keys[i],
                    PositionZ.Keys[i]);
            }

            public BvhChannelCurve RotationX;
            public BvhChannelCurve RotationY;
            public BvhChannelCurve RotationZ;
            public Quaternion GetRotation(int i)
            {
                if (EulerToRotation == null)
                {
                    EulerToRotation = Node.GetEulerToRotation();
                }
                return EulerToRotation(
                    RotationX.Keys[i],
                    RotationY.Keys[i],
                    RotationZ.Keys[i]
                    );
            }

            static void AddCurve(Bvh bvh, AnimationClip clip, BvhChannelCurve ch, float scaling)
            {
                if (ch == null) return;
                var pathWithProp = default(Bvh.PathWithProperty);
                bvh.TryGetPathWithPropertyFromChannel(ch, out pathWithProp);
                var curve = new AnimationCurve();
                for (int i = 0; i < bvh.FrameCount; ++i)
                {
                    var time = (float)(i * bvh.FrameTime.TotalSeconds);
                    var value = ch.Keys[i] * scaling;
                    curve.AddKey(time, value);
                }
                clip.SetCurve(pathWithProp.Path, typeof(Transform), pathWithProp.Property, curve);
            }

            public void AddCurves(Bvh bvh, AnimationClip clip, float scaling)
            {
                AddCurve(bvh, clip, PositionX, -scaling);
                AddCurve(bvh, clip, PositionY, scaling);
                AddCurve(bvh, clip, PositionZ, scaling);

                var pathWithProp = default(Bvh.PathWithProperty);
                bvh.TryGetPathWithPropertyFromChannel(RotationX, out pathWithProp);

                // rotation
                var curveX = new AnimationCurve();
                var curveY = new AnimationCurve();
                var curveZ = new AnimationCurve();
                var curveW = new AnimationCurve();
                for (int i = 0; i < bvh.FrameCount; ++i)
                {
                    var time = (float)(i * bvh.FrameTime.TotalSeconds);
                    var q = GetRotation(i).ReverseX();
                    curveX.AddKey(time, q.x);
                    curveY.AddKey(time, q.y);
                    curveZ.AddKey(time, q.z);
                    curveW.AddKey(time, q.w);
                }
                clip.SetCurve(pathWithProp.Path, typeof(Transform), "localRotation.x", curveX);
                clip.SetCurve(pathWithProp.Path, typeof(Transform), "localRotation.y", curveY);
                clip.SetCurve(pathWithProp.Path, typeof(Transform), "localRotation.z", curveZ);
                clip.SetCurve(pathWithProp.Path, typeof(Transform), "localRotation.w", curveW);
            }
        }

        public static AnimationClip CreateAnimationClip(Bvh bvh, float scaling)
        {
            var clip = new AnimationClip();
            clip.legacy = true;

            var curveMap = new Dictionary<BvhNode, CurveSet>();

            int j = 0;
            foreach (var node in bvh.Root.Traverse())
            {
                var set = new CurveSet(node);
                curveMap[node] = set;

                for (int i = 0; i < node.Channels.Length; ++i, ++j)
                {
                    var curve = bvh.Channels[j];
                    switch (node.Channels[i])
                    {
                        case BvhChannel.Xposition: set.PositionX = curve; break;
                        case BvhChannel.Yposition: set.PositionY = curve; break;
                        case BvhChannel.Zposition: set.PositionZ = curve; break;
                        case BvhChannel.Xrotation: set.RotationX = curve; break;
                        case BvhChannel.Yrotation: set.RotationY = curve; break;
                        case BvhChannel.Zrotation: set.RotationZ = curve; break;
                        default: throw new Exception();
                    }
                }
            }

            foreach (var set in curveMap)
            {
                set.Value.AddCurves(bvh, clip, scaling);
            }

            clip.EnsureQuaternionContinuity();

            return clip;
        }

    }
}
