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

namespace MikuMikuDance.XNA.Motion
{
    /// <summary>
    /// アニメーションベイククラス
    /// </summary>
    public static class AnimationBaker
    {

        //スレッド準備系
        /// <summary>
        /// モーションをベイクする
        /// </summary>
        /// <param name="NumThread">使用するスレッド数</param>
        /// <param name="normal">ベイクするノーマルモーショントラック</param>
        /// <param name="BoneManager">ボーンマネージャ</param>
        /// <param name="FaceManager">フェイスマネージャー</param>
        /// <returns>ベイク済みモーションデータ</returns>
        public static MMDBakedMotion Bake(int NumThread, NormalMotionTrack normal, MMDBoneManager BoneManager, MMDFaceManager FaceManager)
        {
            
            int NumFrame = (int)normal.MaxFrame + 1;//モーション数の限界はuintなのだが、.Net CFは配列が対応してない……
            //モーションに含まれているボーンと表情一覧取得
            List<string> BoneList = new List<string>();
            BoneList.AddRange(normal.Motion.GetBoneList());
            //List<string> FaceList = normal.Motion.GetFaceList();
            //変換テーブルセット
            Dictionary<int, int> BoneTable = new Dictionary<int, int>();
            for (int i = 0; i < BoneList.Count; i++)
            {
                if (!BoneManager.ContainsBone(BoneList[i]))
                {//このモデルにないボーンなので、ベイクから除外
                    BoneList.RemoveAt(i);
                    --i;
                }
                else
                {//変換表作成
                    BoneTable.Add(BoneManager.IndexOf(BoneList[i]), i);

                }
            }
            for (int i = 0; i < BoneList.Count; i++)
            {
                //IKボーンなら影響下ボーンを登録
                int BoneIndex = BoneManager.IndexOf(BoneList[i]);
                if (BoneManager.Bones[BoneIndex].BoneData.HasIK)
                {
                    for (int j = 0; j < BoneManager.Bones[BoneIndex].BoneData.IK.Count; j++)
                    {
                        foreach (var ikchild in BoneManager.Bones[BoneIndex].BoneData.IK[j].IKChildBones)
                        {
                            if (!BoneTable.ContainsKey(ikchild))
                            {
                                BoneList.Add(BoneManager.Bones[ikchild].BoneData.Name);
                                BoneTable.Add(ikchild, BoneList.Count - 1);
                            }
                        }
                    }
                }
            }
            //表情のキーフレームと影響頂点番号をリストアップ
            Dictionary<int, Dictionary<int, int>> KeyVertsWork = new Dictionary<int, Dictionary<int, int>>();
            Dictionary<string, List<uint>> VertIndices = FaceManager.GetVertIndices();
            for (int i = 0; i < normal.Motion.MotionData.FaceMotions.Length; i++)
            {
                MMDFaceMotion fm = normal.Motion.MotionData.FaceMotions[i];
                Dictionary<int,int> verts;
                if (!FaceManager.ContainsFace(fm.FaceName))
                    continue;
                if (KeyVertsWork.ContainsKey((int)fm.FrameNo))
                    verts = KeyVertsWork[(int)fm.FrameNo];
                else
                {
                    verts = new Dictionary<int, int>();
                    KeyVertsWork.Add((int)fm.FrameNo, verts);
                }
                List<uint> v = VertIndices[fm.FaceName];
                foreach (var j in v)
                {
                    if (!verts.ContainsKey((int)j))
                        verts.Add((int)j, 0);
                }
            }
            Dictionary<int, List<int>> KeyVerts = new Dictionary<int, List<int>>();
            int MaxFaceFrame = 0;
            foreach (var i in KeyVertsWork)
            {
                List<int> verts = new List<int>();
                foreach (var j in i.Value)
                {
                    verts.Add(j.Key);
                }
                KeyVerts.Add(i.Key, verts);
                MaxFaceFrame = Math.Max(MaxFaceFrame, i.Key);
            }
            //データ配列をセット
            MMDBakedMotion result = new MMDBakedMotion();
            result.Poses = new BakedBoneData[NumFrame * BoneList.Count];
#if DEBUG && WINDOWS
            bool[] checker = new bool[NumFrame * BoneList.Count];
            Array.Clear(checker, 0, checker.Length);
#endif
            //result.Faces = new Vector4[NumFrame * FaceManager.FaceTranslations.Length];
            result.Faces = new Vector4[NumFrame * FaceManager.FaceRates.Length];
            result.NumFace = FaceManager.FaceRates.Length;
            result.NumBone = BoneList.Count;
            //result.NumFace = FaceManager.FaceTranslations.Length;
            result.MaxFrame = Math.Max(MaxFaceFrame+1, NumFrame);
            result.MaxBoneFrame = NumFrame - 1;
            result.MaxFaceFrame = MaxFaceFrame;
            BakeTaskManager[] tasks = new BakeTaskManager[NumThread];
            for (int thread = 0; thread < tasks.Length; thread++)
            {
                tasks[thread] = new BakeTaskManager();
                tasks[thread].NumThread = tasks.Length;
                tasks[thread].index = thread;
                tasks[thread].NumFrame = NumFrame;
                tasks[thread].BoneUpdated = new bool[BoneManager.Bones.Length];
                tasks[thread].BoneTable = new Dictionary<int, int>(BoneTable);
                //tasks[thread].FaceList = FaceList;
                tasks[thread].result = result;
                tasks[thread]._FaceManager = FaceManager;
                tasks[thread]._normal = normal;
                tasks[thread]._BoneManager = BoneManager;
#if DEBUG && WINDOWS
                tasks[thread].checker = checker;
#endif
                tasks[thread].thread = new Thread(new ThreadStart(tasks[thread].Work));
            }
            Thread.MemoryBarrier();
            //シングルスレッド
            //TODO: どういうわけか、マルチスレッドにするとベイク時にCeleron900でレースコンディションが発生する。理由は不明。
            for (int thread = 0; thread < tasks.Length; thread++)
                tasks[thread].Work();
#if false
            for (int thread = 0; thread < tasks.Length; thread++)
                tasks[thread].thread.Start();//処理が重いのでワーカースレッドに投げる
            
            //表情用の処理
            /*BakeFaceTaskManager ftask = new BakeFaceTaskManager();
            ftask.BoneUpdated = new bool[BoneManager.Count];
            ftask.verts = KeyVerts;
            ftask.result = result;
            ftask._FaceManager = FaceManager;
            ftask._BoneManager = BoneManager;
            ftask._normal = normal;
            ftask.thread = new Thread(new ThreadStart(ftask.Work));
            ftask.thread.Start();*/
            for (int thread = tasks.Length - 1; thread > 0; --thread)
            {
                tasks[thread].thread.Join();//処理終わりまで待つ

            }
#endif
            //チェック
#if DEBUG && WINDOWS
            for (int i = 0; i < checker.Length; i++)
                if (!checker[i])
                    throw new ApplicationException("書き込みチェックに引っかかった" + i.ToString());
#endif
            //ftask.thread.Join();//表情も処理終了までまつ
            return result;
        }


