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

#ifndef FENNEC_CONTAINERS_DYNARRAY_H
#define FENNEC_CONTAINERS_DYNARRAY_H

#include <fennec/lang/utility.h>

#include <fennec/memory/allocator.h>

#include <fennec/containers/initializer_list.h>

#include <fennec/format/formatter.h>

namespace fennec
{

///
///
/// \brief Wrapper for dynamically sized and allocated arrays
/// \details
/// | Property    | Value       |
/// |:-----------:|:-----------:|
/// | stable      |      ⛔     |
/// | dynamic     |      ✅     |
/// | homogeneous |      ✅     |
/// | distinct    |      ⛔     |
/// | ordered     |      ⛔     |
/// | linear      |      ✅     |
/// | space       | \emph{O(N)} |
/// | access      | \emph{O(1)} |
/// | find        | \emph{O(N)} |
/// | insertion   | \emph{O(N)} |
/// | deletion    | \emph{O(N)} |
///
/// This structure prefers shallow moves and deep copies.
///
/// \tparam TypeT value type
template<class TypeT, class Alloc = allocator<TypeT>>
struct dynarray {

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

	/// \name Definitions
	/// @{

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

	/// @}


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

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

	///
	/// \brief Default Constructor, initializes an empty allocation.
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr dynarray()
		: _alloc(8)
		, _size(0) {
	}

	///
	/// \brief Alloc Constructor, initialize empty allocation with allocator instance.
	/// \param alloc An allocator object to copy, for instances where the allocator needs to be initialized with some
	///        data.
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	explicit constexpr dynarray(const alloc_t& alloc)
		: _alloc(8, alloc)
		, _size(0) {
	}

	///
	/// \brief Sized Allocation, initializes a dynarray with \emph{n} elements using the default constructor.
	/// \param n The number of elements.
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	explicit constexpr dynarray(size_t n)
		: _alloc(n)
		, _size(n)
	{
		value_t* addr = _alloc.data();
		for(; n > 0; --n, ++addr) {
			fennec::construct(addr);
		}
	}

	///
	/// \brief Sized Allocation Alloc Constructor, initializes a dynarray with allocator \emph{alloc} and \emph{n} elements
	///        using the default constructor.
	/// \param n The number of elements
	/// \param alloc The allocator object to copy
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr dynarray(size_t n, const alloc_t& alloc)
		: _alloc(n, alloc)
		, _size(n) {
		value_t* addr = _alloc.data();
		for(; n > 0; --n, ++addr) {
			fennec::construct(addr);
		}
	}

	///
	/// \brief Sized Allocation Copy Constructor, Create an allocation of size \emph{n} elements, with each element
	///        constructed using the copy constructor
	/// \param n the number of elements
	/// \param val the value to copy
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr dynarray(size_t n, const TypeT& val)
		: _alloc(n)
		, _size(n) {
		value_t* addr = _alloc.data();
		for(; n > 0; --n, ++addr) {
			fennec::construct(addr, val);
		}
	}

	///
	/// \brief Emplace Constructor
	/// \tparam ArgsT A sequence of argument types
	/// \param n The number of objects to create
	/// \param args The arguments to create each object with
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	template<typename...ArgsT>
	constexpr explicit dynarray(size_t n, ArgsT&&...args)
		: _alloc(n)
		, _size(n) {
		for(; n > 0; --n) {
			fennec::construct(&_alloc[n], fennec::forward<ArgsT>(args)...);
		}
	}

	///
	/// \brief Array Copy Constructor
	/// \tparam N The length of the array, automatically deduced
	/// \param arr The array to copy
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	template<size_t N>
	constexpr dynarray(const TypeT (&arr)[N])
		: _alloc(N)
		, _size(N) {
		for (size_t i = 0; i < N; ++i) {
			_alloc[i] = arr[i];
		}
	}

