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

namespace MikuMikuDance.XNA.Model
{
    /// <summary>
    /// MikuMikuDance for XNAのモデルクラス
    /// </summary>
    public class MMDModel : GameComponent
    {
#region メンバ変数
        internal MikuMikuDanceXNA mmdXNA;
        // ボーン情報を格納するテクスチャ
        FlipTexture2D rotationTexture;      // ボーンの回転部分を格納するテクスチャ
        FlipTexture2D translationTexture;   // ボーンの平行移動部分を格納するテクスチャ
        FlipTexture2D faceRateTexture;          // 表情の割合を格納するテクスチャ(GPUアニメーション)
        FlipTexture2D faceDataTexture;          // 表情計算に必要なデータを格納するテクスチャ(GPUアニメーション用)
        FlipTexture2D faceVertTexture;          // 表情の頂点位置を格納するテクスチャ(CPUアニメーション用)
        //表示
        bool m_bVisible = true;
        //モデル名
        internal string Name { get; set; }
        //表情頂点処理モード(XBoxはデフォルトtrue,PCはノートのGPU対策のためにfalse)
#if GPUAnime
        bool m_GPUAnimation = true;
#else
        bool m_GPUAnimation = false;
#endif
        bool m_UseToon = false;
        bool m_UseShadow = false;
        //前回Drawしたタイムの保持
        long beforeDrawTick = 0;
        //イベント用デリゲート
        DrawDelegate drawDelegate; 
#endregion

