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

namespace MikuMikuDance.XNA.Model
{
    class OneTrack
    {
        public MMDMotion motion;
        Stopwatch timer = new Stopwatch();
        public bool IsLoopPlay;
        decimal frame = 0;
        decimal beforeMS = 0;

        public void SetLoopPlay(bool value) { IsLoopPlay = value; }
        public void Reset() { motion = null; timer.Reset(); IsLoopPlay = false; }

        internal void Stop()
        {
            timer.Stop();
        }

        internal void Start()
        {
            timer.Start();
        }
        internal bool IsPlay
        {
            get
            {
                return timer.IsRunning;
            }
        }
        internal decimal GetFrame(decimal FramePerSecond)
        {
            //現在時刻の取得
            decimal nowMS = (decimal)timer.Elapsed.TotalMilliseconds;
            //前回コール時からの経過フレーム数を取得
            decimal dframe = (nowMS - beforeMS) * FramePerSecond / 1000.0m;
            //フレーム数の更新
            frame += dframe;
            beforeMS = nowMS;

            if (frame > motion.MaxFrame && !IsLoopPlay)
            {
                Stop();
                return (decimal)motion.MaxFrame;
            }
            return frame % (decimal)motion.MaxFrame;
        }
    }
    /// <summary>
    /// モーションファイルからアニメーションを再生するアニメーションプレイヤー
    /// </summary>
    public class AnimationPlayer
    {
        //親のモデル
        MMDModel mmdModel;
        //現在のモーション一覧
        List<OneTrack> mmdMotion = new List<OneTrack>(MikuMikuDanceXNA.MotionTrackCap);
        List<OneTrack> trackPool = new List<OneTrack>(MikuMikuDanceXNA.MotionTrackCap);//オブジェクトプール

        

        /// <summary>
        /// 一秒あたりのフレーム数
        /// </summary>
        public int FramePerSecond { get; set; }
        

        
        /// <summary>
        /// 親のモデルクラスからアニメーションプレイヤーを生成
        /// </summary>
        /// <param name="model">親のモデルクラス</param>
        internal AnimationPlayer(MMDModel model)//内部からのみ
        {
            mmdMotion.Capacity = MikuMikuDanceXNA.MotionTrackCap;
            for (int i = 0; i < MikuMikuDanceXNA.MotionTrackCap; i++)
                trackPool.Add(new OneTrack());
            mmdModel = model;
            FramePerSecond = 30;//MMDは30フレーム/秒が標準
        }

        /// <summary>
        /// モーションをセット
        /// </summary>
        /// <param name="motion">セットするMMDモーションデータ</param>
        /// <param name="SetStartPos">0フレームのデータでポーズを初期化する</param>
        /// <returns>セットしたモーションの番号</returns>
        public int SetMotion(MMDMotion motion, bool SetStartPos)
        {
            if (trackPool.Count == 0)
                mmdMotion.Add(new OneTrack() { motion = motion });
            else
            {
                trackPool[0].Reset();
                trackPool[0].motion = motion;
                mmdMotion.Add(trackPool[0]);
                trackPool.RemoveAt(0);
            }
            if (SetStartPos)
                InnerUpdate(motion, 0m);

            return mmdMotion.Count - 1;
        }
        /// <summary>
        /// モーションをストップする
        /// </summary>
        public void Stop(int index)
        {
            mmdMotion[index].Stop();
        }
        /// <summary>
        /// 全てのモーションをストップする
        /// </summary>
        public void StopAll()
        {
            for (int i = 0; i < Count; i++)
            {
                Stop(i);
            }
        }
        /// <summary>
        /// モーションを再生する
        /// </summary>
        /// <param name="index">モーションインデックス番号</param>
        /// <param name="gameTime">GameTimeオブジェクト</param>
        /// <param name="bLoopPlay">ループプレイ</param>
        public void Start(int index, GameTime gameTime, bool bLoopPlay)
        {
            if (mmdMotion == null)
                throw new ApplicationException("モーションデータがセットされてないのにStartを実行した");
            mmdMotion[index].Start();
            mmdMotion[index].SetLoopPlay(bLoopPlay);
        }
        /// <summary>
        /// 全てのモーションを再生する
        /// </summary>
        /// <param name="gameTime">GameTimeオブジェクト</param>
        /// <param name="bLoopPlay">ループプレイ</param>
        public void StartAll(GameTime gameTime, bool bLoopPlay)
        {
            for (int i = 0; i < Count; i++)
            {
                Start(i, gameTime, bLoopPlay);
            }
        }
        /// <summary>
        /// モーションの再生状態を取得
        /// </summary>
        /// <param name="index">モーションインデックス番号</param>
        /// <returns>再生中ならtrue</returns>
        public bool IsPlay(int index)
        {
            return (mmdMotion[index].IsPlay);
        }
        /// <summary>
        /// モーションがループプレイかどうかを調べる
        /// </summary>
        /// <param name="index">モーションインデックス番号</param>
        /// <returns>ループプレイならtrue</returns>
        public bool IsLoopPlay(int index)
        {
            return mmdMotion[index].IsLoopPlay;
        }
        /// <summary>
        /// 現在のモーション数
        /// </summary>
        public int Count { get { return mmdMotion.Count; } }
        /// <summary>
        /// モーションを外す
        /// </summary>
        /// <param name="index">モーションインデックス番号</param>
        /// <remarks>モーションを消すと、消した番号以降の番号が一つ前にずれるので注意</remarks>
        public void UnsetMotion(int index)
        {
            Stop(index);
            mmdMotion.RemoveAt(index);
        }