	///
	/// \brief Array Move Constructor
	/// \tparam N The length of the array, automatically deduced
	/// \param arr The array to move
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	template<size_t N>
	constexpr dynarray(TypeT (&&arr)[N])
		: _alloc(N)
		, _size(N) {
		for (size_t i = 0; i < N; ++i) {
			_alloc[i] = fennec::move(arr[i]);
		}
	}

	///
	/// \brief Conversion Constructor, copies elements of conv as this \emph{value_t}
	/// \tparam OTypeT The other value type
	/// \tparam OAlloc The other allocator type
	/// \param conv The dynarray to convert
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	template<typename OTypeT, class OAlloc>
	constexpr dynarray(const dynarray<OTypeT, OAlloc>& conv)
		: _alloc(conv.size())
		, _size(conv.size()) {
		size_t i = 0;
		for (const auto& it : conv) {
			fennec::construct(&_alloc[i++], it);
		}
	}

	///
	/// \brief Initializer List Constructor
	/// \param l List of elements to initialize with
	/// \param alloc An allocator object to copy
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr dynarray(initializer_list<value_t> l, const alloc_t& alloc = alloc_t())
		: _alloc(l.size(), alloc)
		, _size(l.size()) {
		size_t i = 0;
		for (auto& it : l) {
			fennec::construct(&_alloc[i++], fennec::move(it));
		}
	}

	///
	/// \brief Copy Constructor, uses the copy constructor to copy each element
	/// \param arr the dynarray to copy
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr dynarray(const dynarray& arr)
		: _alloc(arr._size)
		, _size(arr._size) {
		for (size_t i = 0; i < _size; ++i) {
			fennec::construct(&_alloc[i], arr[i]);
		}
	}

	///
	/// \brief Move Constructor, takes ownership of the allocation
	/// \param arr the dynarray to move
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr dynarray(dynarray&& arr) noexcept
		: _alloc(fennec::move(arr._alloc))
		, _size(arr._size) {
		arr._size = 0;
	}

	///
	/// \brief Default Destructor, destructs all elements and frees the underlying allocation
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr ~dynarray() {
		value_t* addr = _alloc.data();
		if (addr == nullptr) return;
		for(int n = _size; n > 0; --n, ++addr) {
			fennec::destruct(addr);
		}
	}

	/// @}




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

	/// \name Properties
	/// @{

	///
	/// \brief Copy Assignment Operator
	/// \param arr the array to copy
	/// \returns A dynarray after having copied each element of \emph{arr}
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr dynarray& operator=(const dynarray& arr) {
		this->clear();
		_alloc.reallocate(_size = arr._size);
		for (size_t i = 0; i < _size; ++i) {
			fennec::construct(&_alloc[i], fennec::copy(arr[i]));
		}
		return *this;
	}

	///
	/// \brief Move Assignment Operator
	/// \param arr the array to move
	/// \returns A dynarray after having taken ownership of the contents of \emph{arr}
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr dynarray& operator=(dynarray&& arr) noexcept {
		this->clear();
		_alloc = fennec::move(arr._alloc);
		_size  = arr._size;
		arr._size = 0;
		return *this;
	}

	///
	/// \brief Array Copy Assignment Operator
	/// \tparam N the length of the array
	/// \param arr the array to copy
	/// \returns A dynarray after having copied each element of \emph{arr}
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	template<size_t N>
	constexpr dynarray& operator=(const TypeT (&arr)[N]) {
		this->clear();
		_alloc.reallocate(_size = N);
		for (size_t i = 0; i < _size; ++i) {
			fennec::construct(&_alloc[i], fennec::copy(arr[i]));
		}
		return *this;
	}

	///
	/// \brief Array Copy Assignment Operator
	/// \tparam N the length of the array
	/// \param arr the array to copy
	/// \returns A dynarray after having moved each element of \emph{arr}
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	template<size_t N>
	constexpr dynarray& operator=(TypeT (&&arr)[N]) {
		this->clear();
		_alloc.reallocate(_size = N);
		for (size_t i = 0; i < _size; ++i) {
			fennec::construct(&_alloc[i], fennec::move(arr[i]));
		}
		return *this;
	}

