// =====================================================================================================================
//  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/list.h
/// \brief A header containing the definition for a linked list of 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_LIST_H
#define FENNEC_CONTAINERS_LIST_H

#include <fennec/containers/dynarray.h>
#include <fennec/containers/list.h>
#include <fennec/containers/optional.h>
#include <fennec/memory/allocator.h>

#include <fennec/math/common.h>

namespace fennec
{

///
///
/// \brief Data Structure defining lists of elements
/// \details
/// This data-structure behaves like a linked list, but does not use pointers. Instead, it is in-array. This creates the
/// following properties:
///
/// | Property    | Value       |
/// |:-----------:|:-----------:|
/// | stable      |      ⛔     |
/// | dynamic     |      ✅     |
/// | homogeneous |      ✅     |
/// | distinct    |      ⛔     |
/// | ordered     |      ⛔     |
/// | linear      |      ✅     |
/// | space       | \emph{O(N)} |
/// | access      | \emph{O(N)} |
/// | find        | \emph{O(N)} |
/// | insertion   | \emph{O(N)} |
/// | deletion    | \emph{O(N)} |
///
/// \note Access, Insertion, and Deletion are \emph{O(N)} using the index pattern,
///       using the iterator pattern yields \emph{O(1)} runtime.
///
/// \tparam TypeT value type
template<class TypeT, class Alloc = allocator<TypeT>>
struct list {

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

public:

	/// \name Definitions
	/// @{

	/// \brief Alias for the allocator type, rebound to list nodes
	using alloc_t = typename allocator_traits<Alloc>::template rebind<node>;

	using value_t = TypeT; //!< Alias for the value type

	static constexpr size_t npos = -1; //!< Constant representing a non-existant position

	/// @}

	class iterator;
	class const_iterator;

private:
	using table_t = dynarray<node, alloc_t>;
	using freed_t = dynarray<size_t>;


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

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

	///
	/// \brief Default Constructor, initializes an empty list.
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr list()
		: _table(), _freed(), _root(npos), _last(npos), _size(0) {
	}

	///
	/// \brief Copy Constructor, copies all elements in \emph{l} with optimized layout
	/// \param l The list to copy
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr list(const list& l)
		: list() {
		for (const value_t& it : l) {
			this->push_back(it);
		}
	}


	///
	/// \brief Move Constructor, takes ownership of the list
	/// \param l The list to move
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr list(list&& l) noexcept
		: _table(fennec::move(l._table))
		, _freed(fennec::move(l._freed))
		, _root(l._root)
		, _last(l._last)
		, _size(l._size) {
	}

	///
	/// \brief Destructor, destructs all elements then releases the allocation.
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr ~list() {
		clear();
	}

	/// @}

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

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

	///
	/// \brief Copy Assignment Operator
	/// \param l the list to copy
	/// \returns \emph{this} after having copied all elements of \emph{l}
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr list& operator=(const list& l) {
		this->clear();
		for (const value_t& it : l) {
			this->push_back(it);
		}
		return *this;
	}

	///
	/// \brief Move Assignment Operator
	/// \param l the list to copy
	/// \returns \emph{this} after having taken ownership over the contents of \emph{l}
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr list& operator=(list&& l) noexcept {
		this->clear();
		_table = fennec::move(l._table);
		_freed = fennec::move(l._freed);
		_root  = l._root; _last = l._last;
		_size  = l._size;
		return *this;
	}

	/// @}


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

	/// \name Properties
	/// @{

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

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

	///
	/// \returns \emph{true} when the list is empty, \emph{false} otherwise.
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr bool is_empty() const {
		return _root == npos;
	}

	/// @}


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

	/// \name Access
	/// @{

	///
	/// \brief Array Access Operator
	/// \param i Index to access
	/// \returns A reference to the element at \emph{i}
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr value_t& operator[](int i) {
		assertd(i >= 0 && size_t(i) < _size, "Index out of Bounds");
		size_t n = _walk(i);
		assertd(n != npos, "Index out of Bounds");
		return *_table[n].value;
	}

	///
	/// \brief Const Array Access Operator
	/// \param i Index to access
	/// \returns A const-qualified reference to the element at \emph{i}
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr const value_t& operator[](int i) const {
		assertd(i >= 0 && size_t(i) < _size, "Index out of Bounds");
		size_t n = _walk(i);
		assertd(n != npos, "Index out of Bounds");
		return *_table[n].value;
	}

	///
	/// \brief Access Front Element
	/// \returns A reference to the first element in the list
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr value_t& front() {
		return *_table[_root].value;
	}

	///
	/// \brief Const Access Front Element
	/// \returns A const-qualified reference to the first element in the list
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr const value_t& front() const {
		return *_table[_root].value;
	}

	///
	/// \brief Access Back Element
	/// \returns A reference to the last element in the list
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr value_t& back() {
		return *_table[_last].value;
	}

