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

namespace MikuMikuDance.XNA.Model
{
    /// <summary>
    /// ボーンマネージャクラス
    /// </summary>
    public class MMDBoneManager
    {
        //ボーン情報
        MMDBone[] m_Bones;
        /// <summary>
        /// ボーン一覧
        /// </summary>
        public MMDBone[] Bones { get { return m_Bones; } }

        /// <summary>
        /// IKのソルバー設定
        /// </summary>
        internal IKSolver Solver { get; set; }
        /// <summary>
        /// IKの稼働制限設定
        /// </summary>
        internal IKLimitation Limitation { get; set; }

        internal MMDModelData Model { get; set; }
        Dictionary<string, int> BoneDic;
        internal Quaternion[] skinRots = null;//GetSkinRotation用の配列保持用。XBoxのGC回避のため
        internal Vector4[] skinTranses = null;//GetSkinTransration用の配列保持用。XBoxのGC回避のため
        List<ushort> underIKs = new List<ushort>(20);//SolveIKのデータ計算用。XBoxのGC回避のため

        internal MMDBoneManager CloneForBake()
        {
            MMDBoneManager newManager = new MMDBoneManager(Model);
            return newManager;
        }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="model">モデルデータ</param>
        public MMDBoneManager(MMDModelData model)
        {
            Model = model;
            BoneDic= new Dictionary<string, int>();
            int boneindex = 0;
            m_Bones = new MMDBone[model.Bones.Length];
            foreach (MMDBoneData i in model.Bones)
            {
                MMDBone bone = new MMDBone();
                string Name = i.Name;
                bone.BoneData = i;
                m_Bones[boneindex] = bone;
                m_Bones[boneindex].BoneTransform.Rotation = Quaternion.Identity;
                BoneDic.Add(Name, boneindex);
                boneindex++;
            }
            Solver = new IK_CCDSolver();//デフォルトはCCD法
            Limitation = new IKLimitation();//デフォルトリミッタ
            ResetMotion();
            Refresh();//返却用配列の更新
        }
        /// <summary>
        /// 指定したボーン名のindexを返します
        /// </summary>
        /// <param name="boneName">ボーン名</param>
        /// <returns>このオブジェクト内でのindex</returns>
        public int IndexOf(string boneName) 
        { 
            if(BoneDic.ContainsKey(boneName)) 
                return BoneDic[boneName];
            return -1;
        }
        /// <summary>
        /// モデルを初期状態にする
        /// </summary>
        public void ResetMotion()
        {
            // ボーン変換行列をバインドポーズで初期化する
            for (int i = 0; i < m_Bones.Length; i++)
                m_Bones[i].BoneTransform = m_Bones[i].BoneData.BindPose;
            //他のも初期化
            UpdateWorldTransforms();
            UpdateSkinTransforms();
        }
        /// <summary>
        /// ボーン座標を更新する
        /// </summary>
        /// <remarks>モデルのUpdateから呼ばれるので、ユーザーが呼ぶ必要はありません</remarks>
        public void Update()
        {
            foreach (var i in m_Bones)
                i.BoneTransform.Rotation.Normalize();//ノーマライズしておく。計算誤差対策
            UpdateWorldTransforms();
            UpdateSkinTransforms();
        }
        internal void UpdateWithoutSkinTransform()
        {
            foreach (var i in m_Bones)
                i.BoneTransform.Rotation.Normalize();//ノーマライズしておく。計算誤差対策
            UpdateWorldTransforms();
        }
        /// <summary>
        /// WorldTransformsを更新する
        /// Updateメソッドから呼ばれるヘルパーメソッド
        /// </summary>
        internal void UpdateWorldTransforms()
        {
            // ルートボーン
            m_Bones[0].WorldTransform = m_Bones[0].BoneTransform;

            // 子のボーン
            for (int bone = 1; bone < m_Bones.Length; bone++)
            {
                int parentBone = m_Bones[bone].BoneData.SkeletonHierarchy;
                QuatTransform.Multiply(ref m_Bones[bone].BoneTransform,
                        ref m_Bones[parentBone].WorldTransform,
                        out m_Bones[bone].WorldTransform);
                /*m_Bones[bone].WorldTransform = m_Bones[bone].BoneTransform *
                                            m_Bones[parentBone].WorldTransform;*/
            }
        }
        /// <summary>
        /// バインド・ポーズにおけるルートボーンから指定したボーンまでの座標変換を取得
        /// </summary>
        /// <param name="index">ボーン番号</param>
        /// <returns>座標変換を表すQuatTransform</returns>
        public QuatTransform GetBindWorldQuatTransform(int index)
        {
            QuatTransform result = m_Bones[index].BoneData.BindPose;
            while (m_Bones[index].BoneData.SkeletonHierarchy > -1)
            {
                index = m_Bones[index].BoneData.SkeletonHierarchy;
                QuatTransform temp = result;
                QuatTransform.Multiply(ref temp, ref m_Bones[index].BoneData.BindPose, out result);
                //result = result * m_Bones[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)
        {
            m_Bones[0].BoneTransform = m_Bones[0].BoneData.BindPose;
            QuatTransform result = m_Bones[index].BoneTransform;

            while (m_Bones[index].BoneData.SkeletonHierarchy > -1)
            {
                index = m_Bones[index].BoneData.SkeletonHierarchy;
                QuatTransform temp = result;
                QuatTransform.Multiply(ref temp, ref m_Bones[index].BoneTransform, out result);
                //result = result * m_Bones[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()
        {
            if (skinRots == null || skinRots.Length != m_Bones.Length
                || skinTranses == null || skinTranses.Length != m_Bones.Length)
                Refresh();
            for (int bone = 0; bone < m_Bones.Length; bone++)
            {
                QuatTransform xform;
                QuatTransform.Multiply(ref m_Bones[bone].BoneData.InverseBindPose,
                    ref m_Bones[bone].WorldTransform, out xform);

                skinRots[bone] = xform.Rotation;
                skinTranses[bone] = new Vector4(xform.Translation.X, xform.Translation.Y, xform.Translation.Z, skinTranses[bone].W);
            }
        }
        private void Refresh()
        {
            skinRots = new Quaternion[m_Bones.Length];
            skinTranses = new Vector4[m_Bones.Length];
        }
        /// <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(m_Bones[ikbone].BoneData.SkeletonHierarchy).CreateMatrix();
            moveVec = Vector3.Transform(moveVec, invCoord);
#if false
            //まずはIKデータをコピー
            MMDIKData ik = m_Bones[ikbone].BoneData.IK;
            //IKターゲットボーン番号を取得
            ushort iktarget = ik.IKTargetBoneIndex;
            //IK影響下のボーンを取得
            underIKs.Clear();
            //ターゲットボーンから順にIK影響下のボーンを調べる
            int i = iktarget;
            while (true) 
            {
                underIKs.Add((ushort)i);
                //親ボーン取得
                if (m_Bones[i].BoneData.SkeletonHierarchy == -1)
                    break;//あれ？親が無い？
                i = m_Bones[i].BoneData.SkeletonHierarchy;//boneのindexはWORD
                //IK影響下じゃないボーンが見つかるまで探索(固定点決めるようにiKじゃないボーンも一つ入れておく)
                bool flag = false;
                foreach (var j in ik.IKChildBones)
                {
                    if (i == j)
                    {
                        flag = true;
                        break;
                    }
                }
                if (!flag)
                    break;
            }

            Solver.Solve(moveVec, underIKs.ToArray(), ref m_Bones[ikbone], this, Limitation);//IKを解決
#endif
            for (int i = 0; i < m_Bones[ikbone].BoneData.IK.Count; i++)
                Solver.Solve(moveVec, m_Bones[ikbone].BoneData.IK[i].IKChildBones, m_Bones[ikbone].BoneData.IK[i], this, Limitation);//IKを解決
            //解決時に解がボーンに入っているのでそのまま帰る
        }


        
    }
}
