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

namespace VisiMath.Geometry
{
    /// <summary>
    /// Indexed list of quads
    /// </summary>
    public class IndexedQuads : IndexedGeo, ISurfaceGeo, IGeoContainer<Quad>
    {
        /// <summary>
        /// Mesh type - quads
        /// </summary>
        public override PrimitiveTypes MeshType
        {
            get { return PrimitiveTypes.IndexedQuads; }
        }

        /// <summary>
        /// Returns copy of the geometric data
        /// </summary>
        /// <returns>Full copy of the geometry</returns>
        public override IGeometry GetCopy()
        {
            return new IndexedQuads(this);
        }


        /// <summary>
        /// 4 indices per element for quad
        /// </summary>
        public override int IndicesPerElement { get { return 4; } }

        /// <summary>
        /// Standard constructor from XML
        /// </summary>
        /// <param name="reader"></param>
        public IndexedQuads(System.Xml.XmlReader reader) : base(reader)
        {
        }

        /// <summary>
        /// Copy constructor
        /// </summary>
        /// <param name="copy"></param>
        public IndexedQuads(IndexedQuads copy)
            : base(copy)
        {
        }

        /// <summary>
        /// Creates indexed geo with default vertex format, with specified number of vertices, one subset with selected number of indices
        /// </summary>
        /// <param name="vertexcount">Number of vertices</param>
        /// <param name="indexcount">Number of indices</param>
        public IndexedQuads(int vertexcount, int indexcount) : base(vertexcount, indexcount) { }

        /// <summary>
        /// Creates indexed geo with default vertex format, with specified number of vertices, specified number of subset with selected number of indices
        /// </summary>
        /// <param name="vertexcount">Number of vertices</param>
        /// <param name="indexsets">Number of subsets</param>
        /// <param name="indexcounts">Array with length of all subsets</param>
        public IndexedQuads(int vertexcount, int indexsets, int[] indexcounts) : base(vertexcount, indexsets, indexcounts) { }

        /// <summary>
        /// Creates indexed geo of specified format, with specified number of vertices, one subset and specified number of indices
        /// </summary>
        /// <param name="vertextype">Type of vertex (must be IVertex)</param>
        /// <param name="vertexcount">Number of vertices</param>
        /// <param name="indexcount">Number of indices</param>
        public IndexedQuads(Type vertextype, int vertexcount, int indexcount) : base(vertextype, vertexcount, indexcount) { }

        /// <summary>
        /// Creates indexed geo of specified format, with specified number of vertices, specified number of subsets and specified number of indices
        /// </summary>
        /// <param name="vertextype">Type of vertex (must be IVertex)</param>
        /// <param name="vertexcount">Number of vertices</param>
        /// <param name="indexsets">Number of subsets</param>
        /// <param name="indexcounts">Array with length of all subsets</param>
        public IndexedQuads(Type vertextype, int vertexcount, int indexsets, int[] indexcounts) : base(vertextype, vertexcount, indexsets, indexcounts) { }

        /// <summary>
        /// Creates indexed geo from array of vertices one subset and specified number of indices
        /// </summary>
        /// <param name="source">Source vertex array</param>
        /// <param name="indexcount">number of vertices</param>
        public IndexedQuads(IVertexArray source, int indexcount) : base(source, indexcount) { }

        /// <summary>
        /// Creates indexed geo from array of vertices specified number of subsets and specified number of indices
        /// </summary>
        /// <param name="source">Source vertex array</param>
        /// <param name="indexsets">Number of subsets</param>
        /// <param name="indexcounts">Array with length of all subsets</param>
        public IndexedQuads(IVertexArray source, int indexsets, int[] indexcounts) : base(source, indexsets, indexcounts) { }

        /// <summary>
        /// Creates indexed geo from array of vertices specified with one subset of specified indices
        /// </summary>
        /// <param name="source">Source vertex array</param>
        /// <param name="indices">Array with indices</param>
        public IndexedQuads(IVertexArray source, int[] indices) : base(source, indices) { }

        /// <summary>
        /// Creates indexed geo from array of vertices and from indices
        /// </summary>
        /// <param name="source">Source vertex array</param>
        /// <param name="indices">Array with indices</param>
        public IndexedQuads(IVertexArray source, int[][] indices) : base(source, indices) { }


        /// <summary>
        /// Is object geometric renderable primitive from enumaration PrimitiveTypes?
        /// </summary>
        public override bool IsPrimitive
        {
            get { return true; }
        }

        /// <summary>
        /// Preferred primitive type for rendering
        /// </summary>
        public override PrimitiveTypes PreferredPrimitive
        {
            get { return PrimitiveTypes.IndexedQuads; }
        }

