// =====================================================================================================================
//  fennec, a free and open source game engine
//  Copyright © 2025  Medusa Slockbower
//
//  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 <https://www.gnu.org/licenses/>.
// =====================================================================================================================

#ifndef FENNEC_MATH_EXT_QUATERNION_H
#define FENNEC_MATH_EXT_QUATERNION_H

#include <fennec/math/vector_base.h>

namespace fennec
{

template<typename ScalarT> struct quaternion;

template<typename genType> using qua = quaternion<genType>;

using quat  = qua<float>;
using dquat = qua<double>;

///
/// \brief Dot Function
/// \param x the first quaternion
/// \param y the second quaternion
/// \returns The sum of component products.
template<typename genType>
constexpr genType dot(const qua<genType>& x, const qua<genType>& y) {
	return x.w*y.w + x.x*y.x + x.y*y.y + x.z*y.z;
}

///
/// \brief Square Norm Function
/// \param q The Quaternion
/// \returns The square sum of all components.
template<typename genType>
constexpr genType sqnorm(const qua<genType>& q) {
	return fennec::dot(q, q);
}

///
/// \brief Norm Function
/// \param q The Quaternion
/// \returns The Hamilton Tensor of `q`
template<typename genType>
constexpr genType norm(const qua<genType>& q) {
	return fennec::sqrt(sqnorm(q));
}

///
/// \brief Unit Function (Versor)
/// \param q The Quaternion
/// \returns A Quaternion of `q` with norm `1`
template<typename genType>
constexpr qua<genType> unit(const qua<genType>& q) {
	genType n = fennec::norm(q);
	return q * (genType(1) / n);
}

///
/// \brief Reciprocal Function
/// \param q The Quaternion
/// \returns The quaternion \f${q}^{-1}\f$
template<typename genType>
constexpr qua<genType> reciprocal(const qua<genType>& q) {
	return ~q / fennec::sqnorm(q);
}


template<typename ScalarT>
struct quaternion : detail::vector_base_type<ScalarT, 4>
{
public:
// Typedefs ============================================================================================================

	using base_type = detail::vector_base_type<ScalarT, 4>;
	using scalar_t  = ScalarT;
	using quat_t    = quaternion;
	using vec3_t    = vec<scalar_t, 3>;
	using vec4_t    = vec<scalar_t, 4>;

	using base_type::data;
	using base_type::x, base_type::y, base_type::z, base_type::w;


// Constructors ========================================================================================================

	///
	/// \brief Default Constructor, creates a quaternion such that the real part is `1` and all others are `0`
	constexpr quaternion() {
		data = { 0, 0, 0, 1 };
	}

	///
	/// \brief Component Constructor
	/// \param w The scalar component
	/// \param x The coefficient of the `i` component
	/// \param y The coefficient of the `j` component
	/// \param z The coefficient of the `k` component
	constexpr quaternion(scalar_t w, scalar_t x, scalar_t y, scalar_t z) {
		data = { x, y, z, w };
	}

	///
	/// \brief Component Constructor
	/// \param s The scalar component
	/// \param v The vector component
	constexpr quaternion(scalar_t s, vec3_t v) {
		data = { v.x, v.y, v.z, s };
	}

	///
	/// \brief Vector Constructor
	/// \param v A 4d vector
	constexpr quaternion(const vec4_t& v) {
		data = v.data;
	}

	///
	/// \brief Swizzle Constructor
	/// \param s A 4d swizzle
	template<typename DataT, size_t...IndicesV> requires(sizeof...(IndicesV) == 4)
	constexpr quaternion(const detail::swizzle_storage<DataT, scalar_t, IndicesV...>& s) {
		data[0] = s[0];
		data[1] = s[1];
		data[2] = s[2];
		data[3] = s[3];
	}

	///
	/// \brief Copy Constructor
	/// \param q The quaternion to copy
	constexpr quaternion(const quaternion& q) {
		data = q.data;
	}

