// =====================================================================================================================
//  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/set.h
/// \brief A header containing the definition for a set of unique 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_SET_H
#define FENNEC_CONTAINERS_SET_H

// https://programming.guide/robin-hood-hashing.html

#include <fennec/containers/optional.h>
#include <fennec/containers/set.h>
#include <fennec/lang/compare.h>
#include <fennec/math/ext/primes.h>
#include <fennec/memory/allocator.h>
#include <fennec/lang/hashing.h>

namespace fennec
{

///
///
/// \brief wrapper for sets of elements
/// \details
/// This data-structure behaves like a set, but does not use pointers, instead storing the table in-array
///
/// | 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)} |
///
/// \tparam TypeT The type to contain
template<typename TypeT, class Hash = hash<TypeT>, class Equals = equality<TypeT>, class Alloc = allocator<TypeT>>
struct set {

// Definitions =========================================================================================================
private:
	struct node;

public:

	/// \name Definitions
	/// @{

	using alloc_t = typename allocator_traits<Alloc>::template rebind<node>; //!< the allocator type

	using hash_t = Hash;    //!< the hash type
	using equal_t = Equals; //!< the equality type
	using elem_t = TypeT;   //!< the element type

	/// @}


	/// \name Constants
	/// @{

	static constexpr double default_load = 0.8; //!< the default load factor for reallocation

	/// @}


	class iterator;

private:
	static constexpr size_t npos = -1;

	using table_t = dynarray<node, alloc_t>;


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

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

	///
	/// \brief Default Constructor, initializes empty set
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr set()
		: _table()
		, _hash()
		, _size(0)
		, _sumpsl(0)
		, _load(default_load) {
	};

	///
	/// \brief Hash Copy Constructor, initializes empty set with a hash
	/// \param hash the hash object
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr set(const hash_t& hash)
		: _table()
		, _hash(hash)
		, _size(0)
		, _sumpsl(0)
		, _load(default_load) {
	}

	///
	/// \brief Alloc Copy Constructor, initializes empty set with an allocator
	/// \param alloc the allocator object
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr set(const alloc_t& alloc)
		: _table(alloc)
		, _hash()
		, _size(0)
		, _sumpsl(0)
		, _load(default_load) {
	}

	///
	/// \brief Hash Alloc Copy Constructor, initializes empty set with a hash and allocator
	/// \param hash the hash object
	/// \param alloc the allocator object
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr set(const hash_t& hash, const alloc_t& alloc)
		: _table(alloc)
		, _hash(hash)
		, _size(0)
		, _sumpsl(0)
		, _load(default_load) {
	}

	///
	/// \brief Set Copy Constructor
	/// \param set Set to copy
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr set(const set& set)
		: _table(set._table)
		, _hash(set._hash)
		, _size(set._size)
		, _sumpsl(set._sumpsl)
		, _load(set._load) {
	}

	///
	/// \brief Set Move Constructor
	/// \param set Set to move
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr set(set&& set) noexcept
		: _table(fennec::move(set._table))
		, _hash(fennec::move(set._hash))
		, _size(fennec::move(set._size))
		, _sumpsl(set._sumpsl)
		, _load(set._load) {
	}

	///
	/// \brief Destructor, destructs all elements and releases the allocation
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr ~set() {
		for (size_t i = 0; i < capacity(); ++i) {
			_table[i].value = nullopt;
		}
	}

	/// @}


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

	/// \name Properties
	/// @{

	///
	/// \returns Size of the set in elements
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr size_t size() const {
		return _size;
	}

	///
	/// \returns \emph{true} when the set is empty, \emph{false} otherwise
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr bool is_empty() const {
		return _size == 0;
	}

	///
	/// \returns Capacity of the set in elements
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr size_t capacity() const {
		return _table.size();
	}

	/// @}


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

	/// \name Access
	/// @{

