// =====================================================================================================================
//  fennec, a free and open source game engine
//  Copyright © 2025 - 2026  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/>.
// =====================================================================================================================

///
/// \file fennec/threading/atomic.h
/// \brief
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 - 2026 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html))
///
///


#ifndef FENNEC_THREADING_ATOMIC_H
#define FENNEC_THREADING_ATOMIC_H

#include <fennec/threading/detail/_atomic.h>

#include <fennec/lang/type_traits.h>

namespace fennec
{

using ::memory_order;
using ::memory_order_relaxed;
using ::memory_order_consume;
using ::memory_order_acquire;
using ::memory_order_release;
using ::memory_order_acq_rel;
using ::memory_order_seq_cst;

///
/// \brief Wrapper for atomic variables
/// \tparam T The type of the atomic variable
template<typename T>
struct atomic {

// Assertions ==========================================================================================================
public:
	static_assert(is_integral_v<T> or is_pointer_v<T>, "fennec::atomic not defined for the provided type. Default "
													   "implementation only supports integral & pointer types.");



// Definitions =========================================================================================================
public:

	/// \name Definitions
	/// @{

	using value_t = T; //!< The type of the held atomic value

	/// @}


// Constructors ========================================================================================================
public:

	/// \name Constructors & Destructor
	/// @{

	///
	/// \brief Default Constructor
	///
	/// \details Initializes the held atomic variable with \math{0}.
	constexpr atomic() noexcept
		: _value(static_cast<T>(0)) {
	}

	///
	/// \brief Value Constructor
	///
	/// \details Initializes the held atomic variable with \emph{value}.
	/// \param value The value to initialize the atomic variable with.
	constexpr atomic(const T value) noexcept
		: _value(value) {
	}

	///
	/// \brief Copy Constructor
	/// \details Deleted, copying atomics is not allowed.
	atomic(const atomic&) = delete;

	/// @}


// Assignment ==========================================================================================================

	/// \name Assignment
	/// @{

	///
	/// \details Atomically assigns \emph{x} to the atomic variable.
	/// \param x The value to assign
	/// \returns \emph{x}
	T operator=(const T x) noexcept {
		this->store(x);
		return x;
	}

	///
	/// \brief Stores a value into an atomic object
	///
	/// \details Atomically assigns \emph{x} to the atomic variable.
	/// \param x The value to assign
	/// \returns \emph{x}
	T operator=(const T x) volatile noexcept {
		this->store(x);
		return x;
	}

	///
	/// \brief Copy Assignment
	/// \details Deleted, copying atomics is not allowed.
	/// \returns Deleted
	atomic& operator=(const atomic&)          = delete;

	///
	/// \brief Copy Assignment
	/// \details Deleted, copying atomics is not allowed.
	/// \returns Deleted
	atomic& operator=(const atomic&) volatile = delete;

	/// @}



// Access ==============================================================================================================
public:

	/// \name Access
	/// @{

	///
	/// \details Atomically loads and returns the current value of the atomic variable. Equivalent to `load()`.
	operator T() const noexcept {
		return load();
	}

	///
	/// \brief Loads a value from an atomic object
	///
	/// \details Atomically loads and returns the current value of the atomic variable. Equivalent to `load()`.
	operator T() const volatile noexcept {
		return load();
	}

	/// @}



// Modifiers ===========================================================================================================
public:

	/// \name Modifiers
	/// @{

	///
	/// \details Atomically replaces the current value with \emph{x}. Memory is affected according to the value of \emph{order}.
	/// \param x The value to store into the atomic variable
	/// \param order Memory order constraints to enforce
	void store(const T x, memory_order order = memory_order_seq_cst) noexcept {
		::atomic_store_explicit(&_value, x, order);
	}

	///
	/// \brief Atomically replaces the value of the atomic object with a non-atomic argument
	///
	/// \details Atomically replaces the current value with \emph{x}. Memory is affected according to the value of \emph{order}.
	/// \param x The value to store into the atomic variable
	/// \param order Memory order constraints to enforce
	void store(const T x, memory_order order = memory_order_seq_cst) volatile noexcept {
		::atomic_store_explicit(&_value, x, order);
	}


