// =====================================================================================================================
//  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/containers/optional.h
/// \brief A header containing the definition for a container with an optionally present variable
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 - 2026 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html))
///
///

#ifndef FENNEC_CONTAINERS_OPTIONAL_H
#define FENNEC_CONTAINERS_OPTIONAL_H

#include <fennec/lang/utility.h>
#include <fennec/memory/new.h>

#include <fennec/lang/assert.h>

namespace fennec
{
///
/// \brief struct to represent a \emph{null} `fennec::optional`
struct nullopt_t {};

///
/// \brief value representing a \emph{null} `fennec::optional`
constexpr nullopt_t nullopt_v = {};

///
/// \brief alias for representing a \emph{null} `fennec::optional`
#define nullopt nullopt_v

///
/// \brief Structure to hold an optional value.
/// \details
/// | Property    | Value       |
/// |:-----------:|:-----------:|
/// | stable      |      ✅     |
/// | dynamic     |      ⛔     |
/// | homogeneous |      ✅     |
/// | distinct    |      ⛔     |
/// | ordered     |      ⛔     |
/// | linear      |      ⛔     |
/// | space       | \emph{O(1)} |
/// | access      | \emph{O(1)} |
/// | find        |      ⛔     |
/// | insertion   | \emph{O(1)} |
/// | deletion    | \emph{O(1)} |
///
/// \tparam T
template<typename T>
struct optional {

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

	/// \name Definitions
	/// @{

	using reference_t = T&;           //!< reference type
	using pointer_t = T*;             //!< pointer type
	using const_reference_t = T&;     //!< const reference type
	using const_pointer_t = const T*; //!< const pointer type

	/// @}


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

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

	///
	/// \brief Default Constructor
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr optional()
		: _root(0)
		, _set(false) {
	}

	///
	/// \brief Default Constructor
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr optional(nullopt_t)
		: _root(0)
		, _set(false) {
	}

	///
	/// \brief Type Copy Constructor
	/// \param val the value to initialize the underlying object with
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr optional(const T& val)
		: _val(val)
		, _set(true) {
	}

	///
	/// \brief Type Move Constructor
	/// \param val the value to initialize the underlying object with
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr optional(T&& val)
		: _val(fennec::forward<T>(val))
		, _set(true) {
	}

	///
	/// \brief Copy Constructor
	/// \param opt the optional to copy
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr optional(const optional& opt) requires is_copy_assignable_v<T>
		: optional() {
		_set = opt._set;
		if (_set) {
			_val = opt._val;
		}
	}

	///
	/// \brief Move Constructor
	/// \param opt the optional to move
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr optional(optional&& opt) noexcept requires is_move_assignable_v<T>
		: optional() {
		_set = opt._set;
		if (_set) {
			_val = fennec::move(opt._val);
		}
		opt = nullopt;
	}

	///
	/// \brief Emplace Constructor
	/// \tparam ArgsT The argument types
	/// \param args The argument values
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename...ArgsT>
	constexpr optional(ArgsT&&...args)
		: _val(fennec::forward<ArgsT>(args)...)
		, _set(true) {
	}

	///
	/// \brief destructor
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr ~optional() {
		if constexpr(is_fundamental_v<T>) {
			return;
		}
		if (_set) {
			fennec::destruct(&_val);
		}
	}

	/// @}


// Properties ==========================================================================================================

	/// \name Properties
	/// @{

	///
	/// \brief Implicit Boolean Check
	/// \returns \emph{true} when there is a value contained
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr operator bool() const {
		return _set;
	}

	///
	/// \returns \emph{true} when there is no held value, \emph{false} otherwise.
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr bool is_empty() const {
		return not _set;
	}

	/// @}


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

	/// \name Assignment Operators
	/// @{

	///
	/// \brief Null Assignment
	/// \returns A reference to \emph{this}
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr optional& operator=(nullopt_t) {
		if constexpr(not is_fundamental_v<T>) {
			if (_set) {
				fennec::destruct(&_val);
			}
		}
		_root = '\0';
		_set = false;
		return *this;
	}

	///
	/// \brief Type Copy Assignment
	/// \param val The value to set with
	/// \returns A reference to \emph{this}
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr optional& operator=(const T& val) requires is_copy_constructible_v<T> and is_copy_assignable_v<T> {
		if (_set) {
			_val = val;
		} else {
			fennec::construct(&_val, val);
			_set = true;
		}
		return *this;
	}

