/*
 * Copyright (C) 2023  Christopher J. Howard
 *
 * This file is part of Antkeeper source code.
 *
 * Antkeeper source code 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.
 *
 * Antkeeper source code 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 Antkeeper source code.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef ANTKEEPER_MATH_QUATERNION_HPP
#define ANTKEEPER_MATH_QUATERNION_HPP

#include <engine/math/numbers.hpp>
#include <engine/math/matrix.hpp>
#include <engine/math/vector.hpp>
#include <cmath>

namespace math {

/**
 * Quaternion composed of a real scalar part and imaginary vector part.
 *
 * @tparam T Scalar type.
 */
template <class T>
struct quaternion
{
	/// Scalar type.
	using scalar_type = T;
	
	/// Vector type.
	using vector_type = vector<T, 3>;
	
	/// Rotation matrix type.
	using matrix_type = matrix<T, 3, 3>;
	
	/// Quaternion real part.
	scalar_type r;
	
	/// Quaternion imaginary part.
	vector_type i;
	
	/// Returns a reference to the quaternion real part.
	/// @{
	[[nodiscard]] inline constexpr scalar_type& w() noexcept
	{
		return r;
	}
	[[nodiscard]] inline constexpr const scalar_type& w() const noexcept
	{
		return r;
	}
	/// @}
	
	/// Returns a reference to the first element of the quaternion imaginary part.
	/// @{
	[[nodiscard]] inline constexpr scalar_type& x() noexcept
	{
		return i.x();
	}
	[[nodiscard]] inline constexpr const scalar_type& x() const noexcept
	{
		return i.x();
	}
	/// @}
	
	/// Returns a reference to the second element of the quaternion imaginary part.
	/// @{
	[[nodiscard]] inline constexpr scalar_type& y() noexcept
	{
		return i.y();
	}
	[[nodiscard]] inline constexpr const scalar_type& y() const noexcept
	{
		return i.y();
	}
	/// @}
	
	/// Returns a reference to the third element of the quaternion imaginary part.
	/// @{
	[[nodiscard]] inline constexpr scalar_type& z() noexcept
	{
		return i.z();
	}
	[[nodiscard]] inline constexpr const scalar_type& z() const noexcept
	{
		return i.z();
	}
	/// @}
	
	/**
	 * Returns a quaternion representing a rotation about the x-axis.
	 *
	 * @param angle Angle of rotation, in radians.
	 *
	 * @return Quaternion representing an x-axis rotation.
	 */
	[[nodiscard]] static quaternion rotate_x(scalar_type angle)
	{
		return {std::cos(angle * T{0.5}), std::sin(angle * T{0.5}), T{0}, T{0}};
	}
	
	/**
	 * Returns a quaternion representing a rotation about the y-axis.
	 *
	 * @param angle Angle of rotation, in radians.
	 *
	 * @return Quaternion representing an y-axis rotation.
	 */
	[[nodiscard]] static quaternion rotate_y(scalar_type angle)
	{
		return {std::cos(angle * T{0.5}), T{0}, std::sin(angle * T{0.5}), T{0}};
	}
	
	/**
	 * Returns a quaternion representing a rotation about the z-axis.
	 *
	 * @param angle Angle of rotation, in radians.
	 * @return Quaternion representing an z-axis rotation.
	 */
	[[nodiscard]] static quaternion rotate_z(scalar_type angle)
	{
		return {std::cos(angle * T{0.5}), T{0}, T{0}, std::sin(angle * T{0.5})};
	}
	
	/**
	 * Type-casts the quaternion scalars using `static_cast`.
	 *
	 * @tparam U Target scalar type.
	 *
	 * @return Type-casted quaternion.
	 */
	template <class U>
	[[nodiscard]] inline constexpr explicit operator quaternion<U>() const noexcept
	{
		return {static_cast<U>(r), vector<U, 3>(i)};
	}
	
