using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MikuMikuDance.XNA.Model;

namespace MikuMikuDance.XNA.Accessory
{
    
    /// <summary>
    /// MikuMikuDanceのアクセサリデータ
    /// </summary>
    public class MMDAccessory : GameComponent
    {
        internal MMDModel parent = null;
        internal MMD_VAC vacData;
        MikuMikuDanceXNA mmd = null;
        bool m_bVisible = true;
        DrawDelegate drawDelegate;
        /// <summary>
        /// アクセサリに用いるモデルデータ
        /// </summary>
        public Microsoft.Xna.Framework.Graphics.Model ModelData { get; set; }
        /// <summary>
        /// このモデルのワールド座標系の取得/設定1
        /// </summary>
        /// <remarks>MMDモデルにもたせている場合はMMDモデル座標系</remarks>
        public Matrix World { get; set; }
        /// <summary>
        /// このモデルのワールド座標系の取得/設定2
        /// </summary>
        /// <remarks>MMDモデルに持たせている場合でもワールド座標系を返す。モデルに持たせていないときはWorldと同値</remarks>
        public Matrix TrueWorld
        {
            get
            {
                if (parent == null)
                    return World;
                else
                    return World * parent.GetBaseTransform(vacData);
            }
            set
            {
                if (parent == null)
                    World = value;
                else
                    World = value * Matrix.Invert(parent.GetBaseTransform(vacData));
            }
        }
        /// <summary>
        /// MikuMikuDanceXNA.TimeRularをこのアクセサリが呼び出すかどうか
        /// </summary>
        public bool UseTimeRular { get; set; }
        /// <summary>
        /// このオブジェクトがモデルの描画設定を自動で呼ぶかどうか
        /// </summary>
        /// <remarks>falseにして、設定を手動にすることでGPU命令発行を減らすことは可能</remarks>
        public bool CallDrawSetup { get; set; }
        /// <summary>
        /// シャドウマップを利用して影を描画するかどうかのフラグ
        /// </summary>
        public bool UseShadowMap { get; set; }
        /// <summary>
        /// Draw を呼び出す必要があるかどうかを示します。
        /// </summary>
        public bool Visible
        {
            get
            {
                return m_bVisible;
            }
            set
            {
                if (m_bVisible != value)
                {
                    if (value)
                        mmd.DrawModelEvent += drawDelegate;
                    else
                        mmd.DrawModelEvent -= drawDelegate;
                }
            }
        }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="model">モデルデータ</param>
        /// <param name="mmd">MikuMikuDanceXNAオブジェクト</param>
        /// <param name="game">Gameオブジェクト</param>
        /// <param name="world">初期トランスフォームマトリックス</param>
        public MMDAccessory(Microsoft.Xna.Framework.Graphics.Model model,MikuMikuDanceXNA mmd, Game game,Matrix world)
            :base(game)
        {
            this.mmd = mmd;
            ModelData = model;
            World = world;
            UseTimeRular = true;
            CallDrawSetup = true;
            if (game != null)
                game.Components.Add(this);
            drawDelegate = new DrawDelegate(Draw);
            mmd.DrawAccessoryEvent += drawDelegate;
            //アクセサリの初期設定
            foreach (ModelMesh mesh in ModelData.Meshes)
            {
                foreach (Effect effect in mesh.Effects)
                {
                    //スクリーン付きのアクセサリかどうか調べる
                    foreach (var i in effect.Parameters)
                    {
                        if (i.Name == "ScreenMode")
                        {
                            i.SetValue(0);//最初はScreenはoffにしておく
                            break;
                        }
                    }
                    
                }
            }
        }
        /// <summary>
        /// オブジェクトの描画設定
        /// </summary>
        /// <param name="graphics">GraphicDevice</param>
        /// <param name="IsShadow">シャドウマップ描画時かどうか</param>
        public static void GraphicsSetup(GraphicsDevice graphics, bool IsShadow)
        {
            //基本的な設定
            if (IsShadow)
            {
                graphics.RenderState.AlphaBlendEnable = false;
                graphics.RenderState.BlendFunction = BlendFunction.Add;
                graphics.RenderState.SourceBlend = Blend.Zero;
                graphics.RenderState.DestinationBlend = Blend.DestinationColor;
                graphics.RenderState.AlphaTestEnable = true;
                graphics.RenderState.ReferenceAlpha = 0xF0;
                graphics.RenderState.AlphaFunction = CompareFunction.GreaterEqual;
            }
            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;
        }
        /// <summary>
        /// 内部描画ルーチン
        /// </summary>
        /// <param name="graphics">グラフィックスデバイス</param>
        /// <param name="totalTick">GameTime.TotalGameTime.Ticksの値を入れる</param>
        /// <param name="IsShadow">シャドウマップ描画時かどうか</param>
        public void Draw(GraphicsDevice graphics, long totalTick, bool IsShadow)
        {
            if (mmd.TimeRular != null && UseTimeRular)
            {
                mmd.TimeRular.BeginMark(0, "DrawAccessory", Color.Yellow);
            }
            //パラメータ設定
            Matrix trueWorld = TrueWorld;
            foreach (ModelMesh mesh in ModelData.Meshes)
            {
                foreach (Effect effect in mesh.Effects)
                {
                    if (IsShadow)
                    {
                        if (mmd.ShadowMapManager == null)
                            throw new ApplicationException("シャドウマップ描画時にシャドウマップマネージャがセットされていません");
                        effect.CurrentTechnique = effect.Techniques["ShadowMap"];
                        effect.Parameters["World"].SetValue(trueWorld);
                    }
                    else
                    {
                        if (UseShadowMap && mmd.ShadowMapManager != null)
                        {
                            effect.CurrentTechnique = effect.Techniques["BasicWithShadow"];
                        }
                        else
                        {
                            effect.CurrentTechnique = effect.Techniques["BasicEffect"];
                        }
                        effect.Parameters["World"].SetValue(trueWorld);
                        effect.Parameters["DirLight0DiffuseColor"].SetValue(mmd.LightManager.KeyLight.Color.ToVector3());
                        if (mmd.ScreenManager != null)
                        {
                            Texture2D screen;
                            if (mmd.ScreenManager.GetScreenTex(totalTick, out screen))
                            {
                                effect.Parameters["ScreenMode"].SetValue(mmd.ScreenManager.ScreenMode);
                                effect.Parameters["Screen"].SetValue(screen);
                                effect.Parameters["ScreenTexFix"].SetValue(mmd.ScreenManager.ScreenFix);
                            }
                        }
                    }
                }
            }
            //共用パラメータのセット
            mmd.DrawManager.SharedEffectParameterSetup(totalTick, IsShadow, UseShadowMap, ModelData.Meshes[0].Effects[0], graphics, mmd);

            foreach (ModelMesh mesh in ModelData.Meshes)
            {
                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();
                }
            }
            if (mmd.TimeRular != null && UseTimeRular)
            {
                mmd.TimeRular.EndMark(0, "DrawAccessory");
            }
        }
        
    }
}