	/// @}

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

	/// \name Properties
	/// @{

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

	///
	/// \returns The current capacity, in elements, of the underlying allocation
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr size_t capacity() const {
		return _alloc.capacity();
	}

	///
	/// \returns True when there are no elements active, otherwise false
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr bool is_empty() const {
		return _size == 0;
	}

	/// @}


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

	/// \name Access
	/// @{

	///
	/// \brief Array Access Operator
	/// \param i The index to access
	/// \returns A reference to the element at index \emph{i}
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr TypeT& operator[](size_t i) {
		assertd(i < _size, "Array Out of Bounds");
		return _alloc[i];
	}

	///
	/// \brief Array Access Operator (const)
	/// \param i The index to access
	/// \returns A const qualified reference to the element at index \emph{i}
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr const TypeT& operator[](size_t i) const {
		assertd(i < _size, "Array Out of Bounds");
		return _alloc[i];
	}

	///
	/// \returns Reference to the first element in the dynarray
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr TypeT& front() {
		return this->operator[](0);
	}

	///
	/// \returns A const-qualified reference to the first element in the dynarray
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr const TypeT& front() const {
		return this->operator[](0);
	}

	///
	/// \returns A reference to the last element in the dynarray
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr TypeT& back() {
		return this->operator[](size() - 1);
	}

	///
	/// \returns A const-qualified reference to the last element in the dynarray
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr const TypeT& back() const {
		return this->operator[](size() - 1);
	}

	///
	/// \returns A pointer to the underlying allocation
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr TypeT* data() {
		return _alloc.data();
	}

	///
	/// \returns A pointer to the underlying allocation
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr const TypeT* data() const {
		return _alloc.data();
	}

	/// @}


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

	/// \name Modifiers
	/// @{

	///
	/// \brief Move Insertion
	/// \param i index to insert at
	/// \param val the value to initialize with
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr void insert(size_t i, TypeT&& val) {

		// Grow if the size has reached the capacity of the allocation
		if(_size == capacity()) {
			_grow();
		}

		// Move the data if we are not inserting at the end of the array
		if((i = min(i, _size)) < _size) {
			fennec::memmove(
				static_cast<void*>(_alloc.data() + i + 1)
			,	static_cast<void*>(_alloc.data() + i)
			,	(_size - i) * sizeof(TypeT));
		}

		// Insert the element
		fennec::construct(_alloc.data() + i, fennec::forward<TypeT>(val));
		++_size;
	}

	///
	/// \brief Copy Insertion
	/// \param i index to insert at
	/// \param val the value to initialize with
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr void insert(size_t i, const TypeT& val) {

		// Grow if the size has reached the capacity of the allocation
		if(_size == capacity()) {
			_grow();
		}

		// Move the data if we are not inserting at the end of the array
		if((i = min(i, _size)) < _size) {
			fennec::memmove(
				static_cast<void*>(_alloc.data() + i),
				static_cast<void*>(_alloc.data() + i + 1),
				(_size - i) * sizeof(TypeT)
			);
		}

		// Insert the element
		fennec::construct(_alloc.data() + i, val);
		++_size;
	}

	///
	/// \brief Emplace Insertion
	/// \param i index to insert at
	/// \param args Arguments to construct with
	/// \tparam ArgsT Argument types
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	template<typename...ArgsT>
	constexpr void emplace(size_t i, ArgsT&&...args) {

		// Grow if the size has reached the capacity of the allocation
		if(_size == capacity()) {
			_grow();
		}

		// Move the data if we are not inserting at the end of the array
		if((i = min(i, _size)) < _size) {
			fennec::memmove(
				static_cast<void*>(_alloc.data() + i)
			,	static_cast<void*>(_alloc.data() + i + 1)
			,	(_size - i) * sizeof(TypeT));
		}

		// Insert the element
		fennec::construct(_alloc.data() + i, fennec::forward<ArgsT>(args)...);
		++_size;
	}