        /// <summary>
        /// Quad List works as a list of quads, returns true
        /// </summary>
        public override bool IsContainer
        {
            get { return true; }
        }


        /// <summary>
        /// Convert object to list of points
        /// </summary>
        /// <returns>New geometry object containing point based definition of the object</returns>
        /// <param name="quality">
        /// Quality for parametric objects (0-1). Zero is simples possible, 1 maximal for the current dimension of the object. 
        /// If vertexlimit parameter is used, quality will be reduced to fullfill maximal vertex budget
        /// </param>
        /// <param name="VertexType">Required type of the vertex</param>
        /// <param name="vertexlimit">Maximal number of allowed vertices. If set to zero, there's no limit. 
        /// Used for fully parametric objects or to reduce number of the vertices in complicated objects.</param>
        public override Point3DList ToPoints(Type VertexType, float quality = 0, int vertexlimit = 0)
        {
            return new Point3DList(VertexType, this.Vertices);
        }

        /// <summary>
        /// Convert object to list of lines
        /// </summary>
        /// <returns>New geometry object containing edges of the object</returns>
        /// <param name="quality">
        /// Not used
        /// </param>
        /// <param name="VertexType">Required type of the vertex</param>        
        /// <param name="vertexlimit">Maximal number of allowed vertices. If set to zero, there's no limit. 
        /// Used for fully parametric objects or to reduce number of the vertices in complicated objects.
        /// NOT USED!</param>
        public override LineList ToLines(Type VertexType, float quality = 0, int vertexlimit = 0)
        {
            IVertexArray varray = VertexFactory.CreateArray(VertexType, (Vertices.Count / 3) * 8);

            int targetidx = 0;

            for (int i = 0; i < Count; i++)
            {
                varray[targetidx] = VertexFactory.CopyVertex(GetVertex(i * 4 + 0), VertexType);
                varray[targetidx + 1] = VertexFactory.CopyVertex(GetVertex(i * 4 + 1), VertexType);
                varray[targetidx + 2] = VertexFactory.CopyVertex(GetVertex(i * 4 + 1), VertexType);
                varray[targetidx + 3] = VertexFactory.CopyVertex(GetVertex(i * 4 + 2), VertexType);
                varray[targetidx + 4] = VertexFactory.CopyVertex(GetVertex(i * 4 + 2), VertexType);
                varray[targetidx + 5] = VertexFactory.CopyVertex(GetVertex(i * 4 + 3), VertexType);
                varray[targetidx + 6] = VertexFactory.CopyVertex(GetVertex(i * 4 + 3), VertexType);
                varray[targetidx + 7] = VertexFactory.CopyVertex(GetVertex(i * 4 + 0), VertexType);
                targetidx += 8;
            }
            return new LineList(varray);
        }

        /// <summary>
        /// Convert object to list of triangles - returns self.
        /// </summary>
        /// <returns>New geometry object containing triangle faces of the object surface.</returns>
        /// <param name="quality">
        /// Quality for parametric objects (0-1). Zero is simples possible, 1 maximal for the current dimension of the object. 
        /// If vertexlimit parameter is used, quality will be reduced to fullfill maximal vertex budget
        /// </param>
        /// <param name="VertexType">Required type of the vertex</param>
        /// <param name="vertexlimit">Maximal number of allowed vertices. If set to zero, there's no limit. 
        /// Used for fully parametric objects or to reduce number of the vertices in complicated objects.</param>
        /// <remarks>Triangle list returns pointer to self.</remarks>
        public override TriangleList ToTriangles(Type VertexType, float quality = 0, int vertexlimit = 0)
        {
            IVertexArray varray = VertexFactory.CreateArray(VertexType, 6 * Count);

            int targetidx = 0;

            for (int i = 0; i < Count; i++)
            {
                varray[targetidx] = VertexFactory.CopyVertex(GetVertex(i * 4 + 0), VertexType);
                varray[targetidx + 1] = VertexFactory.CopyVertex(GetVertex(i * 4 + 1), VertexType);
                varray[targetidx + 2] = VertexFactory.CopyVertex(GetVertex(i * 4 + 2), VertexType);

                varray[targetidx + 3] = VertexFactory.CopyVertex(GetVertex(i * 4 + 2), VertexType);
                varray[targetidx + 4] = VertexFactory.CopyVertex(GetVertex(i * 4 + 3), VertexType);
                varray[targetidx + 5] = VertexFactory.CopyVertex(GetVertex(i * 4 + 0), VertexType);

                targetidx += 6;
            }
            return new TriangleList(varray);
        }