	/**
	 * Constructs a matrix representing the rotation described the quaternion.
	 *
	 * @return Rotation matrix.
	 */
	/// @{
	[[nodiscard]] constexpr explicit operator matrix_type() const noexcept
	{
		const T xx = x() * x();
		const T xy = x() * y();
		const T xz = x() * z();
		const T xw = x() * w();
		const T yy = y() * y();
		const T yz = y() * z();
		const T yw = y() * w();
		const T zz = z() * z();
		const T zw = z() * w();
		
		return
		{
			T{1} - (yy + zz) * T{2}, (xy + zw) * T{2}, (xz - yw) * T{2},
			(xy - zw) * T{2}, T{1} - (xx + zz) * T{2}, (yz + xw) * T{2},
			(xz + yw) * T{2}, (yz - xw) * T{2}, T{1} - (xx + yy) * T{2}
		};
	}
	
	[[nodiscard]] inline constexpr matrix_type matrix() const noexcept
	{
		return matrix_type(*this);
	}
	/// @}
	
	/**
	 * Casts the quaternion to a 4-element vector, with the real part as the first element and the imaginary part as the following three elements.
	 *
	 * @return Vector containing the real and imaginary parts of the quaternion.
	 */
	[[nodiscard]] inline constexpr explicit operator vector<T, 4>() const noexcept
	{
		return {r, i.x(), i.y(), i.z()};
	}
	
	/// Returns a zero quaternion, where every scalar is equal to zero.
	[[nodiscard]] static constexpr quaternion zero() noexcept
	{
		return {};
	}
	
	/// Returns a rotation identity quaternion.
	[[nodiscard]] static constexpr quaternion identity() noexcept
	{
		return {T{1}, vector_type::zero()};
	}
};

/**
 * Adds two quaternions.
 *
 * @param a First quaternion.
 * @param b Second quaternion.
 *
 * @return Sum of the two quaternions.
 */
template <class T>
[[nodiscard]] constexpr quaternion<T> add(const quaternion<T>& a, const quaternion<T>& b) noexcept;

/**
 * Adds a quaternion and a scalar.
 *
 * @param a First value.
 * @param b Second second value.
 *
 * @return Sum of the quaternion and scalar.
 */
template <class T>
[[nodiscard]] constexpr quaternion<T> add(const quaternion<T>& a, T b) noexcept;

/**
 * Calculates the conjugate of a quaternion.
 *
 * @param q Quaternion from which the conjugate will be calculated.
 *
 * @return Conjugate of the quaternion.
 */
template <class T>
[[nodiscard]] constexpr quaternion<T> conjugate(const quaternion<T>& q) noexcept;

/**
 * Calculates the dot product of two quaternions.
 *
 * @param a First quaternion.
 * @param b Second quaternion.
 *
 * @return Dot product of the two quaternions.
 */
template <class T>
[[nodiscard]] constexpr T dot(const quaternion<T>& a, const quaternion<T>& b) noexcept;

/**
 * Divides a quaternion by another quaternion.
 *
 * @param a First value.
 * @param b Second value.
 *
 * @return Result of the division.
 */
template <class T>
[[nodiscard]] constexpr quaternion<T> div(const quaternion<T>& a, const quaternion<T>& b) noexcept;

/**
 * Divides a quaternion by a scalar.
 *
 * @param a Quaternion.
 * @param b Scalar.
 *
 * @return Result of the division.
 */
template <class T>
[[nodiscard]] constexpr quaternion<T> div(const quaternion<T>& a, T b) noexcept;

/**
 * Divides a scalar by a quaternion.
 *
 * @param a Scalar.
 * @param b Quaternion.
 *
 * @return Result of the division.
 */
template <class T>
[[nodiscard]] constexpr quaternion<T> div(T a, const quaternion<T>& b) noexcept;

/**
 * Calculates the inverse length of a quaternion.
 * 
 * @param q Quaternion to calculate the inverse length of.
 *
 * @return Inverse length of the quaternion.
 */
template <class T>
[[nodiscard]] T inv_length(const quaternion<T>& q);

/**
 * Calculates the length of a quaternion.
 * 
 * @param q Quaternion to calculate the length of.
 *
 * @return Length of the quaternion.
 */
template <class T>
[[nodiscard]] T length(const quaternion<T>& q);

