#region ================== Copyright (c) 2021 Boris Iwanski

/*
 * This program is free software: you can redistribute it and/or modify
 *
 * it under the terms of the GNU General Public License as published by
 * 
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 * 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
 * 
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.If not, see<http://www.gnu.org/licenses/>.
 */

#endregion

#region ================== Namespaces

using CodeImp.DoomBuilder.Geometry;

#endregion

namespace CodeImp.DoomBuilder.UDBScript.Wrapper
{
	internal struct Line2DWrapper
	{
		#region ================== Variables

		/// <summary>
		/// `Vector2D` position of start of the line.
		/// </summary>
		public Vector2DWrapper v1;

		/// <summary>
		/// `Vector2D` position of end of the line.
		/// </summary>
		public Vector2DWrapper v2;

		#endregion

		#region ================== Constructors

		internal Line2DWrapper(Line2D line)
		{
			v1 = new Vector2DWrapper(line.v1);
			v2 = new Vector2DWrapper(line.v2);
		}

		/// <summary>
		/// Creates a new `Line2D` from two points.
		/// ```
		/// let line1 = new UDB.Line2D(new Vector2D(32, 64), new Vector2D(96, 128));
		/// let line2 = new UDB.Line2D([ 32, 64 ], [ 96, 128 ]);
		/// ```
		/// </summary>
		/// <param name="v1">First point</param>
		/// <param name="v2">Second point</param>
		public Line2DWrapper(object v1, object v2)
		{
			try
			{
				this.v1 = new Vector2DWrapper(BuilderPlug.Me.GetVector3DFromObject(v1));
				this.v2 = new Vector2DWrapper(BuilderPlug.Me.GetVector3DFromObject(v2));
			}
			catch (CantConvertToVectorException e)
			{
				throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message);
			}
		}

		#endregion

		#region ================== Internals

		internal Line2D AsLine2D()
		{
			return new Line2D(v1._x, v1._y, v2._x, v2._y);
		}

		#endregion

		#region ================== Statics

		/// <summary>
		/// Checks if two lines intersect. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
		/// </summary>
		/// <param name="line1">First `Line2D`</param>
		/// <param name="line2">Second `Line2D`</param>
		/// <param name="bounded">`true` to use finite length of lines, `false` to use infinite length of lines</param>
		/// <returns>`true` if the lines intersect, `false` if they do not</returns>
		public static bool areIntersecting(Line2DWrapper line1, Line2DWrapper line2, bool bounded=true)
		{
			double u_ray;
			return Line2D.GetIntersection(line1.v1.AsVector2D(), line1.v2.AsVector2D(), line2.v1._x, line2.v1._y, line2.v2._x, line2.v2._y, out u_ray, bounded);
		}

