//---------------------------------------------------------------------------------------------------------------------------------------------------
// <copyright file="Draw.cs" company="DarkWynter Studios">
//     Copyright (C)2007 DarkWynter Studios.  All rights reserved.
// </copyright>
//---------------------------------------------------------------------------------------------------------------------------------------------------
// {Contact : darkwynter.com for licensing information
//---------------------------------------------------------------------------------------------------------------------------------------------------

namespace DarkWynter.Stream
{
    #region Using Statements
    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using System.Xml;
    using Xclna.Xna.Animation.Content;
    using Xclna.Xna.Animation;
    #endregion

    /// <summary>
    /// Vertex data containing Position and Normal info
    /// </summary>
    public struct VertexPositionNormal
    {
        /// <summary>
        /// Position
        /// </summary>
        public Vector3 Position;
        /// <summary>
        /// Normal
        /// </summary>
        public Vector3 Normal;
    }

    /// <summary>
    /// Used to pass instance data to shaders through existing gpu pipelines.
    ///
    /// World matrix values are stored  as follows:
    /// Fog         = World.M11, World.M12, World.M13, World.M14
    /// Binormal    = World.M21, World.M22, World.M23, World.M24
    /// Tangent     = World.M31, World.M32, World.M33, World.M34
    /// Depth       = World.M41, World.M42, World.M43, World.M44
    /// </summary>
    public struct VertexFogBinormalTangentDepth
    {
        /// <summary>
        /// World.M11, World.M12, World.M13, World.M14
        /// </summary>
        public Vector4 Fog;
        /// <summary>
        /// World.M21, World.M22, World.M23, World.M24
        /// </summary>
        public Vector4 Binormal;
        /// <summary>
        /// World.M31, World.M32, World.M33, World.M34
        /// </summary>
        public Vector4 Tangent;
        /// <summary>
        /// World.M41, World.M42, World.M43, World.M44
        /// </summary>
        public Vector4 Depth;
    }

    /// <summary>
    /// Stores all the draw function calls for game objects
    /// </summary>
    public class Draw
    {
        private Model _model;
        private ModelAnimator _animator;
        private Matrix _matrix;
        private List<Texture2D> _textureList = new List<Texture2D>();
        private Matrix _initialTransform = Matrix.Identity;

        private VertexBuffer _vertexBuffer;
        private VertexDeclaration _vertexDeclaration;
        private VertexFogBinormalTangentDepth[] _instanceDataFBTD;

        private Enums_Stream.DrawMethod _drawMethod;
        private string _technique;

        /// <summary>
        /// Model used by this GameObject.
        /// </summary>
        public Model model { get { return _model; } set { _model = value; } }
        /// <summary>
        /// Model animator used by this GameObject.
        /// </summary>
        public ModelAnimator animator { get { return _animator; } set { _animator = value; } }
        /// <summary>
        /// Location and orientation matrix passed to shaders.
        /// </summary>
        public Matrix matrix { get { return _matrix; } set { _matrix = value; } }
        /// <summary>
        /// Textures associated with this object.
        /// </summary>
        public List<Texture2D> textureList { get { return _textureList; } set { _textureList = value; } }
        /// <summary>
        /// Needed to fix the rotation caused to animated models
        /// </summary>
        public Matrix initialTransform { get { return _initialTransform; } set { _initialTransform = value; } }

        /// <summary>
        /// Buffer to store vertices
        /// </summary>
        public VertexBuffer vertexBuffer { get { return _vertexBuffer; } set { _vertexBuffer = value; } }
        /// <summary>
        /// Vertex Declaration.
        /// </summary>
        public VertexDeclaration vertexDeclaration { get { return _vertexDeclaration; } set { _vertexDeclaration = value; } }
        /// <summary>
        /// Instance data (Fog, Binormal, Tangent, Depth)
        /// Used for GameObject instancing
        /// </summary>
        public VertexFogBinormalTangentDepth[] instanceDataFBTD { get {  return _instanceDataFBTD; } set { _instanceDataFBTD = value; } }
        /// <summary>
        /// Usage of the draw object
        /// </summary>
        public Enums_Stream.DrawMethod drawMethod { get { return _drawMethod; } set { _drawMethod = value; } }
        /// <summary>
        /// Shader technique to use
        /// </summary>
        public string technique { get { return _technique; } set { _technique = value; } }


        private GraphicsDevice gd;