/**
 * Performs linear interpolation between two quaternions.
 *
 * @param a First quaternion.
 * @param b Second quaternion.
 * @param t Interpolation factor.
 *
 * @return Interpolated quaternion.
 */
template <class T>
[[nodiscard]] constexpr quaternion<T> lerp(const quaternion<T>& a, const quaternion<T>& b, T t) noexcept;

/**
 * Creates a unit quaternion rotation using forward and up vectors.
 *
 * @param forward Unit forward vector.
 * @param up Unit up vector.
 *
 * @return Unit rotation quaternion.
 */
template <class T>
[[nodiscard]] quaternion<T> look_rotation(const vector<T, 3>& forward, vector<T, 3> up);

/**
 * Multiplies two quaternions.
 *
 * @param a First quaternion.
 * @param b Second quaternion.
 *
 * @return Product of the two quaternions.
 */
template <class T>
[[nodiscard]] constexpr quaternion<T> mul(const quaternion<T>& a, const quaternion<T>& b) noexcept;

/**
 * Multiplies a quaternion by a scalar.
 *
 * @param a First value.
 * @param b Second value.
 *
 * @return Product of the quaternion and scalar.
 */
template <class T>
[[nodiscard]] constexpr quaternion<T> mul(const quaternion<T>& a, T b) noexcept;

/**
 * Rotates a vector by a unit quaternion.
 *
 * @param q Unit quaternion.
 * @param v Vector to rotate.
 *
 * @return Rotated vector.
 *
 * @warning @p q must be a unit quaternion.
 *
 * @see https://fgiesen.wordpress.com/2019/02/09/rotating-a-single-vector-using-a-quaternion/
 */
template <class T>
[[nodiscard]] constexpr vector<T, 3> mul(const quaternion<T>& q, const vector<T, 3>& v) noexcept;

/**
 * Rotates a vector by the inverse of a unit quaternion.
 *
 * @param v Vector to rotate.
 * @param q Unit quaternion.
 *
 * @return Rotated vector.
 *
 * @warning @p q must be a unit quaternion.
 */
template <class T>
[[nodiscard]] constexpr vector<T, 3> mul(const vector<T, 3>& v, const quaternion<T>& q) noexcept;

/**
 * Negates a quaternion.
 *
 * @param q Quaternion to negate.
 *
 * @return Negated quaternion.
 */
template <class T>
[[nodiscard]] constexpr quaternion<T> negate(const quaternion<T>& q) noexcept;

/**
 * Performs normalized linear interpolation between two quaternions.
 *
 * @param a First quaternion.
 * @param b Second quaternion.
 * @param t Interpolation factor.
 *
 * @return Interpolated quaternion.
 */
template <class T>
[[nodiscard]] quaternion<T> nlerp(const quaternion<T>& a, const quaternion<T>& b, T t);

/**
 * Normalizes a quaternion.
 *
 * @param q Quaternion to normalize.
 *
 * @return Unit quaternion.
 */
template <class T>
[[nodiscard]] quaternion<T> normalize(const quaternion<T>& q);

/**
 * Creates a rotation from an angle and axis.
 *
 * @param angle Angle of rotation (in radians).
 * @param axis Axis of rotation
 *
 * @return Quaternion representing the rotation.
 */
template <class T>
[[nodiscard]] quaternion<T> angle_axis(T angle, const vector<T, 3>& axis);

/**
 * Calculates a quaternion representing the minimum rotation from one direction to another directions.
 *
 * @param from Unit vector pointing in the source direction.
 * @param to Unit vector pointing in the target direction.
 * @param tolerance Floating-point tolerance.
 *
 * @return Unit quaternion representing the minimum rotation from direction @p from to direction @p to.
 *
 * @warning @p from and @p to must be unit vectors.
 */
template <class T>
[[nodiscard]] quaternion<T> rotation(const vector<T, 3>& from, const vector<T, 3>& to, T tolerance = T{1e-6});

/**
 * Performs spherical linear interpolation between two quaternions.
 *
 * @param a First quaternion.
 * @param b Second quaternion.
 * @param t Interpolation factor.
 * @param tolerance Floating-point tolerance.
 *
 * @return Interpolated quaternion.
 */