        /// <summary>
        /// Convert object to indexed triangle mesh. Valid only for surface based objects, otherwise returns null.
        /// </summary>
        /// <returns>New geometry object containing triangle faces of the object surface</returns>
        /// <param name="quality">
        /// <param name="VertexType">Required type of the vertex</param>
        /// Quality for parametric objects (0-1). Zero is simples possible, 1 maximal for the current dimension of the object. 
        /// If vertexlimit parameter is used, quality will be reduced to fullfill maximal vertex budget
        /// </param>
        /// <param name="vertexlimit">Maximal number of allowed vertices. If set to zero, there's no limit. 
        /// Used for fully parametric objects or to reduce number of the vertices in complicated objects.</param>
        public override IndexedMesh ToIndexedMesh(Type VertexType, float quality = 0, int vertexlimit = 0)
        {
            IVertexArray varray;
            VertexFactory.CopyVertexArray(VertexType, Vertices, out varray);

            int[] indices = new int[Count * 6];
            
            int idx = 0;

            for (int i = 0; i < Count; i++)
            {
                indices[idx + 0] = CurrentIndices[i * 4 + 0];
                indices[idx + 1] = CurrentIndices[i * 4 + 1];
                indices[idx + 2] = CurrentIndices[i * 4 + 2];
                indices[idx + 3] = CurrentIndices[i * 4 + 2];
                indices[idx + 4] = CurrentIndices[i * 4 + 3];
                indices[idx + 5] = CurrentIndices[i * 4 + 0];
                idx += 6;
            }

            return new IndexedMesh(varray, indices);
        }

        /// <summary>
        /// Returns closest distance from the generic geometric object
        /// </summary>
        /// <param name="obj">Object to check distance with</param>
        /// <returns>Closest distance to the object</returns>
        public override float Distance(IGeometry obj)
        {
            return obj.DistanceQuads(this);
        }

        /// <summary>
        /// Checks if specified position is inside the object - point lies on any triangle
        /// </summary>
        /// <param name="pos">Position to check</param>
        /// <returns>True if vector is inside the object</returns>
        public override bool ContainsPosition(Vector3 pos)
        {
            for (int i = 0; i < Count; i++)
            {
                if (MathTools.PointInTriangle(GetPosition(i * 4), GetPosition(i * 4 +1), GetPosition(i * 4 +2), pos))
                    return true;
                if (MathTools.PointInTriangle(GetPosition(i * 4 +2), GetPosition(i * 4 +3), GetPosition(i * 4 +0), pos))
                    return true;
            }
            return false;
        }


        /// <summary>
        /// Check for intersection and fills intersection info if intersection is closer to the origin of the ray then value before
        /// </summary>
        /// <param name="inters">Data structure with intersection info</param>
        /// <param name="obj">Ray object</param>
        /// <returns>True if there was an intersection and it was closer then previous stored</returns>
        public override bool GetIntersectionInfo(Ray obj, ref IIntersectionInfo inters)
        {

            float dist;
            Vector3 ver;
            bool rval = false;

            for (int i = 0; i < Vertices.Count / 3; i++)
            {
                if (MathTools.IntersectionRayTriangle(obj.Origin, obj.Direction, GetPosition(i * 4), GetPosition(i * 4 +1), GetPosition(i * 4 +2), out dist, out ver, false))
                {
                    if (dist < inters.Distance)
                    {
                        inters.Distance = dist;
                        inters.IntersectionPoint = ver;
                        inters.GeoObject = this;
                        rval = true;
                    }
                }
                if (MathTools.IntersectionRayTriangle(obj.Origin, obj.Direction, GetPosition(i * 4 +2), GetPosition(i * 4 +3), GetPosition(i * 4), out dist, out ver, false))
                {
                    if (dist < inters.Distance)
                    {
                        inters.Distance = dist;
                        inters.IntersectionPoint = ver;
                        inters.GeoObject = this;
                        rval = true;
                    }
                }
            }
            return rval;
        }

        /// <summary>
        /// Check for intersection and adds intersection info if there was any intersection
        /// </summary>
        /// <param name="ilist">List for intersections</param>
        /// <param name="obj">Ray object</param>
        /// <returns>True if there was an intersection</returns>        
        /// <remarks>For now using base version and returns only hit of the closest encountered triangle. Might need reworking to check all tris?</remarks>
        public override bool GetIntersectionInfo(Ray obj, IntersectionList ilist)
        {
            return base.GetIntersectionInfo(obj, ilist);
        }


