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 : DrawableGameComponent
    {
        //内部データ
        internal MMDModelData ModelData { get; private set; }
        internal MikuMikuDanceXNA mmdXNA { get; private set; }
        // ボーン情報を格納するテクスチャ
        FlipTexture2D rotationTexture;      // ボーンの回転部分を格納するテクスチャ
        FlipTexture2D translationTexture;   // ボーンの平行移動部分を格納するテクスチャ
        FlipTexture2D faceRateTexture;          // 表情の割合を格納するテクスチャ(GPUアニメーション)
        FlipTexture2D faceDataTexture;          // 表情計算に必要なデータを格納するテクスチャ(GPUアニメーション用)
        FlipTexture2D faceVertTexture;          // 表情の頂点位置を格納するテクスチャ(CPUアニメーション用)
        //トゥーンライティング用データ
        // Settings controlling the Toon lighting technique.
        readonly Vector2 ToonThresholds = new Vector2(0.8f, 0.4f);
        readonly Vector3 ToonBrightnessLevels = new Vector3(1.3f, 0.9f, 0.5f);
        //エッジ処理用のバッファ
        //RenderTarget2D NormalDepthRenderTarget;//エッジドロー用
        //物理エンジンマネージャ
        MMDPhysics physics;
        //モデル名
        internal string Name { get; set; }
        //モデル付随アクセサリ
        Dictionary<MMDAccessory, MMD_VAC> Accessories = new Dictionary<MMDAccessory, MMD_VAC>(6);
        //表情頂点処理モード(XBoxはデフォルトtrue,PCはノートのGPU対策のためにfalse)
#if GPUAnime
        bool m_GPUAnimation = true;
#else
        bool m_GPUAnimation = false;
#endif
        bool m_UseToon = false;

        //properties...
        /// <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>
        /// <remarks>未実装...</remarks>
        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["UseToonLighting"].SetValue(m_UseToon);
                    }
                }
            }
        }
        
        //このクラスはMikuMikuDanceXNAからしか作れない
        internal MMDModel(Game game)
            :base(game)
        {
            World = Matrix.CreateFromYawPitchRoll(0, 0, 0) * Matrix.CreateTranslation(0, 0, 0);
            physics = new MMDPhysics(this);
            game.Components.Add(this);
            UseTimeRular = true;
        }

        private void ModelFaceSetup(bool UseGPU, MMDModelData modelData)
        {
            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)//メモ：エフェクト17回ループで処理時間食ってる
                    {
                        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;
            BoneManager = new MMDBoneManager(ModelData);
            FaceManager = new MMDFaceManager(modelData);
            Player = new AnimationPlayer(this);
            // 頂点テクスチャの生成
            int width = BoneManager.skinRots.Length;
            int height = 1;

            rotationTexture = new FlipTexture2D(graphics, width, height, 1,
                                    TextureUsage.Linear, SurfaceFormat.Vector4);
            translationTexture = new FlipTexture2D(graphics, width, height, 1,
                                    TextureUsage.Linear, SurfaceFormat.Vector4);
            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);
            
            //エッジ描画用のバッファ作成
            /*PresentationParameters pp = graphics.PresentationParameters;
            NormalDepthRenderTarget = new RenderTarget2D(graphics,
                pp.BackBufferWidth, pp.BackBufferHeight, 1,
                pp.BackBufferFormat, pp.MultiSampleType, pp.MultiSampleQuality);*/
            //その他エフェクトの準備
            //頂点テクスチャのサイズ取得
            Vector2 textureSize = new Vector2(rotationTexture.Texture.Width,
                                                rotationTexture.Texture.Height);
            foreach (ModelMesh mesh in ModelData.ModelData.Meshes)
            {
                foreach (Effect effect in mesh.Effects)//メモ：エフェクト17回ループで処理時間食ってる
                {
                    //ボーン数や表情数は変化しないのでこのタイミングで頂点テクスチャサイズをセットする
                    effect.Parameters["BoneTextureSize"].SetValue(textureSize);
                    //トゥーンライティング設定は変化しないので、このタイミングでセットする
                    effect.Parameters["ToonThresholds"].SetValue(ToonThresholds);
                    effect.Parameters["ToonBrightnessLevels"].SetValue(ToonBrightnessLevels);
                }
            }
            //表情周りのデータセット
            ModelFaceSetup(m_GPUAnimation, modelData);
            //物理エンジン用の剛体作成
            physics.Initialize();

        }
        /// <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);
            
            }
        }

        /// <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;
            Accessories.Add(accessory, vacData);
        }
        /// <summary>
        /// アクセサリのモデルへの関連付けの解除
        /// </summary>
        /// <param name="accessory">アクセサリ</param>
        public void ReleaseAccessory(MMDAccessory accessory)
        {
            Accessories.Remove(accessory);
            accessory.parent = null;
        }

        /// <summary>
        /// モデルを更新
        /// </summary>
        /// <remarks>DrawableGameContentを継承してるため自動更新</remarks>
        public override void Update(GameTime gameTime)
        {
            Update();
            base.Update(gameTime);
        }
        /// <summary>
        /// モデルを更新
        /// </summary>
        /// <remarks>手動更新用</remarks>
        public void Update()
        {
#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");
                //mmdXNA.TimeRular.BeginMark(1, "FaceManager", Color.Cyan);
            }
            //FaceManager.Update();
