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

namespace MikuMikuDance.XNA.Motion
{
    /// <summary>
    /// 通常のモーショントラック
    /// </summary>
    public class NormalMotionTrack : MotionTrack
    {
        MMDMotion motion;
        Stopwatch timer = new Stopwatch();
        decimal frame = 0;
        int numRepeat = 0;
        decimal beforeMS = 0;
        decimal LossTime = 0;
        bool isEmpty = true;
        bool isLoopPlay;
        Dictionary<string, float> FaceRates = new Dictionary<string, float>();//適応中の表情リスト
        Vector3 lastMove;

        /// <summary>
        /// 未初期化フラグ
        /// </summary>
        public bool IsEmpty { get { return isEmpty; } set { isEmpty = value; } }
        /// <summary>
        /// ループプレイフラグ
        /// </summary>
        public bool IsLoopPlay { get { return isLoopPlay; } set { isLoopPlay = value; } }
        /// <summary>
        /// モーショントラック初期化
        /// </summary>
        public void Clear() { motion = null; timer.Reset(); isLoopPlay = false; isEmpty = true; }
        /// <summary>
        /// モーショントラックの現在の再生フレームを取得
        /// </summary>
        /// <remarks>設定もできるが、これは特定フレームのモーションデータを取得したいときにのみ使用すること。
        /// これを修正しても、再生位置は変わらない</remarks>
        public decimal NowFrame { get; set; }
        /// <summary>
        /// モーショントラックのリピート回数を取得
        /// </summary>
        public int NumRepeat { get { return numRepeat; } }
        /// <summary>
        /// モーショントラックの最大フレームを取得
        /// </summary>
        public long MaxFrame { get; set; }
        /// <summary>
        /// モーショントラックの種別を取得
        /// </summary>
        public TrackType Type { get { return TrackType.NormalTrack; } }
        /// <summary>
        /// 逆再生フラグ
        /// </summary>
        public bool Reverse { get; set; }
        /// <summary>
        /// トラック番号
        /// </summary>
        public int TrackNum { get; set; }
        /// <summary>
        /// このモーショントラックに関連付けられているモーションデータを取得/設定
        /// </summary>
        public MMDMotion Motion
        {
            get { return motion; }
            set
            {
                motion = value;
                MaxFrame = motion.MaxFrame;
            }
        }
        