        #region プロパティ
        /// <summary>
        /// このモデルが保持しているmmdx
        /// </summary>
        /// <summary>
        /// このモデルが保持しているモデルデータ
        /// </summary>
        internal MMDModelData ModelData { get; private set; }
        /// <summary>
        /// ボーンマネージャ
        /// </summary>
        public MMDBoneManager BoneManager { get; protected set; }
        /// <summary>
        /// フェイスマネージャ
        /// </summary>
        public MMDFaceManager FaceManager { get; private set; }
        /// <summary>
        /// アニメーションプレイヤー
        /// </summary>
        public AnimationPlayer Player { get; protected set; }
        /// <summary>
        /// このモデルに適応するワールド座標系
        /// </summary>
        public Matrix World { get; set; }
        /// <summary>
        /// MikuMikuDanceXNA.TimeRularをこのモデルが呼び出すかどうか
        /// </summary>
        public bool UseTimeRular { get; set; }
        /// <summary>
        /// トゥーンテクスチャを使うかどうかのフラグ
        /// </summary>
        public bool UseToon
        {
            get { return m_UseToon; }
            set
            {
                m_UseToon = value;
                foreach (ModelMesh mesh in ModelData.ModelData.Meshes)
                {
                    foreach (Effect effect in mesh.Effects)//メモ：エフェクト17回ループで処理時間食ってる
                    {
                        //トゥーンライティング設定
                        effect.Parameters["UseToon"].SetValue(m_UseToon);
                    }
                }
            }
        }
        /// <summary>
        /// 境界線を描画するかどうかのフラグ
        /// </summary>
        public bool DrawBorder { 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;
                }
            }
        }
        /// <summary>
        /// シャドウマップを利用して影を描画するかどうかのフラグ
        /// </summary>
        public bool UseShadowMap
        {
            get { return m_UseShadow; }
            set { m_UseShadow = value;}
        }
        /// <summary>
        /// 表情の頂点アニメーションにGPUを使うかどうかを取得/設定
        /// </summary>
        /// <remarks>設定は重めの処理なので注意。コンパイルオプションでGPUAnimeを付けると(XBox版既定値)デフォルトでtrueとなる</remarks>
        public bool GPUAnimation
        {
            get { return m_GPUAnimation; }
            set
            {
                if (m_GPUAnimation == value)
                    return;
                m_GPUAnimation = value;
                ModelFaceSetup(m_GPUAnimation, ModelData);

            }
        }
        #endregion

        #region コンストラクタ
        //このクラスはMikuMikuDanceXNAからしか作れない
        internal MMDModel(Game game)
            : base(game)
        {
            World = Matrix.CreateFromYawPitchRoll(0, 0, 0) * Matrix.CreateTranslation(0, 0, 0);
            game.Components.Add(this);
            UseTimeRular = true;
            DrawBorder = true;
            drawDelegate += new DrawDelegate(Draw);
        }
        private void ModelFaceSetup(bool UseGPU, MMDModelData modelData)
        {
            if (modelData.FaceData.Length > 0)
            {
                if (UseGPU)
                {
                    //ここでfaceDataTextureにデータをセットしておく

                    faceDataTexture.Texture.SetData<Vector4>(modelData.FaceData);
                    Vector2 faceRateTextureSize = new Vector2(faceRateTexture.Texture.Width, faceRateTexture.Texture.Height);
                    Vector2 faceDataTextureSize = new Vector2(modelData.FaceDataTextureSizeX, modelData.FaceDataTextureSizeY);
                    foreach (ModelMesh mesh in ModelData.ModelData.Meshes)
                    {
                        foreach (Effect effect in mesh.Effects)
                        {
                            effect.Parameters["UseGPUAnime"].SetValue(UseGPU);
                            effect.Parameters["FaceRateTextureSize"].SetValue(faceRateTextureSize);
                            //staticな頂点情報を転送しておく
                            effect.Parameters["FaceVertTexture"].SetValue(faceDataTexture.Texture);
                            effect.Parameters["FaceVertTextureSize"].SetValue(faceDataTextureSize);
                            effect.Parameters["FaceVertTextureColLines"].SetValue(new Vector2(modelData.FaceDataTextureSizeX, 1.0f / (float)modelData.FaceDataTextureSizeX));
                        }
                    }

                }
                else
                {
                    faceVertTexture.Texture.SetData<Vector4>(FaceManager.FaceVerts);
                    Vector2 faceVertTextureSize = new Vector2(modelData.FaceVertTextureSizeX, modelData.FaceVertTextureSizeY);
                    foreach (ModelMesh mesh in ModelData.ModelData.Meshes)
                    {
                        foreach (Effect effect in mesh.Effects)//メモ：エフェクト17回ループで処理時間食ってる
                        {
                            effect.Parameters["UseGPUAnime"].SetValue(UseGPU);
                            effect.Parameters["FaceVertTexture"].SetValue(faceVertTexture.Texture);
                            effect.Parameters["FaceVertTextureSize"].SetValue(faceVertTextureSize);
                            effect.Parameters["FaceVertTextureColLines"].SetValue(new Vector2(modelData.FaceVertTextureSizeX, 1.0f / (float)modelData.FaceVertTextureSizeX));
                        }
                    }
                }
            }
        }

        internal void ModelSetup(MMDModelData modelData, MikuMikuDanceXNA mmdxna, GraphicsDevice graphics)
        {
            ModelData = modelData;
            mmdXNA = mmdxna;
            mmdXNA.DrawModelEvent += drawDelegate;
            BoneManager = new MMDBoneManager(ModelData);
            FaceManager = new MMDFaceManager(modelData);
            Player = new AnimationPlayer(this);
            // 頂点テクスチャの生成
            int width = BoneManager.skinRots.Length;
            int height = 1;
            if (BoneManager.skinRots.Length > 0)
            {
                rotationTexture = new FlipTexture2D(graphics, width, height, 1,
                                        TextureUsage.Linear, SurfaceFormat.Vector4);
                translationTexture = new FlipTexture2D(graphics, width, height, 1,
                                        TextureUsage.Linear, SurfaceFormat.Vector4);
            }
            else
            {
                rotationTexture = null;
                translationTexture = null;
            }
            if (FaceManager.FaceRates.Length > 0)
            {
                faceRateTexture = new FlipTexture2D(graphics, FaceManager.FaceRates.Length, height, 1,
                                        TextureUsage.Linear, SurfaceFormat.Vector4);
                faceDataTexture = new FlipTexture2D(graphics, modelData.FaceDataTextureSizeX, modelData.FaceDataTextureSizeY, 1,
                            TextureUsage.Linear, SurfaceFormat.Vector4);
                faceVertTexture = new FlipTexture2D(graphics, modelData.FaceVertTextureSizeX, modelData.FaceVertTextureSizeY, 1,
                            TextureUsage.Linear, SurfaceFormat.Vector4);
            }
            else
            {
                faceRateTexture = null;
                faceDataTexture = null;
                faceVertTexture = null;
            }
            UseToon = true;
            UseShadowMap = false;
            //その他エフェクトの準備
            //頂点テクスチャのサイズ取得
            if (rotationTexture != null)
            {
                Vector2 textureSize = new Vector2(rotationTexture.Texture.Width,
                                                    rotationTexture.Texture.Height);
                foreach (ModelMesh mesh in ModelData.ModelData.Meshes)
                {
                    foreach (Effect effect in mesh.Effects)
                    {
                        //ボーン数や表情数は変化しないのでこのタイミングで頂点テクスチャサイズをセットする
                        effect.Parameters["BoneTextureSize"].SetValue(textureSize);
                    }
                }
            }
            //表情周りのデータセット
            ModelFaceSetup(m_GPUAnimation, modelData);
            //物理エンジン用の剛体作成
            //physics.Initialize();

        }
        #endregion

        #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


        #region 更新処理

        /// <summary>
        /// モデルを更新
        /// </summary>
        /// <remarks>DrawableGameContentを継承してるため自動更新</remarks>
        public override void Update(GameTime gameTime)
        {
#if TRACE//速度検査用コード。
            if (mmdXNA.TimeRular != null && UseTimeRular)
                mmdXNA.TimeRular.BeginMark(1, "Player", Color.BlueViolet);
#endif
            Player.Update(-1, false);//プレイヤーの更新
#if TRACE
            if (mmdXNA.TimeRular != null && UseTimeRular)
            {
                mmdXNA.TimeRular.EndMark(1, "Player");
                mmdXNA.TimeRular.BeginMark(1, "Bone&FaceManager", Color.Cyan);
            }
#endif
            BoneManager.Update();
            if (!GPUAnimation)
                FaceManager.CalcVertMove();
            if (mmdXNA.TimeRular != null && UseTimeRular)
            {
                mmdXNA.TimeRular.EndMark(1, "Bone&FaceManager");
            }
        }
        internal Matrix GetBaseTransform(MMD_VAC vac)
        {
            Matrix baseMat = BoneManager.GetWorldQuatTransform(BoneManager.IndexOf(vac.BoneName)).CreateMatrix();
            return vac.Transform * baseMat * World;
        }
        #endregion

        #region 描画処理
        /// <summary>
        /// オブジェクトの描画設定
        /// </summary>
        /// <param name="graphics">GraphicDevice</param>
        /// <param name="ShadowMapMode">シャドウマップ描画時はtrue</param>
        public static void GraphicsSetup(GraphicsDevice graphics, bool ShadowMapMode)
        {
            //基本的な設定
            if (ShadowMapMode)
            {
                graphics.RenderState.AlphaBlendEnable = false;
                graphics.RenderState.AlphaTestEnable = false;
            }
            else
            {
                graphics.RenderState.AlphaBlendEnable = true;
                graphics.RenderState.BlendFunction = BlendFunction.Add;
                graphics.RenderState.SourceBlend = Blend.SourceAlpha;
                graphics.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
                graphics.RenderState.AlphaTestEnable = true;
                graphics.RenderState.ReferenceAlpha = 1;
                graphics.RenderState.AlphaFunction = CompareFunction.GreaterEqual;
            }
            graphics.RenderState.DepthBufferEnable = true;
            graphics.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
            graphics.SamplerStates[0].AddressV = TextureAddressMode.Wrap;
            //モデルのCullModeを変更
            graphics.RenderState.CullMode = CullMode.CullCounterClockwiseFace;

        }
        //モデル一つ1フレームごとに1回だけセットするデータはここに記述
        //シャドウマップ時に2回呼ばれないようにする
        private void EffectParameterSetup(long totalTick)
        {
            if (beforeDrawTick >= totalTick)
                return;
            // ボーンのクォータニオンと平行移動部分を取得を頂点テクスチャに書き込み
            if (rotationTexture != null)
            {
                rotationTexture.Flip();
                translationTexture.Flip();
            }
            if (faceRateTexture != null)
            {
                if (GPUAnimation)
                    faceRateTexture.Flip();
                else
                    faceVertTexture.Flip();
            }
            //スキンアニメーション用テクスチャ
            if (rotationTexture != null)
            {
                rotationTexture.Texture.SetData<Quaternion>(BoneManager.skinRots);
                translationTexture.Texture.SetData<Vector4>(BoneManager.skinTranses);
            }
            //フェイステクスチャ
            if (faceRateTexture != null)
            {
                if (GPUAnimation)
                    faceRateTexture.Texture.SetData<Vector4>(FaceManager.FaceRates);
                else
                    faceVertTexture.Texture.SetData<Vector4>(FaceManager.FaceVerts);
            }
            //データのセット
            int count = 0;
            foreach (ModelMesh mesh in ModelData.ModelData.Meshes)
            {
                if (count == 1 && !DrawBorder)
                    continue;//境界線は2メッシュ目。描画しないときは飛ばす
                foreach (Effect effect in mesh.Effects)//メモ：エフェクト17回ループで処理時間食ってる
                {
                    //エフェクト設定
                    //ボーン設定
                    if (rotationTexture != null)
                    {
                        effect.Parameters["BoneRotationTexture"].SetValue(
                                                            rotationTexture.Texture);
                        effect.Parameters["BoneTranslationTexture"].SetValue(
                                                            translationTexture.Texture);
                    }
                    //表情設定
                    if (faceRateTexture != null)
                    {
                        if (GPUAnimation)
                            effect.Parameters["FaceRateTexture"].SetValue(faceRateTexture.Texture);
                        else
                            effect.Parameters["FaceVertTexture"].SetValue(faceVertTexture.Texture);
                    }
                    //表示行列設定
                    effect.Parameters["World"].SetValue(World);
                }
                count++;
            }
            beforeDrawTick = totalTick;
        }
        void Draw(GraphicsDevice graphics, long totalTick, bool IsShadow)
        {
            //モデル描画判定
            if (ModelData.ModelData.Meshes.Count > 0 && Visible)
            {
                if (mmdXNA.TimeRular != null && UseTimeRular)
                {
                    mmdXNA.TimeRular.BeginMark(0, "DrawModel", Color.Yellow);
                }
                //パラメータ設定
                //テクニックの切り替え
                int count = 0;
                foreach (ModelMesh mesh in ModelData.ModelData.Meshes)
                {
                    if (count == 1 && !DrawBorder)
                        continue;//境界線は2メッシュ目。描画しないときは飛ばす
                    foreach (Effect effect in mesh.Effects)
                    {
                        //エフェクトルーチン。
                        //テクニックセット
                        if (IsShadow)
                            effect.CurrentTechnique = effect.Techniques["MMDShadowMap"];
                        else
                        {
                            if (UseShadowMap && mmdXNA.ShadowMapManager != null)
                                effect.CurrentTechnique = effect.Techniques["MMDBasicEffectWithShadow"];
                            else
                                effect.CurrentTechnique = effect.Techniques["MMDBasicEffect"];
                        }
                    }
                    count++;
                }
                //共用パラメータのセット
                mmdXNA.DrawManager.SharedEffectParameterSetup(totalTick, IsShadow, UseShadowMap, ModelData.ModelData.Meshes[0].Effects[0], Game.GraphicsDevice, mmdXNA);
                //パラメータのセット
                EffectParameterSetup(totalTick);

                //描画
                count = 0;
                foreach (ModelMesh mesh in ModelData.ModelData.Meshes)
                {
                    if (count == 1 && !DrawBorder)
                        continue;//境界線は2メッシュ目。描画しないときは飛ばす
                    foreach (ModelMeshPart meshPart in mesh.MeshParts)
                    {
                        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 (mmdXNA.TimeRular != null && UseTimeRular)
                {
                    mmdXNA.TimeRular.EndMark(0, "DrawModel");
                }
                //アクセサリの描画
                /*foreach (var acc in Accessories)
                {
                    if (BoneManager.ContainsBone(acc.Value.BoneName))
                    {
                        Matrix baseMat = BoneManager.GetWorldQuatTransform(BoneManager.IndexOf(acc.Value.BoneName)).CreateMatrix();
                        acc.Key.BaseTransform = acc.Value.Transform * baseMat * World;
                    }
                }*/
            }
        }
        #endregion
    }
}
