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

namespace VisiMath.Geometry
{
    /// <summary>
    /// Basic cylinder
    /// </summary>
    public class Cylinder : BaseGeometry, IVolumetric, IConvexObject
    {
        protected Vector3 _Bottom = new Vector3(0,0,-0.5f);
        protected Vector3 _Top = new Vector3(0,0,0.5f);
        protected Vector3 _Center = new Vector3(0);
        protected Vector3 Direction;
        protected Vector3 Normal;
        protected float _Length;
        protected float _Radius =1;

        /// <summary>
        /// Bottom end of the cylinder
        /// </summary>
        public Vector3 Bottom { 
            get
            {
                return _Bottom;
            }
            set
            {
                _Bottom = value;
                Recalc();
            }
        }

        /// <summary>
        /// Top end of the cylinder
        /// </summary>
        public Vector3 Top {
            get
            {
                return _Top;
            }
            set
            {
                _Top = value;
                Recalc();
            }
        }

        /// <summary>
        /// Length of the cyliner
        /// </summary>
        public float Length { get { return _Length; } }
        

        /// <summary>
        /// Radius of the cylinder
        /// </summary>
        public float Radius { 
            get
            {
                return _Radius;
            }
            set
            {
                _Radius = value;
            }
        }

        /// <summary>
        /// Recalculates data for cylinder
        /// </summary>
        protected void Recalc()
        {
            _Center = _Top + _Bottom;
            _Center.Mul(0.5f);
            Direction = _Top - _Bottom;
            _Length = Direction.Length;
            Normal = new Vector3(ref Direction, 1 / _Length);
        }

        /// <summary>
        /// Standard constructor
        /// </summary>
        /// <param name="start">Start of the cylinder</param>
        /// <param name="end">End point of the cylinder</param>
        /// <param name="rad">Radius of the cylinder</param>
        public Cylinder(Vector3 start, Vector3 end, float rad)
        {
            _Bottom = start;
            _Top = end;
            Radius = rad;
            Recalc();
        }

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

        /// <summary>
        /// Default constructor
        /// </summary>
        public Cylinder()
        {
            Recalc();
        }

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

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

        /// <summary>
        /// Cylinder is not container
        /// </summary>
        /// <returns>Return false</returns>
        public override bool IsContainer
        {
            get { return false; }
        }

        /// <summary>
        /// Returns closest distance from the specified point to object
        /// </summary>
        /// <param name="pos">position</param>
        /// <returns>Closest distance to the object</returns>
        public override float Distance(ref Vector3 pos)
        {
            float r;
            float distance = MathTools.DistanceVectorLineSegment(ref _Top, ref _Bottom, ref pos, out r);

            if ((r < 0) || (r > 1))
                return distance;
            return MathTools.ClampMin(distance - _Radius, 0);
        }


        /// <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 t;
            float distance = MathTools.DistanceRayToLineSegment(ref ray.Origin, ref ray.Direction, ref _Top, ref _Bottom, out t, frontOnly);

            if (distance == float.NaN)
                return float.NaN;

            if ((t < 0) || (t > 1))
                return distance;
            return MathTools.ClampMin(distance - _Radius, 0);
        }

        /// <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 s, t;

            float distance = MathTools.DistanceTwoLineSegments(ref _Top, ref _Bottom, ref pos1, ref pos2, out s, out t);

