using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MikuMikuDance.XNA.Model;
using MikuMikuDance.XNA.Model.ModelData;
using System.Diagnostics;

namespace MikuMikuDance.XNA.Motion
{
    /// <summary>
    /// マルチモーションセット用の構造体
    /// </summary>
    public struct MultiMotionInfo
    {
        /// <summary>
        /// 通常のモーション
        /// </summary>
        public MMDMotion Motion;
        /// <summary>
        /// スムース(前のモーションと接続)
        /// </summary>
        /// <remarks>先頭のモーションについてはこのパラメータは無効</remarks>
        public bool Smooth;
        /// <summary>
        /// 各モーション終了時に発生
        /// </summary>
        public event EventHandler EachMotionEnd;

        internal void OnEachMotionEnd(object sender)
        {
            if (EachMotionEnd != null)
                EachMotionEnd(sender, EventArgs.Empty);
        }
    }
    /// <summary>
    /// マルチモーショントラック
    /// </summary>
    /// <remarks>複数のモーションを繋げて再生可能なトラック</remarks>
    public class MultiMotionTrack : MotionTrack
    {
        Stopwatch timer = new Stopwatch();
        bool isEmpty = true;
        int numRepeat = 0;
        bool isLoopPlay;
        List<NormalMotionTrack> normalTracks = new List<NormalMotionTrack>();
        MultiMotionInfo[] m_info;
        decimal frame = 0;
        decimal beforeMS = 0;
        decimal LossTime = 0;
        decimal m_NowFrame = 0;
        int m_NowTrack = 0;
        decimal m_NowTrackFrame = 0;

        //オブジェクトプール
        static Queue<NormalMotionTrack> ObjPool = new Queue<NormalMotionTrack>();
        /// <summary>
        /// セットアップ
        /// </summary>
        /// <param name="info">設定用の情報</param>
        public void Setup(MultiMotionInfo[] info)
        {
            Clear();
            m_info = info;
            for (int i = 1; i < info.Length; i++)
            {
                if (info[i].Smooth)
                {//スムーシング処理
                    MMDMotion.Smooth(info[i - 1].Motion, info[i].Motion);
                }
            }
            for (int i = 0; i < info.Length; i++)
            {//トラック構築
                NormalMotionTrack temptrack;
                if (ObjPool.Count > 0)
                    temptrack = ObjPool.Dequeue();
                else
                    temptrack = new NormalMotionTrack();
                temptrack.Clear();
                temptrack.Motion = info[i].Motion;
                temptrack.IsEmpty = false;
                normalTracks.Add(temptrack);
            }
        }

        /// <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() 
        {
            for (int i = 0; i < normalTracks.Count; i++)
                ObjPool.Enqueue(normalTracks[i]);
            normalTracks.Clear();
            timer.Reset(); 
            isLoopPlay = false; 
            isEmpty = true; 
        }
        /// <summary>
        /// モーショントラックの現在の再生フレームを取得
        /// </summary>
        /// <remarks>設定もできるが、これは特定フレームのモーションデータを取得したいときにのみ使用すること。
        /// これを修正しても、再生位置は変わらない</remarks>
        public decimal NowFrame {
            get { return m_NowFrame; }
            set
            {
                //内部トラックと調整する
                m_NowTrackFrame += value - m_NowFrame;
                m_NowFrame = value;
                while (m_NowTrackFrame > normalTracks[m_NowTrack].MaxFrame || m_NowTrack < normalTracks.Count - 1)
                {
                    m_info[m_NowTrack].OnEachMotionEnd(this);
                    m_NowTrackFrame -= normalTracks[m_NowTrack++].MaxFrame;
                }
                while (m_NowTrackFrame < 0 || m_NowTrack > 0)
                {
                    m_info[m_NowTrack].OnEachMotionEnd(this);
                    m_NowTrackFrame += normalTracks[m_NowTrack--].MaxFrame;
                }
                normalTracks[m_NowTrack].NowFrame = m_NowTrackFrame;
            }
        }
        /// <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 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 = 0;
            NowFrame = 0;
            numRepeat = 0;
            for (int i = 0; i < normalTracks.Count; i++)
                normalTracks[i].Reset();
        }
        /// <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 && !isLoopPlay)
            {//ループ再生しないパターン
                Stop();//終了
                if (frame > MaxFrame)
                {
                    NowFrame = (decimal)MaxFrame;
                    frame = MaxFrame;
                }
                else
                {
                    NowFrame = 0;
                    frame = 0;
                }
                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;
            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)
        {
            //proxyパターン
            normalTracks[m_NowTrack].ApplyMotion(mmdBone, mmdFace,mmdx, ref transform, ref BoneUpdated);
        }

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

    }
}