	///
	/// \brief Push Back Copy
	/// \param val Value to initialize with
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr void push_back(const TypeT& val) {
		dynarray::insert(_size, val);
	}

	///
	/// \brief Push Back Move
	/// \param val Value to initialize with
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr void push_back(TypeT&& val) {
		dynarray::insert(_size, fennec::forward<TypeT>(val));
	}

	///
	/// \brief Emplace Back
	/// \tparam ArgsT Argument Types
	/// \param args Arguments to construct with
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename...ArgsT>
	constexpr void emplace_back(ArgsT...args) {
		dynarray::emplace(_size, fennec::forward<ArgsT>(args)...);
	}

	///
	/// \brief Erase last element
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr void pop_back() {
		fennec::destruct(&_alloc[--_size]);
	}

	///
	/// \brief Resize the dynarray, invoking the default constructor for all new elements
	/// \param n The new size in elements
	///
	/// \details if \emph{n} is less than the current size, any elements that would be removed are destructed
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr void resize(size_t n) {
		_reduce(n);
		_alloc.reallocate(n);

		for (size_t i = _size; i < n; ++i) {
			fennec::construct(&_alloc[i]);
		}

		_size = n;
	}

	///
	/// \brief Resize the dynarray, invoking the copy constructor for all new elements
	/// \param n The new size in elements
	/// \param val The value to fill with
	///
	/// \details if \emph{n} is less than the current size, any elements that would be removed are destructed
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr void resize(size_t n, const TypeT& val) {
		_reduce(n);
		_alloc.reallocate(n);

		for (size_t i = _size; i < n; ++i) {
			fennec::construct(&_alloc[i], val);
		}

		_size = n;
	}

	///
	/// \brief Reserve the array, allocating new space without initialization
	/// \param n The new capacity in elements
	///
	/// \details if \emph{n} is less than the current size, any elements that would be removed are destructed
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr void reserve(size_t n) {
		_reduce(n);
		_alloc.reallocate(n);
	}

	///
	/// \brief Clears the contents of the dynarray, destructing all elements and releasing the allocation.
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr void clear() {
		_reduce(0);
		_alloc.deallocate();
	}

	/// @}


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

	/// \name Iteration
	/// @{

	///
	/// \returns A pointer to the first element in the dynarray
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr TypeT* begin() { return _alloc; }

	///
	/// \brief C++ Iterator Specification \emph{begin()}
	/// \returns A const qualified pointer to the first element in the dynarray
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr const TypeT* begin() const { return _alloc; }


	///
	/// \return A pointer to the address after the last element in the dynarray
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr TypeT* end() { return begin() + _size; }

	///
	/// \brief C++ Iterator Specification \emph{end()}
	/// \return A const qualified pointer to the address after the last element in the dynarray
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr const TypeT* end() const { return begin() + _size; }

	/// @}

// Public Member Variables =============================================================================================
private:
	allocation<value_t, alloc_t> _alloc;
	size_t _size;


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

	// helper to double the capacity of the allocation
	constexpr void _grow() {
		_alloc.reallocate(_alloc.capacity() * 2);
	}

	// helper to destruct elements past n
	constexpr void _reduce(size_t n) {
		while (_size > n) {
			fennec::destruct(&_alloc[--_size]);
		}
	}
};

///
/// \brief Formatter for `fennec::dynarray`
/// \tparam T The element type.
template<typename T>
struct formatter<dynarray<T>> {
	///
	/// \brief format function
	/// \param str the string argument
	/// \returns the formatted version of \emph{str}
	string operator()(const format_arg& fmt, const dynarray<T>& arr) const {
		string res = string("[ ");
		for (auto& it : arr) {
			res += ' ';
			res += base(fmt, it);
		}
		return res;
	}

	static constexpr formatter<T> base = {};
};

}

#endif // FENNEC_CONTAINERS_DYNARRAY_H
