﻿using System;
using UnityEngine;

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

		#region Public Methods

		/// <summary>
		/// Compares two Quaternion 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 Quaternion instances are exactly equal, false otherwise
		/// </returns>
		/// <param name='quaternion1'>The first Quaternion to compare</param>
		/// <param name='quaternion2'>The second Quaternion to compare</param>
		public static bool operator ==(QuaternionDecimal quaternion1, QuaternionDecimal quaternion2)
		{
			if (quaternion1.IsDistinguishedIdentity || quaternion2.IsDistinguishedIdentity)
			{
				return quaternion1.IsIdentity == quaternion2.IsIdentity;
			}
			else
			{
				return quaternion1.X == quaternion2.X &&
					   quaternion1.Y == quaternion2.Y &&
					   quaternion1.Z == quaternion2.Z &&
					   quaternion1.W == quaternion2.W;
			}
		}

		/// <summary>
		/// Compares two Quaternion 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 Quaternion instances are exactly unequal, false otherwise
		/// </returns>
		/// <param name='quaternion1'>The first Quaternion to compare</param>
		/// <param name='quaternion2'>The second Quaternion to compare</param>
		public static bool operator !=(QuaternionDecimal quaternion1, QuaternionDecimal quaternion2)
		{
			return !(quaternion1 == quaternion2);
		}

		/// <summary>
		/// Compares two Quaternion 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 Quaternion instances are exactly equal, false otherwise
		/// </returns>
		/// <param name='quaternion1'>The first Quaternion to compare</param>
		/// <param name='quaternion2'>The second Quaternion to compare</param>
		public static bool Equals(QuaternionDecimal quaternion1, QuaternionDecimal quaternion2)
		{
			if (quaternion1.IsDistinguishedIdentity || quaternion2.IsDistinguishedIdentity)
			{
				return quaternion1.IsIdentity == quaternion2.IsIdentity;
			}
			else
			{
				return quaternion1.X.Equals(quaternion2.X) &&
					   quaternion1.Y.Equals(quaternion2.Y) &&
					   quaternion1.Z.Equals(quaternion2.Z) &&
					   quaternion1.W.Equals(quaternion2.W);
			}
		}

		/// <summary>
		/// Equals - compares this Quaternion 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 Quaternion 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 QuaternionDecimal))
			{
				return false;
			}

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

		/// <summary>
		/// Equals - compares this Quaternion 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 Quaternion to compare to "this"</param>
		public bool Equals(QuaternionDecimal value)
		{
			return QuaternionDecimal.Equals(this, value);
		}

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

		#endregion Public Methods

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

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

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

		//------------------------------------------------------
		//
		//  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)
		{
			if (IsIdentity)
			{
				return "Identity";
			}

			// 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 + "}{0}{4:" + format + "}",
								 separator,
								 _x,
								 _y,
								 _z,
								 _w);
		}

		#endregion Internal Properties

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

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

		#region Constructors

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

		#endregion Constructors
	}

	/// <summary>
	/// Quaternions.
	/// Quaternions are distinctly 3D entities that represent rotation in three dimensions.
	/// Their power comes in being able to interpolate (and thus animate) between
	/// quaternions to achieve a smooth, reliable interpolation.
	/// The default quaternion is the identity.
	/// </summary>
	public partial struct QuaternionDecimal : IFormattable
	{
		//------------------------------------------------------
		//
		//  Constructors
		//
		//------------------------------------------------------

		#region Constructors

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

		/// <summary>
		/// Constructs a quaternion via specified axis of rotation and an angle.
		/// Throws an InvalidOperationException if given (0,0,0) as axis vector.
		/// </summary>
		/// <param name="axisOfRotation">Vector representing axis of rotation.</param>
		/// <param name="angleInDegrees">Angle to turn around the given axis (in degrees).</param>
		public QuaternionDecimal(Vector3Decimal axisOfRotation, decimal angleInDegrees)
		{
			angleInDegrees %= 360.0M; // Doing the modulo before converting to radians reduces total error
			decimal angleInRadians = angleInDegrees * ((decimal)Math.PI / 180.0M);
			decimal length = axisOfRotation.Length;
			if (length == 0)
			{
				Debug.LogError("Quaternion length is 0!");
				//throw new System.InvalidOperationException(SR.Get(SRID.Quaternion_ZeroAxisSpecified));
				_x = 0;
				_y = 0;
				_z = 0;
				_w = 0;
				_isNotDistinguishedIdentity = true;
			}
			else
			{
				Vector3Decimal v = (axisOfRotation / length) * (decimal)Math.Sin(0.5 * (double)angleInRadians);
				_x = v.X;
				_y = v.Y;
				_z = v.Z;
				_w = (decimal)Math.Cos(0.5 * (double)angleInRadians);
				_isNotDistinguishedIdentity = true;
			}
		}

		#endregion Constructors

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

		#region Public Methods

		/// <summary>
		///     Identity quaternion
		/// </summary>
		public static QuaternionDecimal Identity
		{
			get
			{
				return s_identity;
			}
		}

		/// <summary>
		/// Retrieves quaternion's axis.
		/// </summary>
		public Vector3Decimal Axis
		{
			// q = M [cos(Q/2), sin(Q /2)v]
			// axis = sin(Q/2)v
			// angle = cos(Q/2)
			// M is magnitude
			get
			{
				// Handle identity (where axis is indeterminate) by
				// returning arbitrary axis.
				if (IsDistinguishedIdentity || (_x == 0 && _y == 0 && _z == 0))
				{
					return new Vector3Decimal(0, 1, 0);
				}
				else
				{
					Vector3Decimal v = new Vector3Decimal(_x, _y, _z);
					v.Normalize();
					return v;
				}
			}
		}

		/// <summary>
		/// Retrieves quaternion's angle.
		/// </summary>
		public decimal Angle
		{
			get
			{
				if (IsDistinguishedIdentity)
				{
					return 0;
				}

				// Magnitude of quaternion times sine and cosine
				decimal msin = Vector3Decimal.Sqrt(_x * _x + _y * _y + _z * _z);
				decimal mcos = _w;

				if (!(msin <= decimal.MaxValue))
				{
					// Overflowed probably in squaring, so let's scale
					// the values.  We don't need to include _w in the
					// scale factor because we're not going to square
					// it.
					decimal maxcoeff = Math.Max(Math.Abs(_x), Math.Max(Math.Abs(_y), Math.Abs(_z)));
					decimal x = _x / maxcoeff;
					decimal y = _y / maxcoeff;
					decimal z = _z / maxcoeff;
					msin = Vector3Decimal.Sqrt(x * x + y * y + z * z);
					// Scale mcos too.
					mcos = _w / maxcoeff;
				}

				// Atan2 is better than acos.  (More precise and more efficient.)
				return (decimal)Math.Atan2((double)msin, (double)mcos) * (360.0M / (decimal)Math.PI);
			}
		}

		/// <summary>
		/// Returns whether the quaternion is normalized (i.e. has a magnitude of 1).
		/// </summary>
		public bool IsNormalized
		{
			get
			{
				if (IsDistinguishedIdentity)
				{
					return true;
				}
				decimal norm2 = _x * _x + _y * _y + _z * _z + _w * _w;
				return Math.Abs(norm2 - 1.0M) == 0 ? true : false;
			}
		}

		/// <summary>
		/// Tests whether or not a given quaternion is an identity quaternion.
		/// </summary>
		public bool IsIdentity
		{
			get
			{
				return IsDistinguishedIdentity || (_x == 0 && _y == 0 && _z == 0 && _w == 1);
			}
		}

		/// <summary>
		/// Replaces quaternion with its conjugate
		/// </summary>
		public void Conjugate()
		{
			if (IsDistinguishedIdentity)
			{
				return;
			}

			// Conjugate([x,y,z,w]) = [-x,-y,-z,w]
			_x = -_x;
			_y = -_y;
			_z = -_z;
		}

		/// <summary>
		/// Replaces quaternion with its inverse
		/// </summary>
		public void Invert()
		{
			if (IsDistinguishedIdentity)
			{
				return;
			}

			// Inverse = Conjugate / Norm Squared
			Conjugate();
			decimal norm2 = _x * _x + _y * _y + _z * _z + _w * _w;
			_x /= norm2;
			_y /= norm2;
			_z /= norm2;
			_w /= norm2;
		}

		/// <summary>
		/// Normalizes this quaternion.
		/// </summary>
		public void Normalize()
		{
			if (IsDistinguishedIdentity)
			{
				return;
			}

			decimal norm2 = _x * _x + _y * _y + _z * _z + _w * _w;
			if (norm2 > Decimal.MaxValue)
			{
				// Handle overflow in computation of norm2
				decimal rmax = 1.0M / Max(Math.Abs(_x),
									  Math.Abs(_y),
									  Math.Abs(_z),
									  Math.Abs(_w));

				_x *= rmax;
				_y *= rmax;
				_z *= rmax;
				_w *= rmax;
				norm2 = _x * _x + _y * _y + _z * _z + _w * _w;
			}
			decimal normInverse = 1.0M / Vector3Decimal.Sqrt(norm2);
			_x *= normInverse;
			_y *= normInverse;
			_z *= normInverse;
			_w *= normInverse;
		}

		/// <summary>
		/// Quaternion addition.
		/// </summary>
		/// <param name="left">First quaternion being added.</param>
		/// <param name="right">Second quaternion being added.</param>
		/// <returns>Result of addition.</returns>
		public static QuaternionDecimal operator +(QuaternionDecimal left, QuaternionDecimal right)
		{
			if (right.IsDistinguishedIdentity)
			{
				if (left.IsDistinguishedIdentity)
				{
					return new QuaternionDecimal(0, 0, 0, 2);
				}
				else
				{
					// We know left is not distinguished identity here.
					left._w += 1;
					return left;
				}
			}
			else if (left.IsDistinguishedIdentity)
			{
				// We know right is not distinguished identity here.
				right._w += 1;
				return right;
			}
			else
			{
				return new QuaternionDecimal(left._x + right._x,
									  left._y + right._y,
									  left._z + right._z,
									  left._w + right._w);
			}
		}

		/// <summary>
		/// Quaternion addition.
		/// </summary>
		/// <param name="left">First quaternion being added.</param>
		/// <param name="right">Second quaternion being added.</param>
		/// <returns>Result of addition.</returns>
		public static QuaternionDecimal Add(QuaternionDecimal left, QuaternionDecimal right)
		{
			return (left + right);
		}

		/// <summary>
		/// Quaternion subtraction.
		/// </summary>
		/// <param name="left">Quaternion to subtract from.</param>
		/// <param name="right">Quaternion to subtract from the first quaternion.</param>
		/// <returns>Result of subtraction.</returns>
		public static QuaternionDecimal operator -(QuaternionDecimal left, QuaternionDecimal right)
		{
			if (right.IsDistinguishedIdentity)
			{
				if (left.IsDistinguishedIdentity)
				{
					return new QuaternionDecimal(0, 0, 0, 0);
				}
				else
				{
					// We know left is not distinguished identity here.
					left._w -= 1;
					return left;
				}
			}
			else if (left.IsDistinguishedIdentity)
			{
				// We know right is not distinguished identity here.
				return new QuaternionDecimal(-right._x, -right._y, -right._z, 1 - right._w);
			}
			else
			{
				return new QuaternionDecimal(left._x - right._x,
									  left._y - right._y,
									  left._z - right._z,
									  left._w - right._w);
			}
		}

		/// <summary>
		/// Quaternion subtraction.
		/// </summary>
		/// <param name="left">Quaternion to subtract from.</param>
		/// <param name="right">Quaternion to subtract from the first quaternion.</param>
		/// <returns>Result of subtraction.</returns>
		public static QuaternionDecimal Subtract(QuaternionDecimal left, QuaternionDecimal right)
		{
			return (left - right);
		}

		/// <summary>
		/// Quaternion multiplication.
		/// </summary>
		/// <param name="left">First quaternion.</param>
		/// <param name="right">Second quaternion.</param>
		/// <returns>Result of multiplication.</returns>
		public static QuaternionDecimal operator *(QuaternionDecimal left, QuaternionDecimal right)
		{
			if (left.IsDistinguishedIdentity)
			{
				return right;
			}
			if (right.IsDistinguishedIdentity)
			{
				return left;
			}

			decimal x = left._w * right._x + left._x * right._w + left._y * right._z - left._z * right._y;
			decimal y = left._w * right._y + left._y * right._w + left._z * right._x - left._x * right._z;
			decimal z = left._w * right._z + left._z * right._w + left._x * right._y - left._y * right._x;
			decimal w = left._w * right._w - left._x * right._x - left._y * right._y - left._z * right._z;
			QuaternionDecimal result = new QuaternionDecimal(x, y, z, w);
			return result;
		}

		/// <summary>
		/// Quaternion multiplication.
		/// </summary>
		/// <param name="left">First quaternion.</param>
		/// <param name="right">Second quaternion.</param>
		/// <returns>Result of multiplication.</returns>
		public static QuaternionDecimal Multiply(QuaternionDecimal left, QuaternionDecimal right)
		{
			return left * right;
		}

		/// <summary>
		/// Scale this quaternion by a scalar.
		/// </summary>
		/// <param name="scale">Value to scale by.</param>
		private void Scale(decimal scale)
		{
			if (IsDistinguishedIdentity)
			{
				_w = scale;
				IsDistinguishedIdentity = false;
				return;
			}
			_x *= scale;
			_y *= scale;
			_z *= scale;
			_w *= scale;
		}

		/// <summary>
		/// Return length of quaternion.
		/// </summary>
		private decimal Length()
		{
			if (IsDistinguishedIdentity)
			{
				return 1;
			}

			decimal norm2 = _x * _x + _y * _y + _z * _z + _w * _w;
			if (!(norm2 <= Decimal.MaxValue))
			{
				// Do this the slow way to avoid squaring large
				// numbers since the length of many quaternions is
				// representable even if the squared length isn't.  Of
				// course some lengths aren't representable because
				// the length can be up to twice as big as the largest
				// coefficient.

				decimal max = Math.Max(Math.Max(Math.Abs(_x), Math.Abs(_y)),
									  Math.Max(Math.Abs(_z), Math.Abs(_w)));

				decimal x = _x / max;
				decimal y = _y / max;
				decimal z = _z / max;
				decimal w = _w / max;

				decimal smallLength = Vector3Decimal.Sqrt(x * x + y * y + z * z + w * w);
				// Return length of this smaller vector times the scale we applied originally.
				return smallLength * max;
			}
			return Vector3Decimal.Sqrt(norm2);
		}

		/// <summary>
		/// Smoothly interpolate between the two given quaternions using Spherical
		/// Linear Interpolation (SLERP).
		/// </summary>
		/// <param name="from">First quaternion for interpolation.</param>
		/// <param name="to">Second quaternion for interpolation.</param>
		/// <param name="t">Interpolation coefficient.</param>
		/// <returns>SLERP-interpolated quaternion between the two given quaternions.</returns>
		public static QuaternionDecimal Slerp(QuaternionDecimal from, QuaternionDecimal to, decimal t)
		{
			return Slerp(from, to, t, /* useShortestPath = */ true);
		}

		/// <summary>
		/// Smoothly interpolate between the two given quaternions using Spherical
		/// Linear Interpolation (SLERP).
		/// </summary>
		/// <param name="from">First quaternion for interpolation.</param>
		/// <param name="to">Second quaternion for interpolation.</param>
		/// <param name="t">Interpolation coefficient.</param>
		/// <param name="useShortestPath">If true, Slerp will automatically flip the sign of
		///     the destination Quaternion to ensure the shortest path is taken.</param>
		/// <returns>SLERP-interpolated quaternion between the two given quaternions.</returns>
		public static QuaternionDecimal Slerp(QuaternionDecimal from, QuaternionDecimal to, decimal t, bool useShortestPath)
		{
			if (from.IsDistinguishedIdentity)
			{
				from._w = 1;
			}
			if (to.IsDistinguishedIdentity)
			{
				to._w = 1;
			}

			decimal cosOmega;
			decimal scaleFrom, scaleTo;

			// Normalize inputs and stash their lengths
			decimal lengthFrom = from.Length();
			decimal lengthTo = to.Length();
			from.Scale(1 / lengthFrom);
			to.Scale(1 / lengthTo);

			// Calculate cos of omega.
			cosOmega = from._x * to._x + from._y * to._y + from._z * to._z + from._w * to._w;

			if (useShortestPath)
			{
				// If we are taking the shortest path we flip the signs to ensure that
				// cosOmega will be positive.
				if (cosOmega < 0.0M)
				{
					cosOmega = -cosOmega;
					to._x = -to._x;
					to._y = -to._y;
					to._z = -to._z;
					to._w = -to._w;
				}
			}
			else
			{
				// If we are not taking the UseShortestPath we clamp cosOmega to
				// -1 to stay in the domain of Math.Acos below.
				if (cosOmega < -1.0M)
				{
					cosOmega = -1.0M;
				}
			}

			// Clamp cosOmega to [-1,1] to stay in the domain of Math.Acos below.
			// The logic above has either flipped the sign of cosOmega to ensure it
			// is positive or clamped to -1 already.  We only need to worry about the
			// upper limit here.
			if (cosOmega > 1.0M)
			{
				cosOmega = 1.0M;
			}

			Debug.Assert(!(cosOmega < -1.0M) && !(cosOmega > 1.0M),
				"cosOmega should be clamped to [-1,1]");

			// The mainline algorithm doesn't work for extreme
			// cosine values.  For large cosine we have a better
			// fallback hence the asymmetric limits.
			const decimal maxCosine = 1.0M - 1e-6M;
			const decimal minCosine = 1e-10M - 1.0M;

			// Calculate scaling coefficients.
			if (cosOmega > maxCosine)
			{
				// Quaternions are too close - use linear interpolation.
				scaleFrom = 1.0M - t;
				scaleTo = t;
			}
			else if (cosOmega < minCosine)
			{
				// Quaternions are nearly opposite, so we will pretend to
				// is exactly -from.
				// First assign arbitrary perpendicular to "to".
				to = new QuaternionDecimal(-from.Y, from.X, -from.W, from.Z);

				decimal theta = t * (decimal)Math.PI;

				scaleFrom = (decimal)Math.Cos((double)theta);
				scaleTo = (decimal)Math.Sin((double)theta);
			}
			else
			{
				// Standard case - use SLERP interpolation.
				decimal omega = (decimal)Math.Acos((double)cosOmega);
				decimal sinOmega = (decimal)Math.Sqrt((double)(1.0M - cosOmega * cosOmega));
				scaleFrom = (decimal)Math.Sin((double)((1.0M - t) * omega)) / sinOmega;
				scaleTo = (decimal)Math.Sin((double)(t * omega)) / sinOmega;
			}

			// We want the magnitude of the output quaternion to be
			// multiplicatively interpolated between the input
			// magnitudes, i.e. lengthOut = lengthFrom * (lengthTo/lengthFrom)^t
			//                            = lengthFrom ^ (1-t) * lengthTo ^ t

			decimal lengthOut = lengthFrom * (decimal)Math.Pow((double)(lengthTo / lengthFrom), (double)t);
			scaleFrom *= lengthOut;
			scaleTo *= lengthOut;

			return new QuaternionDecimal(scaleFrom * from._x + scaleTo * to._x,
								  scaleFrom * from._y + scaleTo * to._y,
								  scaleFrom * from._z + scaleTo * to._z,
								  scaleFrom * from._w + scaleTo * to._w);
		}

		#endregion Public Methods

		#region Private Methods

		static private decimal Max(decimal a, decimal b, decimal c, decimal d)
		{
			if (b > a)
				a = b;
			if (c > a)
				a = c;
			if (d > a)
				a = d;
			return a;
		}

		#endregion Private Methods

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

		#region Public Properties

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

			set
			{
				if (IsDistinguishedIdentity)
				{
					this = s_identity;
					IsDistinguishedIdentity = false;
				}
				_x = value;
			}
		}

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

			set
			{
				if (IsDistinguishedIdentity)
				{
					this = s_identity;
					IsDistinguishedIdentity = false;
				}
				_y = value;
			}
		}

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

			set
			{
				if (IsDistinguishedIdentity)
				{
					this = s_identity;
					IsDistinguishedIdentity = false;
				}
				_z = value;
			}
		}

		/// <summary>
		/// W - Default value is 1.
		/// </summary>
		public decimal W
		{
			get
			{
				if (IsDistinguishedIdentity)
				{
					return 1.0M;
				}
				else
				{
					return _w;
				}
			}

			set
			{
				if (IsDistinguishedIdentity)
				{
					this = s_identity;
					IsDistinguishedIdentity = false;
				}
				_w = value;
			}
		}

		#endregion Public Properties

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

		#region Internal Fields

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

		#endregion Internal Fields

		#region Private Fields and Properties

		// If this bool is false then we are a default quaternion with
		// all decimals equal to zero, but should be treated as
		// identity.
		private bool _isNotDistinguishedIdentity;

		private bool IsDistinguishedIdentity
		{
			get
			{
				return !_isNotDistinguishedIdentity;
			}
			set
			{
				_isNotDistinguishedIdentity = !value;
			}
		}

		private static int GetIdentityHashCode()
		{
			// This code is called only once.
			decimal zero = 0;
			decimal one = 1;
			// return zero.GetHashCode() ^ zero.GetHashCode() ^ zero.GetHashCode() ^ one.GetHashCode();
			// But this expression can be simplified because the first two hash codes cancel.
			return zero.GetHashCode() ^ one.GetHashCode();
		}

		private static QuaternionDecimal GetIdentity()
		{
			// This code is called only once.
			QuaternionDecimal q = new QuaternionDecimal(0, 0, 0, 1);
			q.IsDistinguishedIdentity = true;
			return q;
		}

		// Hash code for identity.
		private static int c_identityHashCode = GetIdentityHashCode();

		// Default identity
		private static QuaternionDecimal s_identity = GetIdentity();

		#endregion Private Fields and Properties
	}
}