// =====================================================================================================================
//  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/map.h
/// \brief A header containing the definition for a mapping of keys to values
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 - 2026 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html))
///
///

#ifndef FENNEC_CONTAINERS_MAP_H
#define FENNEC_CONTAINERS_MAP_H

#include <fennec/containers/pair.h>
#include <fennec/containers/set.h>

namespace fennec
{

/* Ramblings
 *
 * Definitions:
 * user = Programmer using this data structure
 *
 * The STL maps are very contrived. Some of its functionality encourages younger programmers to use
 * the exception model. Ideally, I would like this structure to never throw an error with typical use.
 *
 * The array access operator is, in my opinion, poorly implemented. I do not think that this operator should handle
 * insertions and should handle access only. This is the only data structure in STL that has this behavior, no other
 * data structure modifies contents by inherently calling operator[].
 *
 * Currently, I am considering implementing this as the following:
 * Access will be handled only via operator[]. Return value will be a pointer which forces user validation.
 * Insertions will be handled only via an insert/emplace function.
 * Deletions will be handled only via an erase function.
*/

///
/// \brief Data Structure defining a mapping of \emph{key} \emph{KeyT} to \emph{value} \emph{ValueT}
/// \details
/// | Property    | Value       |
/// |:-----------:|:-----------:|
/// | stable      |      ⛔     |
/// | dynamic     |      ✅     |
/// | homogeneous |      ✅     |
/// | distinct    |      ✅     |
/// | ordered     |      ⛔     |
/// | linear      |      ⛔     |
/// | space       | \emph{O(N)} |
/// | access      | \emph{O(1)} |
/// | find        | \emph{O(1)} |
/// | insertion   | \emph{O(1)} |
/// | deletion    | \emph{O(1)} |
///
/// \note These runtimes are amortized, in theory the worst case is \emph{O(N)}, but that is highly improbable.
///
/// \tparam KeyT The Key Type
/// \tparam ValueT The Value Type
/// \tparam Hash The Hash to Use
/// \tparam Alloc The Allocator to Use
template<typename KeyT, typename ValueT, typename Hash = hash<KeyT>, typename Alloc = allocator<pair<KeyT, ValueT>>>
struct map {

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

	/// \name Definitions
	/// @{

	struct key_hash;                                                           //!< Hash for node keys
	struct key_equals;                                                         //!< Comparison for node keys
	using key_t = KeyT;                                                        //!< The key type
	using value_t = ValueT;                                                    //!< The value type
	using elem_t = pair<KeyT, ValueT>;                                         //!< then node type
	using alloc_t = typename allocator_traits<Alloc>::template rebind<elem_t>; //!< Rebinds the allocator type to nodes
	using hash_t = Hash;                                                       //!< The hash type
	using set_t = set<elem_t, key_hash, key_equals, alloc_t>;                  //!< The underlying set
	using iterator = set_t::iterator;                                          //!< Iterator type

	///
	/// \brief key hash helper
	struct key_hash : hash_t {
		///
		/// \brief C++ 11 Hash Specification \emph{operator()}
		/// \param p the pair to hash
		/// \returns the hash of the key
		constexpr size_t operator()(const elem_t& p) const {
			return hash_t::operator()(p.first);
		}
	};

	///
	/// \brief key comparison helper
	struct key_equals : equality<KeyT> {
		///
		/// \brief C++ 11 Compare Specification \emph{operator()}
		/// \param a the first pair
		/// \param b the second pair
		/// \returns \emph{true} if the keys are equal, \emph{false} otherwise
		constexpr bool operator()(const elem_t& a, const elem_t& b) const {
			return equality<KeyT>::operator()(a.first, b.first);
		}
	};

	/// @}


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

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

	///
	/// \brief Default Constructor, initializes empty map
	constexpr map() = default;

	///
	/// \brief Destructor, Destructs all elements and releases the allocation
	constexpr ~map() = default;

	/// @}


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

	/// \name Properties
	/// @{

	///
	/// \returns The size of the set
	constexpr size_t size() const {
		return _set.size();
	}

	///
	/// \returns \emph{true} when there are no elements in the set, \emph{false} otherwise
	constexpr size_t is_empty() const {
		return _set.size();
	}

	///
	/// \returns The capacity of the underlying allocation
	constexpr size_t capacity() const {
		return _set.capacity();
	}

	/// @}


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

	/// \name Access
	/// @{

