#region File Description
//-----------------------------------------------------------------------------
// AlphaTestEffect.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Diagnostics;
#endregion

namespace StockEffects
{
    /// <summary>
    /// Built-in effect that supports alpha testing.
    /// </summary>
    public class DefaultEffect : Effect, IEffectMatrices, IEffectFog
    {
        #region Effect Parameters

        protected EffectParameter textureParam;
        protected EffectParameter diffuseColorParam;
        protected EffectParameter alphaTestParam;
        protected EffectParameter objKeyParam;
        protected EffectParameter fogColorParam;
        protected EffectParameter fogVectorParam;
        protected EffectParameter worldViewProjParam;
        protected EffectParameter worldParam;
        protected EffectParameter shaderIndexParam;
        protected EffectParameter texOffsetParam;
        protected EffectParameter texScaleParam;

        #endregion

        #region Fields
        protected EffectUsage usage = EffectUsage.GameView;
        protected bool fogEnabled;
        protected bool vertexColorEnabled;

        protected Matrix world = Matrix.Identity;
        protected Matrix view = Matrix.Identity;
        protected Matrix projection = Matrix.Identity;

        protected Matrix worldView;

        protected Vector3 diffuseColor = Vector3.One;

        protected float alpha = 1;

        protected float fogStart = 0;
        protected float fogEnd = 1;

        protected int referenceAlpha;

        protected EffectDirtyFlags dirtyFlags = EffectDirtyFlags.All;

        protected Vector2 texOffset = new Vector2();
        protected Vector2 texScale = new Vector2(1f, 1f);

        protected UInt32 htid = 0;
        #endregion

        #region Public Properties


        /// <summary>
        /// Gets or sets the world matrix.
        /// </summary>
        public Matrix World
        {
            get { return world; }
            
            set
            {
                world = value;
                dirtyFlags |= EffectDirtyFlags.WorldViewProj | EffectDirtyFlags.Fog;
            }
        }


        /// <summary>
        /// Gets or sets the view matrix.
        /// </summary>
        public Matrix View
        {
            get { return view; }
            
            set
            {
                view = value;
                dirtyFlags |= EffectDirtyFlags.WorldViewProj | EffectDirtyFlags.Fog;
            }
        }


        /// <summary>
        /// Gets or sets the projection matrix.
        /// </summary>
        public Matrix Projection
        {
            get { return projection; }
            
            set
            {
                projection = value;
                dirtyFlags |= EffectDirtyFlags.WorldViewProj;
            }
        }


        /// <summary>
        /// Gets or sets the material diffuse color (range 0 to 1).
        /// </summary>
        public Vector3 DiffuseColor
        {
            get { return diffuseColor; }
            
            set
            {
                diffuseColor = value;
                dirtyFlags |= EffectDirtyFlags.MaterialColor;
            }
        }

        /// <summary>
        /// Gets or sets the material alpha.
        /// </summary>
        public float Alpha
        {
            get { return alpha; }
            
            set
            {
                alpha = value;
                dirtyFlags |= EffectDirtyFlags.MaterialColor;
            }
        }


        /// <summary>
        /// Gets or sets the fog enable flag.
        /// </summary>
        public bool FogEnabled
        {
            get { return fogEnabled; }
            
            set
            {
                if (fogEnabled != value)
                {
                    fogEnabled = value;
                    dirtyFlags |= EffectDirtyFlags.ShaderIndex | EffectDirtyFlags.FogEnable;
                }
            }
        }


        /// <summary>
        /// Gets or sets the fog start distance.
        /// </summary>
        public float FogStart
        {
            get { return fogStart; }
            
            set
            {
                fogStart = value;
                dirtyFlags |= EffectDirtyFlags.Fog;
            }
        }


        /// <summary>
        /// Gets or sets the fog end distance.
        /// </summary>
        public float FogEnd
        {
            get { return fogEnd; }
            
            set
            {
                fogEnd = value;
                dirtyFlags |= EffectDirtyFlags.Fog;
            }
        }


        /// <summary>
        /// Gets or sets the fog color.
        /// </summary>
        public Vector3 FogColor
        {
            get { return fogColorParam.GetValueVector3(); }
            set { fogColorParam.SetValue(value); }
        }


        /// <summary>
        /// Gets or sets the current texture.
        /// </summary>
        public Texture2D Texture
        {
            get { return textureParam.GetValueTexture2D(); }
            set { textureParam.SetValue(value); }
        }


        /// <summary>
        /// Gets or sets whether vertex color is enabled.
        /// </summary>
        public bool VertexColorEnabled
        {
            get { return vertexColorEnabled; }
            
            set
            {
                if (vertexColorEnabled != value)
                {
                    vertexColorEnabled = value;
                    dirtyFlags |= EffectDirtyFlags.ShaderIndex;
                }
            }
        }

        /// <summary>
        /// Gets or sets the reference alpha value (default 0).
        /// </summary>
        public int ReferenceAlpha
        {
            get { return referenceAlpha; }
            
            set
            {
                referenceAlpha = value;
                dirtyFlags |= EffectDirtyFlags.AlphaTest;
            }
        }

        public Vector2 TextureOffset
        {
            get { return texOffset; }
            set
            {
                texOffset = value;
                dirtyFlags |= EffectDirtyFlags.TextureRegion;
            }
        }

        public Vector2 TextureScale
        {
            get { return texScale; }
            set
            {
                texScale = value;
                dirtyFlags |= EffectDirtyFlags.TextureRegion;
            }
        }

        public EffectUsage Usage {
            get { return usage; }
            set {
                usage = value;
                dirtyFlags |= EffectDirtyFlags.ShaderIndex;
            }
        }