        /// <summary>
        /// Bake処理用にモーショントラックのコピーをとる
        /// </summary>
        /// <returns>コピーされたモーショントラック</returns>
        /// <remarks>MMDMotion motionだけはコピーされない</remarks>
        internal NormalMotionTrack CloneForBake()
        {
            NormalMotionTrack newTrack = new NormalMotionTrack();
            newTrack.motion = motion;
            bool IsRunning = timer.IsRunning;
            if (IsRunning)
                Stop();
            newTrack.frame = frame;
            newTrack.beforeMS = beforeMS;
            newTrack.LossTime = LossTime;
            newTrack.isEmpty = isEmpty;
            newTrack.isLoopPlay = isLoopPlay;
            newTrack.NowFrame = NowFrame;
            newTrack.MaxFrame = MaxFrame;
            return newTrack;
        }
        /// <summary>
        /// モーショントラックの再生停止
        /// </summary>
        public void Stop()
        {
            if (!timer.IsRunning)
                return;
            timer.Stop();
            LossTime = (decimal)timer.Elapsed.TotalMilliseconds - beforeMS;//ロスタイムを計測
        }
        /// <summary>
        /// モーショントラックの再生
        /// </summary>
        public void Start()
        {
            if (timer.IsRunning)
                return;
            timer.Start();
            beforeMS = (decimal)timer.Elapsed.TotalMilliseconds - LossTime;
        }
        /// <summary>
        /// モーショントラックのリセット
        /// </summary>
        public void Reset()
        {
            timer.Reset();
            beforeMS = 0;
            LossTime = 0;
            frame = Reverse ? MaxFrame : 0;
            NowFrame = frame;
            numRepeat = 0;
            lastMove = Vector3.Zero;
        }
        /// <summary>
        /// トラックのシーク
        /// </summary>
        /// <param name="frame">新しいシーク位置</param>
        public void Seek(decimal frame)
        {
            this.frame = frame;
        }
        /// <summary>
        /// モーショントラックを再生中かどうか取得
        /// </summary>
        public bool IsPlay
        {
            get
            {
                return timer.IsRunning;
            }
        }
        /// <summary>
        /// モーショントラックの再生フレームを更新
        /// </summary>
        /// <param name="FramePerSecond">更新に使用するFPS</param>
        /// <returns>ループが発生したらtrue</returns>
        public bool UpdateFrame(decimal FramePerSecond)
        {
            //現在時刻の取得
            decimal nowMS = (decimal)timer.Elapsed.TotalMilliseconds;
            //前回コール時からの経過フレーム数を取得
            decimal dframe = (nowMS - beforeMS) * FramePerSecond / 1000.0m;
            //フレーム数の更新
            frame += dframe * (Reverse ? -1 : 1);
            beforeMS = nowMS;

            if ((frame > MaxFrame || frame < 0) && !isLoopPlay)
            {//ループ再生しないパターン
                Stop();//終了
                if (frame > MaxFrame)
                {
                    NowFrame = (decimal)MaxFrame;
                    frame = MaxFrame;
                }
                else
                {
                    NowFrame = 0;
                    frame = 0;
                }
                //モーション終了イベントを呼び出し
                motion.OnMotionEnd(TrackNum);
                return false;
            }
            if (frame < 0)
            {//逆再生ループのおまじない
                frame += MaxFrame;
            }
            //ループ再生用にフレーム数等を更新
            NowFrame = frame % (decimal)(MaxFrame + 1);
            int nextRepeat = (int)Math.Floor((double)(frame / (decimal)(MaxFrame + 1)));
            bool result = nextRepeat != numRepeat;
            numRepeat = nextRepeat;
            //モーション終了イベントを呼び出し
            if (result)
                motion.OnMotionEnd(TrackNum);
            return result;
        }
        /// <summary>
        /// 現在の再生フレームにあわせてボーンと表情を設定
        /// </summary>
        /// <param name="transform">モデル座標</param>
        /// <param name="mmdBone">ボーンマネージャ</param>
        /// <param name="mmdFace">フェイスマネージャ</param>
        /// <param name="mmdx">MikuMikuDanceXNA</param>
        /// <param name="BoneUpdated">更新されたボーンはtrueとなる</param>
        public void ApplyMotion(MMDBoneManager mmdBone, MMDFaceManager mmdFace, MikuMikuDanceXNA mmdx, ref QuatTransform transform,  ref bool[] BoneUpdated)
        {
            //モーションのボーンリストを取得
            List<string> motionBones = motion.GetBoneList();
            //リストにあるボーンの位置を順番に更新
            foreach (var bone in motionBones)
            {
                if (bone == AnimationPlayer.transBoneName)
                {
                    QuatTransform move = motion.GetBoneTransform(bone, NowFrame);
                    //前のボーン状態と比べて、その分の移動量をModelに適応
                    transform.Translation += move.Translation - lastMove;//計算誤差が発生するからやりたくないんだけど……
                    lastMove = move.Translation;
                }
                else if (mmdBone.ContainsBone(bone))
                {//存在しないボーンへのモーションは無視……
                    int boneIndex = mmdBone.IndexOf(bone);
                    if (mmdx == null || mmdx.UsePhysic == false || !mmdBone.Bones[boneIndex].IsPhysics)
                    {
                        QuatTransform move = motion.GetBoneTransform(bone, NowFrame);
                        //ボーン処理
                        mmdBone.Bones[boneIndex].BoneTransform = move * mmdBone.Bones[boneIndex].BoneData.BindPose;
                        //UpdateフラグをOn
                        BoneUpdated[boneIndex] = true;
                    }
                }

            }
            //フェイスモーションの処理
            List<string> faces = motion.GetFaceList();
            //リストにあるフェイスのデータを順番に処理
            foreach (var face in faces)
            {
                SetFace(face, motion.GetFaceRate(face, NowFrame), FaceRates, mmdFace);
            }
            mmdFace.UpdateRate(FaceRates);
        }

        /// <summary>
        /// 表情のセット
        /// </summary>
        /// <param name="facename">表情名</param>
        /// <param name="rate">表情適用率(0-1)</param>
        /// <param name="FaceRates">セットされる表情率辞書</param>
        /// <param name="mmdFace">フェイスマネージャ</param>
        /// <remarks>ベース表情に戻したい際にはResetFaceを呼び出すこと</remarks>
        private static void SetFace(string facename, float rate, Dictionary<string, float> FaceRates,MMDFaceManager mmdFace)
        {
            if (facename == "base")
                return;//baseは何もしない……
            if (!mmdFace.ContainsFace(facename))
                return;//対象表情が無いのでパス
            //表情情報リストに登録しておく
            if (FaceRates.ContainsKey(facename))
                FaceRates[facename] = rate;
            else
                FaceRates.Add(facename, rate);
        }

        
        /// <summary>
        /// スムーシング処理
        /// </summary>
        /// <param name="BoneManager">ボーンマネージャ</param>
        public void Smooth(MMDBoneManager BoneManager)
        {
            Motion.Smooth(BoneManager);
        }

        
    }
}
