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

namespace VisiMath.Geometry
{
    /// <summary>
    /// Ring (or partial ring) object
    /// </summary>
    public class Ring : IndexedQuads
    {

        Vector3 RingCenter;
        Type VertexType;
        float Radius;
        float Height;
        float Thickness;
        int Segments;
        float Angle;


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

        /// <summary>
        /// Constructs new ring of normalvertextype - circle in X/Y plane
        /// </summary>
        /// <param name="center">Center of the ring</param>
        /// <param name="radius">Radius of the ring</param>
        /// <param name="height">Height of the segment (in Z axis)</param>
        /// <param name="thickness">Thickness of the ring (if zero, only outer shell will be constructed)</param>
        /// <param name="segments">Number of segments</param>
        /// <param name="angle">Total angle. Default 0 means full ring, otherwise only partial piece will be constructed</param>
        public Ring(Vector3 center, float radius, float height, float thickness, int segments=10, float angle = 0)
            : this(typeof(NormalVertex), center, radius, height, thickness, segments, angle)
        {
        }

        /// <summary>
        /// Constructs new ring circle in X/Y plane
        /// </summary>
        /// <param name="center">Center of the ring</param>
        /// <param name="radius">Radius of the ring</param>
        /// <param name="height">Height of the segment (in Z axis)</param>
        /// <param name="thickness">Thickness of the ring (if zero, only outer shell will be constructed)</param>
        /// <param name="segments">Number of segments</param>
        /// <param name="angle">Total angle. Default 0 means full ring, otherwise only partial piece will be constructed</param>
        /// <param name="vertexType">Type of the vertex</param>
        public Ring(Type vertexType, Vector3 center, float radius, float height, float thickness, int segments=10, float angle = 0)
            : base(0, 0)
        {
            Construct(vertexType, center, radius, height, thickness, segments, angle);
        }

        protected void Construct(Type vertextype, Vector3 center, float radius, float height, float thickness, int segments = 10, float angle = 0)
        {
            RingCenter = center;
            VertexType = vertextype;
            Radius = radius;
            Height = height/2;
            Thickness = thickness;
            Segments = segments;
            Angle = angle;
            Construct();
        }