        /// <summary>
        /// アニメーションの更新
        /// </summary>
        /// <remarks>モデルのUpdateから呼ばれるので、ユーザーが呼ぶ必要はありません</remarks>
        internal void Update()
        {
            for (int i=0;i<mmdMotion.Count;i++)
            {
                //再生中かどうかチェック
                if (mmdMotion[i].IsPlay)
                {
                    //現在の再生フレームを更新
                    InnerUpdate(mmdMotion[i].motion, mmdMotion[i].GetFrame(FramePerSecond));
                    
                    
                }
            }
            //こちらではボーンマネージャのUpdateは行わない
        }

        private void InnerUpdate(MMDMotion motion, decimal NowFrame)
        {
#if TRACE
            if (mmdModel.mmdXNA.TimeRular != null && mmdModel.UseTimeRular)
                mmdModel.mmdXNA.TimeRular.BeginMark(2, "Anime-MotionToBone", Color.BlueViolet);
#endif
            //モーションのボーンリストを取得
            List<string> motionBones = motion.GetBoneList();
            //リストにあるボーンの位置を順番に更新
            foreach (var bone in motionBones)
            {
                if (mmdModel.BoneManager.ContainsBone(bone))
                {//存在しないボーンへのモーションは無視……
                    QuatTransform move = motion.GetBoneTransform(bone, NowFrame);
                    //ボーン処理
                    mmdModel.BoneManager[bone].BoneTransform = move * mmdModel.BoneManager[bone].BoneData.BindPose;
                }
            }
#if TRACE
            if (mmdModel.mmdXNA.TimeRular != null && mmdModel.UseTimeRular)
            {
                mmdModel.mmdXNA.TimeRular.EndMark(2, "Anime-MotionToBone");
                mmdModel.mmdXNA.TimeRular.BeginMark(2, "Anime-IKBone", Color.Red);
            }
#endif
            mmdModel.BoneManager.UpdateWorldTransforms();
            //IKボーンの処理
            for (int i = 0; i < mmdModel.BoneManager.Count; i++)
            {
                if (mmdModel.BoneManager[i].BoneData.IK != null)
                    mmdModel.BoneManager.SolveIK(i, mmdModel.BoneManager[i].BoneTransform);
            }
#if TRACE
            if (mmdModel.mmdXNA.TimeRular != null && mmdModel.UseTimeRular)
            {
                mmdModel.mmdXNA.TimeRular.EndMark(2, "Anime-IKBone");
                mmdModel.mmdXNA.TimeRular.BeginMark(2, "Anime-Face", Color.BlueViolet);
            }
#endif
            //フェイスモーションの処理
            List<string> faces = motion.GetFaceList();
            //リストにあるフェイスのデータを順番に処理
            foreach (var face in faces)
            {
                mmdModel.FaceManager.SetFace(face, motion.GetFaceRate(face, NowFrame));
            }
#if TRACE
            if (mmdModel.mmdXNA.TimeRular != null && mmdModel.UseTimeRular)
                mmdModel.mmdXNA.TimeRular.EndMark(2, "Anime-Face");
#endif
        }
    }
}