template <class T>
[[nodiscard]] quaternion<T> slerp(const quaternion<T>& a, const quaternion<T>& b, T t, T tolerance = T{1e-6});

/**
 * Calculates the square length of a quaternion. The square length can be calculated faster than the length because a call to `std::sqrt` is saved.
 * 
 * @param q Quaternion to calculate the square length of.
 *
 * @return Square length of the quaternion.
 */
template <class T>
[[nodiscard]] constexpr T sqr_length(const quaternion<T>& q) noexcept;

/**
 * Subtracts a quaternion from another quaternion.
 *
 * @param a First quaternion.
 * @param b Second quaternion.
 *
 * @return Difference between the quaternions.
 */
template <class T>
[[nodiscard]] constexpr quaternion<T> sub(const quaternion<T>& a, const quaternion<T>& b) noexcept;

/**
 * Subtracts a quaternion and a scalar.
 *
 * @param a First value.
 * @param b Second second.
 *
 * @return Difference between the quaternion and scalar.
 */
/// @{
template <class T>
[[nodiscard]] constexpr quaternion<T> sub(const quaternion<T>& a, T b) noexcept;
template <class T>
[[nodiscard]] constexpr quaternion<T> sub(T a, const quaternion<T>& b) noexcept;
/// @}

/**
 * Decomposes a quaternion into swing and twist rotation components.
 *
 * @param[in] q Unit quaternion to decompose.
 * @param[in] twist_axis Axis of twist rotation.
 * @param[out] swing Swing rotation component.
 * @param[out] twist Twist rotation component.
 * @param[in] tolerance Floating-point tolerance.
 *
 * @warning @p q must be a unit quaternion.
 * @warning @p twist_axis must be a unit vector.
 *
 * @see https://www.euclideanspace.com/maths/geometry/rotations/for/decomposition/
 */
template <class T>
void swing_twist(const quaternion<T>& q, const vector<T, 3>& twist_axis, quaternion<T>& swing, quaternion<T>& twist, T tolerance = T{1e-6});

/**
 * Converts a 3x3 rotation matrix to a quaternion.
 *
 * @param m Rotation matrix.
 *
 * @return Unit quaternion representing the rotation described by @p m.
 */
template <class T>
[[nodiscard]] quaternion<T> quaternion_cast(const matrix<T, 3, 3>& m);

template <class T>
inline constexpr quaternion<T> add(const quaternion<T>& a, const quaternion<T>& b) noexcept
{
	return {a.r + b.r, a.i + b.i};
}

template <class T>
inline constexpr quaternion<T> add(const quaternion<T>& a, T b) noexcept
{
	return {a.r + b, a.i + b};
}

template <class T>
inline constexpr quaternion<T> conjugate(const quaternion<T>& q) noexcept
{
	return {q.r, -q.i};
}

template <class T>
inline constexpr T dot(const quaternion<T>& a, const quaternion<T>& b) noexcept
{
	return a.r * b.r + dot(a.i, b.i);
}

template <class T>
inline constexpr quaternion<T> div(const quaternion<T>& a, const quaternion<T>& b) noexcept
{
	return {a.r / b.r, a.i / b.i};
}

template <class T>
inline constexpr quaternion<T> div(const quaternion<T>& a, T b) noexcept
{
	return {a.r / b, a.i / b};
}

template <class T>
inline constexpr quaternion<T> div(T a, const quaternion<T>& b) noexcept
{
	return {a / b.r, a / b.i};
}

template <class T>
inline T inv_length(const quaternion<T>& q)
{
	return T{1} / length(q);
}

template <class T>
inline T length(const quaternion<T>& q)
{
	return std::sqrt(sqr_length(q));
}

template <class T>
inline constexpr quaternion<T> lerp(const quaternion<T>& a, const quaternion<T>& b, T t) noexcept
{
	return
	{
		(b.r - a.r) * t + a.r,
		(b.i - a.i) * t + a.i
	};
}

