using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using MikuMikuDance.XNA.Model.ModelData;
using MikuMikuDance.XNA.Motion.MotionData;
using MikuMikuDance.XNA.Model;

namespace MikuMikuDance.XNA.Motion
{
    /// <summary>
    /// モーショントラックのコールバック用デリゲート
    /// </summary>
    /// <param name="sender">モーショントラック</param>
    /// <param name="TrackNum">トラック番号</param>
    public delegate void MotionEndDelegate(MMDMotion sender,int TrackNum);
    /// <summary>
    /// MikuMikuDance for XNAのモーションクラス
    /// </summary>
    public class MMDMotion
    {//このクラスは指定したフレーム⇔データの管理クラス
        //内部データ
        internal MMDMotionData MotionData { get; private set; }

        //モーションデータで使用するボーンの一覧
        List<string> BoneDictionary;
        //ボーン名ごとに時系列順に並べ直したモーションデータ
        Dictionary<string, MMDBoneMotion[]> BoneMotions;
        //モーションデータで使用する表情モーションの一覧
        List<string> FaceDictionary;
        //表情名ごとに時系列順に並べ直したモーションデータ
        Dictionary<string, MMDFaceMotion[]> FaceMotions;
        internal long MaxFrame { get; private set; }

        /// <summary>
        /// 各モーション終了時に発生
        /// </summary>
        public event MotionEndDelegate MotionEnd;
        /// <summary>
        /// ユーザーデータ
        /// </summary>
        public object UserData;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MMDMotion() { }

        /// <summary>
        /// モーションデータを元に初期化
        /// </summary>
        /// <param name="motionData">モーションデータ</param>
        public void Initialize(MMDMotionData motionData)
        {
            MotionData = motionData;
            //モーションデータ内を検索し、ボーン辞書を作っておく
            BoneDictionary = new List<string>();
            Dictionary<string, int> bones = new Dictionary<string, int>();
            MaxFrame = 0;
            for (long i = 0; i < MotionData.BoneMotions.Length; i++)
            {
                if (!bones.ContainsKey(MotionData.BoneMotions[i].BoneName))
                {
                    BoneDictionary.Add(MotionData.BoneMotions[i].BoneName);
                    bones.Add(MotionData.BoneMotions[i].BoneName, 1);
                }
                else
                {
                    bones[MotionData.BoneMotions[i].BoneName]++;
                }
                if (MaxFrame < motionData.BoneMotions[i].FrameNo)
                    MaxFrame = motionData.BoneMotions[i].FrameNo;
            }
            //モーションデータをボーンごとに仕分け
            BoneMotions = new Dictionary<string, MMDBoneMotion[]>();
            Dictionary<string, int> counter = new Dictionary<string, int>();
            foreach (var i in bones)
            {
                BoneMotions.Add(i.Key, new MMDBoneMotion[i.Value]);//まずは枠を作る
                counter.Add(i.Key, 0);//カウンタ
            }
            //登録
            for (long i = 0; i < MotionData.BoneMotions.Length; i++)
            {
                BoneMotions[MotionData.BoneMotions[i].BoneName][counter[MotionData.BoneMotions[i].BoneName]]
                    = motionData.BoneMotions[i];
                counter[MotionData.BoneMotions[i].BoneName]++;
            }
            //キーフレーム番号でソート
            foreach (var i in BoneMotions)
            {
                Array.Sort(i.Value,(x,y)=>(int)((long)x.FrameNo-(long)y.FrameNo));// new BoneComparer());
            }
            //表情モーションの作成
            //モーションデータ内を検索し、表情辞書を作っておく
            FaceDictionary = new List<string>();
            Dictionary<string, int> faces = new Dictionary<string, int>();
            for (long i = 0; i < motionData.FaceMotions.Length; i++)
            {
                if (!faces.ContainsKey(MotionData.FaceMotions[i].FaceName))
                {
                    FaceDictionary.Add(MotionData.FaceMotions[i].FaceName);
                    faces.Add(MotionData.FaceMotions[i].FaceName, 1);
                }
                else
                {
                    faces[MotionData.FaceMotions[i].FaceName]++;
                }
                if (MaxFrame < motionData.FaceMotions[i].FrameNo)
                    MaxFrame = motionData.FaceMotions[i].FrameNo;
            }
            //モーションデータをボーンごとに仕分け
            FaceMotions = new Dictionary<string, MMDFaceMotion[]>();
            counter = new Dictionary<string, int>();
            foreach (var i in faces)
            {
                FaceMotions.Add(i.Key, new MMDFaceMotion[i.Value]);//まずは枠を作る
                counter.Add(i.Key, 0);//カウンタ
            }
            //登録
            for (long i = 0; i < MotionData.FaceMotions.Length; i++)
            {
                FaceMotions[MotionData.FaceMotions[i].FaceName][counter[MotionData.FaceMotions[i].FaceName]]
                    = motionData.FaceMotions[i];
                counter[MotionData.FaceMotions[i].FaceName]++;
            }
            //キーフレーム番号でソート
            foreach (var i in FaceMotions)
            {
                Array.Sort(i.Value,(x,y)=>(int)((long)x.FrameNo-(long)y.FrameNo));
            }

        }
        internal List<string> GetBoneList()
        {
            return BoneDictionary;
        }
        internal List<string> GetFaceList()
        {
            return FaceDictionary;
        }
        /// <summary>
        /// 指定したボーンとフレームのトランスフォームを取得
        /// </summary>
        /// <param name="bone">ボーン名</param>
        /// <param name="NowFrame">フレーム番号</param>
        /// <returns>トランスフォーム</returns>
        internal QuatTransform GetBoneTransform(string bone, decimal NowFrame)
        {
            MMDBoneMotion[] motions = BoneMotions[bone];
            //前後のフレームをチェック
            long BeforePos = 0;
            long NextPos = 0;
            for (NextPos = 0; NextPos < motions.Length; NextPos++)
            {
                if (motions[NextPos].FrameNo > NowFrame)
                    break;
            }
            BeforePos = NextPos - 1;
            Quaternion q;
            Vector3 v;
            if (NextPos >= motions.Length)
            {
                if (BeforePos == -1)
                {
                    //モーションが無い……？
                    q = Quaternion.CreateFromYawPitchRoll(0, 0, 0);
                    v = new Vector3(0, 0, 0);
                }
                else
                {
                    q = motions[BeforePos].Quatanion;
                    v = motions[BeforePos].Location;
                }
            }
            else if (BeforePos == -1)
            {
                q = motions[NextPos].Quatanion;
                v = motions[NextPos].Location;
            }
            else
            {

                float Progress = ((float)(NowFrame - motions[BeforePos].FrameNo)) / ((float)(motions[NextPos].FrameNo - motions[BeforePos].FrameNo));
                float ProgX, ProgY, ProgZ;
                ProgX = motions[NextPos].Curve[0].Evaluate(Progress);
                ProgY = motions[NextPos].Curve[1].Evaluate(Progress);
                ProgZ = motions[NextPos].Curve[2].Evaluate(Progress);

                float ProgR = motions[NextPos].Curve[3].Evaluate(Progress);
                float x, y, z;
                x = MathHelper.Lerp(motions[BeforePos].Location.X, motions[NextPos].Location.X, ProgX);
                y = MathHelper.Lerp(motions[BeforePos].Location.Y, motions[NextPos].Location.Y, ProgY);
                z = MathHelper.Lerp(motions[BeforePos].Location.Z, motions[NextPos].Location.Z, ProgZ);

                v = new Vector3(x, y, z);
                q = Quaternion.Slerp(motions[BeforePos].Quatanion, motions[NextPos].Quatanion, ProgR);
            }
            return new QuatTransform(q, v);
        }

