﻿using System;

namespace Assets.BAndCCore.CustomTypes
{
	[Serializable]
	public partial struct Vector3Decimal : IFormattable
	{
		//------------------------------------------------------
		//
		//  Public Methods
		//
		//------------------------------------------------------

		#region Public Methods

		/// <summary>
		/// Compares two Vector3D instances for exact equality.
		/// Note that decimal values can acquire error when operated upon, such that
		/// an exact comparison between two values which are logically equal may fail.
		/// Furthermore, using this equality operator, Decimal.NaN is not equal to itself.
		/// </summary>
		/// <returns>
		/// bool - true if the two Vector3D instances are exactly equal, false otherwise
		/// </returns>
		/// <param name='vector1'>The first Vector3D to compare</param>
		/// <param name='vector2'>The second Vector3D to compare</param>
		public static bool operator ==(Vector3Decimal vector1, Vector3Decimal vector2)
		{
			return vector1.X == vector2.X &&
				   vector1.Y == vector2.Y &&
				   vector1.Z == vector2.Z;
		}

		/// <summary>
		/// Compares two Vector3D instances for exact inequality.
		/// Note that decimal values can acquire error when operated upon, such that
		/// an exact comparison between two values which are logically equal may fail.
		/// Furthermore, using this equality operator, Decimal.NaN is not equal to itself.
		/// </summary>
		/// <returns>
		/// bool - true if the two Vector3D instances are exactly unequal, false otherwise
		/// </returns>
		/// <param name='vector1'>The first Vector3D to compare</param>
		/// <param name='vector2'>The second Vector3D to compare</param>
		public static bool operator !=(Vector3Decimal vector1, Vector3Decimal vector2)
		{
			return !(vector1 == vector2);
		}

		/// <summary>
		/// Compares two Vector3D instances for object equality.  In this equality
		/// Decimal.NaN is equal to itself, unlike in numeric equality.
		/// Note that decimal values can acquire error when operated upon, such that
		/// an exact comparison between two values which
		/// are logically equal may fail.
		/// </summary>
		/// <returns>
		/// bool - true if the two Vector3D instances are exactly equal, false otherwise
		/// </returns>
		/// <param name='vector1'>The first Vector3D to compare</param>
		/// <param name='vector2'>The second Vector3D to compare</param>
		public static bool Equals(Vector3Decimal vector1, Vector3Decimal vector2)
		{
			return vector1.X.Equals(vector2.X) &&
				   vector1.Y.Equals(vector2.Y) &&
				   vector1.Z.Equals(vector2.Z);
		}

		/// <summary>
		/// Equals - compares this Vector3D with the passed in object.  In this equality
		/// Decimal.NaN is equal to itself, unlike in numeric equality.
		/// Note that decimal values can acquire error when operated upon, such that
		/// an exact comparison between two values which
		/// are logically equal may fail.
		/// </summary>
		/// <returns>
		/// bool - true if the object is an instance of Vector3D and if it's equal to "this".
		/// </returns>
		/// <param name='o'>The object to compare to "this"</param>
		public override bool Equals(object o)
		{
			if ((null == o) || !(o is Vector3Decimal))
			{
				return false;
			}

			Vector3Decimal value = (Vector3Decimal)o;
			return Vector3Decimal.Equals(this, value);
		}

		/// <summary>
		/// Equals - compares this Vector3D with the passed in object.  In this equality
		/// Decimal.NaN is equal to itself, unlike in numeric equality.
		/// Note that decimal values can acquire error when operated upon, such that
		/// an exact comparison between two values which
		/// are logically equal may fail.
		/// </summary>
		/// <returns>
		/// bool - true if "value" is equal to "this".
		/// </returns>
		/// <param name='value'>The Vector3D to compare to "this"</param>
		public bool Equals(Vector3Decimal value)
		{
			return Vector3Decimal.Equals(this, value);
		}