        //ワーカースレッド系
        /// <summary>
        /// ベイク用ワーカースレッドのためのクラス
        /// </summary>
        class BakeTaskManager
        {
            public Thread thread;
            public int index;
            public int NumThread;
            public int NumFrame;
            public bool[] BoneUpdated;
            //readだけなので、Clone要らず
            public Dictionary<int, int> BoneTable;
            //public List<string> FaceList;
            //処理分けするのでClone要らず
            public MMDBakedMotion result;
#if DEBUG && WINDOWS
            public bool[] checker;
#endif
            //要Clone
            public NormalMotionTrack _normal;
            public MMDBoneManager _BoneManager;
            public MMDFaceManager _FaceManager;


            public void Work()
            {
#if XBOX360
                Thread.CurrentThread.SetProcessorAffinity(index == 3 ? 1 : 5 - index);
#endif
                NormalMotionTrack normal = _normal.CloneForBake();
                MMDBoneManager BoneManager = _BoneManager.CloneForBake();
                MMDFaceManager FaceManager = _FaceManager.CloneForBake();
                //ベイクのために1フレームずつ再生する
                for (int Frame = index; Frame < NumFrame; Frame += NumThread)
                {
                    for (int i = 0; i < FaceManager.FaceRates.Length; i++)
                        FaceManager.FaceRates[i].X = -1;
                    Array.Clear(BoneUpdated, 0, BoneUpdated.Length);
                    //現在の再生フレームをセットし更新
                    normal.NowFrame = Frame;
                    QuatTransform trans = new QuatTransform();
                    normal.ApplyMotion(BoneManager, FaceManager, null, ref trans, ref BoneUpdated);

                    //IKボーンの処理前にモデルのワールド座標系更新
                    BoneManager.UpdateWorldTransforms();
                    //IKボーンの処理
                    for (int i2 = 0; i2 < BoneManager.Bones.Length; i2++)
                    {
                        if (BoneManager.Bones[i2].BoneData.HasIK && BoneUpdated[i2])
                            BoneManager.SolveIK(i2, BoneManager.Bones[i2].BoneTransform);
                    }
                    //処理済みボーン情報を抽出して保存
                    foreach (var it in BoneTable)
                    {
                        result.Poses[Frame * result.NumBone + it.Value].BoneIndex = it.Key;
                        result.Poses[Frame * result.NumBone + it.Value].Poses = BoneManager.Bones[it.Key].BoneTransform;
#if DEBUG && WINDOWS
                        checker[Frame * result.NumBone + it.Value] = true;
                        if (!MMDMath.IsValid(ref result.Poses[Frame * result.NumBone + it.Value].Poses.Rotation))
                            throw new ApplicationException("ベイクループ内で無効なrotationを発見");
#endif
                    }
                    //表情の抽出
                    Array.Copy(FaceManager.FaceRates, 0, result.Faces, Frame * FaceManager.FaceRates.Length, FaceManager.FaceRates.Length);
                    
                    //Threadを休ませる
                    Thread.Sleep(0);
                    //for (int i = 0; i < FaceList.Count; i++)
                    //{
                    //    result.Faces[Frame * result.NumFace + i].Rate = normal.Motion.GetFaceRate(FaceList[i], Frame);
                    //    result.Faces[Frame * result.NumFace + i].FaceName = FaceList[i];
                    //}
                }
                Thread.MemoryBarrier();
            }
        }
        /*class BakeFaceTaskManager
        {
            public Thread thread;
            //public int NumFrame;
            public bool[] BoneUpdated;
            //readだけなので、Clone要らず
            public Dictionary<int, List<int>> verts;
            //public List<string> FaceList;
            //処理分けするのでClone要らず
            public MMDBakedMotion result;
            //要Clone
            public NormalMotionTrack _normal;
            public MMDBoneManager _BoneManager;
            public MMDFaceManager _FaceManager;


            public void Work()
            {
#if XBOX360
                Thread.CurrentThread.SetProcessorAffinity(1);
#endif
                NormalMotionTrack normal = _normal.CloneForBake();
                MMDBoneManager BoneManager = _BoneManager.CloneForBake();
                MMDFaceManager FaceManager = _FaceManager.CloneForBake();
                List<BakedFaceData>[] bFaceData = new List<BakedFaceData>[result.Faces.Length];
                for (int i = 0; i < bFaceData.Length; i++)
                {
                    bFaceData[i] = new List<BakedFaceData>();
                }
                //ベイクのために表情のキーフレームごとに再生する
                //キーフレームに影響を受ける頂点の座標を覚えてキーフレームごとに線形補間することで計算可能。
                //領域計算量と時間計算量を両方押さえる必要があるのでこうした
                foreach (var it in verts)
                {
                    Array.Clear(BoneUpdated, 0, BoneUpdated.Length);
                    //現在の再生フレームをセットし更新
                    normal.NowFrame = it.Key;
                    normal.ApplyMotion(BoneManager, FaceManager, ref BoneUpdated);
                    //表情の抽出
                    foreach (var v in it.Value)
                    {
                        BakedFaceData bface = new BakedFaceData()
                        {
                            Frame = (int)it.Key,
                            FaceVert = new Vector3(FaceManager.FaceTranslations[v].X, FaceManager.FaceTranslations[v].Y, FaceManager.FaceTranslations[v].Z)
                        };
                        bFaceData[v].Add(bface);
                    }
                }
                for (int i = 0; i < result.Faces.Length; i++)
                {
                    if (bFaceData[i].Count > 0)
                        result.Faces[i] = bFaceData[i].ToArray();
                    else
                        result.Faces[i] = null;
                }
                
            }
        }*/
    }
}