	///
	/// \brief Const Access Back Element
	/// \returns A const-qualified reference to the last element in the list
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr const value_t& back() const {
		return *_table[_last].value;
	}

	/// @}


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

	/// \name Modifiers
	/// @{

	///
	/// \brief Copy Insertion
	/// \param it Location to insert at
	/// \param x value to copy
	/// \returns The id of the inserted node
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr iterator insert(const iterator& it, const value_t& x) {
		return this->_insert(it._n, x);
	}

	///
	/// \brief Move Insertion
	/// \param it Location to insert at
	/// \param x value to move
	/// \returns The id of the inserted node
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr iterator insert(const iterator& it, value_t&& x) {
		return this->_insert(it._n, fennec::forward<value_t>(x));
	}

	///
	/// \brief Copy Insertion
	/// \param i Index to insert at
	/// \param x value to copy
	/// \returns The id of the inserted node
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr iterator insert(size_t i, const value_t& x) {
		assert(i <= size(), "Index out of Bounds");

		size_t n = _walk(min(i, size_t(size() - 1)));
		return this->_insert(n, x);
	}

	///
	/// \brief Move Insertion
	/// \param i Index to insert at
	/// \param x value to move
	/// \returns The id of the inserted node
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr iterator insert(size_t i, value_t&& x) {
		assert(i <= size(), "Index out of Bounds");

		size_t n = _walk(min(i, size_t(size() - 1)));
		return this->_insert(n, fennec::forward<value_t>(x));
	}

	///
	/// \brief Emplace Insertion
	/// \tparam ArgsT Argument types
	/// \param i Index to insert at
	/// \param args Arguments to construct with
	/// \returns The id of the inserted node
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	template<typename...ArgsT>
	constexpr iterator emplace(size_t i, ArgsT&&...args) {
		assert(i <= size(), "Index out of Bounds");

		size_t n = _walk(min(i, size_t(size() - 1)));
		return this->_insert(n, fennec::forward<ArgsT>(args)...);
	}

	///
	/// \brief Emplace Insertion
	/// \tparam ArgsT Argument types
	/// \param it Location to insert at
	/// \param args Arguments to construct with
	/// \returns The id of the inserted node
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename...ArgsT>
	constexpr iterator emplace(const iterator& it, ArgsT&&...args) {
		return this->_insert(it._n, fennec::forward<ArgsT>(args)...);
	}

	///
	/// \brief Push Front Copy
	/// \param x Value to copy
	/// \returns The id of the inserted node
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr iterator push_front(const value_t& x) {
		return this->_insert(_root, x);
	}

	///
	/// \brief Push Front Move
	/// \param x Value to move
	/// \returns The id of the inserted node
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr iterator push_front(value_t&& x) {
		return this->_insert(_root, fennec::forward<value_t>(x));
	}

	///
	/// \brief Emplace Front
	/// \param args Arguments to construct with
	/// \tparam  ArgsT Argument types
	/// \returns The id of the inserted node
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename...ArgsT>
	constexpr iterator emplace_front(ArgsT&&...args) {
		return this->_insert(_root, fennec::forward<ArgsT>(args)...);
	}

	///
	/// \brief Push Back Copy
	/// \param x Value to copy
	/// \returns The id of the inserted node
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr iterator push_back(const value_t& x) {
		return this->_insert(npos, x);
	}

	///
	/// \brief Push Back Move
	/// \param x Value to move
	/// \returns The id of the inserted node
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr iterator push_back(value_t&& x) {
		return this->_insert(npos, fennec::forward<value_t>(x));
	}

	///
	/// \brief Emplace Back
	/// \param args Arguments to construct with
	/// \tparam  ArgsT Argument types
	/// \returns The id of the inserted node
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename...ArgsT>
	constexpr iterator emplace_back(ArgsT&&...args) {
		return this->_insert(npos, fennec::forward<ArgsT>(args)...);
	}

	///
	/// \brief Erase Element
	/// \param i Index to erase
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr void erase(size_t i) {
		assert(i < size(), "Index out of Bounds!");
		const size_t n = _walk(i);
		_erase(n);
	}

	///
	/// \brief Erase Element
	/// \param it Location to Erase
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr void erase(const iterator& it) {
		this->_erase(it._n);
	}

	///
	/// \brief Pop Front, erases first element
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr void pop_front() {
		_erase(_root);
	}

	///
	/// \brief Pop Back, erases first element
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr void pop_back() {
		_erase(_last);
	}

	///
	/// \brief Clears the list, destructing all elements in order
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr void clear() {
		size_t i = _root;
		while (i != npos) {
			_table[i].value = nullopt;
			i = this->_next(i);
		}
		_table.clear();
		_root = npos;
		_size = 0;
	}

	/// @}

// Iterator ============================================================================================================
public:

	/// \name Iteration
	/// @{

	///
	/// \returns An iterator for the first element in the list
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr iterator begin() {
		return iterator(this, _root);
	}