		/// <summary>
		/// Returns the HashCode for this Vector3D
		/// </summary>
		/// <returns>
		/// int - the HashCode for this Vector3D
		/// </returns>
		public override int GetHashCode()
		{
			// Perform field-by-field XOR of HashCodes
			return X.GetHashCode() ^
				   Y.GetHashCode() ^
				   Z.GetHashCode();
		}

		#endregion Public Methods

		//------------------------------------------------------
		//
		//  Public Properties
		//
		//------------------------------------------------------

		#region Public Properties

		/// <summary>
		///     X - decimal.  Default value is 0.
		/// </summary>
		public decimal X
		{
			get
			{
				return _x;
			}

			set
			{
				_x = value;
			}
		}

		/// <summary>
		///     Y - decimal.  Default value is 0.
		/// </summary>
		public decimal Y
		{
			get
			{
				return _y;
			}

			set
			{
				_y = value;
			}
		}

		/// <summary>
		///     Z - decimal.  Default value is 0.
		/// </summary>
		public decimal Z
		{
			get
			{
				return _z;
			}

			set
			{
				_z = value;
			}
		}

		#endregion Public Properties

		//------------------------------------------------------
		//
		//  Protected Methods
		//
		//------------------------------------------------------

		//------------------------------------------------------
		//
		//  Internal Methods
		//
		//------------------------------------------------------

		// x - a number, from which we need to calculate the square root
		// epsilon - an accuracy of calculation of the root from our number.
		// The result of the calculations will differ from an actual value
		// of the root on less than epsilon.
		public static decimal Sqrt(decimal x, decimal epsilon = 0.0M)
		{
			if (x < 0) throw new OverflowException("Cannot calculate square root from a negative number");

			decimal current = (decimal)Math.Sqrt((double)x), previous;
			do
			{
				previous = current;
				if (previous == 0.0M) return 0;
				current = (previous + x / previous) / 2;
			}
			while (Math.Abs(previous - current) > epsilon);
			return current;
		}

		//------------------------------------------------------
		//
		//  Internal Properties
		//
		//------------------------------------------------------

		#region Internal Properties

		/// <summary>
		/// Creates a string representation of this object based on the current culture.
		/// </summary>
		/// <returns>
		/// A string representation of this object.
		/// </returns>
		public override string ToString()
		{
			// Delegate to the internal method which implements all ToString calls.
			return ConvertToString(null /* format string */, null /* format provider */);
		}

		/// <summary>
		/// Creates a string representation of this object based on the IFormatProvider
		/// passed in.  If the provider is null, the CurrentCulture is used.
		/// </summary>
		/// <returns>
		/// A string representation of this object.
		/// </returns>
		public string ToString(IFormatProvider provider)
		{
			// Delegate to the internal method which implements all ToString calls.
			return ConvertToString(null /* format string */, provider);
		}

		/// <summary>
		/// Creates a string representation of this object based on the format string
		/// and IFormatProvider passed in.
		/// If the provider is null, the CurrentCulture is used.
		/// See the documentation for IFormattable for more information.
		/// </summary>
		/// <returns>
		/// A string representation of this object.
		/// </returns>
		string IFormattable.ToString(string format, IFormatProvider provider)
		{
			// Delegate to the internal method which implements all ToString calls.
			return ConvertToString(format, provider);
		}

		/// <summary>
		/// Creates a string representation of this object based on the format string
		/// and IFormatProvider passed in.
		/// If the provider is null, the CurrentCulture is used.
		/// See the documentation for IFormattable for more information.
		/// </summary>
		/// <returns>
		/// A string representation of this object.
		/// </returns>
		internal string ConvertToString(string format, IFormatProvider provider)
		{
			// Helper to get the numeric list separator for a given culture.
			char separator = ',';
			return String.Format(provider,
								 "{1:" + format + "}{0}{2:" + format + "}{0}{3:" + format + "}",
								 separator,
								 _x,
								 _y,
								 _z);
		}

		#endregion Internal Properties