	///
	/// \details Atomically loads and returns the current value of the atomic variable.
	///          Memory is affected according to the value of \emph{order}.
	/// \param order Memory order constraints to enforce
	/// \returns The current value of the atomic variable.
	T load(memory_order order = memory_order_seq_cst) const noexcept {
		return ::atomic_load_explicit(&_value, order);
	}

	///
	/// \brief Atomically obtains the value of the atomic object
	///
	/// \details Atomically loads and returns the current value of the atomic variable.
	///          Memory is affected according to the value of \emph{order}.
	/// \param order Memory order constraints to enforce
	/// \returns The current value of the atomic variable.
	T load(memory_order order = memory_order_seq_cst) const volatile noexcept {
		return ::atomic_load_explicit(&_value, order);
	}


	///
	/// \details Atomically replaces the underlying value with \emph{x} (a read-modify-write operation).
	///          Memory is affected according to the value of \emph{order}.
	/// \param x Value to assign
	/// \param order Memory order constraints to enforce
	/// \return The value of the atomic variable before the call.
	T exchange(const T x, memory_order order = memory_order_seq_cst) noexcept {
		return ::atomic_exchange_explicit(&_value, x, order);
	}

	///
	/// \brief Atomically replaces the value of the atomic object and obtains the value held previously
	///
	/// \details Atomically replaces the underlying value with \emph{x} (a read-modify-write operation).
	///          Memory is affected according to the value of \emph{order}.
	/// \param x Value to assign
	/// \param order Memory order constraints to enforce
	/// \return The value of the atomic variable before the call.
	T exchange(const T x, memory_order order = memory_order_seq_cst) volatile noexcept {
		return ::atomic_exchange_explicit(&_value, x, order);
	}


	///
	/// \details Atomically compares the value of `*this` with that of \emph{exp}.
	///          If they are equal, replaces the former with \emph{x} (performs read-modify-write operation).
	///          Otherwise, loads the actual value stored in *this into \emph{exp} (performs load operation).
	/// \param exp Reference to the value expected to be found in the atomic object.
	/// \param x The value to store in the atomic object if it is as expected.
	/// \param succ The memory synchronization ordering for the read-modify-write operation if the comparison succeeds.
	/// \param fail The memory synchronization ordering for the load operation if the comparison fails.
	/// \returns \emph{true} if the underlying atomic value was successfully changed, \emph{false} otherwise.
	bool compare_exchange_weak(T& exp, T x, memory_order succ, memory_order fail) noexcept {
		return ::atomic_compare_exchange_weak_explicit(&_value, &exp, x, succ, fail);
	}

	///
	/// \details Atomically compares the value of `*this` with that of \emph{exp}.
	///          If they are equal, replaces the former with \emph{x} (performs read-modify-write operation).
	///          Otherwise, loads the actual value stored in *this into \emph{exp} (performs load operation).
	/// \param exp Reference to the value expected to be found in the atomic object.
	/// \param x The value to store in the atomic object if it is as expected.
	/// \param succ The memory synchronization ordering for the read-modify-write operation if the comparison succeeds.
	/// \param fail The memory synchronization ordering for the load operation if the comparison fails.
	/// \returns \emph{true} if the underlying atomic value was successfully changed, \emph{false} otherwise.
	bool compare_exchange_weak(T& exp, T x, memory_order succ, memory_order fail) volatile noexcept {
		return ::atomic_compare_exchange_weak_explicit(&_value, &exp, x, succ, fail);
	}

