// =====================================================================================================================
//  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/thread.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_THREAD_H
#define FENNEC_THREADING_THREAD_H

#include <pthread.h>

#include <fennec/memory/bytes.h>

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

#include <fennec/format/formatter.h>

namespace fennec
{

///
/// \brief Wrapper class to encapsulate threads, implementation uses pthread
class thread {

// Constants & Definitions =============================================================================================
private:
	static constexpr pthread_t nullthread = 0;

public:

	/// \name Definitions
	/// @{

	using handle_t = pthread_t; //!< The underlying handle type

	///
	/// \brief Structure representing the id of a thread
	struct id {

	// Constructors & Destructor ---------------------------------------------------------------------------------------
	public:

		/// \name Constructor
		/// @{

		///
		/// \brief Default Constructor
		/// \details References a null thread.
		constexpr id() noexcept
			: _id(nullthread) {
		}

		///
		/// \brief Destructor
		constexpr ~id() noexcept {
		}

		/// @}


	// Comparison ------------------------------------------------------------------------------------------------------
	public:

		///
		/// \brief Equality Operator
		/// \param lhs Left-hand side of the operation
		/// \param rhs Right-hand side of the operation
		/// \returns \emph{true} if both ids represent the same thread, \emph{false} otherwise.
		constexpr friend bool operator==(id lhs, id rhs) noexcept {
			if (lhs._id == nullthread) {
				return rhs._id == nullthread;
			}
			if (rhs._id == nullthread) {
				return false;
			}
			return pthread_equal(lhs._id, rhs._id) != 0;
		}

		///
		/// \brief Inequality Operator
		/// \param lhs Left-hand side of the operation
		/// \param rhs Right-hand side of the operation
		/// \returns \emph{false} if both ids represent the same thread, \emph{true} otherwise.
		constexpr friend bool operator!=(id lhs, id rhs) noexcept {
			return !(lhs == rhs);
		}


	// Private Member Variables ----------------------------------------------------------------------------------------
	private:
		friend class thread;
		friend struct hash<id>;
		friend struct formatter<id>;

		constexpr id(pthread_t id) : _id(id) {}

		pthread_t _id;
	};

	/// @}


// Constructors & Destructor ===========================================================================================
public:

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

	///
	/// \brief Default Constructor
	/// \details Initializes a null thread.
	thread() noexcept
		: _handle(nullthread) {
	}

	///
	/// \brief Move Constructor
	/// \param th Thread to take ownership of.
	thread(thread&& th) noexcept
		: _handle(th._handle) {
		th._handle = nullthread;
	}

	///
	/// \brief Copy Constructor
	/// \details Deleted, no semantics for copying a thread.
	thread(const thread&) = delete;

	///
	/// \brief Thread Constructor
	/// \tparam FuncT The function type
	/// \tparam ArgsT The argument types
	/// \param func The function to execute on the new thread
	/// \param args The arguments to pass
	/// \details Initializes a thread and runs \emph{func} with \emph{args...}
	template<typename FuncT, typename...ArgsT>
		requires(not is_same_v<thread, remove_cvref_t<FuncT>>)
	thread(FuncT&& func, ArgsT&&...args)
		: _handle(nullthread) {
		using proxy_t = detail::thread_proxy_t<decay_t<FuncT>, decay_t<ArgsT>...>;
		unique_ptr<proxy_t> proxy = fennec::make_unique<proxy_t>(
			fennec::decay_copy(fennec::forward<FuncT>(func)),
			fennec::decay_copy(fennec::forward<ArgsT>(args))...
		);

		const int_t res = ::pthread_create(&_handle, nullptr, &detail::_run_thread<proxy_t>, proxy.get());
		assertf(res == 0, "Thread creation failed!");

		proxy.release();
	}

	///
	/// \brief Thread Destructor
	/// \details Attempts to join the thread, asserts if unable to.
	~thread() {
		assertf(_handle == nullthread or joinable(), "Attempted to destruct a running thread!");
		join();
	}

	/// @}


// Assignment ==========================================================================================================
public:

	/// \name Assignment
	/// @{

	///
	/// \brief Move Assignment
	/// \param th Thread to take ownership of.
	/// \returns A reference to \emph{this} after taking ownership of \emph{th}.
	thread& operator=(thread&& th) noexcept {
		assertf(_handle == nullthread, "Attempted to overwrite running thread!");

		_handle    = th._handle;
		th._handle = nullthread;

		return *this;
	}

	///
	/// \brief Deleted, no semantics for copying a thread.
	thread& operator=(const thread&) = default;

	/// @}


// Properties ==========================================================================================================
public:

	/// \name Properties
	/// @{

	///
	/// \brief Check if the thread is joinable.
	/// \returns \math{\textbf{this}\neq\textbf{null} \& \textbf{this}\neq{p_{curr}}}
	bool joinable() const noexcept {
		return _handle != nullthread and pthread_self() != _handle;
	}

	///
	/// \returns The id of the thread.
	id get_id() const {
		return _handle;
	}

	///
	/// \returns The id of the current thread.
	static id current() {
		return pthread_self();
	}

	/// @}


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

	/// \name Operations
	/// @{

	///
	/// \brief Join with this thread.
	/// \details Blocks until this thread finishes its execution.
	void join() {
		if (not joinable()) {
			return;
		}

		const int_t res = pthread_join(_handle, nullptr);
		assertf(res == 0, "Failed to join thread!");
		_handle = nullthread;
	}

	///
	/// \brief Detatch this thread.
	/// \details Separates the threads execution, releasing it.
	void detatch() {
		if (not joinable()) {
			return;
		}

		const int_t res = pthread_detach(_handle);
		assertf(res == 0, "Failed to detach thread!");
		_handle = nullthread;
	}

	///
	/// \brief Swap the held handles with \emph{other}.
	/// \param other The thread to swap handles with.
	void swap(thread& other) {
		fennec::swap(_handle, other._handle);
	}

	///
	/// \brief Relinquish execution.
	/// \details This thread is pushed to the back of the CPU's scheduler queue.
	static void yield() {
		assertf(sched_yield() == 0, "Error yielding thread!");
	}

	/// @}


// Private Member Variables ============================================================================================
private:
	pthread_t _handle;
};

///
/// \brief Specialization of `fennec::hash` for `fennec::thread::id`
template <>
struct hash<thread::id> {
	size_t operator()(thread::id v) const noexcept {
		return hash<thread::handle_t>()(v._id);
	}
};

///
/// \brief Specialization of `fennec::formatter` for `fennec::thread::id`
template<>
struct formatter<thread::id> {
	string operator()(const format_arg& fmt, thread::id v) const noexcept {
		return formatter<thread::handle_t>()(fmt, v._id);
	}
};

}

#endif // FENNEC_THREADING_THREAD_H