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

namespace MikuMikuDance.XNA.Model
{
    /// <summary>
    /// MikuMikuDance for XNAのモデルクラス
    /// </summary>
    /// <remarks>MMDModelをGCに回収させたい場合はDisposeを呼ぶこと</remarks>
    public class MMDHiDefModel : MMDReachModel, MMDModel
    {
#region メンバ変数
        // ボーン情報を格納するテクスチャ
        FlipTexture2D rotationTexture;      // ボーンの回転部分を格納するテクスチャ
        FlipTexture2D translationTexture;   // ボーンの平行移動部分を格納するテクスチャ
        FlipTexture2D faceRateTexture;          // 表情の割合を格納するテクスチャ(GPUアニメーション)
        FlipTexture2D faceDataTexture;          // 表情計算に必要なデータを格納するテクスチャ(GPUアニメーション用)
        FlipTexture2D faceVertTexture;          // 表情の頂点位置を格納するテクスチャ(CPUアニメーション用)
        //Zオーダー用
        PartCenterCompare ZCompare = new PartCenterCompare();
        
        //表情頂点処理モード(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;
         
#endregion

        #region プロパティ
        /// <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>
        /// シャドウマップを利用して影を描画するかどうかのフラグ
        /// </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 MMDHiDefModel(Game game)
            : base(game)
        {
            DrawBorder = true;
        }
        bool disposed = false;
        /// <summary>
        /// 廃棄処理
        /// </summary>
        /// <remarks>MMDModelをGCに回収させたい場合はDisposeを呼ぶこと</remarks>
        protected override void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (rotationTexture != null)
                    rotationTexture.Dispose();
                if (translationTexture != null)
                    translationTexture.Dispose();
                if (faceRateTexture != null)
                    faceRateTexture.Dispose();
                if (faceVertTexture != null)
                    faceVertTexture.Dispose();
                rotationTexture = null;
                translationTexture = null;
                faceRateTexture = null;
                faceVertTexture = null;
                disposed = true;
            }
            base.Dispose(disposing);
        }
#if TRACE
        /// <summary>
        /// 既定のデストラクタ
        /// </summary>
        ~MMDHiDefModel()
        {
            Console.WriteLine("MMDHiDefModel Destructor");
        }
#endif
        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 override void ModelSetup(MMDModelData modelData, MikuMikuDanceXNA mmdxna, GraphicsDevice graphics)
        {
            base.ModelSetup(modelData, mmdxna, graphics);
            // 頂点テクスチャの生成
            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);
            //Zオーダー用にインデックス設定
            for (int i = 0; i < ModelData.PartCenters.Length; i++)
                modelData.PartCenters[i].Order = i;
            
        }
        #endregion

        


        #region 更新処理

        /// <summary>
        /// モデルを更新
        /// </summary>
        /// <remarks>DrawableGameContentを継承してるため自動更新</remarks>
        public override void Update(GameTime gameTime)
        {
            if (disposed)
                return;//破棄済みでも呼ばれるので排除
            base.Update(gameTime);//MMDReachModelの処理
#if TRACE
            if (mmdXNA.TimeRular != null && UseTimeRular)
            {
                mmdXNA.TimeRular.EndMark(1, "FaceManager");
            }
#endif
            if (!GPUAnimation)
                FaceManager.CalcVertMove();
#if TRACE
            if (mmdXNA.TimeRular != null && UseTimeRular)
            {
                mmdXNA.TimeRular.EndMark(1, "FaceManager");
            }
#endif
        }
        
        #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.RenderState.DepthBufferWriteEnable = 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(Transform.CreateMatrix());
                }
                count++;
            }
            beforeDrawTick = totalTick;
        }
        /// <summary>
        /// モデル描画処理(HiDef用)
        /// </summary>
        public override void Draw(GraphicsDevice graphics, long totalTick, bool IsShadow)
        {
            //HiDefは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)
                {
                    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], graphics, mmdXNA);
                //パラメータのセット
                EffectParameterSetup(totalTick);
                //Zオーダー計算
                if (ModelData.UsePartCenterZSort)
                    ModelData.CalcDrawOrder(Transform.CreateMatrix(), mmdXNA.DrawManager.View, ZCompare);
                //描画
                count = 0;
                foreach (ModelMesh mesh in ModelData.ModelData.Meshes)
                {
                    if (count == 1 && !DrawBorder)
                        continue;//境界線は2メッシュ目。描画しないときは飛ばす
                    for (int i = 0;
                        i < ((ModelData.UsePartCenterZSort && count == 0) ?
                        ModelData.PartCenters.Length : mesh.MeshParts.Count); i++)
                    {
                        ModelMeshPart meshPart;
                        if (ModelData.UsePartCenterZSort && count == 0)
                            meshPart = mesh.MeshParts[ModelData.PartCenters[i].Order];
                        else
                            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
                //アクセサリの描画
                /*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

        
    }
}