		/// <summary>
		/// Checks if two lines defined by their start and end points intersect. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
		/// </summary>
		/// <param name="a1">First point of first line</param>
		/// <param name="a2">Second point of first line</param>
		/// <param name="b1">First point of second line</param>
		/// <param name="b2">Second point of second line</param>
		/// <param name="bounded">`true` (default) to use finite length of lines, `false` to use infinite length of lines</param>
		/// <returns>`true` if the lines intersect, `false` if they do not</returns>
		public static bool areIntersecting(object a1, object a2, object b1, object b2, bool bounded=true)
		{
			try
			{
				Vector2D v1 = BuilderPlug.Me.GetVector3DFromObject(a1);
				Vector2D v2 = BuilderPlug.Me.GetVector3DFromObject(a2);
				Vector2D v3 = BuilderPlug.Me.GetVector3DFromObject(b1);
				Vector2D v4 = BuilderPlug.Me.GetVector3DFromObject(b2);
				double u_ray;

				return Line2D.GetIntersection(v1, v2, v3.x, v3.y, v4.x, v4.y, out u_ray, bounded);
			}
			catch (CantConvertToVectorException e)
			{
				throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message);
			}
		}

		/// <summary>
		/// Returns the intersection point of two lines as `Vector2D`. If the lines do not intersect the `x` and `y` properties of the `Vector2D` are `NaN`. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
		/// </summary>
		/// <param name="a1">First point of first line</param>
		/// <param name="a2">Second point of first line</param>
		/// <param name="b1">First point of second line</param>
		/// <param name="b2">Second point of second line</param>
		/// <param name="bounded">`true` (default) to use finite length of lines, `false` to use infinite length of lines</param>
		/// <returns>The intersection point as `Vector2D`</returns>
		public static Vector2DWrapper getIntersectionPoint(object a1, object a2, object b1, object b2, bool bounded = true)
		{
			try
			{
				Vector2D v1 = BuilderPlug.Me.GetVector3DFromObject(a1);
				Vector2D v2 = BuilderPlug.Me.GetVector3DFromObject(a2);
				Vector2D v3 = BuilderPlug.Me.GetVector3DFromObject(b1);
				Vector2D v4 = BuilderPlug.Me.GetVector3DFromObject(b2);

				return new Vector2DWrapper(Line2D.GetIntersectionPoint(new Line2D(v1, v2), new Line2D(v3, v4), bounded));
			}
			catch (CantConvertToVectorException e)
			{
				throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message);
			}
		}

		/// <summary>
		/// Returns which the of the line defined by its start and end point a given point is on.
		/// </summary>
		/// <param name="v1">First point of the line</param>
		/// <param name="v2">Second point of the line</param>
		/// <param name="p">Point to check</param>
		/// <returns>`&lt; 0` if `p` is on the front (right) side, `&gt; 0` if `p` is on the back (left) side, `== 0` if `p` in on the line</returns>
		public static double getSideOfLine(object v1, object v2, object p)
		{
			try
			{
				Vector2D v11 = BuilderPlug.Me.GetVector3DFromObject(v1);
				Vector2D v21 = BuilderPlug.Me.GetVector3DFromObject(v2);
				Vector2D p1 = BuilderPlug.Me.GetVector3DFromObject(p);

				return Line2D.GetSideOfLine(v11, v21, p1);
			}
			catch (CantConvertToVectorException e)
			{
				throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message);
			}
		}

		/// <summary>
		/// Returns the shortest distance from point `p` to the line defined by its start and end points. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
		/// </summary>
		/// <param name="v1">First point of the line</param>
		/// <param name="v2">Second point of the line</param>
		/// <param name="p">Point to get the distance to</param>
		/// <param name="bounded">`true` (default) to use finite length of lines, `false` to use infinite length of lines</param>
		/// <returns>The shortest distance to the line</returns>
		public static double getDistanceToLine(object v1, object v2, object p, bool bounded=true)
		{
			try
			{
				Vector2D v11 = BuilderPlug.Me.GetVector3DFromObject(v1);
				Vector2D v21 = BuilderPlug.Me.GetVector3DFromObject(v2);
				Vector2D p1 = BuilderPlug.Me.GetVector3DFromObject(p);

				return Line2D.GetDistanceToLine(v11, v21, p1, bounded);
			}
			catch (CantConvertToVectorException e)
			{
				throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message);
			}
		}

		/// <summary>
		/// Returns the shortest square distance from point `p` to the line defined by its start and end points. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
		/// </summary>
		/// <param name="v1">First point of the line</param>
		/// <param name="v2">Second point of the line</param>
		/// <param name="p">Point to get the distance to</param>
		/// <param name="bounded">`true` (default) to use finite length of lines, `false` to use infinite length of lines</param>
		/// <returns>The shortest square distance to the line</returns>
		public static double getDistanceToLineSq(object v1, object v2, object p, bool bounded = true)
		{
			try
			{
				Vector2D v11 = BuilderPlug.Me.GetVector3DFromObject(v1);
				Vector2D v21 = BuilderPlug.Me.GetVector3DFromObject(v2);
				Vector2D p1 = BuilderPlug.Me.GetVector3DFromObject(p);

				return Line2D.GetDistanceToLineSq(v11, v21, p1, bounded);
			}
			catch (CantConvertToVectorException e)
			{
				throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message);
			}
		}

		/// <summary>
		/// Returns the offset coordinate on the line nearest to the given point. `0.0` being on the first point, `1.0` being on the second point, and `u = 0.5` being in the middle between the points.
		/// </summary>
		/// <param name="v1">First point of the line</param>
		/// <param name="v2">Second point of the line</param>
		/// <param name="p">Point to get the nearest offset coordinate from</param>
		/// <returns>The offset value relative to the first point of the line.</returns>
		public static double getNearestOnLine(object v1, object v2, object p)
		{
			try
			{
				Vector2D v11 = BuilderPlug.Me.GetVector3DFromObject(v1);
				Vector2D v21 = BuilderPlug.Me.GetVector3DFromObject(v2);
				Vector2D p1 = BuilderPlug.Me.GetVector3DFromObject(p);

				return Line2D.GetNearestOnLine(v11, v21, p1);
			}
			catch (CantConvertToVectorException e)
			{
				throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message);
			}
		}

		/// <summary>
		/// Returns the coordinate on a line defined by its start and end points as `Vector2D`.
		/// </summary>
		/// <param name="v1">First point of the line</param>
		/// <param name="v2">Second point of the line</param>
		/// <param name="u">Offset coordinate relative to the first point of the line</param>
		/// <returns>Point on the line as `Vector2D`</returns>
		public static Vector2DWrapper getCoordinatesAt(object v1, object v2, double u)
		{
			try
			{
				Vector2D v11 = BuilderPlug.Me.GetVector3DFromObject(v1);
				Vector2D v21 = BuilderPlug.Me.GetVector3DFromObject(v2);

				return new Vector2DWrapper(Line2D.GetCoordinatesAt(v11, v21, u));
			}
			catch (CantConvertToVectorException e)
			{
				throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message);
			}
		}

		#endregion

		#region ================== Methods

		/// <summary>
		/// Returns the coordinates on the line, where `u` is the position between the first and second point, `u = 0.0` being on the first point, `u = 1.0` being on the second point, and `u = 0.5` being in the middle between the points.
		/// </summary>
		/// <param name="u">Position on the line, between 0.0 and 1.0</param>
		/// <returns>Position on the line as `Vector2D`</returns>
		public Vector2DWrapper getCoordinatesAt(double u)
		{
			return new Vector2DWrapper(new Line2D(v1._x, v1._y, v2._x, v2._y).GetCoordinatesAt(u));
		}

		/// <summary>
		/// Returns the length of the `Line2D`.
		/// </summary>
		/// <returns>Length of the `Line2D`</returns>
		public double getLength()
		{
			return Line2D.GetLength(v2._x - v1._x, v2._y - v1._y);
		}

		/// <summary>
		/// Returns the angle of the `Line2D` in radians.
		/// </summary>
		/// <returns>Angle of `Line2D` in radians</returns>
		public double getAngleRad()
		{
			return new Line2D(v1.AsVector2D(), v2.AsVector2D()).GetAngle();
		}
		
		/// <summary>
		/// Return the angle of the `Line2D` in degrees.
		/// </summary>
		/// <returns>Angle of the `Line2D` in degrees</returns>
		public double getAngle()
		{
			return Angle2D.RadToDeg(new Line2D(v1.AsVector2D(), v2.AsVector2D()).GetAngle());
		}

		/// <summary>
		/// Returns the perpendicular of this line as `Vector2D`.
		/// </summary>
		/// <returns>Perpendicular of this line as `Vector2D`</returns>
		public Vector2DWrapper getPerpendicular()
		{
			return new Vector2DWrapper(new Line2D(v1._x, v1._y, v2._x, v2._y).GetPerpendicular());
		}

		/// <summary>
		/// Checks if the given `Line2D` intersects this line. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
		/// </summary>
		/// <param name="ray">`Line2D` to check against</param>
		/// <param name="bounded">`true` (default) to use finite length of lines, `false` to use infinite length of lines</param>
		/// <returns>`true` if lines intersect, `false` if they do not intersect</returns>
		public bool isIntersecting(Line2DWrapper ray, bool bounded=true)
		{
			double u_ray;
			return AsLine2D().GetIntersection(ray.v1._x, ray.v1._y, ray.v2._x, ray.v2._y, out u_ray, bounded);
		}

		/// <summary>
		/// Checks if the given line intersects this line. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
		/// </summary>
		/// <param name="a1">First point of the line to check against</param>
		/// <param name="a2">Second point of the line to check against</param>
		/// <param name="bounded">`true` (default) to use finite length of lines, `false` to use infinite length of lines</param>
		/// <returns>`true` if the lines intersect, `false` if they do not</returns>
		public bool isIntersecting(object a1, object a2, bool bounded = true)
		{
			try
			{
				Vector2D v3 = BuilderPlug.Me.GetVector3DFromObject(a1);
				Vector2D v4 = BuilderPlug.Me.GetVector3DFromObject(a2);
				double u_ray;

				return AsLine2D().GetIntersection(v3.x, v3.y, v4.x, v4.y, out u_ray, bounded);
			}
			catch (CantConvertToVectorException e)
			{
				throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message);
			}
		}

		/// <summary>
		/// Returns the intersection point of of the given line defined by its start and end points with this line as `Vector2D`. If the lines do not intersect the `x` and `y` properties of the `Vector2D` are `NaN`. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
		/// </summary>
		/// <param name="a1">First point of first line</param>
		/// <param name="a2">Second point of first line</param>
		/// <param name="bounded">`true` (default) to use finite length of lines, `false` to use infinite length of lines</param>
		/// <returns>The intersection point as `Vector2D`</returns>
		public Vector2DWrapper getIntersectionPoint(object a1, object a2, bool bounded = true)
		{
			try
			{
				Vector2D v3 = BuilderPlug.Me.GetVector3DFromObject(a1);
				Vector2D v4 = BuilderPlug.Me.GetVector3DFromObject(a2);
				Line2D line = AsLine2D();
				double u_ray;
				line.GetIntersection(v3.x, v3.y, v4.x, v4.y, out u_ray, bounded);

				return new Vector2DWrapper(line.GetCoordinatesAt(u_ray));
			}
			catch (CantConvertToVectorException e)
			{
				throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message);
			}
		}

		/// <summary>
		/// Returns the intersection point of of the given line with this line as `Vector2D`. If the lines do not intersect the `x` and `y` properties of the `Vector2D` are `NaN`. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
		/// </summary>
		/// <param name="ray">Other `Line2D` to get the intersection point from</param>
		/// <param name="bounded">`true` (default) to use finite length of lines, `false` to use infinite length of lines</param>
		/// <returns>The intersection point as `Vector2D`</returns>
		public Vector2DWrapper getIntersectionPoint(Line2DWrapper ray, bool bounded=true)
		{
			Line2D thisline = AsLine2D();
			Line2D otherline = ray.AsLine2D();
			double u_ray;
			thisline.GetIntersection(otherline, out u_ray, bounded);

			return new Vector2DWrapper(thisline.GetCoordinatesAt(u_ray));
		}

		/// <summary>
		/// Returns which the of the line defined by its start and end point a given point is on.
		/// </summary>
		/// <param name="p">Point to check</param>
		/// <returns>`&lt; 0` if `p` is on the front (right) side, `&gt; 0` if `p` is on the back (left) side, `== 0` if `p` in on the line</returns>
		public double getSideOfLine(object p)
		{
			try
			{
				Vector2D p1 = BuilderPlug.Me.GetVector3DFromObject(p);

				return AsLine2D().GetSideOfLine(p1);
			}
			catch (CantConvertToVectorException e)
			{
				throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message);
			}
		}

		public override string ToString()
		{
			return AsLine2D().ToString();
		}

		#endregion
	}
}