	///
	/// \brief Find an Element
	/// \param val Value to find
	/// \returns An iterator at the location of the value
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr iterator find(const elem_t& val) const {
		if (capacity() == 0) {
			return end();
		}
		size_t s = _hash(val) % capacity();              // Initial search index
		int    psl = (_size != 0) ? _sumpsl / _size : 0; // Initial psl
		size_t i = (s + psl) % capacity();               // Median search
		size_t n = 0;

		// Check the first element;
		if (_table[i].psl >= psl && _table[i].value) {
			if (_equal(*_table[i].value, val)) {
				return iterator(this, i);
			}
		}

		// Loop while there is a value and its psl is greater than our probe
		while (true) {
			++n;
			size_t i0 = (i + capacity() - n) % capacity(); // Prevent index underflow
			size_t i1 = (i + n) % capacity();
			int    p0 = psl - n, p1 = psl + n;
			bool   c0  = p0 >= 0 && _table[i0].psl >= p0, c1 = _table[i1].psl >= p1; // Check that we are in range

			if (c0 && _table[i0].value) {
				if (_equal(*_table[i0].value, val)) {
					return iterator(this, i0);
				}
			}

			if (c1 && _table[i1].value) {
				if (_equal(*_table[i1].value, val)) {
					return iterator(this, i1);
				}
			}

			if (not(c0 or c1)) {
				break;
			}
		}

		return iterator(this, npos);
	}

	///
	/// \brief Check if a set contains a value
	/// \param val Value to check
	/// \returns \emph{true} if \emph{val} can be found, \emph{false} otherwise
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr bool contains(const elem_t& val) const {
		return this->find(val) != end();
	}

	///
	/// \brief Iterator Access
	/// \param it Location to access
	/// \returns A pointer to the element, \emph{nullptr} if not found.
	///          The value should not be changed in a manner that will change the hash of the element.
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr elem_t* at(const iterator& it) {
		if (it == end()) {
			return nullptr;
		}
		if (not _table[it._i].value) {
			return nullptr;
		}
		return &*_table[it._i].value;
	}

	///
	/// \brief Iterator Const Access
	/// \param it Location to access
	/// \returns A const-qualified pointer to the element, \emph{nullptr} if not found.
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr const elem_t* at(const iterator& it) const {
		if (not _table[it._i].value) return nullptr;
		return &*_table[it._i].value;
	}

	/// @}

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

	/// \name Modifiers
	/// @{

	///
	/// \brief Move Insertion
	/// \param val Value to insert
	/// \returns An iterator at the held value
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr iterator insert(elem_t&& val) {
		return this->_insert(fennec::forward<elem_t>(val));
	}

	///
	/// \brief Copy Insertion
	/// \param val Value to insert
	/// \returns An iterator at the held value
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr iterator insert(const elem_t& val) {
		return this->_insert(val);
	}

	///
	/// \brief Emplace Insertion
	/// \tparam ArgsT Argument types
	/// \param args Arguments to construct with
	/// \returns An iterator at the held value
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename...ArgsT>
	constexpr iterator emplace(ArgsT&&...args) {
		return this->_insert(fennec::forward<ArgsT>(args)...);
	}

	///
	/// \brief Element Erase
	/// \param it Location to erase
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr void erase(iterator it) {
		size_t i = it._i;
		if (i >= capacity()) {
			return;
		} // These are separated due to compilers being inconsistent
		if (not _table[i].value) {
			return;
		}

		_table[i].value = nullopt;
		_sumpsl -= _table[i].psl;
		--_size;
		size_t p = i;
		while (_table[i = (i + 1) % capacity()].value) {
			if (_table[i].psl == 0) break;

			fennec::swap(_table[p].value, _table[i].value);
			--_table[p].psl, --_sumpsl;
			p = i;
		}
	}

	///
	/// \brief Element Erase
	/// \param val Value to erase
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr void erase(const elem_t& val) {
		this->erase(this->find(val));
	}

	///
	/// \brief Clear all elements from the set, destructing them
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr void clear() {
		_table.clear();
	}

	/// @}


// ITERATOR ============================================================================================================

	/// \name Iteration
	/// @{

	///
	/// \brief C++ Iterator Specification \emph{begin()}
	/// \returns An iterator for all elements of the set in no particular order
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr iterator begin() const {
		iterator it(this, 0);
		while (not _table[it._i].value) {
			++it;
		}
		return it;
	}

