// =====================================================================================================================
//  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/variant.h
/// \brief Contains the definition for a structure that holds a single value from multiple types
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 - 2026 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html))
///
///

#ifndef FENNEC_CONTAINERS_VARIANT_H
#define FENNEC_CONTAINERS_VARIANT_H

#include <fennec/containers/optional.h>
#include <fennec/lang/type_sequences.h>
#include <fennec/math/ext/common.h>
#include <fennec/rtti/type.h>

namespace fennec
{

///
/// \brief A structure that represents a union between \emph{TypesT\ldots}
/// \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 TypesT The types to hold in the variant
template<typename...TypesT>
struct variant {
// Assertions ==========================================================================================================
public:

	static_assert(
		     is_unique_v<TypesT...>         and // No two types in TypesT... may be equivalent
		not (is_reference_v<TypesT> or ...) and // No type in TypesT... may be a reference
		not (is_array_v<TypesT> or ...)     and // No type in TypesT... may be an array
		not (is_void_v<TypesT> or ...)          // No type in TypesT... may be void
	);


// Definitions & Constants =============================================================================================
public:

	/// \name Constants
	/// @{

	static constexpr size_t size = max_element_size_v<TypesT...>; //!< size of the variant in bytes
	static constexpr size_t nulltype = sizeof...(TypesT);         //!< id for a null type

	/// @}


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

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

	///
	/// \brief Default Constructor, constructs the first type in \emph{TypesT...} that is default constructible
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	variant()
		: _bytes {}
		, _handle(&_bytes)
		, _type(nulltype) {
		using construct_t = search_element_t<is_default_constructible, TypesT...>;
		this->_construct<construct_t>();
	}

	///
	/// \brief Conversion Constructor, constructs the type in \emph{TypesT...} that is identical to \emph{T}
	///        or the first that is constructible with \emph{T}
	/// \tparam T The type of the value
	/// \param t The value to forward
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename T>
	variant(T&& t)
		: _bytes {}
		, _handle(&_bytes)
		, _type() {
		using same_t = search_element_args<is_same, type_sequence<T>, TypesT...>::type;
		using convert_t = search_element_args<is_constructible, type_sequence<T>, TypesT...>::type;
		using construct_t = conditional_t<is_void_v<same_t>, convert_t, same_t>;
		this->_construct<construct_t>(fennec::forward<T>(t));
	}

	///
	/// \brief Emplace Constructor, constructs a type \emph{T} that is in \emph{TypesT...} that is constructible with \emph{ArgsT...}
	/// \tparam ArgsT The arguments of the constructor
	/// \param args The argument values
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename T, typename...ArgsT>
	variant(type_identity<T>, ArgsT&&...args)
		: _bytes{}
		, _handle(&_bytes)
		, _type(nulltype) {
		static_assert(contains_element_v<T, TypesT...>, "T must be in TypesT...");
		this->_construct<T>(fennec::forward<ArgsT>(args)...);
		_type = find_element_v<T>;
	}

	///
	/// \brief Copy Constructor
	/// \param v The variant to copy
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	variant(const variant& v)
		: _bytes {}
		, _handle(&_bytes)
		, _type(nulltype) {

		if (v._type == nulltype) {
			return;
		}

		((v._type == find_element_v<TypesT, TypesT...> ?
			this->_construct<TypesT>(v.get<TypesT>()) :
			((void)0)
		), ...);
		_type = v._type;
	}

	///
	/// \brief Move Constructor
	/// \param v The variant to move
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	variant(variant&& v) noexcept
		: _bytes {}
		, _handle(&_bytes)
		, _type() {

		if (v._type == nulltype) {
			return;
		}

		((v._type == find_element_v<TypesT, TypesT...> ?
			this->_construct<TypesT>(fennec::move(v.get<TypesT>())) :
			((void)0)
		), ...);
		_type = v._type;
	}

	///
	/// \brief Destructor, if a type is held, destruct it.
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	~variant() {
		_clear();
	}

	/// @}


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

