using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using MikuMikuDance.XNA.Model.ModelData;
using MikuMikuDance.XNA.Motion;
using MikuMikuDance.XNA.Accessory;
using Microsoft.Xna.Framework.Graphics;

namespace MikuMikuDance.XNA.Model
{
    /// <summary>
    /// MikuMikuDance for XNAのReachモデルクラス
    /// </summary>
    /// <remarks>MMDModelをGCに回収させたい場合はDisposeを呼ぶこと</remarks>
    public class MMDReachModel : GameComponent, MMDModel
    {
        internal MikuMikuDanceXNA mmdXNA;
        //表示
        bool m_bVisible = true;

        /// <summary>
        /// モデル名
        /// </summary>
        public string Name { get; internal set; }
        /// <summary>
        /// 前回Drawしたタイムの保持
        /// </summary>
        protected long beforeDrawTick = 0;
        //イベント用デリゲート
        DrawDelegate drawDelegate;

        /// <summary>
        /// このモデルが保持しているモデルデータ
        /// </summary>
        internal MMDModelData ModelData { get; private set; }
        /// <summary>
        /// ボーンマネージャ
        /// </summary>
        public MMDBoneManager BoneManager { get; protected set; }
        /// <summary>
        /// フェイスマネージャ
        /// </summary>
        /// <remarks>Reachでは空となる</remarks>
        public MMDFaceManager FaceManager { get; private set; }
        /// <summary>
        /// アニメーションプレイヤー
        /// </summary>
        public AnimationPlayer Player { get; protected set; }
        /// <summary>
        /// 物理マネージャー
        /// </summary>
        public MMDPhysicsManager Physics { get; private set; }
        /// <summary>
        /// このモデルの位置と回転を表すフィールド
        /// </summary>
        public QuatTransform Transform;
        /// <summary>
        /// MikuMikuDanceXNA.TimeRularをこのモデルが呼び出すかどうか
        /// </summary>
        public bool UseTimeRular { get; set; }
        /// <summary>
        /// Draw を呼び出す必要があるかどうかを示します。
        /// </summary>
        public bool Visible
        {
            get
            {
                return m_bVisible;
            }
            set
            {
                if (m_bVisible != value)
                {
                    if (value)
                        mmdXNA.DrawModelEvent += drawDelegate;
                    else
                        mmdXNA.DrawModelEvent -= drawDelegate;
                }
                m_bVisible = value;
            }
        }
        #region イベント
        /// <summary>
        /// モーションを適用する直前に呼ばれる
        /// </summary>
        public event UpdateDelegate BeforeApplyMotion;
        /// <summary>
        /// モーションを適応した直後、MMDBoneManager.Update()(MMDBone.WorldTransformを計算する関数)が呼ばれるまでの間に呼ばれる
        /// </summary>
        /// <remarks>モーションをプログラムから操作したい場合はこのイベントを利用する</remarks>
        public event UpdateDelegate AfterApplyMotion;
        /// <summary>
        /// MMDBoneManager.Update()を行った直後に呼ばれる
        /// </summary>
        /// <remarks>このタイミングでボーンのWorldTransformの更新まで終わっている</remarks>
        public event UpdateDelegate AfterBoneManagerUpdate;
        #endregion
        
        //このクラスはMikuMikuDanceXNAからしか作れない
        internal MMDReachModel(Game game)
            : base(game)
        {
            Transform = new QuatTransform(Quaternion.CreateFromYawPitchRoll(0, 0, 0), Vector3.Zero);
            if (game != null)
                game.Components.Add(this);
            UseTimeRular = true;
            drawDelegate += new DrawDelegate(Draw);
            Physics = new MMDPhysicsManager();
        }
        bool disposed = false;
        /// <summary>
        /// 廃棄処理
        /// </summary>
        /// <remarks>MMDModelをGCに回収させたい場合はDisposeを呼ぶこと</remarks>
        protected override void Dispose(bool disposing)
        {
            if (!disposed)
            {
                Physics.Dispose();
                Visible = false;
                drawDelegate -= new DrawDelegate(Draw);
                if (Game != null)
                    Game.Components.Remove(this);
                disposed = true;
            }
            
            base.Dispose(disposing);
        }
#if TRACE
        /// <summary>
        /// 既定のデストラクタ
        /// </summary>
        ~MMDReachModel()
        {
            Console.WriteLine("MMDReachModel Destructor");
        }
#endif

        internal virtual void ModelSetup(MMDModelData modelData, MikuMikuDanceXNA mmdxna, GraphicsDevice graphics)
        {
            ModelData = modelData;
            mmdXNA = mmdxna;
            mmdXNA.DrawModelEvent += drawDelegate;
            BoneManager = new MMDBoneManager(ModelData);
            //ここでFaceManager作っておく。Reachならば、空FaceManagerが作られる(モーションとの兼ね合い)
            FaceManager = new MMDFaceManager(modelData);
            Player = new AnimationPlayer(this);
            //物理セット
            Physics.Setup(modelData, this, mmdxna.Physic, mmdxna.PhysicsThread);
        }
        #region アクセサリ保持
        /// <summary>
        /// アクセサリをモデルに関連付け
        /// </summary>
        /// <param name="accessory">アクセサリ</param>
        /// <param name="transform">アクセサリの位置</param>
        /// <param name="boneName">基準ボーン名</param>
        public void SetAccessory(MMDAccessory accessory, Matrix transform, string boneName)
        {
            SetAccessory(accessory, new MMD_VAC() { Transform = transform, BoneName = boneName });
        }
        /// <summary>
        /// アクセサリをモデルに関連付け
        /// </summary>
        /// <param name="accessory">アクセサリ</param>
        /// <param name="vacData">VAC設定データ</param>
        public void SetAccessory(MMDAccessory accessory, MMD_VAC vacData)
        {
            accessory.parent = this;
            accessory.vacData = vacData;
        }
        /// <summary>
        /// アクセサリのモデルへの関連付けの解除
        /// </summary>
        /// <param name="accessory">アクセサリ</param>
        public void ReleaseAccessory(MMDAccessory accessory)
        {
            accessory.parent = null;
        }
        #endregion

