using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace VisiMath.Geometry
{
    /// <summary>
    /// Indexed mesh builder
    /// </summary>
    public class IndexedMeshBuilder<T>
        where T : IVertex
    {
        /// <summary>
        /// Vertices list
        /// </summary>
        protected VertexList<T> Vertices;
        /// <summary>
        /// Indices list
        /// </summary>
        protected List<List<int>> Indices;

        int SubsetSize;
        Type MyVertexType;
        
        /// <summary>
        /// Creates new mesh builder
        /// </summary>
        /// <param name="startvertexcount">Initial capacity for vertices</param>
        /// <param name="startindexblocks">Initial capacity for subsets</param>
        /// <param name="startindexsize">Initial capacity for index subset size</param>
        public IndexedMeshBuilder(int startvertexcount = 0, int startindexblocks = 0, int startindexsize = 0)
        {
            Vertices = new VertexList<T>(startvertexcount);
            Indices = new List<List<int>>(startindexblocks);
            Indices.Add(new List<int>(startindexsize));
            SubsetSize = startindexsize;
            MyVertexType = typeof(T);
        }


        /// <summary>
        /// Adds triangle mesh to builder
        /// </summary>
        /// <param name="tlist">Triangle list</param>
        /// <param name="trans">Optional transformation used to modify all vertices in mesh</param>
        /// <param name="indexblock">Indices index to save into. If -1, new indexset will be created (for each of source subsets, if using all) </param>
        /// <param name="currentmeshsubset">Use only current mesh subset?</param>
        public void Add(TriangleList tlist, Transformation trans = null, bool currentmeshsubset = true, int indexblock = 0)
        {
            Type sourcetype = tlist.Vertices[0].GetType();

            IVertexArray varray;

            if ((sourcetype == MyVertexType) && (trans == null))
            {
                varray = tlist.Vertices;
            }
            else
            {
                VertexFactory.CopyVertexArray(MyVertexType, tlist.Vertices, out varray);
            }

            if (trans != null)
            {
                trans.TransMatrix.TransformFast(varray);
            }

            int IndexBase = Vertices.Count;

            Vertices.AddArray(varray);

            int[] indices = new int[tlist.Count];

            for (int i = 0; i < indices.Length; i++)
            {
                indices[i] = i;
            }

            AddIndices(IndexBase, indices, indexblock);
        
        }

        /// <summary>
        /// Adds mesh to builder
        /// </summary>
        /// <param name="mesh">Mesh</param>
        /// <param name="trans">Optional transformation used to modify all vertices in mesh</param>
        /// <param name="indexblock">Indices index to save into. If -1, new indexset will be created (for each of source subsets, if using all) </param>
        /// <param name="currentmeshsubset">Use only current mesh subset?</param>
        public void Add(IndexedMesh mesh, Transformation trans = null, bool currentmeshsubset = true, int indexblock = 0)
        {            
            Type sourcetype = mesh.Vertices[0].GetType();

            IVertexArray varray;

            if ((sourcetype == MyVertexType) && (trans == null))
            {
                varray = mesh.Vertices;
            }
            else
            {
                VertexFactory.CopyVertexArray(MyVertexType, mesh.Vertices, out varray);
            }



            if (trans != null)
            {
                trans.TransMatrix.TransformFast(varray);
            }

            int IndexBase = Vertices.Count;

            Vertices.AddArray(varray);

            if (currentmeshsubset)
            {
                AddIndices(IndexBase, mesh.CurrentIndices, indexblock);
            }
            else
            {
                for (int i = 0; i < mesh.Indices.Length; i++)
                {
                    AddIndices(IndexBase, mesh.Indices[i], indexblock);
                }
            }
        }

        /// <summary>
        /// Adds indices
        /// </summary>
        /// <param name="indexbase">Index base modifier</param>
        /// <param name="indices">Indices array</param>
        /// <param name="indexblock">Indices index to save into. If -1, new indexset will be created (for each of source subsets, if using all) </param>
        public void AddIndices(int indexbase, int[] indices, int indexblock)
        {
            if (indexblock < 0)
            {
                Indices.Add(new List<int>(indices.Length));
                indexblock = Indices.Count-1;
            }
            else
            {
                while (Indices.Count <= indexblock)
                    Indices.Add(new List<int>(SubsetSize));
            }

            List<int> target = Indices[indexblock];

            if (target.Capacity < target.Count + indices.Length)
            {
                target.Capacity = target.Count + indices.Length;
            }
            for (int i = 0; i < indices.Length; i++)
            {
                target.Add(indices[i] + indexbase);
            }            
        }

        /// <summary>
        /// Converts to final mesh
        /// </summary>
        /// <returns></returns>
        public IndexedMesh GetMesh()
        {
            IVertexArray<T> verts = Vertices.ToVertexArray();
            
            int[][] inds = new int[Indices.Count][];

            for (int i = 0; i < Indices.Count; i++)
            {
                inds[i] = Indices[i].ToArray();
            }

            return new IndexedMesh(verts, inds);
        }
    }
}