        /// <summary>
        /// Returns closest distance to the ray
        /// </summary>
        /// <param name="ray">Ray to check distance to</param>
        /// <param name="frontOnly">Check only objects in front of the origin</param>
        /// <returns>Closest distance to the ray, if frontOnly and object is behind the ray, returns NaN</returns>
        public override float Distance(Ray ray, bool frontOnly = false)
        {
            float dist = float.MaxValue;
            for (int i = 0; i < Count; i++)
            {
                float newdst = MathTools.DistanceRayTriangle(ray.Origin, ray.Direction, GetPosition(i * 4 +0), GetPosition(i * 4 +1), GetPosition(i * 4 +2), frontOnly);
                if (newdst != float.NaN)
                {
                    if (newdst < dist)
                    {
                        dist = newdst;
                    }
                }
                newdst = MathTools.DistanceRayTriangle(ray.Origin, ray.Direction, GetPosition(i * 4 +2), GetPosition(i * 4 +3), GetPosition(i * 4), frontOnly);
                if (newdst != float.NaN)
                {
                    if (newdst < dist)
                    {
                        dist = newdst;
                    }
                }
            }
            if (dist == float.MaxValue)
                return float.NaN;
            return dist;
        }

        /// <summary>
        /// For generic geometry returns distance from the center of the object
        /// </summary>
        /// <param name="pos"></param>
        /// <returns></returns>
        public override float Distance(ref Vector3 pos)
        {
            float dist = float.MaxValue;
            for (int i = 0; i < Count; i++)
            {
                float newdst = MathTools.DistanceVectorTriangle(GetPosition(i * 4 +0), GetPosition(i * 4 +1), GetPosition(i * 4 +2), pos);
                if (newdst != float.NaN)
                {
                    if (newdst < dist)
                    {
                        dist = newdst;
                    }
                }
                newdst = MathTools.DistanceVectorTriangle(GetPosition(i * 4 +2), GetPosition(i * 4 +3), GetPosition(i * 4 +0), pos);
                if (newdst != float.NaN)
                {
                    if (newdst < dist)
                    {
                        dist = newdst;
                    }
                }
            }
            return dist;
        }

        /// <summary>
        /// Returns closest distance to the line segment. Generic, from center, OVERRIDE!
        /// </summary>
        ///<param name="pos1">Start of the line</param>
        ///<param name="pos2">end of the line</param>
        /// <returns>Closest distance</returns>
        public override float DistanceLineSegment(ref Vector3 pos1, ref Vector3 pos2)
        {
            float dist = float.MaxValue;
            for (int i = 0; i < Count; i++)
            {
                float newdst = MathTools.DistanceLineSegmentQuad(pos1, pos2, GetPosition(i * 3), GetPosition(i * 3 + 1), GetPosition(i * 3 + 2), GetPosition(i * 3 + 3));
                if (newdst < dist)
                {
                    dist = newdst;
                }
            }
            return dist;
        }

        /// <summary>
        /// Returns closest distance to the sphere
        /// </summary>
        /// <param name="center">center of the sphere</param>
        /// <param name="radius">radius of the sphere</param>
        /// <returns>Closest distance</returns>
        public override float DistanceSphere(ref Vector3 center, float radius)
        {
            float dist = float.MaxValue;
            for (int i = 0; i < Count; i++)
            {
                float newdst = MathTools.DistanceSphereQuad(center, radius, GetPosition(i * 3), GetPosition(i * 3 + 1), GetPosition(i * 3 + 2), GetPosition(i * 3 + 3));
                if (newdst < dist)
                {
                    dist = newdst;
                }
            }
            return dist;
        }

        /// <summary>
        /// Returns closest distance to the triangle
        /// </summary>
        /// <param name="pos1">First corner</param>
        /// <param name="pos2">Second corner</param>
        /// <param name="pos3">Third corner</param>
        /// <returns>Distance</returns>
        public override float DistanceTriangle(ref Vector3 pos1, ref Vector3 pos2, ref Vector3 pos3)
        {
            float dist = float.MaxValue;
            for (int i = 0; i < Count; i++)
            {
                float newdst = MathTools.DistanceTriangleQuad(pos1, pos2, pos3, GetPosition(i * 3), GetPosition(i * 3 + 1), GetPosition(i * 3 + 2), GetPosition(i * 3 + 3));
                if (newdst < dist)
                {
                    dist = newdst;
                }
            }
            return dist;
        }