        protected void Construct()
        {
            

            if (Angle == 0)
                Angle = MathTools.DoublePi;
            Angle = MathTools.ClampMax(MathTools.Abs(Angle), MathTools.DoublePi);

            int add = 0;
            
            if (Angle != MathTools.DoublePi)
            {
                add = 1;
            }

            int vertexcount = (Segments +add) * 2;
            int indexcount = (Segments + add) * 4;

            bool innershell = false;

            int innervert = vertexcount;
            int innerind = indexcount;

            if (Thickness != 0)
            {
                vertexcount *= 4;
                indexcount *= 4;
                innershell = true;

                if (add == 1)
                {
                    vertexcount += 8;
                    indexcount += 8;
                }
            }


            Vertices = VertexFactory.CreateArray(VertexType, vertexcount);
            Indices = new int[1][];
            Indices[0] = new int[indexcount];

            IVertex vert = VertexFactory.CreateNewVertex(VertexType);
            bool donormal = VertexFactory.HasNormal(VertexType);
            bool dotx = VertexFactory.HasTexCoord(VertexType);
            if (VertexFactory.HasColor(VertexType))
                vert.Color = ColorByte.White;

            int vertidx = 0, indidx = 0;


            for (int i = 0; i < (Segments+add); i++)
            {
                //Create outer shell
                float currang = i * (Angle / (Segments));
                float cosangle = MathTools.Cos(currang);
                float sinangle = MathTools.Sin(currang);

                float xp = cosangle * Radius;
                float yp =  sinangle* Radius;

                Vector3 pos1 = new Vector3(RingCenter.X + xp, RingCenter.Y + yp, RingCenter.Z - Height);
                Vector3 pos2 = new Vector3(RingCenter.X + xp, RingCenter.Y + yp, RingCenter.Z + Height);

                if (donormal)
                    vert.Normal = new Vector3(cosangle, sinangle, 0);

                vert.Position = pos1; 
                if (dotx)
                    vert.TexCoord2 = new Vector2(i / (Segments + add), 0);

                Vertices[vertidx] = vert;
                vert.Position = pos2;
                if (dotx)
                    vert.TexCoord2 = new Vector2(i / (Segments + add), 1);
                Vertices[vertidx+1] = vert;

                if (innershell)
                {
                    //inner shell
                    xp = cosangle * (Radius - Thickness);
                    yp = sinangle * (Radius - Thickness);

                    Vector3 pos3 = new Vector3(RingCenter.X + xp, RingCenter.Y + yp, RingCenter.Z - Height);
                    Vector3 pos4 = new Vector3(RingCenter.X + xp, RingCenter.Y + yp, RingCenter.Z + Height);
                                        
                    if (donormal)
                        vert.Normal = new Vector3(-cosangle, -sinangle, 0);

                    if (dotx)
                        vert.TexCoord2 = new Vector2(i / (Segments + add), 0);
                    vert.Position = pos3;
                    Vertices[innervert + vertidx] = vert;
                    if (dotx)
                        vert.TexCoord2 = new Vector2(i / (Segments + add), 1);
                    vert.Position = pos4;
                    Vertices[innervert + vertidx + 1] = vert;

                    //bottom part

                    if (donormal)
                        vert.Normal = new Vector3(0, 0, -1);
                    if (dotx)
                        vert.TexCoord2 = new Vector2(i / (Segments + add), 0);
                    vert.Position = pos1;
                    Vertices[innervert*2 + vertidx] = vert;
                    if (dotx)
                        vert.TexCoord2 = new Vector2(i / (Segments + add), 1);
                    vert.Position = pos3;
                    Vertices[innervert*2 + vertidx + 1] = vert;

                    //top part
                    if (donormal)
                        vert.Normal = new Vector3(0, 0, 1);
                    if (dotx)
                        vert.TexCoord2 = new Vector2(i / (Segments + add), 0);
                    vert.Position = pos2;
                    Vertices[innervert * 3 + vertidx] = vert;
                    if (dotx)
                        vert.TexCoord2 = new Vector2(i / (Segments + add), 1);
                    vert.Position = pos4;
                    Vertices[innervert * 3 + vertidx + 1] = vert;
                }
                vertidx += 2;
            }

            for (int i = 0; i < (Segments + add-1); i++)
            {
                //indices for outer shell
                CurrentIndices[indidx] = i * 2;
                CurrentIndices[indidx+1] = (i+1) * 2;
                CurrentIndices[indidx+2] = (i+1) * 2 +1;
                CurrentIndices[indidx + 3] = i * 2 + 1;

                if (innershell)
                {
                    //inner shell
                    CurrentIndices[innerind+ indidx] = innervert+ i * 2;
                    CurrentIndices[innerind + indidx + 1] = innervert + i * 2 + 1;
                    CurrentIndices[innerind + indidx + 2] = innervert + (i + 1) * 2 + 1;
                    CurrentIndices[innerind + indidx + 3] = innervert + (i + 1) * 2;                

                    //top ring
                    CurrentIndices[innerind * 3 + indidx] = innervert *3 + i * 2;
                    CurrentIndices[innerind * 3 + indidx + 1] = innervert * 3 + (i + 1) * 2;
                    CurrentIndices[innerind * 3 + indidx + 2] = innervert * 3 + (i + 1) * 2 + 1;
                    CurrentIndices[innerind * 3 + indidx + 3] = innervert * 3 + i * 2 + 1;                

                    //bottom ring

                    CurrentIndices[innerind * 2 + indidx] = innervert * 2 + i * 2;
                    CurrentIndices[innerind * 2 + indidx + 1] = innervert * 2 + i * 2 + 1;
                    CurrentIndices[innerind * 2 + indidx + 2] = innervert * 2 + (i + 1) * 2 + 1;
                    CurrentIndices[innerind * 2 + indidx + 3] = innervert * 2 + (i + 1) * 2;
                }

                indidx += 4;
            }

            if (Angle == MathTools.DoublePi)
            {
                CurrentIndices[indidx] = 0;
                CurrentIndices[indidx + 1] = 1;
                CurrentIndices[indidx + 2] = innervert - 1;
                CurrentIndices[indidx + 3] = innervert - 2;

                if (innershell)
                {
                    //inner shell
                    CurrentIndices[innerind + indidx] = innervert;
                    CurrentIndices[innerind + indidx + 1] = innervert + innervert -2;
                    CurrentIndices[innerind + indidx + 2] = innervert + innervert -1;
                    CurrentIndices[innerind + indidx + 3] = innervert + 1;

                    //top ring
                    CurrentIndices[innerind * 3 + indidx] = innervert * 3;
                    CurrentIndices[innerind * 3 + indidx + 1] = innervert * 3 + 1;
                    CurrentIndices[innerind * 3 + indidx + 2] = innervert * 3 + innervert -1;
                    CurrentIndices[innerind * 3 + indidx + 3] = innervert * 3 + innervert - 2;

                    //bottom ring

                    CurrentIndices[innerind * 2 + indidx] = innervert * 2;
                    CurrentIndices[innerind * 2 + indidx + 1] = innervert * 2 + innervert -2;
                    CurrentIndices[innerind * 2 + indidx + 2] = innervert * 2 + innervert - 1;
                    CurrentIndices[innerind * 2 + indidx + 3] = innervert * 2 + 1;
                }
                
            }



            if (innershell && Angle != MathTools.DoublePi)
            {
                //start and end caps of the ring

                //start cap
                Vector3 capnormal = MathTools.CalcTriNormal(Vertices[0].Position, Vertices[1].Position, Vertices[innervert + 1].Position);
                
                vert = Vertices[0];
                if (donormal)
                    vert.Normal = capnormal;
                Vertices[innervert * 4] = vert;

                vert = Vertices[1];
                if (donormal)
                    vert.Normal = capnormal;
                Vertices[innervert * 4 + 1] = vert;

                vert = Vertices[innervert +1];
                if (donormal)
                    vert.Normal = capnormal;
                Vertices[innervert * 4+2] = vert;

                vert = Vertices[innervert];
                if (donormal)
                    vert.Normal = capnormal;
                Vertices[innervert * 4+3] = vert;

                //end cap

                capnormal = MathTools.CalcTriNormal(Vertices[innervert - 1].Position, Vertices[innervert - 2].Position, Vertices[innervert * 2 - 2].Position);

                vert = Vertices[innervert - 1];
                if (donormal)
                    vert.Normal = capnormal;
                Vertices[innervert * 4 + 4] = vert;

                vert = Vertices[innervert - 2];
                if (donormal)
                    vert.Normal = capnormal;
                Vertices[innervert * 4 + 5] = vert;

                vert = Vertices[innervert * 2 - 2];
                if (donormal)
                    vert.Normal = capnormal;
                Vertices[innervert * 4 + 6] = vert;

                vert = Vertices[innervert * 2 - 1];
                if (donormal)
                    vert.Normal = capnormal;
                Vertices[innervert * 4 + 7] = vert;


                //indices for both caps
                for (int i = 0; i < 8; i++)
                    CurrentIndices[innerind * 4 + i] = innervert * 4 + i;            
            }
        }