	///
	/// \brief Key Access Operator
	/// \param key Key value to access
	/// \returns A pointer to the value associated with \emph{key}, \emph{nullptr} if \emph{key} is not present.
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr value_t* operator[](const KeyT& key) {
		auto it = _set.at(this->_find(key));
		return it ? &it->second : nullptr;
	}

	///
	/// \brief Key Const Access Operator
	/// \param key Key value to access
	/// \returns A const-qualified pointer to the value associated with \emph{key}, \emph{nullptr} if \emph{key} is not present.
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr const value_t* operator[](const KeyT& key) const {
		auto it = _set.at(this->_find(key));
		return it ? &it->second : nullptr;
	}

	///
	/// \brief Argument Key Access Operator
	/// \tparam ArgsT Argument Types
	/// \param args Arguments to construct the key with
	/// \returns A pointer to the value associated with \emph{key}, \emph{nullptr} if \emph{key} is not present.
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename...ArgsT>
	constexpr value_t* operator[](ArgsT&&...args) {
		auto it = _set.at(this->_find(fennec::forward<ArgsT>(args)...));
		return it ? &it->second : nullptr;
	}

	///
	/// \brief Argument Key Const Access Operator
	/// \tparam ArgsT Argument Type
	/// \param args Argument to construct the key with
	/// \returns A const-qualified pointer to the value associated with \emph{key}, \emph{nullptr} if \emph{key} is not present.
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename...ArgsT>
	constexpr const value_t* operator[](ArgsT&&...args) const {
		auto it = _set.at(this->_find(fennec::forward<ArgsT>(args)...));
		return it ? &it->second : nullptr;
	}

	/// @}


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

	/// \name Modifiers
	/// @{

	///
	/// \brief Key-Value Insertion
	/// \param pair a pair containing the key and its value
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr void insert(elem_t&& pair) {
		this->_insert(fennec::forward<elem_t>(pair));
	}

	///
	/// \brief Key-Value Insertion
	/// \param key key to insert
	/// \param args Arguments for constructing the key-value pair
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename...ArgsT>
	constexpr void emplace(const KeyT& key, ArgsT&&...args) {
		this->_insert(key, fennec::forward<ArgsT>(args)...);
	}

	///
	/// \brief Key-Value Insertion
	/// \param args Arguments for constructing the key-value pair
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename...ArgsT>
	constexpr void emplace(ArgsT&&...args) {
		this->_insert(fennec::forward<ArgsT>(args)...);
	}

	///
	/// \brief Erase a key
	/// \param key key to erase
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr void erase(KeyT&& key) {
		_set.erase(this->_find(fennec::forward<KeyT>(key)));
	}

	///
	/// \brief Erase a key
	/// \param key key to erase
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr void erase(const KeyT& key) {
		_set.erase(this->_find(key));
	}

	///
	/// \brief Argument Erase
	/// \tparam ArgsT Argument Types
	/// \param args Arguments to construct a key to erase
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename...ArgsT>
	constexpr void erase(ArgsT&&...args) {
		_set.erase(this->_find(fennec::forward<ArgsT>(args)...));
	}

	///
	/// \brief Clears the map destructing all elements
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	void clear() {
		_set.clear();
	}

	/// @}


// Iteration ===========================================================================================================
public:

	/// \name Iteration
	/// @{

	///
	/// \brief C++ Iterator Specification \emph{begin()}
	/// \returns an iterator at the start of the map
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr iterator begin() {
		return _set.begin();
	}


	///
	/// \brief C++ Iterator Specification \emph{end()}
	/// \returns an iterator at the end of the map
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr iterator end() {
		return _set.end();
	}

	/// @}


// Private Member Variables ============================================================================================
private:
	 set_t _set;


// Private Helpers =====================================================================================================
private:
	template<typename...ArgsT>
	set_t::iterator _find(ArgsT&&...args) const {
		union U { // Hacky way of avoiding constructing the value, TODO: Check for warnings on other compilers
			pair<KeyT, char[sizeof(ValueT)]> root;
			pair<KeyT, ValueT> val;

			~U() {
				fennec::destruct(&root);
			}
		} trick = {
			.root = { KeyT(fennec::forward<ArgsT>(args)...), 0 }
		};
		return _set.find(trick.val);
	}

	template<typename...ArgsT>
	constexpr void _insert(ArgsT&&...args) {
		elem_t elem(fennec::forward<ArgsT>(args)...);
		auto it = this->_find(elem.first);
		if (it != _set.end()) {
			_set.at(it)->second = fennec::move(elem.second);
		} else {
			_set.insert(fennec::move(elem));
		}
	}
};

}

#endif // FENNEC_CONTAINERS_MAP_H