		//------------------------------------------------------
		//
		//  Dependency Properties
		//
		//------------------------------------------------------

		//------------------------------------------------------
		//
		//  Internal Fields
		//
		//------------------------------------------------------

		#region Internal Fields

		private decimal _x;
		private decimal _y;
		private decimal _z;

		#endregion Internal Fields

		#region Constructors

		//------------------------------------------------------
		//
		//  Constructors
		//
		//------------------------------------------------------

		#endregion Constructors
	}

	/// <summary>
	/// Vector3D - 3D vector representation.
	/// </summary>
	public partial struct Vector3Decimal
	{
		//------------------------------------------------------
		//
		//  Constructors
		//
		//------------------------------------------------------

		#region Constructors

		/// <summary>
		/// Constructor that sets vector's initial values.
		/// </summary>
		/// <param name="x">Value of the X coordinate of the new vector.</param>
		/// <param name="y">Value of the Y coordinate of the new vector.</param>
		/// <param name="z">Value of the Z coordinate of the new vector.</param>
		public Vector3Decimal(decimal x, decimal y, decimal z)
		{
			_x = x;
			_y = y;
			_z = z;
		}

		#endregion Constructors

		//------------------------------------------------------
		//
		//  Public Methods
		//
		//------------------------------------------------------

		#region Public Methods

		/// <summary>
		/// Length of the vector.
		/// </summary>
		public decimal Length
		{
			get
			{
				return Sqrt(_x * _x + _y * _y + _z * _z);
			}
		}

		/// <summary>
		/// Length of the vector squared.
		/// </summary>
		public decimal LengthSquared
		{
			get
			{
				return _x * _x + _y * _y + _z * _z;
			}
		}

		/// <summary>
		/// Updates the vector to maintain its direction, but to have a length
		/// of 1. Equivalent to dividing the vector by its Length.
		/// Returns NaN if length is zero.
		/// </summary>
		public void Normalize()
		{
			// Computation of length can overflow easily because it
			// first computes squared length, so we first divide by
			// the largest coefficient.
			decimal m = Math.Abs(_x);
			decimal absy = Math.Abs(_y);
			decimal absz = Math.Abs(_z);
			if (absy > m)
			{
				m = absy;
			}
			if (absz > m)
			{
				m = absz;
			}

			_x /= m;
			_y /= m;
			_z /= m;

			decimal length = Sqrt(_x * _x + _y * _y + _z * _z);
			this /= length;
		}

		/// <summary>
		/// Computes the angle between two vectors.
		/// </summary>
		/// <param name="vector1">First vector.</param>
		/// <param name="vector2">Second vector.</param>
		/// <returns>
		/// Returns the angle required to rotate vector1 into vector2 in degrees.
		/// This will return a value between [0, 180] degrees.
		/// (Note that this is slightly different from the Vector member
		/// function of the same name.  Signed angles do not extend to 3D.)
		/// </returns>
		public static decimal AngleBetween(Vector3Decimal vector1, Vector3Decimal vector2)
		{
			vector1.Normalize();
			vector2.Normalize();

			decimal ratio = DotProduct(vector1, vector2);

			// The "straight forward" method of acos(u.v) has large precision
			// issues when the dot product is near +/-1.  This is due to the
			// steep slope of the acos function as we approach +/- 1.  Slight
			// precision errors in the dot product calculation cause large
			// variation in the output value.
			//
			//        |                   |
			//         \__                |
			//            ---___          |
			//                  ---___    |
			//                        ---_|_
			//                            | ---___
			//                            |       ---___
			//                            |             ---__
			//                            |                  \
			//                            |                   |
			//       -|-------------------+-------------------|-
			//       -1                   0                   1
			//
			//                         acos(x)
			//
			// To avoid this we use an alternative method which finds the
			// angle bisector by (u-v)/2:
			//
			//                            _>
			//                       u  _-  \ (u-v)/2
			//                        _-  __-v
			//                      _=__--
			//                    .=----------->
			//                            v
			//
			// Because u and v and unit vectors, (u-v)/2 forms a right angle
			// with the angle bisector.  The hypotenuse is 1, therefore
			// 2 * asin(|u-v|/2) gives us the angle between u and v.
			//
			// The largest possible value of |u-v| occurs with perpendicular
			// vectors and is sqrt(2)/2 which is well away from extreme slope
			// at +/-1.
			//
			// (See Windows OS Bug #1706299 for details)

			decimal theta;

			if (ratio < 0)
			{
				theta = (decimal)Math.PI - 2.0M * (decimal)Math.Asin((double)((-vector1 - vector2).Length / 2.0M));
			}
			else
			{
				theta = 2.0M * (decimal)Math.Asin((double)((vector1 - vector2).Length / 2.0M));
			}

			return theta * (180.0M / (decimal)Math.PI);
		}

