using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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>
        /// このモデルに適応するワールド座標系
        /// </summary>
        /// <remarks>MMDモデルにもたせている場合はMMDモデル座標系</remarks>
        public Matrix World { get; set; }
        /// <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;
            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>
        internal void Draw(GraphicsDevice graphics, long totalTick, bool IsShadow)
        {
            if (Visible)
            {
                if (mmd.TimeRular != null && UseTimeRular)
                {
                    mmd.TimeRular.BeginMark(0, "DrawAccessory", Color.Yellow);
                }
                //パラメータ設定
                Matrix trueWorld;
                if (parent == null)
                    trueWorld = World;
                else
                    trueWorld = World * parent.GetBaseTransform(vacData);
                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], Game.GraphicsDevice, mmd);

                foreach (ModelMesh mesh in ModelData.Meshes)
                {
                    foreach (ModelMeshPart meshpart in mesh.MeshParts)
                    {
                        Effect effect = meshpart.Effect;
                        effect.Begin();
                        effect.CurrentTechnique.Passes[0].Begin();
                        Game.GraphicsDevice.Vertices[0].SetSource(mesh.VertexBuffer, meshpart.StreamOffset, meshpart.VertexStride);
                        Game.GraphicsDevice.VertexDeclaration = meshpart.VertexDeclaration;
                        Game.GraphicsDevice.Indices = mesh.IndexBuffer;
                        effect.CommitChanges();
                        Game.GraphicsDevice.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");
                }
            }
        }
    }
}
