using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MikuMikuDance.Motion.Motion2;
using Microsoft.Xna.Framework;

namespace MikuMikuDance.Motion.BaseBoneProcess
{
    /// <summary>
    /// ベースボーンを元にしたモーション加工クラス
    /// </summary>
    public static class BaseProcessor
    {
        /// <summary>
        /// ベースボーンを元にしたモーション加工
        /// </summary>
        /// <param name="motion">モーションファイル</param>
        /// <param name="baseBoneName">ベースボーン名(親なしボーン)</param>
        /// <param name="EffectedBoneNames">ベースボーンと同じ親なしボーン</param>
        /// <param name="transBoneName">移動ボーン名に使用する名前</param>
        /// <remarks>ボーンは全て、親なしボーンを指定しなければならない</remarks>
        public static void Process(MMDMotion2 motion, string baseBoneName, string[] EffectedBoneNames, string transBoneName)
        {
            List<MotionData> transes = new List<MotionData>();
            //計算遅いけど無理やりやる。最適化めんどい
            for (int targetMotionIndex = 0; targetMotionIndex < motion.Motions.Length; targetMotionIndex++)
            {//計算ターゲットとするモーションを探索
                if (motion.Motions[targetMotionIndex].BoneName == baseBoneName)
                {
                    //計算ターゲットとするモーションからトランスフォームモーションデータ生成
                    MotionData trans = new MotionData();
                    transes.Add(trans);
                    //ボーン名はトランスフォームボーン名にする
                    trans.BoneName = transBoneName;
                    //フレーム番号をコピー
                    trans.FrameNo = motion.Motions[targetMotionIndex].FrameNo;
                    //保管データのコピー
                    for (int i = 0; i < trans.Interpolation.Length; i++)
                        for (int j = 0; j < trans.Interpolation[i].Length; j++)
                            for (int k = 0; k < trans.Interpolation[i][j].Length; k++)
                                trans.Interpolation[i][j][k] = motion.Motions[targetMotionIndex].Interpolation[i][j][k];
                    //回転はコピーしない
                    Array.Clear(trans.Quatanion, 0, 4);
                    trans.Quatanion[3] = 1;
                    //回転クォータニオンを生成
                    Quaternion quat = new Quaternion(motion.Motions[targetMotionIndex].Quatanion[0],
                                                motion.Motions[targetMotionIndex].Quatanion[1],
                                                motion.Motions[targetMotionIndex].Quatanion[2],
                                                motion.Motions[targetMotionIndex].Quatanion[3]);
                    //対象ボーンの移動量を取得
                    Vector3 boneMove = new Vector3(motion.Motions[targetMotionIndex].Location[0],
                                            motion.Motions[targetMotionIndex].Location[1],
                                            motion.Motions[targetMotionIndex].Location[2]);
                    //そこからひとつ上のボーン(=rootボーン)の移動に変換
                    Vector3 baseVec;
                    quat = Quaternion.Inverse(quat);
                    Vector3.Transform(ref boneMove, ref quat, out baseVec);//ルートのボーン移動取得
                    //トランスフォームモーションの移動量とする
                    trans.Location[0] = baseVec.X;
                    trans.Location[1] = baseVec.Y;
                    trans.Location[2] = baseVec.Z;
                    //ターゲットボーンの移動量を削除
                    Array.Clear(motion.Motions[targetMotionIndex].Location, 0, 3);
                    //ターゲットの探索
                    for (int effectedMotionIndex = 0; effectedMotionIndex < motion.Motions.Length; effectedMotionIndex++)
                    {
                        if (motion.Motions[effectedMotionIndex].FrameNo == motion.Motions[targetMotionIndex].FrameNo &&
                            motion.Motions[effectedMotionIndex].BoneName != baseBoneName)
                        {//同フレームでベースボーンではなく
                            foreach (var name in EffectedBoneNames)
                            {
                                if (motion.Motions[effectedMotionIndex].BoneName == name)
                                {//対象ボーン(=親が0xFFFFなボーン)の処理を行う
                                    //対象ボーンのその時点での回転を取得
                                    Quaternion rot = new Quaternion(motion.Motions[effectedMotionIndex].Quatanion[0],
                                                            motion.Motions[effectedMotionIndex].Quatanion[1],
                                                            motion.Motions[effectedMotionIndex].Quatanion[2],
                                                            motion.Motions[effectedMotionIndex].Quatanion[3]);
                                    //ローカルにおけるベクトルを計算し減算処理を行う
                                    Vector3 localVec;
                                    Vector3.Transform(ref baseVec, ref rot, out localVec);
                                    motion.Motions[effectedMotionIndex].Location[0] -= localVec.X;
                                    motion.Motions[effectedMotionIndex].Location[1] -= localVec.Y;
                                    motion.Motions[effectedMotionIndex].Location[2] -= localVec.Z;
                                    //ベジェ曲線パラメータの合成。(多分これで合ってる)
                                    for (int i = 0; i < 3; i++)
                                    {
                                        for (int j = 0; j < 4; j++)
                                        {
                                            motion.Motions[effectedMotionIndex].Interpolation[0][j][i] = (byte)(
                                                (motion.Motions[effectedMotionIndex].Interpolation[0][j][i] + motion.Motions[targetMotionIndex].Interpolation[0][j][i]) / 2);
                                        }
                                    }
                                    break;
                                }
                            }
                        }
                    }
                }
            }
            //トランスフォームを後部に追加
            int destIndex = motion.Motions.Length;
            Array.Resize(ref motion.m_Motions, motion.m_Motions.Length + transes.Count);
            Array.Copy(transes.ToArray(), 0, motion.Motions, destIndex, transes.Count);
        }
    }
}