template <class T>
quaternion<T> look_rotation(const vector<T, 3>& forward, vector<T, 3> up)
{
	const vector<T, 3> right = normalize(cross(forward, up));
	up = cross(right, forward);
	
	const matrix<T, 3, 3> m =
	{
		right,
		up,
		-forward
	};
	
	// Convert to quaternion
	return normalize(quaternion_cast(m));
}

template <class T>
constexpr quaternion<T> mul(const quaternion<T>& a, const quaternion<T>& b) noexcept
{
	return
	{
		a.w() * b.w() - a.x() * b.x() - a.y() * b.y() - a.z() * b.z(),
		a.w() * b.x() + a.x() * b.w() + a.y() * b.z() - a.z() * b.y(),  
		a.w() * b.y() - a.x() * b.z() + a.y() * b.w() + a.z() * b.x(),
		a.w() * b.z() + a.x() * b.y() - a.y() * b.x() + a.z() * b.w()
	};
}

template <class T>
inline constexpr quaternion<T> mul(const quaternion<T>& a, T b) noexcept
{
	return {a.r * b, a.i * b};
}

template <class T>
constexpr vector<T, 3> mul(const quaternion<T>& q, const vector<T, 3>& v) noexcept
{
	const auto t = cross(q.i, v) * T{2};
	return v + q.r * t + cross(q.i, t);
}

template <class T>
inline constexpr vector<T, 3> mul(const vector<T, 3>& v, const quaternion<T>& q) noexcept
{
	return mul(conjugate(q), v);
}

template <class T>
inline constexpr quaternion<T> negate(const quaternion<T>& q) noexcept
{
	return {-q.r, -q.i};
}

template <class T>
quaternion<T> nlerp(const quaternion<T>& a, const quaternion<T>& b, T t)
{
	return normalize(add(mul(a, T{1} - t), mul(b, t * std::copysign(T{1}, dot(a, b)))));
}

template <class T>
inline quaternion<T> normalize(const quaternion<T>& q)
{
	return mul(q, inv_length(q));
}

template <class T>
quaternion<T> angle_axis(T angle, const vector<T, 3>& axis)
{
	return {std::cos(angle * T{0.5}), axis * std::sin(angle * T{0.5})};
}

template <class T>
quaternion<T> rotation(const vector<T, 3>& from, const vector<T, 3>& to, T tolerance)
{
	const auto cos_theta = dot(from, to);
	
	if (cos_theta <= T{-1} + tolerance)
	{
		// Direction vectors are opposing, return 180 degree rotation about arbitrary axis
		return quaternion<T>{T{0}, {T{1}, T{0}, T{0}}};
	}
	else if (cos_theta >= T{1} - tolerance)
	{
		// Direction vectors are parallel, return identity quaternion
		return quaternion<T>::identity();
	}
	else
	{
		const auto r = cos_theta + T{1};
		const auto i = cross(from, to);
		const auto inv_length = T{1.0} / std::sqrt(r + r);
		
		return quaternion<T>{r * inv_length, i * inv_length};
	}
}

template <class T>
quaternion<T> slerp(const quaternion<T>& a, const quaternion<T>& b, T t, T tolerance)
{
	T cos_theta = dot(a, b);
	if (cos_theta >= T{1} - tolerance)
	{
		// Use linear interpolation if quaternions are nearly aligned
		return normalize(lerp(a, b, t));
	}
	
	// Clamp to acos domain
	cos_theta = std::min<T>(std::max<T>(cos_theta, T{-1}), T{1});
	
	const T theta = std::acos(cos_theta) * t;
	
	const quaternion<T> c = normalize(sub(b, mul(a, cos_theta)));
	
	return add(mul(a, std::cos(theta)), mul(c, std::sin(theta)));
}

template <class T>
inline constexpr T sqr_length(const quaternion<T>& q) noexcept
{
	return q.r * q.r + sqr_length(q.i);
}

template <class T>
inline constexpr quaternion<T> sub(const quaternion<T>& a, const quaternion<T>& b) noexcept
{
	return {a.r - b.r, a.i - b.i};
}

template <class T>
inline constexpr quaternion<T> sub(const quaternion<T>& a, T b) noexcept
{
	return {a.r - b, a.i - b};
}