/*#if TRACE
            if (mmdXNA.TimeRular != null && UseTimeRular)
                mmdXNA.TimeRular.EndMark(1, "FaceManager");
#endif*/
            if (mmdXNA.UsePhysic)
                physics.Update();
        }

        /// <summary>
        /// モデルの描画
        /// </summary>
        /// <remarks>DrawableGameContentを継承してるため自動更新</remarks>
        public override void Draw(GameTime gameTime)
        {
            Draw(gameTime.TotalGameTime.Ticks);
            //base.Draw(gameTime);
        }
        /// <summary>
        /// モデルの描画
        /// </summary>
        /// <param name="totalTicks">GameTime.TotalGameTime.Ticksの値を入れる</param>
        /// <remarks>手動更新用</remarks>
        public void Draw(long totalTicks)
        {
            //カメラとライトのUpdateを呼び出し(totalTickで判別)
            if (ModelData.ModelData.Meshes.Count > 0 && ModelData.ModelData.Meshes[0].Effects.Count > 0)
            {
                Effect effect = ModelData.ModelData.Meshes[0].Effects[0];
                mmdXNA.Camera.SetEffectParam(effect, totalTicks, Game.GraphicsDevice);
                mmdXNA.LightManager.SetEffectParam(effect, totalTicks);
            }
            
            //エッジの描画
            //描画の退避がうまくできないので、実装中断
#if false
            //ターゲットの退避
            RenderTarget beforeTarget = graphics.GetRenderTarget(0);
            DepthStencilBuffer beforeDepth = graphics.DepthStencilBuffer;
            Viewport viewport = graphics.Viewport;
            
            //エッジ描画用のバッファをセット
            //graphics.SetRenderTarget(0, NormalDepthRenderTarget);
            graphics.DepthStencilBuffer = new DepthStencilBuffer(graphics, graphics.PresentationParameters.BackBufferWidth, graphics.PresentationParameters.BackBufferHeight, DepthFormat.Depth24);
            //graphics.Clear(Color.White);

            //モデル描画用設定
            //ターゲットの復元
            if (beforeTarget == null)
                graphics.SetRenderTarget(0, null);
            else if (beforeTarget.GetType() == typeof(RenderTarget2D))
                graphics.SetRenderTarget(0, (RenderTarget2D)beforeTarget);
            else if (beforeTarget.GetType() == typeof(RenderTargetCube))
                throw new ApplicationException("RenderTargetCubeが設定されてる状態でのDraw呼び出しには対応していません");
            else
                throw new ApplicationException("不明な型" + beforeTarget.GetType().ToString() + "がGraphicsDeviceに設定されていました");
            
            graphics.DepthStencilBuffer = beforeDepth;
            graphics.Viewport = viewport;
#endif
            ModelDraw(Game.GraphicsDevice, "MMDBasicEffect");//モデルの描画
            
        }

        /*public bool Break = false;
        System.Diagnostics.Stopwatch debug = new System.Diagnostics.Stopwatch();
        double Start = 0;*/

        private void ModelDraw(GraphicsDevice graphics, string EffectTechniqueName)
        {
            if (mmdXNA.TimeRular != null && UseTimeRular)
            {
                mmdXNA.TimeRular.BeginMark(0, "DrawModel", Color.Yellow);
            }
            // ボーンのクォータニオンと平行移動部分を取得を頂点テクスチャに書き込み
            rotationTexture.Flip();
            translationTexture.Flip();
            if (GPUAnimation)
                faceRateTexture.Flip();
            else
                faceVertTexture.Flip();

            //スキンアニメーション用テクスチャ
            rotationTexture.Texture.SetData<Quaternion>(BoneManager.skinRots);
            translationTexture.Texture.SetData<Vector4>(BoneManager.skinTranses);
            //フェイステクスチャ
            if (GPUAnimation)
                faceRateTexture.Texture.SetData<Vector4>(FaceManager.FaceRates);
            else
                faceVertTexture.Texture.SetData<Vector4>(FaceManager.FaceVerts);
            //モデルdraw用の設定
            graphics.RenderState.AlphaBlendEnable = false;
            graphics.RenderState.AlphaTestEnable = false;
            //モデルのCullModeを変更
            CullMode mode = graphics.RenderState.CullMode;
            graphics.RenderState.CullMode = CullMode.None;

            
            foreach (ModelMesh mesh in ModelData.ModelData.Meshes)
            {
                foreach (Effect effect in mesh.Effects)//メモ：エフェクト17回ループで処理時間食ってる
                {
                    //エフェクトルーチン。
                    //ボーン設定
                    effect.Parameters["BoneRotationTexture"].SetValue(
                                                        rotationTexture.Texture);
                    effect.Parameters["BoneTranslationTexture"].SetValue(
                                                        translationTexture.Texture);
                    //表情設定
                    if (GPUAnimation)
                        effect.Parameters["FaceRateTexture"].SetValue(faceRateTexture.Texture);
                    else
                        effect.Parameters["FaceVertTexture"].SetValue(faceVertTexture.Texture);
                    //表示行列設定
                    effect.Parameters["World"].SetValue(World);
                }
                mesh.Draw();
            }
            //CullModeの変更を戻す
            graphics.RenderState.CullMode = mode;
            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();
                    if (acc.Key.Enabled)
                    {
                        acc.Key.Draw(acc.Value.Transform * baseMat * World);
                    }
                }
            }
        }
        
    }
}