	///
	/// \brief Move Constructor
	/// \param q The quaternion to move
	constexpr quaternion(quaternion&& q) noexcept {
		data = q.data;
	}


// Assignment Operators ================================================================================================

	///
	/// \brief Copy Assignment Operator
	/// \param q The quaternion to copy
	constexpr quat_t& operator=(const quat_t& q) {
		data = q.data;
		return *this;
	}

	///
	/// \brief Move Assignment Operator
	/// \param q The quaternion to move
	constexpr quat_t& operator=(quat_t&& q) noexcept {
		data = q.data;
		return *this;
	}


// Comparison Operators ================================================================================================

	///
	/// \brief Equality Operator
	/// \param lhs the left hand side of the expression
	/// \param rhs the right hand side of the expression
	/// \returns `true` when all components of `lhs` and `rhs` are equal, false otherwise
	constexpr friend bool operator==(const quat_t& lhs, const quat_t& rhs) {
		return lhs.data == rhs.data;
	}

	///
	/// \brief Inequality Operator
	/// \param lhs the left hand side of the expression
	/// \param rhs the right hand side of the expression
	/// \returns `true` when any component of `lhs` and `rhs` are not equal, false otherwise
	constexpr friend bool operator!=(const quat_t& lhs, const quat_t& rhs) {
		return lhs.data != rhs.data;
	}


// Unary Operators =====================================================================================================

	///
	/// \brief Unary Negation Operator
	/// \param rhs The quaternion to negate
	/// \returns A quaternion with each component of `rhs` negated.
	constexpr friend quat_t operator-(const quat_t& rhs) {
		return quat_t(-rhs.w, -rhs.x, -rhs.y, -rhs.z);
	}

	///
	/// \brief Unary Conjugation Operator
	/// \param rhs The quaternion to conjugate
	/// \returns A quaternion with each vector component of `rhs` negated.
	constexpr friend quat_t operator~(const quat_t& rhs) {
		return quat_t(rhs.w, -rhs.x, -rhs.y, -rhs.z);
	}


// Quaternion Scalar Arithmetic Operators ==============================================================================

	///
	/// \brief Quaternion-Scalar Multiplication Operator
	/// \param lhs The quaternion
	/// \param rhs The scalar
	/// \returns A quaternion with each component of `lhs` multiplied by `rhs`
	constexpr friend quat_t operator*(const quat_t& lhs, scalar_t rhs) {
		return quat_t(lhs.w * rhs, lhs.x * rhs, lhs.y * rhs, lhs.z * rhs);
	}

	///
	/// \brief Scalar-Quaternion Multiplication Operator
	/// \param lhs The scalar
	/// \param rhs The quaternion
	/// \returns A quaternion with each component of `rhs` multiplied by `lhs`
	constexpr friend quat_t operator*(scalar_t lhs, const quat_t& rhs) {
		return quat_t(lhs * rhs.w, lhs * rhs.x, lhs * rhs.y, lhs * rhs.z);
	}

	///
	/// \brief Quaternion-Scalar Division Operator
	/// \param lhs The quaternion
	/// \param rhs The scalar
	/// \returns A quaternion with each component of `lhs` divided by `rhs`
	constexpr friend quat_t operator/(const quat_t& lhs, scalar_t rhs) {
		return quat_t(lhs.w / rhs, lhs.x / rhs, lhs.y / rhs, lhs.z / rhs);
	}

	///
	/// \brief Scalar-Quaternion Division Operator
	/// \param lhs The scalar
	/// \param rhs The quaternion
	/// \returns A quaternion with each component of `rhs` divided by `lhs`
	constexpr friend quat_t operator/(scalar_t lhs, const quat_t& rhs) {
		return quat_t(lhs / rhs.w, lhs / rhs.x, lhs / rhs.y, lhs / rhs.z);
	}


// Quaternion Scalar Arithmetic Assignment Operators ===================================================================

