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);
        //物理エンジンマネージャ
        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>
        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; }
        
        //このクラスは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;
            DrawBorder = 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);
            
            //トゥーンテクスチャの整理
            bool NeedToSetToons = ModelData.ToonTextures.Initialize();
            UseToon = true;
            //その他エフェクトの準備
            //頂点テクスチャのサイズ取得
            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);
                    //トゥーンテクスチャのセット
                    if (NeedToSetToons)
                    {
                        effect.Parameters["ToonTexture1"].SetValue(ModelData.ToonTextures[0]);
                        effect.Parameters["ToonTexture2"].SetValue(ModelData.ToonTextures[1]);
                        effect.Parameters["ToonTexture3"].SetValue(ModelData.ToonTextures[2]);
                        effect.Parameters["ToonTexture4"].SetValue(ModelData.ToonTextures[3]);
                        effect.Parameters["ToonTexture5"].SetValue(ModelData.ToonTextures[4]);
                        effect.Parameters["ToonTexture6"].SetValue(ModelData.ToonTextures[5]);
                        effect.Parameters["ToonTexture7"].SetValue(ModelData.ToonTextures[6]);
                        effect.Parameters["ToonTexture8"].SetValue(ModelData.ToonTextures[7]);
                        effect.Parameters["ToonTexture9"].SetValue(ModelData.ToonTextures[8]);
                        effect.Parameters["ToonTexture10"].SetValue(ModelData.ToonTextures[9]);
                    }
                }
            }
            //表情周りのデータセット
            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");
            }
            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);
            }
            
            ModelDraw(Game.GraphicsDevice, "MMDBasicEffect");//モデルの描画
            
        }


        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を変更
            graphics.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
            int count = 0;
            foreach (ModelMesh mesh in ModelData.ModelData.Meshes)
            {
                if (count == 1 && !DrawBorder)
                    continue;//境界線は2メッシュ目。描画しないときは飛ばす
                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();
                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();
                    if (acc.Key.Enabled)
                    {
                        acc.Key.Draw(acc.Value.Transform * baseMat * World);
                    }
                }
            }
        }
        
    }
}