        /// <summary>
        /// Checks for intersection between object and moving sphere
        /// </summary>
        ///<param name="radius">Radius of the sphere</param>
        /// <param name="maxdistance">Maximal distance to travel</param>
        /// <param name="ray">Ray describing movement of the sphere</param>
        /// <returns>Distance to the intersection or -1 if no intersection exists</returns>
        public override float IntersectsMoving(float radius, Ray ray, float maxdistance)
        {
            Vector3 targ = Vector3.MoveInDirection(ref ray.Origin, ref ray.Direction, maxdistance);
            float t;

            float mindist = float.MaxValue;

            for (int i = 0; i < Count; i++)
            {
                Vector3 me0 = GetPosition(i * 4);
                Vector3 me1 = GetPosition(i * 4 + 1);
                Vector3 me2 = GetPosition(i * 4 + 2);

                if (MathTools.IntersectionTriangleCapsule(ref me0, ref me1, ref me2, ref ray.Origin, ref targ, radius, out t))
                {
                    float newdst = t * maxdistance;
                    mindist = MathTools.Min(mindist, newdst);
                }

                me0 = GetPosition(i * 4 + 2);
                me1 = GetPosition(i * 4 + 3);
                me2 = GetPosition(i * 4 + 0);

                if (MathTools.IntersectionTriangleCapsule(ref me0, ref me1, ref me2, ref ray.Origin, ref targ, radius, out t))
                {
                    float newdst = t * maxdistance;
                    mindist = MathTools.Min(mindist, newdst);
                }

            }
            if (mindist == float.MaxValue)
                return -1;
            return mindist;
        }



        #region IGeoContainer<Quad> Members

        /// <summary>
        /// Returns or sets quads in list
        /// </summary>
        /// <param name="index">index of the quad</param>
        /// <returns>New quad object</returns>
        /// <remarks>Warning, if vertex is shared, it will be changed for all quads!</remarks>
        public Quad this[int index]
        {
            get
            {
                IVertexArray varray = VertexFactory.CreateArray(Vertices[0].GetType(), 4);
                for (int i = 0; i < 4; i++)
                    varray[i] = GetVertex(index * 4 + i);

                return new Quad(varray);
            }
            set
            {
                for (int i = 0; i < 4; i++)
                {
                    SetVertex(index * 4 + i, value.Vertices[i]);
                }
            }
        }

        /// <summary>
        /// Returns number of the triangles in list
        /// </summary>
        public int Count
        {
            get { return CurrentIndices.Length / 4; }
        }

        /// <summary>
        /// Not dynamic, cannot add! Does nothing
        /// </summary>
        /// <param name="geoobject"></param>
        public void Add(Quad geoobject)
        {
        }

        #endregion

        #region IEnumerable<IGeometry> Members

        /// <summary>
        /// Standard enumerator
        /// </summary>
        /// <returns></returns>
        public IEnumerator<IGeometry> GetEnumerator()
        {
            return new GeoEnumerator<Quad>(this);
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return Vertices.GetEnumerator(); //????
        }

        #endregion

        #region ISurfaceGeo Members

        /// <summary>
        /// Recalculates normals for all quads
        /// </summary>
        public void RecalculateNormals()
        {
            if (!Vertices.Descriptor.HasNormal())
                throw new ArgumentException("Can't generate normals because vertex type doesn't contain normals!");

            int indicesperelement;

            switch (MeshType)
            {
                case PrimitiveTypes.IndexedTriangles:
                    indicesperelement = 3;
                    break;
                case PrimitiveTypes.IndexedQuads:
                    indicesperelement = 4;
                    break;
                default:
                    throw new ArgumentException("Can't generate normals for other indexed type then IndexedQuads or IndexedTriangles", "MeshType");
            }

            for (int i = 0; i < Vertices.Count; i++)
            {
                Vertices[i].Normal = new Vector3(0);
            }

            int[] cnt = new int[Vertices.Count];

            //calculate normals from all triangles

            for (int i = 0; i < Indices.Length; i++)
            {
                int index = 0;
                for (int j = 0; j < Indices[i].Length / indicesperelement; j++)
                {
                    Vector3 norm = MathTools.CalcTriNormal(
                        Vertices[Indices[i][index]].Position,
                        Vertices[Indices[i][index + 1]].Position,
                        Vertices[Indices[i][index + 2]].Position, true);  //note!!! test with normalize set to false

                    for (int k = 0; k < indicesperelement; k++)
                    {
                        Vector3 tmp;
                        tmp = Vertices[Indices[i][index + k]].Normal; tmp.Add(norm);
                        Vertices[Indices[i][index + k]].Normal = tmp;
                        cnt[Indices[i][index + k]]++;
                    }
                    index += indicesperelement;
                }
            }

            for (int i = 0; i < Vertices.Count; i++)
            {
                Vector3 norm = Vertices[i].Normal;
                norm.Divide(cnt[i]);
                norm.Normalize();
                Vertices[i].Normal = norm;
            }
        }

        #endregion
    }


}