	///
	/// \brief C++ Iterator Specification \emph{end()}
	/// \returns An iterator representing the end of the set
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr iterator end() const {
		return iterator(this, npos);
	}

	/// @}

	///
	/// \brief C++ Iterator Specification \emph{iterator}
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	class iterator {
	public:
		///
		/// \brief destructor
		constexpr ~iterator() {
			_set = nullptr;
		}

		///
		/// \brief prefix increment operator
		/// \param it the iterator to increment
		/// \returns \emph{it} after having moved to the next element in the list
		constexpr friend iterator& operator++(iterator& it) {
			while (++it._i < it._set->capacity()) {
				if (it._set->_table[it._i].value) {
					return it;
				}
			}
			it._i = npos;
			return it;
		}

		///
		/// \brief postfix increment operator
		/// \param it the iterator to increment
		/// \returns \emph{it} before having moved to the next element in the list
		constexpr friend iterator operator++(iterator& it, int) {
			iterator prev = it;
			++it;
			return prev;
		}

		///
		/// \brief dereference operator
		/// \returns a reference to the value pointed by the iterator
		constexpr const elem_t& operator*() const {
			return *_set->_table[_i].value;
		}

		///
		/// \brief pointer access operator
		/// \returns a pointer to the value pointed by the iterator
		constexpr const elem_t* operator->() const {
			if (not _set->_table[_i].value) return nullptr;
			return &*_set->_table[_i].value;
		}

		///
		/// \brief iterator equality operator
		/// \param it the iterator to compare with
		/// \returns \emph{true} if the iterators are identical, \emph{false} otherwise
		constexpr bool operator==(const iterator& it) const {
			return _set == it._set and _i == it._i;
		}

		///
		/// \brief iterator inequality operator
		/// \param it the iterator to compare with
		/// \returns \emph{true} if the iterators are different, \emph{false} otherwise
		constexpr bool operator!=(const iterator& it) const {
			return _set != it._set or _i != it._i;
		}

	private:
		const set* _set;
		size_t _i;

		constexpr iterator(const set* set, size_t i)
			: _set(set)
			, _i(i) {
		}

		friend set;
	};


// Private Member Variables ============================================================================================
private:
	table_t _table;
	hash_t  _hash;
	equal_t _equal;
	size_t  _size;
	size_t  _sumpsl;
	float   _load;


// Private Helpers =====================================================================================================
private:
	constexpr void _expand() {
		set cpy; // Create a new set
		cpy._table.resize(
			fennec::next_prime2(_table.capacity())
		);

		// rehash
		for (size_t i = 0; i < capacity(); ++i) {
			if (_table[i].value) {
				cpy.insert(fennec::move(*_table[i].value));
			}
		}

		// Swap buffers
		fennec::swap(_table, cpy._table);
	}

	template<typename...ArgsT>
	constexpr iterator _insert(ArgsT&&...args) {
		if (_size == 0 or static_cast<float>(_size) / capacity() >= _load) { // expand when full
			_expand();
		}

		elem_t value(fennec::forward<ArgsT>(args)...);
		size_t i = _hash(value) % capacity(); // Initial search index
		int psl = 0;
		while (_table[i].value) { // Search for empty cell
			if (_equal(*_table[i].value, value)) { // Check to see if this element is already inserted
				return iterator(this, i);
			}
			if (psl > _table[i].psl) { // When psl is higher, swap
				_sumpsl += psl - _table[i].psl;
				fennec::swap(_table[i].psl, psl);
				fennec::swap(*_table[i].value, value);
			}
			i = (i + 1) % capacity(); ++psl;
		}
		_table[i].value = fennec::move(value);
		_sumpsl += (_table[i].psl = psl);
		++_size;
		return iterator(this, npos);
	}


// Private Definitions =================================================================================================
private:
	struct node {
		optional<elem_t> value;
		int psl;

		constexpr node() = default;
		constexpr ~node() = default;
	};
};

}

#endif // FENNEC_CONTAINERS_SET_H
