//BSD License  
//DXGfx® - http://www.eteractions.com
//Copyright (c) 2005
//by Guillaume Randon
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software
//and associated documentation files (the "Software"), to deal in the Software without restriction,
//including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
//and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//The above copyright notice and this permission notice shall be included in all copies or substantial
//portions of the Software.
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
//INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
//AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
//DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Microsoft.Samples.DirectX.UtilityToolkit;
using System.Drawing;

namespace DXGfxLib
{
    /// <summary>
    /// This structure is meant to be used to store information about position and rotation of one instance of the geometry
    /// we want to render. It is anticipated that this structure will be used for geometries on the groung thus the only
    /// rotation information in that structure is the rotation around the Y axis.
    /// In fact this structure is not used at the moment.
    /// </summary>
    public struct InstanceInfo
    {
        public Vector4 positionRot;
    }

    /// <summary>
    /// The batch class. This class can be useful if we have many instances of a geometry with a small number of faces. In that
    /// specific situation you can make a batch out of these geometries. They will be pre transform and but in one big mesh.
    /// At the moment you should use geometries with only one subset, if you have several materials and subsets in your mesh
    /// all vertices that will be put in the big mesh will be rendered with first material and texture as if they were all
    /// part of subset 0.
    /// During my testing I have attempted to use this class for the tress of the demo. I was doing these tests with trees of
    /// 1600 faces (quite a lot for a tree but good for testing :)) this did not give good results. In fact it was much better
    /// to have each tree as a separate SceneObject. This might change based on which machine you run the program on but there will
    /// always be some tradeoff to make. At the moment the limit for the big buffer is 5000. Maybe that's still too high.
    /// 
    /// I wanted to test hardware instanciating from the class but I add no time to do that so far. THis is a potential TODO.
    /// </summary>
    public class Batch : IDisposable, IDrawable
    {
        /// <summary>
        /// Max number of faces in our big mesh. If we have too many instances to reside in one big mesh we do in fact
        /// create several big buffers.
        /// </summary>
        private int maxNumberFaces = 5000;

        /// <summary>
        /// The associated effect group.
        /// </summary>
        private EffectGroup associatedEffetGroup = null;

        /// <summary>
        /// The associated effect group accessors.
        /// </summary>
        public EffectGroup AssociatedEffectGroup
        {
            get { return associatedEffetGroup; }
            set { associatedEffetGroup = value; }
        }

        /// <summary>
        /// A boolean which allow use to specific if the SceneObject should be displayed or not.
        /// </summary>
        protected bool hide = false;

        /// <summary>
        /// Accessor for hide boolean. When true the scene  object won't be rendered.
        /// </summary>
        public bool Hide
        {
            get { return hide; }
            set { hide = value; }
        }

        /// <summary>
        /// Our big mesh materials. Only the first one is used at the moment. AttributesId are not correctly populated in the big
        /// mesh. This is a potential TODO.
        /// </summary>
        public Material[] meshMaterials; // Materials for our mesh

        /// <summary>
        /// These are our big mesh textures.
        /// </summary>
        public Texture[] meshTextures; // Textures for our mesh

        /// <summary>
        /// Our big meshes (in case we need several because there are too many instances to fit in one single big mesh).
        /// </summary>
        public Mesh[] meshes = null;

        /// <summary>
        /// The initial geometry that we want to display in several locations.
        /// </summary>
        private Mesh initialMesh = null;

        /// <summary>
        /// The world matrices associated with our instances.
        /// </summary>
        private Matrix[] worldMatrices = null;

        /// <summary>
        /// Our default constructor. Before calling this constructor we need to have initialized DXGfxManager and to have
        /// passed a valid D3D device to it.
        /// </summary>
        public Batch()
        {
            associatedEffetGroup = DXGfxManager.GetGlobalInstance().defaultEffectGroup;
        }

        /// <summary>
        /// The usual Dispose method. We need to dispose our meshes and textures.
        /// </summary>
        public void Dispose()
        {
            if (meshes != null)
            {
                foreach (Mesh mesh in meshes)
                {
                    mesh.Dispose();
                }
            }
            if (meshTextures != null)
            {
                foreach(Texture tex in meshTextures)
                {
                    tex.Dispose();
                }
            }
        }

        /// <summary>
        /// All classes implementing IDrawable interface have this. In fact this method is called during rendering to leave an
        /// opportunity to each class implementing that interface to pass values to the effect that will be used for rendering.
        /// This is quite important as a single effect can be used for a lot of different geometries but several parameters are
        /// bound to vary. In case we have vertex shader the world mat will change... textures to feed to the various texture
        /// stages are also going to change...
        /// </summary>
        /// <param name="d3ddevice"></param>
        /// <param name="eff"></param>
        /// <param name="pass"></param>
        public void SetEffectsValues(Device d3ddevice, Effect eff, int pass)
        {
            d3ddevice.SetTransform(TransformType.World, Matrix.Identity);
        }