        #region Stream formats for instancing
        /// <summary>
        /// Tells the GPU how to format the streams for instancing
        /// Uses the Fog, Binormal, Tangent and Depth fields of the stream
        /// </summary>
        public VertexElement[] FBTDElements = new VertexElement[]
        {
            new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position,  0),
            new VertexElement(0, sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal,  0),
            new VertexElement(0, sizeof(float) * 6, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0),
            new VertexElement(1, 0, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1),
            new VertexElement(1, sizeof(float) * 4, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 2),
            new VertexElement(1, sizeof(float) * 8, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 3),
            new VertexElement(1, sizeof(float) * 12, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 4),
        };

        /// <summary>
        /// Tells the GPU how to format the streams for instancing
        /// Used for instanced animation
        /// </summary>
        public VertexElement[] InstAnimElements = new VertexElement[]
        {
            new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position,  0),
            new VertexElement(0, sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal,  0),
            new VertexElement(0, sizeof(float) * 6, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0),
            new VertexElement(0, sizeof(float) * 8, VertexElementFormat.Byte4, VertexElementMethod.Default, VertexElementUsage.BlendIndices, 0),
            new VertexElement(0, sizeof(float) * 9, VertexElementFormat.Color, VertexElementMethod.Default, VertexElementUsage.BlendWeight, 0),
            new VertexElement(1, 0, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1),
            new VertexElement(1, sizeof(float) * 4, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 2),
            new VertexElement(1, sizeof(float) * 8, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 3),
            new VertexElement(1, sizeof(float) * 12, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 4),
        };
        #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        public Draw()
        {
            gd = Statics_Stream.RenderSettings.graphics.GraphicsDevice;
        }


        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="method">The draw method to use</param>
        /// <param name="drawTechnique">Technique to use</param>
        public Draw(Enums_Stream.DrawMethod method, string drawTechnique)
        {
            gd = Statics_Stream.RenderSettings.graphics.GraphicsDevice;
            drawMethod = method;
            technique = drawTechnique;
        }

        /// <summary>
        /// Base draw function which calls the required functions depending on the draw method of this object
        /// </summary>
        public void DoDraw()
        {
            switch (drawMethod)
            {
                case Enums_Stream.DrawMethod.Terrain_Draw:
                    {
                        DrawTerrain();
                        break;
                    }
                case Enums_Stream.DrawMethod.BasicGameObject_Draw:
                    {
                        DrawBasicGO();
                        break;
                    }
                case Enums_Stream.DrawMethod.PropList_Draw:
                    {
                        DrawPropList();
                        break;
                    }
                case Enums_Stream.DrawMethod.PropListAnimated_Draw:
                    {
                        DrawAnimatedPropList();
                        break;
                    }
                case Enums_Stream.DrawMethod.BillBoards_Draw:
                    {
                        DrawBillBoards();
                        break;
                    }
                case Enums_Stream.DrawMethod.GPUObjectList_Draw:
                    {
                        DrawGPUObjectList();
                        break;
                    }
                case Enums_Stream.DrawMethod.Shield_Draw:
                    {
                        DrawShield();
                        break;
                    }
                case Enums_Stream.DrawMethod.Animated_Draw:
                    {
                        DrawAnimation();
                        break;
                    }
            }
        }

        private void DrawTerrain()
        {
            vertexBuffer = new VertexBuffer(gd,
                                     typeof(VertexFogBinormalTangentDepth),
                                     instanceDataFBTD.Length,
                                     BufferUsage.WriteOnly);
            
            vertexBuffer.SetData<VertexFogBinormalTangentDepth>(instanceDataFBTD);

            // Set the vertex declaration
            gd.VertexDeclaration = vertexDeclaration;
            gd.Vertices[0].SetFrequencyOfIndexData(instanceDataFBTD.Length);

            // Tell the GPU how many times to run through the instance data and set the stream.
            gd.Vertices[1].SetSource(vertexBuffer, 0, gd.VertexDeclaration.GetVertexStrideSize(1));
            gd.Vertices[1].SetFrequencyOfInstanceData(1);

            InstancedTriStrips();
        }

        private void DrawBasicGO()
        {
            ShaderParameters.DrawFX.World.SetValue(matrix);

            ShaderParameters.DrawFX.modelTexture1.SetValue(textureList[0]);
            ShaderParameters.DrawFX.bumpTexture1.SetValue(textureList[1]);

            // Draw the model
            TriStrips();
        }

        private void DrawPropList()
        {
            ShaderParameters.DrawFX.modelTexture1.SetValue(textureList[0]);
            ShaderParameters.DrawFX.bumpTexture1.SetValue(textureList[1]);

            // Set the vertex declaration
            gd.VertexDeclaration = vertexDeclaration;
            gd.Vertices[0].SetFrequencyOfIndexData(instanceDataFBTD.Length);

            // Tell the GPU how many times to run through the instance data and set the stream.
            gd.Vertices[1].SetSource(vertexBuffer, 0, gd.VertexDeclaration.GetVertexStrideSize(1));
            gd.Vertices[1].SetFrequencyOfInstanceData(1);

            InstancedTriStrips();
        }

        private void DrawAnimatedPropList()
        {
            ShaderParameters.DrawFX.modelTexture1.SetValue(textureList[0]);
            ShaderParameters.DrawFX.bumpTexture1.SetValue(textureList[1]);

            // Set the vertex declaration
            gd.VertexDeclaration = vertexDeclaration;
            gd.Vertices[0].SetFrequencyOfIndexData(instanceDataFBTD.Length);

            // Tell the GPU how many times to run through the instance data and set the stream.
            gd.Vertices[1].SetSource(vertexBuffer, 0, gd.VertexDeclaration.GetVertexStrideSize(1));
            gd.Vertices[1].SetFrequencyOfInstanceData(1);

            animator.world = matrix;
            animator.Update();
            
            InstancedAnimation();
        }

        private void DrawBillBoards()
        {
            // Set Shader Params
            ShaderParameters.DrawFX.World.SetValue(matrix);

            // Set Textures
            ShaderParameters.DrawFX.modelTexture1.SetValue(textureList[0]);
            //ShaderParameters.DrawFX.bumpTexture1.SetValue(billboardList[i].texture);

            ShaderParameters.DrawFX.ViewProj.SetValue(Statics_Stream.RenderSettings.matrixView * Statics_Stream.RenderSettings.matrixProjection);

            TriStrips();
        }

        private void DrawGPUObjectList()
        {
            ShaderParameters.DrawFX.modelTexture1.SetValue(textureList[0]);
            ShaderParameters.DrawFX.bumpTexture1.SetValue(textureList[1]);
            
            // Set the vertex declaration
            gd.VertexDeclaration = vertexDeclaration;
            gd.Vertices[0].SetFrequencyOfIndexData(instanceDataFBTD.Length);

            // Tell GPU how many times to run through the instance data and set the stream.
            gd.Vertices[1].SetSource(vertexBuffer, 0, gd.VertexDeclaration.GetVertexStrideSize(1));
            gd.Vertices[1].SetFrequencyOfInstanceData(1);

            // Draw the model
            InstancedTriStrips();
        }

        private void DrawShield()
        {
            ShaderParameters.DrawFX.World.SetValue(matrix);

            TriStrips();
        }

        private void DrawAnimation()
        {
            ShaderParameters.DrawFX.World.SetValue(matrix);

            ShaderParameters.DrawFX.modelTexture1.SetValue(textureList[0]);
            ShaderParameters.DrawFX.bumpTexture1.SetValue(textureList[1]);

            animator.world = matrix;
            animator.Update();

            Animation();
        }

        /// <summary>
        /// Draws a stand alone model with no instancing or triangle stripping.
        /// </summary>
        private void TriList()
        {
            foreach (ModelMesh mesh in model.Meshes)
            {
                gd.Indices = mesh.IndexBuffer;

                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    gd.VertexDeclaration = part.VertexDeclaration;
                    gd.Vertices[0].SetSource(mesh.VertexBuffer, part.StreamOffset, part.VertexStride);

                    part.Effect.CurrentTechnique = part.Effect.Techniques[technique];
                    part.Effect.Begin();
                    for (int k = 0; k < part.Effect.CurrentTechnique.Passes.Count; k++)
                    {
                        EffectPass pass = part.Effect.CurrentTechnique.Passes[k];
                        pass.Begin();

                        gd.DrawIndexedPrimitives(PrimitiveType.TriangleList,
                                                 part.BaseVertex,
                                                 0,
                                                 part.NumVertices,
                                                 part.StartIndex,
                                                 part.PrimitiveCount);
                        pass.End();
                    }
                    part.Effect.End();
                }
            }
        }

        /// <summary>
        /// Draws a stand alone model that uses the triangle-strip content processor for loading.
        /// </summary>
        private void TriStrips()
        {
            foreach (ModelMesh mesh in model.Meshes)
            {
                gd.Indices = mesh.IndexBuffer;

                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    part.Effect.CurrentTechnique = part.Effect.Techniques[technique];

                    gd.VertexDeclaration = part.VertexDeclaration;
                    gd.Vertices[0].SetSource(mesh.VertexBuffer, part.StreamOffset, part.VertexStride);

                    int numPrimitives;
                    if (mesh.IndexBuffer.IndexElementSize == IndexElementSize.SixteenBits)
                    {
                        numPrimitives = mesh.IndexBuffer.SizeInBytes / sizeof(ushort);
                    }
                    else
                    {
                        numPrimitives = mesh.IndexBuffer.SizeInBytes / sizeof(int);
                    }
                    numPrimitives -= 2;

                    part.Effect.Begin();
                    foreach (EffectPass pass in part.Effect.CurrentTechnique.Passes)
                    {
                        pass.Begin();

                        gd.DrawIndexedPrimitives(PrimitiveType.TriangleStrip,
                                                 part.BaseVertex,
                                                 0,
                                                 part.NumVertices,
                                                 part.StartIndex,
                                                 numPrimitives);
                        pass.End();
                    }
                    part.Effect.End();
                }
            }
        }

        /// <summary>
        /// Draws an instanced model that uses the standard content processor.
        /// </summary>
        private void InstancedTriList()
        {
            foreach (ModelMesh mesh in model.Meshes)
            {
                // Set the index buffer
                gd.Indices = mesh.IndexBuffer;

                // Tell the GPU how many times to run through the vertex data and set the stream.
                gd.Vertices[0].SetSource(mesh.VertexBuffer, 0, gd.VertexDeclaration.GetVertexStrideSize(0));

                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    part.Effect.CurrentTechnique = part.Effect.Techniques[technique];
                    part.Effect.Begin();

                    for (int k = 0; k < part.Effect.CurrentTechnique.Passes.Count; k++)
                    {
                        EffectPass pass = part.Effect.CurrentTechnique.Passes[k];
                        pass.Begin();

                        gd.DrawIndexedPrimitives(PrimitiveType.TriangleList,
                                                 part.BaseVertex,
                                                 0,
                                                 part.NumVertices,
                                                 part.StartIndex,
                                                 part.PrimitiveCount);
                        pass.End();
                    }
                    part.Effect.End();
                }
            }
        }

        /// <summary>
        /// Draws an instanced model that uses the triangle-strip content processor.
        /// </summary>
        private void InstancedTriStrips()
        {
            foreach (ModelMesh mesh in model.Meshes)
            {
                // Set the index buffer
                gd.Indices = mesh.IndexBuffer;

                int numPrimitives;
                if (mesh.IndexBuffer.IndexElementSize == IndexElementSize.SixteenBits)
                {
                    numPrimitives = mesh.IndexBuffer.SizeInBytes / sizeof(ushort);
                }
                else
                {
                    numPrimitives = mesh.IndexBuffer.SizeInBytes / sizeof(int);
                }
                numPrimitives -= 2;

                // Tell the GPU how many times to run through the vertex data and set the stream.
                gd.Vertices[0].SetSource(mesh.VertexBuffer, 0, gd.VertexDeclaration.GetVertexStrideSize(0));

                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    part.Effect.CurrentTechnique = part.Effect.Techniques[technique];
                    part.Effect.Begin();

                    for (int k = 0; k < part.Effect.CurrentTechnique.Passes.Count; k++)
                    {
                        EffectPass pass = part.Effect.CurrentTechnique.Passes[k];
                        pass.Begin();

                        gd.RenderState.FillMode = Stream.Statics_Stream.RenderSettings.fillMode;

                        gd.DrawIndexedPrimitives(PrimitiveType.TriangleStrip,
                                                 part.BaseVertex,
                                                 0,
                                                 part.NumVertices,
                                                 part.StartIndex,
                                                 numPrimitives);
                        pass.End();
                    }
                    part.Effect.End();
                }
            }
        }

        /// <summary>
        /// Draw the animated player model
        /// </summary>
        private void Animation()
        {
            try
            {
                int index = 0;
                // Update all the effects with the palette and world and draw the meshes
                for (int i = 0; i < animator.numMeshes; i++)
                {
                    // Get the mesh
                    ModelMesh mesh = model.Meshes[i];

                    // The starting index for the modelEffects array
                    int effectStartIndex = index;

                    if (animator.palette[i] != null && animator.matrixPaletteParams[index] != null)
                    {
                        foreach (Effect effect in mesh.Effects)
                        {
                            animator.worldParams[index].SetValue(matrix);
                            animator.matrixPaletteParams[index].SetValue(animator.palette[i]);
                            index++;
                        }
                    }
                    else
                    {
                        foreach (Effect effect in mesh.Effects)
                        {
                            animator.worldParams[index].SetValue(animator.pose[mesh.ParentBone.Index] * matrix);
                            index++;
                        }
                    }

                    int numParts = mesh.MeshParts.Count;
                    gd.Indices = mesh.IndexBuffer;
                    for (int j = 0; j < numParts; j++)
                    {
                        ModelMeshPart currentPart = mesh.MeshParts[j];
                        if (currentPart.NumVertices == 0 || currentPart.PrimitiveCount == 0)
                            continue;
                        Effect currentEffect = animator.modelEffects[effectStartIndex + j];

                        currentEffect.CurrentTechnique = currentEffect.Techniques[technique];

                        gd.VertexDeclaration = currentPart.VertexDeclaration;
                        gd.Vertices[0].SetSource(mesh.VertexBuffer, currentPart.StreamOffset, currentPart.VertexStride);

                        currentEffect.Begin();
                        EffectPassCollection passes = currentEffect.CurrentTechnique.Passes;
                        int numPasses = passes.Count;
                        for (int k = 0; k < numPasses; k++)
                        {
                            EffectPass pass = passes[k];
                            pass.Begin();
                            gd.DrawIndexedPrimitives(PrimitiveType.TriangleList, currentPart.BaseVertex,
                                0, currentPart.NumVertices, currentPart.StartIndex, currentPart.PrimitiveCount);
                            pass.End();
                        }

                        currentEffect.End();
                    }
                }
            }
            catch (NullReferenceException)
            {
                throw new InvalidOperationException("The effects on the model for a " +
                    "ModelAnimator were changed without calling ModelAnimator.InitializeEffectParams().");
            }
            catch (InvalidCastException)
            {
                throw new InvalidCastException("ModelAnimator has thrown an InvalidCastException.  This is " +
                    "likely because the model uses too many bones for the matrix palette.  The default palette size "
                    + "is 56 for windows and 40 for Xbox.");
            }
        }

        /// <summary>
        /// Draw the instanced animated model
        /// </summary>
        private void InstancedAnimation()
        {
            try
            {
                int index = 0;
                // Update all the effects with the palette and world and draw the meshes
                for (int i = 0; i < animator.numMeshes; i++)
                {
                    // Get the mesh
                    ModelMesh mesh = model.Meshes[i];

                    // The starting index for the modelEffects array
                    int effectStartIndex = index;

                    if (animator.palette[i] != null && animator.matrixPaletteParams[index] != null)
                    {
                        foreach (Effect effect in mesh.Effects)
                        {
                            animator.worldParams[index].SetValue(matrix);
                            animator.matrixPaletteParams[index].SetValue(animator.palette[i]);
                            index++;
                        }
                    }
                    else
                    {
                        foreach (Effect effect in mesh.Effects)
                        {
                            animator.worldParams[index].SetValue(animator.pose[mesh.ParentBone.Index] * matrix);
                            index++;
                        }
                    }

                    int numParts = mesh.MeshParts.Count;
                    gd.Indices = mesh.IndexBuffer;
                    for (int j = 0; j < numParts; j++)
                    {
                        ModelMeshPart currentPart = mesh.MeshParts[j];
                        if (currentPart.NumVertices == 0 || currentPart.PrimitiveCount == 0)
                            continue;
                        Effect currentEffect = animator.modelEffects[effectStartIndex + j];

                        currentEffect.CurrentTechnique = currentEffect.Techniques[technique];

                        //device.VertexDeclaration = currentPart.VertexDeclaration;
                        gd.Vertices[0].SetSource(mesh.VertexBuffer, currentPart.StreamOffset, gd.VertexDeclaration.GetVertexStrideSize(0));

                        currentEffect.Begin();
                        EffectPassCollection passes = currentEffect.CurrentTechnique.Passes;
                        int numPasses = passes.Count;
                        for (int k = 0; k < numPasses; k++)
                        {
                            EffectPass pass = passes[k];
                            pass.Begin();
                            gd.DrawIndexedPrimitives(PrimitiveType.TriangleList, currentPart.BaseVertex,
                                0, currentPart.NumVertices, currentPart.StartIndex, currentPart.PrimitiveCount);
                            pass.End();
                        }

                        currentEffect.End();
                    }
                }
            }
            catch (NullReferenceException)
            {
                throw new InvalidOperationException("The effects on the model for a " +
                    "ModelAnimator were changed without calling ModelAnimator.InitializeEffectParams().");
            }
            catch (InvalidCastException)
            {
                throw new InvalidCastException("ModelAnimator has thrown an InvalidCastException.  This is " +
                    "likely because the model uses too many bones for the matrix palette.  The default palette size "
                    + "is 56 for windows and 40 for Xbox.");
            }
        }
    }
}