        public UInt32 HitTestID {
            get { return htid; }
            set { htid = value; }
        }
        #endregion

        #region Methods


        /// <summary>
        /// Creates a new AlphaTestEffect with default parameter settings.
        /// </summary>
        public DefaultEffect(GraphicsDevice device)
            : base(device, Resources.DefaultEffect)
        {
            CacheEffectParameters();
        }

        /// <summary>
        /// for override usage
        /// </summary>
        /// <param name="device"></param>
        /// <param name="code"></param>
        protected DefaultEffect(GraphicsDevice device, byte[] code)
            : base(device, code) {
            CacheEffectParameters();
        }


        /// <summary>
        /// Creates a new AlphaTestEffect by cloning parameter settings from an existing instance.
        /// </summary>
        protected DefaultEffect(DefaultEffect cloneSource)
            : base(cloneSource)
        {
            CacheEffectParameters();

            fogEnabled = cloneSource.fogEnabled;
            vertexColorEnabled = cloneSource.vertexColorEnabled;

            world = cloneSource.world;
            view = cloneSource.view;
            projection = cloneSource.projection;

            diffuseColor = cloneSource.diffuseColor;

            alpha = cloneSource.alpha;

            fogStart = cloneSource.fogStart;
            fogEnd = cloneSource.fogEnd;
            
            referenceAlpha = cloneSource.referenceAlpha;
            texOffset = cloneSource.texOffset;
            texScale = cloneSource.texScale;
        }


        /// <summary>
        /// Creates a clone of the current AlphaTestEffect instance.
        /// </summary>
        public override Effect Clone()
        {
            return new DefaultEffect(this);
        }

        public void SetTextureRegion(Rectangle region)
        {
            if (Texture != null)
            {
                int w = Texture.Width;
                int h = Texture.Height;
                if (region.X >= 0 && region.Y >= 0 && region.Width <= w && region.Height <= h)
                {
                    TextureScale = new Vector2((float)region.Width / (float)w, (float)region.Height / (float)h);
                    TextureOffset = new Vector2((float)region.X / (float)w, (float)region.Y / (float)h);
                    return;
                }
                throw new ArgumentException("Texture region out of range.");
            }
        }

        /// <summary>
        /// Looks up shortcut references to our effect parameters.
        /// </summary>
        protected virtual void CacheEffectParameters()
        {
            textureParam        = Parameters["Texture"];
            diffuseColorParam   = Parameters["DiffuseColor"];
            alphaTestParam      = Parameters["AlphaTest"];
            objKeyParam         = Parameters["ObjKey"];
            fogColorParam       = Parameters["FogColor"];
            fogVectorParam      = Parameters["FogVector"];
            worldViewProjParam  = Parameters["WorldViewProj"];
            worldParam          = Parameters["World"];
            shaderIndexParam = Parameters["ShaderIndex"];
            texOffsetParam      = Parameters["TextureOffset"];
            texScaleParam       = Parameters["TextureScale"];
        }


        /// <summary>
        /// Lazily computes derived parameter values immediately before applying the effect.
        /// </summary>
        protected override void OnApply()
        {
            Vector3 v3 = new Vector3((htid & 0xffff)/65536f, (htid >>16)/65536f, 1);
            objKeyParam.SetValue(v3);
            // Recompute the world+view+projection matrix or fog vector?
            dirtyFlags = EffectHelpers.SetWorldViewProjAndFog(dirtyFlags, ref world, ref view, ref projection, ref worldView, fogEnabled, fogStart, fogEnd, worldViewProjParam, fogVectorParam);
            //if((dirtyFlags & EffectDirtyFlags.World) != 0)
                worldParam.SetValue(world);
            // Recompute the texture offset?
            if ((dirtyFlags & EffectDirtyFlags.TextureRegion) != 0)
            {
                texOffsetParam.SetValue(texOffset);
                texScaleParam.SetValue(texScale);
                dirtyFlags &= ~EffectDirtyFlags.TextureRegion;
            }
            
            // Recompute the diffuse/alpha material color parameter?
            if ((dirtyFlags & EffectDirtyFlags.MaterialColor) != 0)
            {
                diffuseColorParam.SetValue(new Vector4(diffuseColor * alpha, alpha));

                dirtyFlags &= ~EffectDirtyFlags.MaterialColor;
            }

            // Recompute the alpha test settings?
            if ((dirtyFlags & EffectDirtyFlags.AlphaTest) != 0)
            {
                Vector4 alphaTest = new Vector4();
                
                // Convert reference alpha from 8 bit integer to 0-1 float format.
                float reference = (float)referenceAlpha / 255f;
                
                // Comparison tolerance of half the 8 bit integer precision.
                const float threshold = 0.5f / 255f;

                // Shader will evaluate: clip((a < x) ? z : w)
                alphaTest.X = reference + threshold;
                alphaTest.Z = -1;
                alphaTest.W = 1; 
                                
                alphaTestParam.SetValue(alphaTest);

                dirtyFlags &= ~EffectDirtyFlags.AlphaTest;

            }

            // Recompute the shader index?
            if ((dirtyFlags & EffectDirtyFlags.ShaderIndex) != 0)
            {
                int shaderIndex = ((int)usage)*2;
                if (vertexColorEnabled)
                    shaderIndex ++;
                
                //if (!fogEnabled)
                //    shaderIndex += 1;
                
                //if (vertexColorEnabled)
                //    shaderIndex += 2;
                
                shaderIndexParam.SetValue(shaderIndex);

                dirtyFlags &= ~EffectDirtyFlags.ShaderIndex;
            }
        }

        #endregion
    }
}