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

namespace VisiMath.Geometry
{
    /// <summary>
    /// Ray for picking and collisions.
    /// </summary>
    public class Ray : BaseGeometry
    {
        /// <summary>
        /// Origin of the ray
        /// </summary>
        public Vector3 Origin;
        /// <summary>
        /// Normalized direction of the ray
        /// </summary>
        public Vector3 Direction;

        public Ray(System.Xml.XmlReader reader) : base(reader)
        {
        }


        /// <summary>
        /// Creates default ray (from zero, direction unitZ)
        /// </summary>
        public Ray()
        {
            FromDirection(Vector3.Zero, Vector3.UnitZ);
        }

        /// <summary>
        /// Constructs ray from origin and normalized direction
        /// </summary>
        /// <param name="ori">origin</param>
        /// <param name="dir">direction</param>
        public Ray(Vector3 ori, Vector3 dir)
        {
            FromDirection(ori, dir);
        }

        /// <summary>
        /// Changes origin and direction of the ray
        /// </summary>
        /// <param name="ori"></param>
        /// <param name="dir"></param>
        public void FromDirection(Vector3 ori, Vector3 dir)
        {
            Origin = ori;
            Direction = dir;
            Direction.Normalize();
        }

        /// <summary>
        /// Creates ray from first and second vector
        /// </summary>
        /// <param name="start">Origin of the ray</param>
        /// <param name="second">Target</param>
        public void FromTwoPoints(Vector3 start, Vector3 second)
        {
            Origin = start;
            second.Substract(ref start);
            Direction = second;
            Direction.Normalize();
        }

        /// <summary>
        /// Creates new ray from two points
        /// </summary>
        /// <param name="start">Origin of the ray</param>
        /// <param name="second">Target</param>
        /// <returns>New Ray</returns>
        public static Ray CreateFromPoints(Vector3 start, Vector3 second)
        {
            Ray rval = new Ray();
            rval.FromTwoPoints(start, second);
            return rval;
        }

        /// <summary>
        /// Createes new ray from origin and normalized direction
        /// </summary>
        /// <param name="ori">Origin</param>
        /// <param name="dir">Normalized direction</param>
        /// <returns>New Ray</returns>
        public static Ray CreateFromDirection(Vector3 ori, Vector3 dir)
        {
            Ray rval = new Ray();
            rval.FromDirection(ori, dir);
            return rval;
        }

        /// <summary>
        /// Transforms ray by specified matrix
        /// </summary>
        /// <param name="mat">Tranformation matrix</param>
        public void Transform(ref Matrix4 mat)
        {
            mat.TransformFast(ref Origin);
            mat.TransformNormalFast(ref Direction);
            FromDirection(Origin, Direction);
        }


        /// <summary>
        /// Travels on the ray from the origin to specified distance
        /// </summary>
        /// <param name="distance">Required distance</param>
        /// <returns>New position on the ray</returns>
        public Vector3 TravelTo(float distance)
        {
            Vector3 rval;
            TravelTo(distance, out rval);
            return rval;
        }

        /// <summary>
        /// Travels on the ray from the origin to specified distance
        /// </summary>
        /// <param name="distance">Required distance</param>
        /// <param name="rval">Vector to be filled with new position</param>
        public void TravelTo(float distance, out Vector3 rval)
        {
            rval = Origin;
            rval.X += Direction.X * distance;
            rval.Y += Direction.Y * distance;
            rval.Z += Direction.Z * distance;
        }

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

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

        /// <summary>
        /// If geometry object is container, it's made of several joined objects
        /// </summary>
        /// <returns>false</returns>
        public override bool IsContainer { get {return false;} }

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

        /// <summary>
        /// Returns closest distance from the specified point to object
        /// </summary>
        /// <param name="pos">point to check distance</param>
        /// <returns>Closest distance to the object</returns>
        public override float Distance(ref Vector3 pos)
        {
            return Distance(pos, false);
        }

        /// <summary>
        /// Returns closest distance to the line segment
        /// </summary>
        /// <param name="l1">Start of the segment</param>
        /// <param name="l2">End of the segment</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 float DistanceToLineSegment(ref Vector3 l1, ref Vector3 l2, bool frontOnly = true)
        {
            return MathTools.DistanceRayToLineSegment(ref Origin, ref Direction, ref l1, ref l2, frontOnly);
        }

        /// <summary>
        /// Returns closest distance to the specified point
        /// </summary>
        /// <param name="pos">Position 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 float Distance(Vector3 pos, bool frontOnly = true)
        {
            float u = Direction.Dot(ref pos);
            if (u < 0)
            {
                //point is behind the origin of the ray
                if (frontOnly)
                    return float.NaN;
                return Origin.Distance(ref pos);
            }

            pos.Mul(-1);
            pos.Add(ref Origin);

            Vector3 v = Direction.Cross(ref pos);
            return v.Length;
        }