	///
	/// \details Atomically compares the value of `*this` with that of \emph{exp}.
	///          If they are equal, replaces the former with \emph{x} (performs read-modify-write operation).
	///          Otherwise, loads the actual value stored in *this into \emph{exp} (performs load operation).
	/// \param exp Reference to the value expected to be found in the atomic object.
	/// \param x The value to store in the atomic object if it is as expected.
	/// \param order The memory synchronization ordering for both operations.
	/// \returns \emph{true} if the underlying atomic value was successfully changed, \emph{false} otherwise.
	bool compare_exchange_weak(T& exp, T x, memory_order order = memory_order_seq_cst) noexcept {
		return ::atomic_compare_exchange_weak_explicit(&_value, &exp, x, order, order);
	}

	///
	/// \brief Atomically compares the value of the atomic object with non-atomic argument and performs atomic exchange if equal or atomic load if not
	///
	/// \details Atomically compares the value of `*this` with that of \emph{exp}.
	///          If they are equal, replaces the former with \emph{x} (performs read-modify-write operation).
	///          Otherwise, loads the actual value stored in *this into \emph{exp} (performs load operation).
	/// \param exp Reference to the value expected to be found in the atomic object.
	/// \param x The value to store in the atomic object if it is as expected.
	/// \param order The memory synchronization ordering for both operations.
	/// \returns \emph{true} if the underlying atomic value was successfully changed, \emph{false} otherwise.
	bool compare_exchange_weak(T& exp, T x, memory_order order = memory_order_seq_cst) volatile noexcept {
		return ::atomic_compare_exchange_weak_explicit(&_value, &exp, x, order, order);
	}


	///
	/// \details Atomically compares the value of `*this` with that of \emph{exp}.
	///          If they are equal, replaces the former with \emph{x} (performs read-modify-write operation).
	///          Otherwise, loads the actual value stored in *this into \emph{exp} (performs load operation).
	/// \param exp Reference to the value expected to be found in the atomic object.
	/// \param x The value to store in the atomic object if it is as expected.
	/// \param succ The memory synchronization ordering for the read-modify-write operation if the comparison succeeds.
	/// \param fail The memory synchronization ordering for the load operation if the comparison fails.
	/// \returns \emph{true} if the underlying atomic value was successfully changed, \emph{false} otherwise.
	bool compare_exchange_strong(T& exp, T x, memory_order succ, memory_order fail) noexcept {
		return ::atomic_compare_exchange_strong_explicit(&_value, &exp, x, succ, fail);
	}

	///
	/// \details Atomically compares the value of `*this` with that of \emph{exp}.
	///          If they are equal, replaces the former with \emph{x} (performs read-modify-write operation).
	///          Otherwise, loads the actual value stored in *this into \emph{exp} (performs load operation).
	/// \param exp Reference to the value expected to be found in the atomic object.
	/// \param x The value to store in the atomic object if it is as expected.
	/// \param succ The memory synchronization ordering for the read-modify-write operation if the comparison succeeds.
	/// \param fail The memory synchronization ordering for the load operation if the comparison fails.
	/// \returns \emph{true} if the underlying atomic value was successfully changed, \emph{false} otherwise.
	bool compare_exchange_strong(T& exp, T x, memory_order succ, memory_order fail) volatile noexcept {
		return ::atomic_compare_exchange_strong_explicit(&_value, &exp, x, succ, fail);
	}

	///
	/// \details Atomically compares the value of `*this` with that of \emph{exp}.
	///          If they are equal, replaces the former with \emph{x} (performs read-modify-write operation).
	///          Otherwise, loads the actual value stored in *this into \emph{exp} (performs load operation).
	/// \param exp Reference to the value expected to be found in the atomic object.
	/// \param x The value to store in the atomic object if it is as expected.
	/// \param order The memory synchronization ordering for both operations.
	/// \returns \emph{true} if the underlying atomic value was successfully changed, \emph{false} otherwise.
	bool compare_exchange_strong(T& exp, T x, memory_order order = memory_order_seq_cst) noexcept {
		return ::atomic_compare_exchange_strong_explicit(&_value, &exp, x, order, order);
	}