        /// <summary>
        /// モデルを更新
        /// </summary>
        public override void Update(GameTime gameTime)
        {
            if (disposed)
                return;//破棄済みでも呼ばれるので排除
#if TRACE//速度検査用コード。
            if (mmdXNA.TimeRular != null && UseTimeRular)
                mmdXNA.TimeRular.BeginMark(1, "Player", Color.BlueViolet);
#endif
            //BeforeApplyMotionイベント
            if (BeforeApplyMotion != null)
                BeforeApplyMotion(gameTime);
            bool rigidReset = Player.Update(-1, false);//プレイヤーの更新
            //AfterApplyMotionイベント
            if (AfterApplyMotion != null)
                AfterApplyMotion(gameTime);
#if TRACE
            if (mmdXNA.TimeRular != null && UseTimeRular)
            {
                mmdXNA.TimeRular.EndMark(1, "Player");
                mmdXNA.TimeRular.BeginMark(1, "BoneManager", Color.Cyan);
            }
#endif
            BoneManager.UpdateWithoutSkinTransform();
            if (rigidReset)
                Physics.ResetRigidWithoutBoneUpdate();
            if (AfterBoneManagerUpdate != null)
                AfterBoneManagerUpdate(gameTime);

            if (mmdXNA.UsePhysic)
                Physics.Update();
            BoneManager.UpdateSkinTransforms();
#if TRACE
            if (mmdXNA.TimeRular != null && UseTimeRular)
            {
                mmdXNA.TimeRular.EndMark(1, "BoneManager");
            }
#endif
            base.Update(gameTime);
        }
        internal Matrix GetBaseTransform(MMD_VAC vac)
        {
            Matrix baseMat = BoneManager.GetWorldQuatTransform(BoneManager.IndexOf(vac.BoneName)).CreateMatrix();
            return vac.Transform * baseMat * Transform.CreateMatrix();
        }

        //モデル一つ1フレームごとに1回だけセットするデータはここに記述
        //シャドウマップ時に2回呼ばれないようにする
        private void EffectParameterSetup(long totalTick)
        {
            if (beforeDrawTick >= totalTick)
                return;
            //データのセット
            foreach (ModelMesh mesh in ModelData.ModelData.Meshes)
            {
                foreach (Effect effect in mesh.Effects)//メモ：エフェクト17回ループで処理時間食ってる
                {
                    //エフェクト設定
                    //ボーン設定
                    effect.Parameters["BoneRotations"].SetValue(BoneManager.skinRots);
                    effect.Parameters["BoneTranslations"].SetValue(BoneManager.skinTranses);
                    //表示行列設定
                    effect.Parameters["World"].SetValue(Transform.CreateMatrix());
                }
            }
            beforeDrawTick = totalTick;
        }
        /// <summary>
        /// モデル描画処理(Reach用)
        /// </summary>
        public virtual void Draw(GraphicsDevice graphics, long totalTick, bool IsShadow)
        {
            if (IsShadow)
                return;//シャドウマップはReachでは描画しない
            //モデル描画判定
            if (ModelData.ModelData.Meshes.Count > 0)
            {
#if TRACE
                if (mmdXNA.TimeRular != null && UseTimeRular)
                {
                    mmdXNA.TimeRular.BeginMark(0, "DrawModel", Color.Yellow);
                }
#endif
                //パラメータ設定
                //テクニックの切り替え
                int count = 0;
                foreach (ModelMesh mesh in ModelData.ModelData.Meshes)
                {
                    foreach (Effect effect in mesh.Effects)
                    {
                        //エフェクトルーチン。
                        //テクニックセット
                        effect.CurrentTechnique = effect.Techniques["MMDBasicEffect"];
                        
                    }
                    count++;
                }
                //共用パラメータのセット
                mmdXNA.DrawManager.SharedEffectParameterSetup(totalTick, false, false, ModelData.ModelData.Meshes[0].Effects[0], graphics, mmdXNA);
                //パラメータのセット
                EffectParameterSetup(totalTick);
                //描画
                count = 0;
                foreach (ModelMesh mesh in ModelData.ModelData.Meshes)
                {
                    for (int i = 0;
                        i < mesh.MeshParts.Count; i++)
                    {
                        ModelMeshPart meshPart;
                        meshPart = mesh.MeshParts[i];
                        Effect effect = meshPart.Effect;
                        effect.Begin();
                        effect.CurrentTechnique.Passes[0].Begin();
                        graphics.Vertices[0].SetSource(mesh.VertexBuffer, meshPart.StreamOffset, meshPart.VertexStride);
                        graphics.VertexDeclaration = meshPart.VertexDeclaration;
                        graphics.Indices = mesh.IndexBuffer;
                        effect.CommitChanges();
                        graphics.DrawIndexedPrimitives(PrimitiveType.TriangleList,
                            meshPart.BaseVertex, 0, meshPart.NumVertices, meshPart.StartIndex, meshPart.PrimitiveCount);
                        effect.CurrentTechnique.Passes[0].End();
                        effect.End();
                    }
                    ++count;
                }
#if TRACE
                if (mmdXNA.TimeRular != null && UseTimeRular)
                {
                    mmdXNA.TimeRular.EndMark(0, "DrawModel");
                }
#endif
                
            }
        }
    }
}