        /// <summary>
        /// The name says it all. Don't be lured by the method code. In fact when we build meshes attributesId are lost and thus
        /// all vertices appears as part of subset 0.
        /// </summary>
        /// <param name="d3ddevice"></param>
        public void Draw(Device d3ddevice)
        {
            // Meshes are divided into subsets, one for each material. Render them in
            // a loop
            for (int i = 0; i < meshMaterials.Length; i++)
            {
                // Set the material and texture for this subset
                d3ddevice.Material = meshMaterials[i];
                d3ddevice.SetTexture(0, meshTextures[i]);

                // Draw the mesh subset
                foreach (Mesh mesh in meshes)
                {
                    mesh.DrawSubset(i);
                }
            }
        }

        /// <summary>
        /// This method is called to prepare the Batch for rendering. You pass the mesh for the geometry you want to draw and
        /// you pass in your materials, textures and matrices. The only scope of this method is to determine how many instances
        /// can be put per BigMesh. The rest of the work is performed in BuildOneBigMeshForStaticInstanciating.
        /// </summary>
        /// <param name="initialMesh"></param>
        /// <param name="materials"></param>
        /// <param name="textures"></param>
        /// <param name="matrices"></param>
        public void Populate(Mesh initialMesh, Material[] materials, Texture[] textures, Matrix[] matrices)
        {
            this.initialMesh = initialMesh.Clone(initialMesh.Options.Value, CustomVertex.PositionNormalTextured.Format, initialMesh.Device);
            this.worldMatrices = matrices;

            meshMaterials = materials;
            meshTextures = textures;

            int max = Math.Max(initialMesh.NumberFaces, initialMesh.NumberVertices);
            // Cannot create meshes with more than 65534 faces or vertices (check doc).
            int maxInstanceInOneMesh = (int)Math.Floor((double)(maxNumberFaces / max));

            if (matrices.Length > maxInstanceInOneMesh)
            {
                int nbIter = (int)Math.Ceiling((double)(matrices.Length / maxInstanceInOneMesh));

                meshes = new Mesh[nbIter];

                for (int i = 0; i < nbIter; i++)
                {
                    int start = Math.Min(i * maxInstanceInOneMesh, matrices.Length);
                    int end = Math.Min((i + 1) * maxInstanceInOneMesh, matrices.Length);

                    meshes[i] = BuildOneBigMeshForStaticInstanciating(worldMatrices, start, end);
                }
            }
            else
            {
                meshes = new Mesh[1];
                meshes[0] = BuildOneBigMeshForStaticInstanciating(worldMatrices, 0, matrices.Length);
            }
        }

        /// <summary>
        /// Called from Populate. This method do perform the real job of pre transforming all the vertices and feeding them
        /// in the BigMesh. For pre transforming the vertices we call the Transform method of this class.
        /// This method will be called one time per BigMesh we are going to create (obviously with different
        /// parameters each time).
        /// </summary>
        /// <param name="worldMatrices"></param>
        /// <param name="startIndex"></param>
        /// <param name="endIndex"></param>
        /// <returns></returns>
        public Mesh BuildOneBigMeshForStaticInstanciating(Matrix[] worldMatrices, int startIndex, int endIndex)
        {
            if (initialMesh == null)
            {
                throw new Exception("We need a valid initial mesh, please define one before calling this method!");
            }

            int nbInstance = endIndex - startIndex;
            Mesh mesh = new Mesh(nbInstance * initialMesh.NumberFaces, nbInstance * initialMesh.NumberVertices, initialMesh.Options.Value,
                CustomVertex.PositionNormalTextured.Format, initialMesh.Device);

            CustomVertex.PositionNormalTextured[] initialVertices = (CustomVertex.PositionNormalTextured[])initialMesh.LockVertexBuffer(typeof(CustomVertex.PositionNormalTextured),
                LockFlags.ReadOnly, initialMesh.NumberVertices);

            GraphicsStream stm = mesh.LockVertexBuffer(LockFlags.NoSystemLock);

            Int16[] initialIndices = (Int16[])initialMesh.LockIndexBuffer(typeof(Int16), LockFlags.ReadOnly, 3 * initialMesh.NumberFaces);

            GraphicsStream stmi = mesh.LockIndexBuffer(LockFlags.None);

            int count = 0;
            for (int i = startIndex; i < endIndex; i++)
            {
                Matrix mat = worldMatrices[i];
                CustomVertex.PositionNormalTextured[] instanceVertices = Transform(initialVertices, mat);

                stm.Write(instanceVertices);


                Int16[] instanceIndices = AddOffset(initialIndices, (Int16)(count * initialVertices.Length));

                stmi.Write(instanceIndices);

                count++;
            }

            initialMesh.UnlockVertexBuffer();
            mesh.UnlockVertexBuffer();

            initialMesh.UnlockIndexBuffer();
            mesh.UnlockIndexBuffer();

            //int[] adjacency = new int[3*initialMesh.NumberFaces];
            //initialMesh.GenerateAdjacency(0.05f, adjacency);
            //mesh.Optimize(MeshFlags.Managed, adjacency);

            mesh.SetAttributeTable(initialMesh.GetAttributeTable());
            int[] initialAttributes = initialMesh.LockAttributeBufferArray(LockFlags.ReadOnly);

            int[] destAttributes = mesh.LockAttributeBufferArray(LockFlags.NoSystemLock);

            count = 0;
            for (int i = startIndex; i < endIndex; i++)
            {
                initialAttributes.CopyTo(destAttributes, count * initialAttributes.Length);
                count++;
            }

            initialMesh.UnlockAttributeBuffer();
            mesh.UnlockAttributeBuffer();

            return mesh;
        }