template <class T>
inline constexpr quaternion<T> sub(T a, const quaternion<T>& b) noexcept
{
	return {a - b.r, a - b.i};
}

template <class T>
void swing_twist(const quaternion<T>& q, const vector<T, 3>& twist_axis, quaternion<T>& swing, quaternion<T>& twist, T tolerance)
{
	if (sqr_length(q.i) <= tolerance)
	{
		// Singularity, rotate 180 degrees about twist axis
		twist = angle_axis(pi<T>, twist_axis);
		
		const auto rotated_twist_axis = mul(q, twist_axis);
		
		auto swing_axis = cross(twist_axis, rotated_twist_axis);
		const auto swing_axis_sqr_length = sqr_length(swing_axis);
		
		if (swing_axis_sqr_length <= tolerance)
		{
			// Swing axis and twist axis are parallel, no swing
			swing = quaternion<T>::identity();
		}
		else
		{
			const auto cos_swing_angle = std::min<T>(std::max<T>(dot(twist_axis, rotated_twist_axis), T{-1}), T{1});
			swing = angle_axis(std::acos(cos_swing_angle), swing_axis * (T{1} / std::sqrt(swing_axis_sqr_length)));
		}
	}
	else
	{
		twist = normalize(quaternion<T>{q.r, twist_axis * dot(twist_axis, q.i)});
		swing = mul(q, conjugate(twist));
	}
}

template <class T>
quaternion<T> quaternion_cast(const matrix<T, 3, 3>& m)
{
	const T t = trace(m);
	
	if (t > T{0})
	{
		const T s = T{0.5} / std::sqrt(t + T{1});
		return
		{
			T{0.25} / s,
			(m[1][2] - m[2][1]) * s,
			(m[2][0] - m[0][2]) * s,
			(m[0][1] - m[1][0]) * s
		};
	}
	else
	{
		if (m[0][0] > m[1][1] && m[0][0] > m[2][2])
		{
			const T s = T{2} * std::sqrt(T{1} + m[0][0] - m[1][1] - m[2][2]);
			
			return
			{
				(m[1][2] - m[2][1]) / s,
				T{0.25} * s,
				(m[1][0] + m[0][1]) / s,
				(m[2][0] + m[0][2]) / s
			};
		}
		else if (m[1][1] > m[2][2])
		{
			const T s = T{2} * std::sqrt(T{1} + m[1][1] - m[0][0] - m[2][2]);
			return
			{
				(m[2][0] - m[0][2]) / s,
				(m[1][0] + m[0][1]) / s,
				T{0.25} * s,
				(m[2][1] + m[1][2]) / s
			};
		}
		else
		{
			const T s = T{2} * std::sqrt(T{1} + m[2][2] - m[0][0] - m[1][1]);
			return
			{
				(m[0][1] - m[1][0]) / s,
				(m[2][0] + m[0][2]) / s,
				(m[2][1] + m[1][2]) / s,
				T{0.25} * s
			};
		}
	}
}

