#region License

/* MoonWorks - Game Development Framework
 * Copyright 2021 Evan Hemsley
 */

/* Derived from code by Ethan Lee (Copyright 2009-2021).
 * Released under the Microsoft Public License.
 * See fna.LICENSE for details.

 * Derived from code by the Mono.Xna Team (Copyright 2006).
 * Released under the MIT License. See monoxna.LICENSE for details.
 */

#endregion

#region Using Statements
using System;
using System.Diagnostics;

#endregion

namespace MoonWorks.Math.Float
{
	[Serializable]
	[DebuggerDisplay("{DebugDisplayString,nq}")]
	public struct Plane : IEquatable<Plane>
	{
		#region Internal Properties

		internal string DebugDisplayString
		{
			get
			{
				return string.Concat(
					Normal.DebugDisplayString, " ",
					D.ToString()
				);
			}
		}

		#endregion

		#region Public Fields

		public Vector3 Normal;
		public float D;

		#endregion

		#region Public Constructors

		public Plane(Vector4 value)
			: this(new Vector3(value.X, value.Y, value.Z), value.W)
		{
		}

		public Plane(Vector3 normal, float d)
		{
			Normal = normal;
			D = d;
		}

		public Plane(Vector3 a, Vector3 b, Vector3 c)
		{
			Vector3 ab = b - a;
			Vector3 ac = c - a;

			Vector3 cross = Vector3.Cross(ab, ac);
			Vector3.Normalize(ref cross, out Normal);
			D = -(Vector3.Dot(Normal, a));
		}

		public Plane(float a, float b, float c, float d)
			: this(new Vector3(a, b, c), d)
		{

		}

		#endregion

		#region Public Methods

		public float Dot(Vector4 value)
		{
			return (
				(this.Normal.X * value.X) +
				(this.Normal.Y * value.Y) +
				(this.Normal.Z * value.Z) +
				(this.D * value.W)
			);
		}

		public void Dot(ref Vector4 value, out float result)
		{
			result = (
				(this.Normal.X * value.X) +
				(this.Normal.Y * value.Y) +
				(this.Normal.Z * value.Z) +
				(this.D * value.W)
			);
		}

		public float DotCoordinate(Vector3 value)
		{
			return (
				(this.Normal.X * value.X) +
				(this.Normal.Y * value.Y) +
				(this.Normal.Z * value.Z) +
				this.D
			);
		}

		public void DotCoordinate(ref Vector3 value, out float result)
		{
			result = (
				(this.Normal.X * value.X) +
				(this.Normal.Y * value.Y) +
				(this.Normal.Z * value.Z) +
				this.D
			);
		}

		public float DotNormal(Vector3 value)
		{
			return (
				(this.Normal.X * value.X) +
				(this.Normal.Y * value.Y) +
				(this.Normal.Z * value.Z)
			);
		}

		public void DotNormal(ref Vector3 value, out float result)
		{
			result = (
				(this.Normal.X * value.X) +
				(this.Normal.Y * value.Y) +
				(this.Normal.Z * value.Z)
			);
		}

		public void Normalize()
		{
			float length = Normal.Length();
			float factor = 1.0f / length;
			Vector3.Multiply(ref Normal, factor, out Normal);
			D = D * factor;
		}

		public PlaneIntersectionType Intersects(BoundingBox box)
		{
			return box.Intersects(this);
		}

		public void Intersects(ref BoundingBox box, out PlaneIntersectionType result)
		{
			box.Intersects(ref this, out result);
		}

		public PlaneIntersectionType Intersects(BoundingSphere sphere)
		{
			return sphere.Intersects(this);
		}

		public void Intersects(ref BoundingSphere sphere, out PlaneIntersectionType result)
		{
			sphere.Intersects(ref this, out result);
		}

		public PlaneIntersectionType Intersects(BoundingFrustum frustum)
		{
			return frustum.Intersects(this);
		}

		#endregion

		#region Internal Methods