            if ((s < 0) || (s > 1))
                return distance;
            return MathTools.ClampMin(distance - _Radius, 0);
        }

        /// <summary>
        /// Returns closest distance to the triangle. GENERIC with center, override!!!
        /// </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 dst = MathTools.DistanceLineSegmentTriangle(ref _Top, ref _Bottom, ref pos1, ref pos2, ref pos3);
            return MathTools.ClampMin(dst - _Radius, 0);
            //!!! this is for capsule! need closestpoint linesegmenttriangle for correct check!
        }

        /// <summary>
        /// Returns closest distance to the quad - calls distance from triangle
        /// </summary>
        /// <param name="p1">quad corner</param>
        /// <param name="p2">quad corner</param>
        /// <param name="p3">quad corner</param>
        /// <param name="p4">quad corner></param>
        /// <returns>Closest distance</returns>        
        public override  float DistanceQuad(ref Vector3 p1, ref Vector3 p2, ref Vector3 p3, ref Vector3 p4)
        {
            //TODO: optimize
            float distance1 = DistanceTriangle(ref p1, ref p2, ref p3);
            float distance2 = DistanceTriangle(ref p3, ref p4, ref p1);
            if (distance1 < distance2)
                return distance1;
            return distance2;
        }

        /// <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 = Distance(ref center);

            return MathTools.ClampMin(dist - _Radius, 0);
        }

        /// <summary>
        /// Checks if specified position is inside the object
        /// </summary>
        /// <param name="pos">Position to check</param>
        /// <returns>True if vector is inside the object</returns>
        public override bool ContainsPosition(Vector3 pos)
        {
            if (Distance(ref pos) == 0)
                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)
        {
            return false;
            //throw new NotImplementedException();
        }

        /// <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)
        {
            float s, t;

            Vector3 pos1, pos2;
            pos1 = ray.Origin;
            pos2 = new Vector3(ref ray.Direction, maxdistance);

            float distance = MathTools.DistanceTwoLineSegments(ref _Top, ref _Bottom, ref pos1, ref pos2, out s, out t) - radius;

            if ((s >= 0) && (s <= 1))
                distance = distance - _Radius;
            distance = MathTools.ClampMin(distance - _Radius, 0);

            if (distance == 0)
            {
                return t * maxdistance;
            }
            else return -1;
        }

        /// <summary>
        /// Preferred type of the primitive
        /// </summary>
        public override PrimitiveTypes PreferredPrimitive
        {
            get { return PrimitiveTypes.TriangleList; }
        }

        /// <summary>
        /// Preferred type of the vertex
        /// </summary>
        public override Type PreferredVertexType
        {
            get { return typeof(NormalVertex); }
        }


        /// <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="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>
        /// <param name="vertextype">Required type of the vertex</param>
        public override Point3DList ToPoints(Type vertextype, float quality = 0, int vertexlimit = 0)
        {
            TriangleList tl = ToTriangles(vertextype, quality, vertexlimit);
            return tl.ToPoints();
        }

        /// <summary>
        /// Convert object to list of lines
        /// </summary>
        /// <returns>New geometry object containing edges 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="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>
        /// <param name="vertextype">Required type of the vertex</param>
        public override LineList ToLines(Type vertextype, float quality = 0, int vertexlimit = 0)
        {
            TriangleList tl = ToTriangles(vertextype, quality, vertexlimit);
            return tl.ToLines();
        }

        /// <summary>
        /// Convert object to list of triangles. 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">
        /// 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>
        /// <param name="vertextype">Required type of the vertex</param>
        public override TriangleList ToTriangles(Type vertextype, float quality = 0, int vertexlimit = 0)
        {
            //notimplemented
            return null;

            /*
            int segments = (int)(100 * quality);
            segments = MathTools.ClampMin(segments, 4);


            int vcount = 4 * segments + 2 * (segments * 3);

            IVertexArray varray = VertexFactory.CreateArray(vertextype, vcount);*/
        }

        /// <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">
        /// 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>
        /// <param name="vertextype">Required type of the vertex</param>
        public override IndexedMesh ToIndexedMesh(Type vertextype, float quality = 0, int vertexlimit = 0)
        {
            TriangleList tl = ToTriangles(vertextype, quality, vertexlimit);
            return tl.ToIndexedMesh();
        }

        /// <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 == "Bottom")
                    {
                        _Bottom.ReadXml(reader);
                    }
                    else if (reader.Name == "Top")
                    {
                        _Top.ReadXml(reader);
                    }
                    else if (reader.Name == "Radius")
                    {
                        _Radius = XMLHelper.ReadAsFloat(reader);
                    }
                }
                else break;
            }
            Recalc();
        }

        /// <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);
            _Bottom.WriteXml(writer, "Bottom");
            _Top.WriteXml(writer, "Top");            
            XMLHelper.WriteFloat(writer, "Radius", Radius);
        }

        /// <summary>
        /// Returns copy of the geometric data
        /// </summary>
        /// <returns>Full copy of the geometry</returns>
        public override IGeometry GetCopy()
        {
            return new Cylinder(_Bottom, _Top, Radius);
        }

        #endregion



        #region IVolumetric Members

        /// <summary>
        /// Volume of the object
        /// </summary>
        public float Volume
        {
            get
            {
                return MathTools.Pi * _Radius * _Radius * _Length;
             }
        }

        #endregion

        #region IConvexObject Members

        /// <summary>
        /// Returns implicit (math expression) or explicit (vertex based) type of convex object
        /// </summary>
        public ConvexObjectType BaseType
        {
            get { return ConvexObjectType.Implicit; }
        }

        /// <summary>
        /// Returns number of vertices, valid for explicit object types
        /// </summary>
        public int VertexCount
        {
            get { return 0; }
        }

        /// <summary>
        /// Returns vertex with specified index, valid for explicit object types
        /// </summary>
        /// <param name="index">index of vertex</param>
        /// <param name="vertex">Vector to save vertex</param>
        public void GetVertex(int index, out Vector3 vertex)
        {
            vertex = new Vector3();
        }

        /// <summary>
        /// Gets supporting vertex in local coordinates
        /// </summary>
        /// <param name="vertex">Save vertex</param>
        /// <param name="direction">Direction vector</param>
        public void GetLocalSupportingVertex(ref Vector3 direction, out Vector3 vertex)
        {
            vertex = new Vector3();
            //throw new NotImplementedException();
        }

        #endregion
    }
}