        public override bool ContainsPosition(Vector3 pos)
        {
            //todo, return correct volumetric position
            return base.ContainsPosition(pos);
        }

        /// <summary>
        /// Center of the object
        /// </summary>
        public override Vector3 Center
        {
            get
            {
                return RingCenter;
            }
        }

        //todo: intersections could be sped up

        /// <summary>
        /// Serializes object to XML
        /// </summary>
        /// <param name="writer">XML writer to save values</param>
        public override void WriteXml(System.Xml.XmlWriter writer)
        {
            writer.WriteAttributeString("type", this.GetType().Name);
            RingCenter.WriteXml(writer, "RingCenter");
            writer.WriteStartElement("VertexType"); writer.WriteString(VertexType.ToString()); writer.WriteEndElement();
            XMLHelper.WriteFloat(writer, "Radius", Radius);
            XMLHelper.WriteFloat(writer, "Height", Height);
            XMLHelper.WriteFloat(writer, "Thickness", Thickness);
            writer.WriteElementString("Segments", Segments.ToString());
            XMLHelper.WriteFloat(writer, "Angle", Angle);
        }

        /// <summary>
        /// Deserializes object from XML
        /// </summary>
        /// <param name="reader">XML reader</param>
        public override void ReadXml(System.Xml.XmlReader reader)
        {
            if (reader["type"] != this.GetType().Name)
                throw new ArgumentException("IGeometry:ReadXML - wrong xml node type!");

            while (reader.Read())
            {
                if (reader.IsStartElement())
                {
                    if (reader.Name == "RingCenter")
                    {
                        RingCenter.ReadXml(reader);
                    }
                    else if (reader.Name == "VertexType")
                    {
                        VertexType = VertexFactory.GetTypeFromName(reader.ReadString());
                    }
                    else if (reader.Name == "Radius")
                    {
                        Radius = XMLHelper.ReadAsFloat(reader);
                    }
                    else if (reader.Name == "Height")
                    {
                        Height = XMLHelper.ReadAsFloat(reader);
                    }
                    else if (reader.Name == "Thickness")
                    {
                        Thickness = XMLHelper.ReadAsFloat(reader);
                    }
                    else if (reader.Name == "Segments")
                    {
                        Segments = XMLHelper.ReadAsInt(reader);
                        if (Segments == 0) Segments = 1;
                    }
                    else if (reader.Name == "Angle")
                    {
                        Angle = XMLHelper.ReadAsFloat(reader);;
                    }
                }
                else break;
            }
            Construct();
        }
    }
}