		internal PlaneIntersectionType Intersects(ref Vector3 point)
		{
			float distance;
			DotCoordinate(ref point, out distance);
			if (distance > 0)
			{
				return PlaneIntersectionType.Front;
			}
			if (distance < 0)
			{
				return PlaneIntersectionType.Back;
			}
			return PlaneIntersectionType.Intersecting;
		}

		#endregion

		#region Public Static Methods

		public static Plane Normalize(Plane value)
		{
			Plane ret;
			Normalize(ref value, out ret);
			return ret;
		}

		public static void Normalize(ref Plane value, out Plane result)
		{
			float length = value.Normal.Length();
			float factor = 1.0f / length;
			Vector3.Multiply(ref value.Normal, factor, out result.Normal);
			result.D = value.D * factor;
		}

		/// <summary>
		/// Transforms a normalized plane by a matrix.
		/// </summary>
		/// <param name="plane">The normalized plane to transform.</param>
		/// <param name="matrix">The transformation matrix.</param>
		/// <returns>The transformed plane.</returns>
		public static Plane Transform(Plane plane, Matrix4x4 matrix)
		{
			Plane result;
			Transform(ref plane, ref matrix, out result);
			return result;
		}

		/// <summary>
		/// Transforms a normalized plane by a matrix.
		/// </summary>
		/// <param name="plane">The normalized plane to transform.</param>
		/// <param name="matrix">The transformation matrix.</param>
		/// <param name="result">The transformed plane.</param>
		public static void Transform(
			ref Plane plane,
			ref Matrix4x4 matrix,
			out Plane result
		)
		{
			/* See "Transforming Normals" in
			 * http://www.glprogramming.com/red/appendixf.html
			 * for an explanation of how this works.
			 */
			Matrix4x4 transformedMatrix;
			Matrix4x4.Invert(ref matrix, out transformedMatrix);
			Matrix4x4.Transpose(
				ref transformedMatrix,
				out transformedMatrix
			);
			Vector4 vector = new Vector4(plane.Normal, plane.D);
			Vector4 transformedVector;
			Vector4.Transform(
				ref vector,
				ref transformedMatrix,
				out transformedVector
			);
			result = new Plane(transformedVector);
		}

		/// <summary>
		/// Transforms a normalized plane by a quaternion rotation.
		/// </summary>
		/// <param name="plane">The normalized plane to transform.</param>
		/// <param name="rotation">The quaternion rotation.</param>
		/// <returns>The transformed plane.</returns>
		public static Plane Transform(Plane plane, Quaternion rotation)
		{
			Plane result;
			Transform(ref plane, ref rotation, out result);
			return result;
		}

		/// <summary>
		/// Transforms a normalized plane by a quaternion rotation.
		/// </summary>
		/// <param name="plane">The normalized plane to transform.</param>
		/// <param name="rotation">The quaternion rotation.</param>
		/// <param name="result">The transformed plane.</param>
		public static void Transform(
			ref Plane plane,
			ref Quaternion rotation,
			out Plane result
		)
		{
			Vector3.Transform(
				ref plane.Normal,
				ref rotation,
				out result.Normal
			);
			result.D = plane.D;
		}

		#endregion

		#region Public Static Operators and Override Methods

		public static bool operator !=(Plane plane1, Plane plane2)
		{
			return !plane1.Equals(plane2);
		}

		public static bool operator ==(Plane plane1, Plane plane2)
		{
			return plane1.Equals(plane2);
		}

		public override bool Equals(object obj)
		{
			return (obj is Plane) && this.Equals((Plane) obj);
		}

		public bool Equals(Plane other)
		{
			return (Normal == other.Normal && D == other.D);
		}

		public override int GetHashCode()
		{
			return Normal.GetHashCode() ^ D.GetHashCode();
		}

		public override string ToString()
		{
			return (
				"{Normal:" + Normal.ToString() +
				" D:" + D.ToString() +
				"}"
			);
		}

		#endregion
	}
}