		/// <summary>
		/// Operator -Vector (unary negation).
		/// </summary>
		/// <param name="vector">Vector being negated.</param>
		/// <returns>Negation of the given vector.</returns>
		public static Vector3Decimal operator -(Vector3Decimal vector)
		{
			return new Vector3Decimal(-vector._x, -vector._y, -vector._z);
		}

		/// <summary>
		/// Negates the values of X, Y, and Z on this Vector3D
		/// </summary>
		public void Negate()
		{
			_x = -_x;
			_y = -_y;
			_z = -_z;
		}

		/// <summary>
		/// Vector addition.
		/// </summary>
		/// <param name="vector1">First vector being added.</param>
		/// <param name="vector2">Second vector being added.</param>
		/// <returns>Result of addition.</returns>
		public static Vector3Decimal operator +(Vector3Decimal vector1, Vector3Decimal vector2)
		{
			return new Vector3Decimal(vector1._x + vector2._x,
								vector1._y + vector2._y,
								vector1._z + vector2._z);
		}

		/// <summary>
		/// Vector addition.
		/// </summary>
		/// <param name="vector1">First vector being added.</param>
		/// <param name="vector2">Second vector being added.</param>
		/// <returns>Result of addition.</returns>
		public static Vector3Decimal Add(Vector3Decimal vector1, Vector3Decimal vector2)
		{
			return new Vector3Decimal(vector1._x + vector2._x,
								vector1._y + vector2._y,
								vector1._z + vector2._z);
		}

		/// <summary>
		/// Vector subtraction.
		/// </summary>
		/// <param name="vector1">Vector that is subtracted from.</param>
		/// <param name="vector2">Vector being subtracted.</param>
		/// <returns>Result of subtraction.</returns>
		public static Vector3Decimal operator -(Vector3Decimal vector1, Vector3Decimal vector2)
		{
			return new Vector3Decimal(vector1._x - vector2._x,
								vector1._y - vector2._y,
								vector1._z - vector2._z);
		}

		/// <summary>
		/// Vector subtraction.
		/// </summary>
		/// <param name="vector1">Vector that is subtracted from.</param>
		/// <param name="vector2">Vector being subtracted.</param>
		/// <returns>Result of subtraction.</returns>
		public static Vector3Decimal Subtract(Vector3Decimal vector1, Vector3Decimal vector2)
		{
			return new Vector3Decimal(vector1._x - vector2._x,
								vector1._y - vector2._y,
								vector1._z - vector2._z);
		}

		/// <summary>
		/// Scalar multiplication.
		/// </summary>
		/// <param name="vector">Vector being multiplied.</param>
		/// <param name="scalar">Scalar value by which the vector is multiplied.</param>
		/// <returns>Result of multiplication.</returns>
		public static Vector3Decimal operator *(Vector3Decimal vector, decimal scalar)
		{
			return new Vector3Decimal(vector._x * scalar,
								vector._y * scalar,
								vector._z * scalar);
		}