	///
	/// \brief Quaternion-Scalar Multiplication Assignment Operator
	/// \param lhs The quaternion
	/// \param rhs The scalar
	/// \returns `lhs` with each component multiplied by `rhs`
	constexpr friend quat_t& operator*=(quat_t& lhs, scalar_t rhs) {
		lhs.x *= rhs;
		lhs.y *= rhs;
		lhs.z *= rhs;
		lhs.w *= rhs;
		return lhs;
	}

	///
	/// \brief Quaternion-Scalar Division Assignment Operator
	/// \param lhs The quaternion
	/// \param rhs The scalar
	/// \returns `lhs` with each component divided by `rhs`
	constexpr friend quat_t& operator/=(const quat_t& lhs, scalar_t rhs) {
		lhs.x /= rhs;
		lhs.y /= rhs;
		lhs.z /= rhs;
		lhs.w /= rhs;
		return lhs;
	}


// Quaternion Vector Arithmetic Operators ==============================================================================

	/// \brief
	constexpr friend vec3_t operator*(const quat_t& q, const vec3_t& v) {
		const vec3_t u   = q.xyz;
		const vec3_t uv  = fennec::cross(u, v);
		const vec3_t uuv = fennec::cross(u, uv);
		return v + (uv*q.w + uuv) * scalar_t(2);
	}

	constexpr friend vec3_t operator*(const vec3_t& v, const quat_t& q) {
		return fennec::reciprocal(q) * v;
	}

	constexpr friend vec4_t operator*(const quat_t& q, const vec4_t& v) {
		return vec4_t(q * v.xyz, v.w);
	}

	constexpr friend vec4_t operator*(const vec4_t& v, const quat_t& q) {
		return fennec::reciprocal(q) * v;
	}


// Quaternion Quaternion Arithmetic Operators ==========================================================================

	constexpr friend quaternion operator+(const quaternion& lhs, const quaternion& rhs) {
		return quaternion(
			lhs.w + rhs.w,
			lhs.x + rhs.x,
			lhs.y + rhs.y,
			lhs.z + rhs.z
		);
	}

	constexpr friend quaternion operator-(const quaternion& lhs, const quaternion& rhs) {
		return quaternion(
			lhs.w - rhs.w,
			lhs.x - rhs.x,
			lhs.y - rhs.y,
			lhs.z - rhs.z
		);
	}

	constexpr friend quaternion operator*(const quaternion& lhs, const quaternion& rhs) {
		return quaternion(
			lhs.w*rhs.w - lhs.x*rhs.x - lhs.y*rhs.y - lhs.z * rhs.z,
			lhs.w*rhs.x + lhs.x*rhs.w + lhs.y*rhs.z - lhs.z * rhs.y,
			lhs.w*rhs.y - lhs.x*rhs.z + lhs.y*rhs.w + lhs.z * rhs.x,
			lhs.w*rhs.z - lhs.x*rhs.y - lhs.y*rhs.x + lhs.z * rhs.w
		);
	}


// Quaternion Quaternion Arithmetic Assignment Operators ===============================================================

	constexpr friend quaternion& operator+=(quaternion& lhs, const quaternion& rhs) {
		lhs.w += rhs.w;
		lhs.x += rhs.x;
		lhs.y += rhs.y;
		lhs.z += rhs.z;
		return lhs;
	}

	constexpr friend quaternion& operator-=(quaternion& lhs, const quaternion& rhs) {
		lhs.w -= rhs.w;
		lhs.x -= rhs.x;
		lhs.y -= rhs.y;
		lhs.z -= rhs.z;
		return lhs;
	}

	constexpr friend quaternion& operator*=(quaternion& lhs, const quaternion& rhs) {
		return lhs = lhs * rhs;
	}

private:

};

}

#endif // FENNEC_MATH_EXT_QUATERNION_H
