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

namespace MikuMikuDance.XNA.Model
{
    /// <summary>
    /// ボーンマネージャクラス
    /// </summary>
    public class MMDBoneManager : List<MMDBone>
    {
        /// <summary>
        /// IKのソルバー設定
        /// </summary>
        internal IKSolver Solver { get; set; }
        /// <summary>
        /// IKの稼働制限設定
        /// </summary>
        internal IKLimitation Limitation { get; set; }

        internal MMDModel Model { get; set; }
        Dictionary<string, int> BoneDic;
        Quaternion[] skinRots = null;//GetSkinRotation用の配列保持用。XBoxのGC回避のため
        Vector4[] skinTranses = null;//GetSkinTransration用の配列保持用。XBoxのGC回避のため
        List<int> underIKs = new List<int>(20);//SolveIKのデータ計算用。XBoxのGC回避のため
        
        internal MMDBoneManager(MMDModel model)
        {
            Model = model;
            BoneDic= new Dictionary<string, int>();
            int boneindex = 0;
            foreach (MMDBoneData i in model.ModelData.Bones)
            {
                MMDBone bone = new MMDBone();
                string Name = i.Name;
                bone.BoneData = i;
                Add(bone);
                BoneDic.Add(Name, boneindex);
                boneindex++;
            }
            Solver = new IK_CCDSolver();//デフォルトはCCD法
            //Solver = new IK_ParticleSolver();//デフォルトはパーティクル法
            Limitation = new IKLimitation();//デフォルトリミッタ
            ResetMotion();
            Refresh();//返却用配列の更新
        }
        /// <summary>
        /// 指定したボーン名に対するMMDBoneDataオブジェクトを返します
        /// </summary>
        /// <param name="boneName">ボーン名</param>
        /// <returns>MMDBoneDataオブジェクト</returns>
        public MMDBone this[string boneName]
        {
            get
            {
                return this[BoneDic[boneName]];
            }
        }
        /// <summary>
        /// 指定したボーン名のindexを返します
        /// </summary>
        /// <param name="boneName">ボーン名</param>
        /// <returns>このオブジェクト内でのindex</returns>
        public int IndexOf(string boneName) { return BoneDic[boneName]; }
        /// <summary>
        /// モデルを初期状態にする
        /// </summary>
        public void ResetMotion()
        {
            // ボーン変換行列をバインドポーズで初期化する
            for (int i = 0; i < Count; i++)
                this[i].BoneTransform = this[i].BoneData.BindPose;
            //他のも初期化
            UpdateWorldTransforms();
            UpdateSkinTransforms();
        }
        /// <summary>
        /// ボーン座標を更新する
        /// </summary>
        /// <remarks>モデルのUpdateから呼ばれるので、ユーザーが呼ぶ必要はありません</remarks>
        public void Update()
        {
            UpdateBoneTransforms();
            UpdateWorldTransforms();
            UpdateSkinTransforms();
        }