        /// <summary>
        /// This method only perform the pre transformation of the vertices position in 3D space so that they will be displayed
        /// with the desired position and orientation even if the Draw call with be performed with a world mat different from
        /// what that specific instance world mat would have been. This is needed with batch we render several geometries in one
        /// call so the world mat is the same for all instances. (Note that if one day I have time to add shader instanciating
        /// methods this won't be true anymore as the positions and orientations could be passed as shader constants.
        /// </summary>
        /// <param name="vertices"></param>
        /// <param name="mat"></param>
        /// <returns></returns>
        public CustomVertex.PositionNormalTextured[] Transform(CustomVertex.PositionNormalTextured[] vertices, Matrix mat)
        {
            CustomVertex.PositionNormalTextured[] outVertices = new CustomVertex.PositionNormalTextured[vertices.Length];
            int compteur = 0;

            foreach (CustomVertex.PositionNormalTextured vertex in vertices)
            {
                Vector3 inVec = new Vector3(vertex.X, vertex.Y, vertex.Z);
                Vector3 inNorm = new Vector3(vertex.Nx, vertex.Ny, vertex.Nz);

                inVec.TransformCoordinate(mat);
                inNorm.TransformCoordinate(mat);

                outVertices[compteur].X = inVec.X;
                outVertices[compteur].Y = inVec.Y;
                outVertices[compteur].Z = inVec.Z;

                outVertices[compteur].Nx = inNorm.X;
                outVertices[compteur].Ny = inNorm.Y;
                outVertices[compteur].Nz = inNorm.Z;

                outVertices[compteur].Tu = vertex.Tu;
                outVertices[compteur].Tv = vertex.Tv;
                compteur++;
            }

            return outVertices;
        }

        /// <summary>
        /// Name is pretty explicit. This is simple but needed.
        /// </summary>
        /// <param name="inIndices"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        public Int16[] AddOffset(Int16[] inIndices, int offset)
        {
            Int16[] outIndices = new Int16[inIndices.Length];
            int compteur = 0;

            foreach (Int16 index in inIndices)
            {
                outIndices[compteur] = (Int16)(index + offset);
                compteur++;
            }

            return outIndices;
        }

        /// <summary>
        /// Each scene object has an associated effect. We want SceneObject to implement the CompareTo method, thus when they
        /// will be added to the sorted list of objects to be rendered, objects are going to be ordered based on the effect filename
        /// they use.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public int CompareTo(Object obj)
        {
            int ret = 1;
            IDrawable drawable = obj as IDrawable;

            if (drawable != null)
            {
                ret = AssociatedEffectGroup.CompareTo(drawable.AssociatedEffectGroup);
            }
            return ret;
        }

        /// <summary>
        /// Each scene object has an associated effect. We want SceneObject to implement the CompareTo method, thus when they
        /// will be added to the sorted list of objects to be rendered, objects are going to be ordered based on the effect filename
        /// they use.
        /// </summary>
        /// <param name="sceneObj"></param>
        /// <returns></returns>
        public int CompareTo(SceneObject sceneObj)
        {
            return AssociatedEffectGroup.CompareTo(sceneObj.AssociatedEffectGroup);
        }

        /// <summary>
        /// Each scene object has an associated effect. We want SceneObject to implement the CompareTo method, thus when they
        /// will be added to the sorted list of objects to be rendered, objects are going to be ordered based on the effect filename
        /// they use.
        /// </summary>
        /// <param name="sceneObj"></param>
        /// <returns></returns>
        public int CompareTo(IDrawable sceneObj)
        {
            return AssociatedEffectGroup.CompareTo(sceneObj.AssociatedEffectGroup);
        }
    }
}
