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

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 faceTexture;          // 表情による頂点の移動を格納するテクスチャ
        //トゥーンライティング用データ
        // Settings controlling the Toon lighting technique.
        Vector2 ToonThresholds = new Vector2(0.8f, 0.4f);
        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);

        //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>
        /// トゥーン処理をするかどうかのフラグ
        /// </summary>
        /// <remarks>未実装...</remarks>
        public bool UseToon { get; set; }
        /// <summary>
        /// MikuMikuDanceXNA.TimeRularをこのモデルが呼び出すかどうか
        /// </summary>
        public bool UseTimeRular { get; set; }
        //このクラスはMikuMikuDanceXNAからしか作れない
        internal MMDModel(Game game)
            :base(game)
        {
            World = Matrix.CreateFromYawPitchRoll(0, 0, 0) * Matrix.CreateTranslation(0, 0, 0);
            UseToon = false;
            physics = new MMDPhysics(this);
            game.Components.Add(this);
            UseTimeRular = true;
        }

        internal void ModelSetup(MMDModelData modelData, MikuMikuDanceXNA mmdxna, GraphicsDevice graphics)
        {
            ModelData = modelData;
            mmdXNA = mmdxna;
            BoneManager = new MMDBoneManager(this);
            FaceManager = new MMDFaceManager(this);
            Player = new AnimationPlayer(this);
            // 頂点テクスチャの生成
            int width = BoneManager.GetSkinRotation().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);
            faceTexture = new FlipTexture2D(graphics, FaceManager.GetFaceTranslation().Length, height, 1,
                                    TextureUsage.Linear, SurfaceFormat.Vector4);
            //エッジ描画用のバッファ作成
            /*PresentationParameters pp = graphics.PresentationParameters;
            NormalDepthRenderTarget = new RenderTarget2D(graphics,
                pp.BackBufferWidth, pp.BackBufferHeight, 1,
                pp.BackBufferFormat, pp.MultiSampleType, pp.MultiSampleQuality);*/
            //物理エンジン用の剛体作成
            physics.Initialize();

        }
        /// <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();
#if TRACE
            if (mmdXNA.TimeRular != null && UseTimeRular)
            {
                mmdXNA.TimeRular.EndMark(1, "Player");
                mmdXNA.TimeRular.BeginMark(1, "BoneManager", Color.Cyan);
            }
#endif
            BoneManager.Update();
#if TRACE
            if (mmdXNA.TimeRular != null && UseTimeRular)
                mmdXNA.TimeRular.EndMark(1, "BoneManager");
#endif
            if (mmdXNA.UsePhysic)
                physics.Update();
        }

        /// <summary>
        /// モデルの描画
        /// </summary>
        /// <remarks>DrawableGameContentを継承してるため自動更新</remarks>
        public override void Draw(GameTime gameTime)
        {
            Draw();
            base.Draw(gameTime);
        }
        /// <summary>
        /// モデルの描画
        /// </summary>
        /// <remarks>手動更新用</remarks>
        public void Draw()
        {
            //エッジの描画
            //描画の退避がうまくできないので、実装中断
#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");//モデルの描画
            
        }

        private StringBuilder Direction = new StringBuilder("DirLightDirection");

        private void ModelDraw(GraphicsDevice graphics, string EffectTechniqueName)
        {
            //ビューとプロジェクション取得
            Matrix view = mmdXNA.Camera.GetViewMatrix();
            Matrix projection = mmdXNA.Camera.GetProjectionMatrix(graphics);
            // ボーンのクォータニオンと平行移動部分を取得を頂点テクスチャに書き込み
            rotationTexture.Flip();
            translationTexture.Flip();
            faceTexture.Flip();

            //スキンアニメーション用テクスチャ
            rotationTexture.Texture.SetData<Quaternion>(BoneManager.GetSkinRotation());
            translationTexture.Texture.SetData<Vector4>(BoneManager.GetSkinTranslation());
            //フェイステクスチャ
            faceTexture.Texture.SetData<Vector4>(FaceManager.GetFaceTranslation());

            Vector2 textureSize = new Vector2(rotationTexture.Texture.Width,
                                                rotationTexture.Texture.Height);
            //フェイステクスチャのサイズ
            Vector2 faceTextureSize = new Vector2(faceTexture.Texture.Width, faceTexture.Texture.Height);

            //ライティング設定の取得
            Vector3[] LightVectors, LightColors;
            mmdXNA.LightManager.GetParameters(out LightVectors, out LightColors);
            //モデルのCullModeを変更
            CullMode mode = graphics.RenderState.CullMode;
            graphics.RenderState.CullMode = CullMode.None;
            
            foreach (ModelMesh mesh in ModelData.ModelData.Meshes)
            {
                foreach (Effect effect in mesh.Effects)
                {
                    //エフェクトルーチン。調整が必要
                    //テクニックセット
                    effect.CurrentTechnique = effect.Techniques[EffectTechniqueName];
                    //ライティングセット(すげーアホなコード書いてるが、ToStringするとGC発生しちゃうのでやむなく……)
                    effect.Parameters["DirLight0Direction"].SetValue(LightVectors[0]);
                    effect.Parameters["DirLight0DiffuseColor"].SetValue(LightColors[0]);
                    //effect.Parameters["DirLight0SpecularColor"].SetValue(LightSpeculars[0]);
                    effect.Parameters["DirLight1Direction"].SetValue(LightVectors[1]);
                    effect.Parameters["DirLight1DiffuseColor"].SetValue(LightColors[1]);
                    //effect.Parameters["DirLight1SpecularColor"].SetValue(LightSpeculars[1]);
                    effect.Parameters["DirLight2Direction"].SetValue(LightVectors[2]);
                    effect.Parameters["DirLight2DiffuseColor"].SetValue(LightColors[2]);
                    //effect.Parameters["DirLight2SpecularColor"].SetValue(LightSpeculars[2]);
                    
                    //ボーン設定
                    effect.Parameters["BoneRotationTexture"].SetValue(
                                                        rotationTexture.Texture);
                    effect.Parameters["BoneTranslationTexture"].SetValue(
                                                        translationTexture.Texture);
                    effect.Parameters["BoneTextureSize"].SetValue(textureSize);
                    
                    //表情設定
                    effect.Parameters["FaceTranslationTexture"].SetValue(faceTexture.Texture);
                    effect.Parameters["FaceTextureSize"].SetValue(faceTextureSize);
                    //トゥーンライティング設定
                    effect.Parameters["UseToonLighting"].SetValue(UseToon ? 1.0f : 0.0f);
                    effect.Parameters["ToonThresholds"].SetValue(ToonThresholds);
                    effect.Parameters["ToonBrightnessLevels"].SetValue(ToonBrightnessLevels);

                    //表示行列設定
                    effect.Parameters["World"].SetValue(World);
                    effect.Parameters["View"].SetValue(view);
                    effect.Parameters["Projection"].SetValue(projection);
                    
                }
                mesh.Draw();
            }
            //CullModeの変更を戻す
            graphics.RenderState.CullMode = mode;
            //アクセサリの描画
            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(baseMat * acc.Value.Transform);
                    }
                }
            }
        }
        
    }
}