	///
	/// \brief Atomically compares the value of the atomic object with non-atomic argument and performs atomic exchange if equal or atomic load if not
	///
	/// \details Atomically compares the value of `*this` with that of \emph{exp}.
	///          If they are equal, replaces the former with \emph{x} (performs read-modify-write operation).
	///          Otherwise, loads the actual value stored in *this into \emph{exp} (performs load operation).
	/// \param exp Reference to the value expected to be found in the atomic object.
	/// \param x The value to store in the atomic object if it is as expected.
	/// \param order The memory synchronization ordering for both operations.
	/// \returns \emph{true} if the underlying atomic value was successfully changed, \emph{false} otherwise.
	bool compare_exchange_strong(T& exp, T x, memory_order order = memory_order_seq_cst) volatile noexcept {
		return ::atomic_compare_exchange_strong_explicit(&_value, &exp, x, order, order);
	}

	/// @}



// Operations ==========================================================================================================
public:

	/// \name Operations
	/// @{

	///
	/// \param x The value to add.
	/// \param order The memory order to abide by.
	/// \returns The value previously held by the atomic.
	T fetch_add(const T x, memory_order order = memory_order_seq_cst) noexcept {
		return ::atomic_fetch_add_explicit(&_value, x, order);
	}

	///
	/// \brief Atomically add a value and return the previously held value.
	/// \param x The value to add.
	/// \param order The memory order to abide by.
	/// \returns The value previously held by the atomic.
	T fetch_add(const T x, memory_order order = memory_order_seq_cst) volatile noexcept {
		return ::atomic_fetch_add_explicit(&_value, x, order);
	}


	///
	/// \param x The value to add.
	/// \returns The result of the addition.
	T operator+=(const T x) noexcept {
		return this->fetch_add(x) + x;
	}

	///
	/// \brief Atomically add a value and return the result.
	/// \param x The value to add.
	/// \returns The result of the addition.
	T operator+=(const T x) volatile noexcept {
		return this->fetch_add(x) + x;
	}


	///
	/// \returns The held value incremented by 1
	T operator++() noexcept {
		return this->fetch_add(1) + 1;
	}

	///
	/// \brief Post-Increment
	/// \returns The held value incremented by 1
	T operator++() volatile noexcept {
		return this->fetch_add(1) + 1;
	}


	///
	/// \returns The previously held value before the increment.
	T operator++(int) noexcept {
		return this->fetch_add(1);
	}

	///
	/// \brief Pre-Increment
	/// \returns The previously held value before the increment.
	T operator++(int) volatile noexcept {
		return this->fetch_add(1);
	}



	///
	/// \param x The value to subtract.
	/// \param order The memory order to abide by.
	/// \returns The value previously held by the atomic.
	T fetch_sub(const T x, memory_order order = memory_order_seq_cst) noexcept {
		return ::atomic_fetch_sub_explicit(&_value, x, order);
	}

	///
	/// \brief Atomically subtract a value and return the previously held value.
	/// \param x The value to subtract.
	/// \param order The memory order to abide by.
	/// \returns The value previously held by the atomic.
	T fetch_sub(const T x, memory_order order = memory_order_seq_cst) volatile noexcept {
		return ::atomic_fetch_sub_explicit(&_value, x, order);
	}


	///
	/// \param x The value to subtract.
	/// \returns The result of the subtraction.
	T operator-=(const T x) noexcept {
		return this->fetch_sub(x) - x;
	}

	///
	/// \brief Atomically subtract a value and return the result.
	/// \param x The value to subtract.
	/// \returns The result of the addition.
	T operator-=(const T x) volatile noexcept {
		return this->fetch_sub(x) - x;
	}


	///
	/// \returns The held value decremented by 1
	T operator--() noexcept {
		return this->fetch_sub(1) - 1;
	}

	///
	/// \brief Post-Decrement
	/// \returns The held value decremented by 1
	T operator--() volatile noexcept {
		return this->fetch_sub(1) - 1;
	}


	///
	/// \returns The previously held value before the decrement.
	T operator--(int) noexcept {
		return this->fetch_sub(1);
	}