	///
	/// \brief C++ Iterator Specification \emph{begin()}
	/// \returns A const iterator for the first element in the list
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr const_iterator begin() const {
		return const_iterator(this, _root);
	}

	///
	/// \returns An iterator for the end of the list
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr iterator end() {
		return iterator(this, npos);
	}

	///
	/// \brief Const C++ Iterator Specification \emph{end()}
	/// \returns A const iterator for the end of the list
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr const_iterator end() const {
		return const_iterator(this, npos);
	}

	///
	/// \brief C++ Iterator Specification \emph{iterator}
	class iterator {
	public:
		///
		/// \brief destructor
		~iterator() {
			_list = nullptr;
		}

		///
		/// \brief prefix increment operator
		/// \param rhs the iterator to increment
		/// \returns \emph{rhs} after having moved to the next element in the list
		constexpr friend iterator& operator++(iterator& rhs) {
			rhs._n = rhs._list->_next(rhs._n);
			return rhs;
		}

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

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

		///
		/// \brief pointer access operator
		/// \returns a pointer to the value pointed by the iterator
		constexpr value_t* operator->() {
			return &*(_list->_table[_n].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) {
			return _list == it._list and _n == it._n;
		}

		///
		/// \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) {
			return _list != it._list or _n != it._n;
		}

	private:
		list*  _list;
		size_t _n;
		friend list;

		iterator(list* ls, size_t n)
			: _list(ls)
			, _n(n) {
		}
	};

	///
	/// \brief Iterator Class for Const Access
	class const_iterator {
	public:
		///
		/// \brief destructor
		~const_iterator() {
			_list = nullptr;
		}

		///
		/// \brief prefix increment operator
		/// \param rhs the iterator to increment
		/// \returns \emph{rhs} after having moved to the next element in the list
		constexpr friend const_iterator& operator++(const_iterator& rhs) {
			if (rhs._list->_next(rhs._n) < rhs._list->capacity()) {
				return rhs;
			}
			rhs._n = npos;
			return rhs;
		}

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

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

		///
		/// \brief pointer access operator
		/// \returns a pointer to the value pointed by the iterator
		constexpr const value_t* operator->() {
			return &*(_list->_table[_n].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 const_iterator& it) {
			return _list == it._list and _n == it._n;
		}

		///
		/// \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 const_iterator& it) {
			return _list != it._list or _n != it._n;
		}

	private:
		const list*  _list;
		size_t _n;
		friend list;

		const_iterator(const list* ls, size_t n)
			: _list(ls)
			, _n(n) {
		}
	};

	/// @}

// Private Member Variables ============================================================================================
private:
	table_t _table;
	freed_t _freed;
	size_t _root, _last, _size;

	friend class iterator;


// Private Helpers =====================================================================================================
private:
	constexpr void _expand() {
		_table.resize(fennec::max(_table.size(), size_t(4)) * 2);
	}

	struct node {
		optional<value_t> value;
		size_t prev, next;

		constexpr node()
			: value()
			, prev(npos)
			, next(npos) {
		}

		constexpr ~node() = default;

		constexpr void clear() {
			value = nullopt;
			prev = npos;
			next = npos;
		}
	};

	constexpr size_t _next(size_t n) const {
		return _table[n].next;
	}

	constexpr size_t _prev(size_t n) const {
		return _table[n].prev;
	}

	constexpr size_t _walk(size_t i) const {
		size_t n = _root;
		if (n == npos) return n;
		while (i > 0 && n != npos) {
			n = _next(n); --i;
		}
		return n;
	}

	constexpr size_t _next_free() {
		if (not _freed.is_empty()) {
			const size_t n = _freed.back();
			_freed.pop_back();
			return n;
		}
		return _size;
	}

	template<typename...ArgsT>
	constexpr iterator _insert(size_t n, ArgsT&&...args) {
		if (size() == capacity()) {
			_expand();
		}

		size_t i = _next_free();
		++_size;
		_table[i].value.emplace(fennec::forward<ArgsT>(args)...);

		if (_root == npos) {
			_table[i].prev = npos;
			_table[i].next = npos;
			_root = _last = i;
			return iterator(this, i);
		}

		if (n == npos) {
			_table[_last].next = i;
			_table[i].prev = _last;
			_table[i].next = npos;
			_last = i;
			return iterator(this, i);
		}

		_table[i].prev = _prev(n);
		_table[i].next = n;
		_table[n].prev = i;
		_root = n == _root ? i : _root;
		return iterator(this, i);
	}

	constexpr void _erase(size_t n) {
		if (n == npos) return;

		_table[n].value = nullopt;
		_freed.push_back(n);
		--_size;

		size_t prev = _prev(n);
		size_t next = _next(n);

		if (prev != npos) {
			_table[prev].next = next;
		}

		if (next != npos) {
			_table[next].prev = prev;
		}

		_root = (n == _root) ? next : _root;
		_last = (n == _last) ? prev : _last;
	}
};

}

#endif // FENNEC_CONTAINERS_LIST_H