	///
	/// \brief Type Move Assignment
	/// \param val The value to set with
	/// \returns A reference to \emph{this}
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr optional& operator=(T&& val) requires is_move_constructible_v<T> and is_move_assignable_v<T> {
		if (_set) {
			_val = fennec::forward<T>(val);
		} else {
			fennec::construct(&_val, fennec::forward<T>(val));
			_set = true;
		}
		return *this;
	}

	///
	/// \brief Copy Assignment
	/// \param opt The optional to copy
	/// \returns A reference to \emph{this}
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr optional& operator=(const optional& opt) requires is_copy_constructible_v<T> and is_copy_assignable_v<T> {
		if (_set != opt._set) {
			_set = opt._set;
			if (_set) { // Construct
				fennec::construct(&_val, opt._val);
			} else { // Destruct
				fennec::destruct(&_val);
				_root = 0;
			}
		} else if (_set) { // Copy Assignment
			_val = opt._val;
		}
		return *this;
	}

	///
	/// \brief Move Assignment
	/// \param opt The optional to move
	/// \returns A reference to \emph{this}
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr optional& operator=(optional&& opt) noexcept requires is_move_constructible_v<T> and is_move_assignable_v<T> {
		if (_set != opt._set) {
			_set = opt._set;
			if (_set) { // Construct
				fennec::construct(&_val, fennec::move(opt._val));
			} else { // Destruct
				fennec::destruct(&_val);
				_root = 0;
			}
		} else if (_set) { // Copy Assignment
			_val = fennec::move(opt._val);
		}
		return *this;
	}

	/// @}


// Access ==============================================================================================================

	/// \name Access
	/// @{

	///
	/// \returns A pointer to the value, \emph{nullptr} if there is no value
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr pointer_t operator->() noexcept {
		return _set ? &_val : nullptr;
	}

	///
	/// \returns A const-qualified pointer to the value, \emph{nullptr} if there is no value
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr const_pointer_t operator->() const noexcept {
		return _set ? &_val : nullptr;
	}

	///
	/// \brief Dereference Operator
	/// \returns A reference to the value
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr T& operator*() & noexcept {
		assertd(_set, "Attempted to reference the value of an unset optional");
		return _val;
	}

	///
	/// \brief Const Dereference Operator
	/// \returns A const-qualified reference to the value
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr const T& operator*() const& noexcept {
		assertd(_set, "Attempted to reference the value of an unset optional");
		return _val;
	}

	///
	/// \brief Dereference Operator
	/// \returns A reference to the value
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr T&& operator*() && noexcept {
		assertd(_set, "Attempted to reference the value of an unset optional");
		return _val;
	}

	///
	/// \brief Const Dereference Operator
	/// \returns A const-qualified reference to the value
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr const T&& operator*() const&& noexcept {
		assertd(_set, "Attempted to reference the value of an unset optional");
		return _val;
	}

	/// @}

// Modifiers ===========================================================================================================

	/// \name Modifiers
	/// @{

	///
	/// \brief Emplace Assignment, Move overload
	/// \param val The object to take ownership of
	/// \returns A reference to the held value
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr T& emplace(T&& val) {
		if (_set) {
			_val = fennec::forward<T>(val);
		} else {
			fennec::construct(&_val, fennec::forward<T>(val));
			_set = true;
		}
		return _val;
	}

	///
	/// \brief Emplace Assignment, Copy overload
	/// \param val The object to copy
	/// \returns A reference to the held value
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr T& emplace(const T& val) {
		if (_set) {
			_val = val;
		} else {
			fennec::construct(&_val, val);
			_set = true;
		}
		return _val;
	}

	///
	/// \brief Emplace Assignment
	/// \param args The arguments to construct with
	/// \returns A reference to the held value
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename...ArgsT>
	constexpr T& emplace(ArgsT&&...args) {
		if (_set) {
			_val = T(fennec::forward<ArgsT>(args)...);
		} else {
			fennec::construct(&_val, fennec::forward<ArgsT>(args)...);
			_set = true;
		}
		return _val;
	}

	///
	/// \brief Reset the Optional
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	void reset() {
		this->operator=(nullopt);
	}

	/// @}




private:
	union {
		char _root;
		T _val;
	};
	bool _set;
};

}

#endif // FENNEC_CONTAINERS_OPTIONAL_H