	///
	/// \brief Pre-Decrement
	/// \returns The previously held value before the decrement.
	T operator--(int) volatile noexcept {
		return this->fetch_sub(1);
	}

	/// @}



// Bit-Wise Operations =================================================================================================

	/// \name Bit-Wise Operations
	/// @{


	///
	/// \param x The value to and against.
	/// \param order The memory order to abide by.
	/// \returns The value previously held by the atomic.
	T fetch_and(const T x, memory_order order = memory_order_seq_cst) noexcept requires is_integral_v<T> {
		return ::atomic_fetch_and_explicit(&_value, x, order);
	}

	///
	/// \brief Atomically perform a bit-wise and with a value and return the previously held value.
	/// \param x The value to and against.
	/// \param order The memory order to abide by.
	/// \returns The value previously held by the atomic.
	T fetch_and(const T x, memory_order order = memory_order_seq_cst) volatile noexcept requires is_integral_v<T> {
		return ::atomic_fetch_and_explicit(&_value, x, order);
	}


	///
	/// \param x The value to and against.
	/// \returns The result of the operation.
	T operator&=(const T x) noexcept requires is_integral_v<T> {
		return this->fetch_and(x) & x;
	}

	///
	/// \param x The value to and against.
	/// \brief Atomically perform a bit-wise and with a value and return the result.
	/// \returns The result of the operation.
	T operator&=(const T x) volatile noexcept requires is_integral_v<T> {
		return this->fetch_and(x) & x;
	}



	///
	/// \param x The value to or with.
	/// \param order The memory order to abide by.
	/// \returns The value previously held by the atomic.
	T fetch_or(const T x, memory_order order = memory_order_seq_cst) noexcept requires is_integral_v<T> {
		return ::atomic_fetch_or_explicit(&_value, x, order);
	}

	///
	/// \brief Atomically perform a bit-wise or with a value and return the previously held value.
	/// \param x The value to or with.
	/// \param order The memory order to abide by.
	/// \returns The value previously held by the atomic.
	T fetch_or(const T x, memory_order order = memory_order_seq_cst) volatile noexcept requires is_integral_v<T> {
		return ::atomic_fetch_or_explicit(&_value, x, order);
	}


	///
	/// \param x The value to or with.
	/// \returns The result of the operation.
	T operator|=(const T x) noexcept requires is_integral_v<T> {
		return this->fetch_or(x) & x;
	}

	///
	/// \param x The value to or with.
	/// \brief Atomically perform a bit-wise or with a value and return the result.
	/// \returns The result of the operation.
	T operator|=(const T x) volatile noexcept requires is_integral_v<T> {
		return this->fetch_or(x) & x;
	}



	///
	/// \param x The value to xor with.
	/// \param order The memory order to abide by.
	/// \returns The value previously held by the atomic.
	T fetch_xor(const T x, memory_order order = memory_order_seq_cst) noexcept requires is_integral_v<T> {
		return ::atomic_fetch_xor_explicit(&_value, x, order);
	}

	///
	/// \brief Atomically perform a bit-wise xor with a value and return the previously held value.
	/// \param x The value to xor with.
	/// \param order The memory order to abide by.
	/// \returns The value previously held by the atomic.
	T fetch_xor(const T x, memory_order order = memory_order_seq_cst) volatile noexcept requires is_integral_v<T> {
		return ::atomic_fetch_xor_explicit(&_value, x, order);
	}


	///
	/// \param x The value to xor with.
	/// \returns The result of the operation.
	T operator^=(const T x) noexcept requires is_integral_v<T> {
		return this->fetch_xor(x) & x;
	}

	///
	/// \param x The value to xor with.
	/// \brief Atomically perform a bit-wise xor with a value and return the result.
	/// \returns The result of the operation.
	T operator^=(const T x) volatile noexcept requires is_integral_v<T> {
		return this->fetch_xor(x) & x;
	}

	/// @}


// Private Member Variables ============================================================================================
private:
	_Atomic T _value;
};

}

#endif // FENNEC_THREADING_ATOMIC_H