        /// <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)
        {
            return MathTools.DistanceTwoRays(ref ray.Origin, ref ray.Direction, ref Origin, ref Direction, frontOnly);
        }

        /// <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.Distance(this);
        }

        /// <summary>
        /// Checks for intersection between object and ray
        /// </summary>
        /// <param name="ray"></param>
        /// <returns>True if ray intersects the object</returns>
        public override bool Intersects(Ray ray)
        {
            if (Distance(ray, true) == 0)
                return true;
            return false;
        }

        /// <summary>
        /// Checks for intersection with generic geo object
        /// </summary>
        /// <param name="obj">Object to check with</param>
        /// <returns>True if intersection exists</returns>
        public override bool Intersects(IGeometry obj)
        {
            return obj.Intersects(this);
        }



        /// <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(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)
        {
            float dist;
            Vector3 interspoint;

            if (MathTools.IntersectionTwoRays(ref obj.Origin, ref obj.Direction, ref Origin, ref Direction, out dist, out interspoint))
            {
                if (dist < inters.Distance)
                {
                    inters.Distance = dist;
                    inters.IntersectionPoint = interspoint;
                    inters.GeoObject = this;
                    return true;
                }
            }
            return false;
        }

        /// <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;
            float dist = MathTools.DistanceTwoRays(ref ray.Origin, ref ray.Direction, ref Origin, ref Direction, out s, true);

            if (dist == float.NaN)
                return -1;

            if (dist > radius)
                return -1;

            return s * maxdistance;           
        }



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

        /// <summary>
        /// Converts object to new geometry object with specified primitive type and optionally with quality parameters.
        /// Ray can be converted either to linelist or pointlist
        /// </summary>
        /// <param name="primType">Specified type of the primitive</param>
        /// <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 vertex type</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>
        /// <returns>New object of specified type</returns>
        public override IGeometry ConvertToPrimitive(Type VertexType, PrimitiveTypes primType, float quality = 0, int vertexlimit = 0)
        {
            switch (primType)
            {
                case PrimitiveTypes.LineList:
                    return (IGeometry)ToLines(VertexType, quality, vertexlimit);
                case PrimitiveTypes.PointList:
                    return (IGeometry)ToPoints(VertexType, quality, vertexlimit);
                default:
                    return null;
            }
        }

        /// <summary>
        /// Convert ray to list of points - series of points from center leading in direction. With max quality creates hundred of points with 1 spacing
        /// </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 vertex type</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)
        {
            Point3DList plist = new Point3DList(VertexType,100);
            for (int i = 0; i < 100; i++)
            {
                plist.Vertices[0].Position = Vector3.MoveInDirection(ref Origin, ref Direction, i);
            }
            return plist;
        }

        /// <summary>
        /// Convert Ray to list of one line
        /// </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="VertexType">Required vertex type</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 LineList ToLines(Type VertexType, float quality = 0, int vertexlimit = 0)
        {
            LineList rval = new LineList(VertexType, 2);
            rval.Vertices[0].Position = Origin;
            rval.Vertices[1].Position = Vector3.MoveInDirection(ref Origin, ref Direction, 10000);
            return rval;
        }

        /// <summary>
        /// Ray cannot be converted to triangles. Returns null.
        /// </summary>
        /// <returns>Null.</returns>
        /// <param name="VertexType">unused</param>
        /// <param name="quality">unused</param>
        /// <param name="vertexlimit">unused</param>
        public override TriangleList ToTriangles(Type VertexType, float quality = 0, int vertexlimit = 0)
        {
            return null;
        }

        /// <summary>
        /// Ray cannot be converted to triangles. Returns null.
        /// </summary>
        /// <returns>Null.</returns>
        /// <param name="VertexType">unused</param>
        /// <param name="quality">unused</param>
        /// <param name="vertexlimit">unused</param>
        public override IndexedMesh ToIndexedMesh(Type VertexType, float quality = 0, int vertexlimit = 0)
        {
            return null;
        }

        /// <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 == "Origin")
                    {
                        Origin.ReadXml(reader);
                    }
                    else if (reader.Name == "Direction")
                    {
                        Direction.ReadXml(reader);
                    }
                }
                else break;
            }
        }

        /// <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);
            writer.WriteStartElement("Origin");
            Origin.WriteXml(writer);
            writer.WriteEndElement();
            writer.WriteStartElement("Direction");
            Direction.WriteXml(writer);
            writer.WriteEndElement();
        }

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

    } //ray

}