	/// \name Assignment
	/// @{


	///
	/// \brief value assignment operator
	/// \tparam T The type to assign
	/// \param t the value to assign
	/// \returns a reference to \emph{self} after assigning \emph{t}
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename T>
	variant& operator=(T&& t) {

		// First, check if T is in TypesT...
		if constexpr((contains_element_v<T, TypesT> or ...)) {
			using type_t = remove_reference_t<T>;
			if (_type == find_element_v<type_t, TypesT...>) {
				*_get<type_t>() = fennec::forward<T>(t);
			} else {
				_clear();
				this->_construct<type_t>(fennec::forward<T>(t));
				_type = find_element_v<type_t, TypesT...>;
			}
			return *this;
		}

		// Next, try to assign using the currently held type
		bool assigned = false;
		if (_type != nulltype) {
			((_type == find_element_v<TypesT, TypesT...> ?
				(*_get<TypesT>() = fennec::forward<T>(t), assigned = true) :
				((void)0)
			), ...);
		}

		if (assigned) {
			return *this;
		}

		// Otherwise, destruct, then construct
		_clear();
		using construct_t = search_element_args<is_constructible, type_sequence<T>, TypesT...>;
		this->_construct<construct_t>(fennec::forward<T>(t));
		return *this;
	}

	///
	/// \brief emplace function
	/// \tparam T The type to construct
	/// \tparam ArgsT the argument types
	/// \param args the argument values
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename T, typename...ArgsT> requires(contains_element_v<T, TypesT...>)
	void emplace(ArgsT&&...args) {
		_clear();
		this->_construct<T>(fennec::forward<ArgsT>(args)...);
	}

	///
	/// \brief deduced emplace function
	/// \tparam ArgsT the argument types
	/// \param args the argument values
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<size_t I, typename...ArgsT>
	void emplace(ArgsT&&...args) {
		using type_t = nth_element_t<I, TypesT...>;
		_clear();
		this->_construct<type_t>(fennec::forward<ArgsT>(args)...);
	}

	/// @}


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

	/// \name Access
	/// @{

	///
	/// \brief get the value of the variant interpreted as \emph{T}
	/// \tparam T the type to interpret as
	/// \returns The value interpreted as \emph{T}
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename T> requires(contains_element_v<T, TypesT...>)
	T& get() {
		return *_get<T>();
	}

	///
	/// \tparam T the type to interpret as
	/// \returns The value interpreted as \emph{T}
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename T> requires(contains_element_v<T, TypesT...>)
	const T& get() const {
		return *_get<T>();
	}

	///
	/// \tparam T the type to interpret as
	/// \returns The value interpreted as \emph{T}
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<size_t I, typename T = nth_element_t<I, TypesT...>> requires(contains_element_v<T, TypesT...>)
	T& get() {
		return *_get<T>();
	}

	///
	/// \tparam T the type to interpret as
	/// \returns The value interpreted as \emph{T}
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<size_t I, typename T = nth_element_t<I, TypesT...>> requires(contains_element_v<T, TypesT...>)
	const T& get() const {
		return *_get<T>();
	}

	/// @}


// Private Member Variables ============================================================================================
private:
	byte_t _bytes[size];
	void*  _handle;
	size_t _type;


// Private Helpers =====================================================================================================
private:

	template<typename T>
	T* _get() const {
		return static_cast<T*>(_handle);
	}

	void _clear() {
		if (_type == nulltype) {
			return;
		}

		((_type == find_element_v<TypesT, TypesT...> ?
			this->_destruct<TypesT>() :
			((void)0)
		), ...);
		_type = nulltype;
	}

	template<typename ConstructT, typename...ArgsT>
	void _construct(ArgsT&&...args) {
		fennec::construct<ConstructT>(_get<ConstructT>(), fennec::forward<ArgsT>(args)...);
	}

	template<typename DestructT>
	void _destruct() {
		fennec::destruct(_get<DestructT>());
	}
};

}

#endif // FENNEC_CONTAINERS_VARIANT_H