		/// <summary>
		/// Scalar multiplication.
		/// </summary>
		/// <param name="vector">Vector being multiplied.</param>
		/// <param name="scalar">Scalar value by which the vector is multiplied.</param>
		/// <returns>Result of multiplication.</returns>
		public static Vector3Decimal Multiply(Vector3Decimal vector, decimal scalar)
		{
			return new Vector3Decimal(vector._x * scalar,
								vector._y * scalar,
								vector._z * scalar);
		}

		/// <summary>
		/// Scalar multiplication.
		/// </summary>
		/// <param name="scalar">Scalar value by which the vector is multiplied</param>
		/// <param name="vector">Vector being multiplied.</param>
		/// <returns>Result of multiplication.</returns>
		public static Vector3Decimal operator *(decimal scalar, Vector3Decimal vector)
		{
			return new Vector3Decimal(vector._x * scalar,
								vector._y * scalar,
								vector._z * scalar);
		}

		/// <summary>
		/// Scalar multiplication.
		/// </summary>
		/// <param name="scalar">Scalar value by which the vector is multiplied</param>
		/// <param name="vector">Vector being multiplied.</param>
		/// <returns>Result of multiplication.</returns>
		public static Vector3Decimal Multiply(decimal scalar, Vector3Decimal vector)
		{
			return new Vector3Decimal(vector._x * scalar,
								vector._y * scalar,
								vector._z * scalar);
		}

		/// <summary>
		/// Scalar division.
		/// </summary>
		/// <param name="vector">Vector being divided.</param>
		/// <param name="scalar">Scalar value by which we divide the vector.</param>
		/// <returns>Result of division.</returns>
		public static Vector3Decimal operator /(Vector3Decimal vector, decimal scalar)
		{
			return vector * (1.0M / scalar);
		}

		/// <summary>
		/// Scalar division.
		/// </summary>
		/// <param name="vector">Vector being divided.</param>
		/// <param name="scalar">Scalar value by which we divide the vector.</param>
		/// <returns>Result of division.</returns>
		public static Vector3Decimal Divide(Vector3Decimal vector, decimal scalar)
		{
			return vector * (1.0M / scalar);
		}

		/// <summary>
		/// Vector dot product.
		/// </summary>
		/// <param name="vector1">First vector.</param>
		/// <param name="vector2">Second vector.</param>
		/// <returns>Dot product of two vectors.</returns>
		public static decimal DotProduct(Vector3Decimal vector1, Vector3Decimal vector2)
		{
			return DotProduct(ref vector1, ref vector2);
		}

		/// <summary>
		/// Faster internal version of DotProduct that avoids copies
		///
		/// vector1 and vector2 to a passed by ref for performance and ARE NOT MODIFIED
		/// </summary>
		internal static decimal DotProduct(ref Vector3Decimal vector1, ref Vector3Decimal vector2)
		{
			return vector1._x * vector2._x +
				   vector1._y * vector2._y +
				   vector1._z * vector2._z;
		}

		/// <summary>
		/// Vector cross product.
		/// </summary>
		/// <param name="vector1">First vector.</param>
		/// <param name="vector2">Second vector.</param>
		/// <returns>Cross product of two vectors.</returns>
		public static Vector3Decimal CrossProduct(Vector3Decimal vector1, Vector3Decimal vector2)
		{
			Vector3Decimal result;
			CrossProduct(ref vector1, ref vector2, out result);
			return result;
		}

		/// <summary>
		/// Faster internal version of CrossProduct that avoids copies
		///
		/// vector1 and vector2 to a passed by ref for performance and ARE NOT MODIFIED
		/// </summary>
		internal static void CrossProduct(ref Vector3Decimal vector1, ref Vector3Decimal vector2, out Vector3Decimal result)
		{
			result._x = vector1._y * vector2._z - vector1._z * vector2._y;
			result._y = vector1._z * vector2._x - vector1._x * vector2._z;
			result._z = vector1._x * vector2._y - vector1._y * vector2._x;
		}

		#endregion Public Methods
	}
}