        private void UpdateBoneTransforms()
        {
            foreach (var i in this)
            {
                i.BoneTransform.Rotation.Normalize();//ノーマライズしておく。計算誤差対策
            }
        }
        /// <summary>
        /// WorldTransformsを更新する
        /// Updateメソッドから呼ばれるヘルパーメソッド
        /// </summary>
        internal void UpdateWorldTransforms()
        {
            // ルートボーン
            this[0].WorldTransform = this[0].BoneTransform;

            // 子のボーン
            for (int bone = 1; bone < Count; bone++)
            {
                int parentBone = this[bone].BoneData.SkeletonHierarchy;

                this[bone].WorldTransform = this[bone].BoneTransform *
                                            this[parentBone].WorldTransform;
            }
        }
        /// <summary>
        /// バインド・ポーズにおけるルートボーンから指定したボーンまでの座標変換を取得
        /// </summary>
        /// <param name="index">ボーン番号</param>
        /// <returns>座標変換を表すQuatTransform</returns>
        public QuatTransform GetBindWorldQuatTransform(int index)
        {
            QuatTransform result = this[index].BoneData.BindPose;
            while (this[index].BoneData.SkeletonHierarchy > -1)
            {
                index = this[index].BoneData.SkeletonHierarchy;
                result = result * this[index].BoneData.BindPose;
            }
            return result;
        }
        /// <summary>
        /// バインド・ポーズにおけるルートボーンから指定したボーンまでの位置ベクトルを取得
        /// </summary>
        /// <param name="index">ボーン番号</param>
        /// <returns>位置ベクトル</returns>
        public Vector3 GetBindWorldTransform(int index)
        {
            return GetBindWorldQuatTransform(index).Translation;
        }
        /// <summary>
        /// 現在のポーズにおけるルートボーンから指定したボーンまでの座標変換を取得
        /// </summary>
        /// <param name="index">ボーン番号</param>
        /// <returns>座標変換を表すQuatTransform</returns>
        public QuatTransform GetWorldQuatTransform(int index)
        {
            this[0].BoneTransform = this[0].BoneData.BindPose;
            QuatTransform result = this[index].BoneTransform;

            while (this[index].BoneData.SkeletonHierarchy > -1)
            {
                index = this[index].BoneData.SkeletonHierarchy;
                result = result * this[index].BoneTransform;
            }

            return result;
        }
        /// <summary>
        /// 現在のポーズにおけるルートボーンから指定したボーンまでの位置ベクトルを取得
        /// </summary>
        /// <param name="index">ボーン番号</param>
        /// <returns>位置ベクトル</returns>
        public Vector3 GetWorldTransform(int index)
        {
            return GetWorldQuatTransform(index).Translation;
        }
        /// <summary>
        /// SkinTransformsの更新
        /// Updateメソッドから呼ばれるヘルパーメソッド
        /// </summary>
        internal void UpdateSkinTransforms()
        {
            for (int bone = 0; bone < Count; bone++)
            {
                QuatTransform xform =
                    this[bone].BoneData.InverseBindPose * this[bone].WorldTransform;

                this[bone].SkinRotation = xform.Rotation;
                this[bone].SkinTranslation = new Vector4(xform.Translation.X, xform.Translation.Y, xform.Translation.Z, this[bone].SkinTranslation.W);
            }
        }
        internal Quaternion[] GetSkinRotation() 
        {
            if (skinRots == null || skinRots.Length != Count)
                Refresh();
            for (int bone = 0; bone < Count; bone++)
            {
                skinRots[bone] = this[bone].SkinRotation;
            }
            return skinRots; 
        }
        internal Vector4[] GetSkinTranslation() 
        {
            if (skinTranses == null || skinTranses.Length != Count)
                Refresh();
            for (int bone = 0; bone < Count; bone++)
            {
                skinTranses[bone] = this[bone].SkinTranslation;
            }
            return skinTranses; 
        }
        private void Refresh()
        {
            skinRots = new Quaternion[Count];
            skinTranses = new Vector4[Count];
        }
        /// <summary>
        /// 指定した名前のボーンがあるかどうか調べます
        /// </summary>
        /// <param name="bone">ボーン名</param>
        /// <returns>あるならtrue, ないならfalse</returns>
        public bool ContainsBone(string bone)
        {
            return BoneDic.ContainsKey(bone);
        }
        /// <summary>
        /// IKを解決
        /// </summary>
        /// <param name="ikbone">解決するIKボーン番号</param>
        /// <param name="move">ボーンの位置(各ボーンの座標系)</param>
        internal void SolveIK(int ikbone, QuatTransform move)
        {
            //IKボーンの最終的な移動先(モデル全体の座標系に変換)を計算
            Vector3 moveVec = move.Translation;
            //親ボーン(多分ルートだろうけど)の座標系からボーン全体の座標系変換マトリクス取得
            Matrix invCoord = GetWorldQuatTransform(this[ikbone].BoneData.SkeletonHierarchy).CreateMatrix();
            moveVec = Vector3.Transform(moveVec, invCoord);
            //まずはIKデータをコピー
            MMDIKData ik = this[ikbone].BoneData.IK;
            //IKターゲットボーン番号を取得
            ushort iktarget = ik.IKTargetBoneIndex;
            //IK影響下のボーンを取得
            underIKs.Clear();
            //ターゲットボーンから順にIK影響下のボーンを調べる
            int i = iktarget;
            do
            {
                underIKs.Add(i);
                //親ボーン取得
                if (this[i].BoneData.SkeletonHierarchy == -1)
                    break;//あれ？親が無い？
                i = this[i].BoneData.SkeletonHierarchy;//boneのindexはWORD
                //親ボーンを登録
            } while (ik.IKChildBones.IndexOf((ushort)i) >= 0); //IK影響下じゃないボーンが見つかるまで探索(固定点決めるようにiKじゃないボーンも一つ入れておく)
#if TRACE
            if(Model.mmdXNA.TimeRular!=null && Model.UseTimeRular)
                Model.mmdXNA.TimeRular.BeginMark(4,"SolveIK",Color.Green);
#endif
            Solver.Solve(moveVec, underIKs, this[ikbone], this, Limitation);//IKを解決
#if TRACE
            if (Model.mmdXNA.TimeRular != null && Model.UseTimeRular)
                Model.mmdXNA.TimeRular.EndMark(4, "SolveIK");
#endif
            //解決時に解がボーンに入っているのでそのまま帰る
        }

    }
}