        /// <summary>
        /// 指定したフレームの表情モーション量を取得
        /// </summary>
        /// <param name="face">表情</param>
        /// <param name="NowFrame">フレーム番号</param>
        /// <returns>表情適応率</returns>
        internal float GetFaceRate(string face, decimal NowFrame)
        {
            MMDFaceMotion[] faces = FaceMotions[face];
            //前後のフレームをチェック
            long BeforePos = 0;
            long NextPos = 0;
            for (NextPos = 0; NextPos < faces.Length; NextPos++)
            {
                if (faces[NextPos].FrameNo > NowFrame)
                    break;
            }
            BeforePos = NextPos - 1;
            float rate;
            if (NextPos >= faces.Length)
            {
                if (BeforePos == -1)
                {
                    //モーションが無い……？
                    rate = 0;
                }
                else
                {
                    rate = faces[BeforePos].Rate;
                }
            }
            else if (BeforePos == -1)
            {
                rate = faces[NextPos].Rate;

            }
            else
            {
                float Progress = ((float)(NowFrame - faces[BeforePos].FrameNo)) / ((float)(faces[NextPos].FrameNo - faces[BeforePos].FrameNo));
                rate = MathHelper.Lerp(faces[BeforePos].Rate, faces[NextPos].Rate, Progress);
            }
            return rate;
        }



        internal static void Smooth(MMDMotion sourceMotion, MMDMotion targetMotion)
        {
            for (int i = 0; i < targetMotion.BoneDictionary.Count; i++)
            {
                if (sourceMotion.BoneMotions.ContainsKey(targetMotion.BoneDictionary[i]))
                {
                    MMDBoneMotion[] sourceBoneMotions = sourceMotion.BoneMotions[targetMotion.BoneDictionary[i]];
                    targetMotion.BoneMotions[targetMotion.BoneDictionary[i]][0].Location = sourceBoneMotions[sourceBoneMotions.Length - 1].Location;
                    targetMotion.BoneMotions[targetMotion.BoneDictionary[i]][0].Quatanion = sourceBoneMotions[sourceBoneMotions.Length - 1].Quatanion;
                }
            }
        }

        internal void Smooth(MMDBoneManager BoneManager)
        {
            for (int i = 0; i < BoneDictionary.Count; i++)
            {
                int index = BoneManager.IndexOf(BoneDictionary[i]);
                if (index >= 0)
                {
                    QuatTransform temp = BoneManager.Bones[index].BoneTransform * BoneManager.Bones[index].BoneData.InverseBindPose;
                    BoneMotions[BoneDictionary[i]][0].Location = temp.Translation;
                    BoneMotions[BoneDictionary[i]][0].Quatanion = temp.Rotation;
                }
            }
        }

        internal void OnMotionEnd(int TrackNum)
        {
            if (MotionEnd != null)
                MotionEnd(this, TrackNum);
        }
    }
}
