// =====================================================================================================================
//  fennec, a free and open source game engine
//  Copyright © 2025  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 dynarray.h
/// \brief dynamically allocated array wrapper
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 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/memory/new.h>

namespace fennec
{

///
///
/// \brief wrapper for dynamically sized arrays
/// \details
/// | Property  | Value      |
/// |-----------|:----------:|
/// | stable    | \emoji x   |
/// | access    | \f$O(1)\f$ |
/// | insertion | \f$O(N)\f$ |
/// | deletion  | \f$O(N)\f$ |
/// | space     | \f$O(N)\f$ |
///
/// \tparam TypeT value type
template<class TypeT, class Alloc = allocator<TypeT>>
class dynarray {
public:

// Definitions =========================================================================================================

	using element_t = TypeT;
	using alloc_t = Alloc;


// Constructors ========================================================================================================

	///
	/// \brief Default Constructor, initializes an empty allocation.
	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.
	constexpr dynarray(const alloc_t& alloc)
		: _alloc(8, alloc)
		, _size(0) {
	}

	///
	/// \brief Alloc Move 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.
	constexpr dynarray(alloc_t&& alloc) noexcept
		: _alloc(8, alloc)
		, _size(0) {
	}

	///
	/// \brief Sized Allocation, create an allocation of size `n` elements, initialized with the default constructor.
	constexpr dynarray(size_t n)
		: _alloc(n)
		, _size(n)
	{
		element_t* addr = _alloc.data();
		for(; n > 0; --n, ++addr) {
			fennec::construct(addr);
		}
	}

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

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

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

	// This constructor should not be invokable since moving is a single object operation and will cause undefined
	// behaviour when moving to multiple elements
	constexpr dynarray(size_t n, TypeT&&) = delete;

	///
	/// \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
	template<typename...ArgsT>
	constexpr dynarray(size_t n, ArgsT&&...args) {
		element_t* addr = _alloc.data();
		for(; n > 0; --n, ++addr) {
			fennec::construct(addr, fennec::forward<ArgsT>(args)...);
		}
	}

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


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

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

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

	///
	/// \returns True when there are no elements active, otherwise false
	constexpr bool empty() const {
		return _size == 0;
	}


// Element Access ======================================================================================================

	///
	/// \brief Array Access Operator
	/// \param i The index to access
	/// \returns A reference to the element at index `i`
	constexpr TypeT& operator[](int i) {
		assertd(i >= 0 and size_t(i) < _size, "Array Out of Bounds");
		return _alloc.data()[i];
	}

	///
	/// \brief Array Access Operator (const)
	/// \param i The index to access
	/// \returns A const qualified reference to the element at index `i`
	constexpr const TypeT& operator[](int i) const {
		assertd(i >= 0 and size_t(i) < _size, "Array Out of Bounds");
		return _alloc.data()[i];
	}

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

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

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

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

	///
	/// \brief "Iterator" Begin Function
	/// \returns A pointer to the first element in the dynarray
	constexpr TypeT* begin() { return _alloc.data(); }

	///
	/// \brief "Iterator" End Function
	/// \return A pointer to the address after the last element in the dynarray
	constexpr TypeT* end()   { return begin() + _size; }

	///
	/// \brief Const "Iterator" Begin Function
	/// \returns A const qualified pointer to the first element in the dynarray
	constexpr const TypeT* begin() const { return _alloc; }

	///
	/// \brief Const "Iterator" End Function
	/// \return A const qualified pointer to the address after the last element in the dynarray
	constexpr const TypeT* end()   const { return begin() + _size; }


// Insertion & Deletion ================================================================================================

	///
	/// \brief Move Insertion
	/// \param i index to insert at
	/// \param val the value to initialize with
	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(
				(void*)(_alloc.data() + i + 1)
			,	(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
	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(
				(void*)(_alloc.data() + i),
				(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
	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(
				(void*)(_alloc.data() + i)
			,	(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
	constexpr void push_back(const TypeT& val) {
		dynarray::insert(_size, val);
	}

	///
	/// \brief Push Back Move
	/// \param val Value to initialize with
	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
	template<typename...ArgsT>
	constexpr void emplace_back(ArgsT...args) {
		dynarray::emplace(_size, fennec::forward<ArgsT>(args)...);
	}

	///
	/// \brief Erase last element
	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
	constexpr void resize(size_t n) {
		_alloc.reallocate(n);

		while (_size < n) {
			emplace_back();
		}
	}

private:
	constexpr void _grow() {
		_alloc.reallocate(_alloc.capacity() * 2);
	}

	allocation<element_t, alloc_t> _alloc;
	size_t _size;
};

}

#endif // FENNEC_CONTAINERS_DYNARRAY_H