namespace operators {

/// @copydoc add(const quaternion<T>&, const quaternion<T>&)
template <class T>
inline constexpr quaternion<T> operator+(const quaternion<T>& a, const quaternion<T>& b) noexcept
{
	return add(a, b);
}

/// @copydoc add(const quaternion<T>&, T)
/// @{
template <class T>
inline constexpr quaternion<T> operator+(const quaternion<T>& a, T b) noexcept
{
	return add(a, b);
}
template <class T>
inline constexpr quaternion<T> operator+(T a, const quaternion<T>& b) noexcept
{
	return add(b, a);
}
/// @}

/// @copydoc div(const quaternion<T>&, const quaternion<T>&)
template <class T>
inline constexpr quaternion<T> operator/(const quaternion<T>& a, const quaternion<T>& b) noexcept
{
	return div(a, b);
}

/// @copydoc div(const quaternion<T>&, T)
template <class T>
inline constexpr quaternion<T> operator/(const quaternion<T>& a, T b) noexcept
{
	return div(a, b);
}

/// @copydoc div(T, const quaternion<T>&)
template <class T>
inline constexpr quaternion<T> operator/(T a, const quaternion<T>& b) noexcept
{
	return div(a, b);
}

/// @copydoc mul(const quaternion<T>&, const quaternion<T>&)
template <class T>
inline constexpr quaternion<T> operator*(const quaternion<T>& a, const quaternion<T>& b) noexcept
{
	return mul(a, b);
}

/// @copydoc mul(const quaternion<T>&, T)
/// @{
template <class T>
inline constexpr quaternion<T> operator*(const quaternion<T>& a, T b) noexcept
{
	return mul(a, b);
}
template <class T>
inline constexpr quaternion<T> operator*(T a, const quaternion<T>& b) noexcept
{
	return mul(b, a);
}
/// @}

/// @copydoc mul(const quaternion<T>&, const vector<T, 3>&)
template <class T>
inline constexpr vector<T, 3> operator*(const quaternion<T>& q, const vector<T, 3>& v) noexcept
{
	return mul(q, v);
}

/// @copydoc mul(const vector<T, 3>&, const quaternion<T>&)
template <class T>
inline constexpr vector<T, 3> operator*(const vector<T, 3>& v, const quaternion<T>& q) noexcept
{
	return mul(v, q);
}

/// @copydoc sub(const quaternion<T>&, const quaternion<T>&)
template <class T>
inline constexpr quaternion<T> operator-(const quaternion<T>& a, const quaternion<T>& b) noexcept
{
	return sub(a, b);
}

/// @copydoc sub(const quaternion<T>&, T)
/// @{
template <class T>
inline constexpr quaternion<T> operator-(const quaternion<T>& a, T b) noexcept
{
	return sub(a, b);
}
template <class T>
inline constexpr quaternion<T> operator-(T a, const quaternion<T>& b) noexcept
{
	return sub(a, b);
}
/// @}

/// @copydoc negate(const quaternion<T>&)
template <class T>
inline constexpr quaternion<T> operator-(const quaternion<T>& q) noexcept
{
	return negate(q);
}

/**
 * Adds two values and stores the result in the first value.
 *
 * @param a First value.
 * @param b Second value.
 *
 * @return Reference to the first value.
 */
/// @{
template <class T>
inline constexpr quaternion<T>& operator+=(quaternion<T>& a, const quaternion<T>& b) noexcept
{
	return (a = a + b);
}
template <class T>
inline constexpr quaternion<T>& operator+=(quaternion<T>& a, T b) noexcept
{
	return (a = a + b);
}
/// @}

/**
 * Subtracts the first value by the second value and stores the result in the first value.
 *
 * @param a First value.
 * @param b Second value.
 *
 * @return Reference to the first value.
 */
/// @{
template <class T>
inline constexpr quaternion<T>& operator-=(quaternion<T>& a, const quaternion<T>& b) noexcept
{
	return (a = a - b);
}
template <class T>
inline constexpr quaternion<T>& operator-=(quaternion<T>& a, T b) noexcept
{
	return (a = a - b);
}
/// @}

/**
 * Multiplies two values and stores the result in the first value.
 *
 * @param a First value.
 * @param b Second value.
 *
 * @return Reference to the first value.
 */
/// @{
template <class T>
inline constexpr quaternion<T>& operator*=(quaternion<T>& a, const quaternion<T>& b) noexcept
{
	return (a = a * b);
}
template <class T>
inline constexpr quaternion<T>& operator*=(quaternion<T>& a, T b) noexcept
{
	return (a = a * b);
}
/// @}

/**
 * Divides the first value by the second value and stores the result in the first value.
 *
 * @param a First value.
 * @param b Second value.
 *
 * @return Reference to the first value.
 */
/// @{
template <class T>
inline constexpr quaternion<T>& operator/=(quaternion<T>& a, const quaternion<T>& b) noexcept
{
	return (a = a / b);
}
template <class T>
inline constexpr quaternion<T>& operator/=(quaternion<T>& a, T b) noexcept
{
	return (a = a / b);
}
/// @}

} // namespace operators

} // namespace math

using namespace math::operators;

#endif // ANTKEEPER_MATH_QUATERNION_HPP