// =====================================================================================================================
//  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
{

template<class TypeT, class Alloc>
class dynarray
{
public:
	using element_t = TypeT;
	using alloc_t = Alloc;

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

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

	///
	/// \brief
	/// \param n
	/// \param alloc
	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 Create an allocation of size `n`, with each element constructed using the copy constructor
	/// \brief n the number of elements
	dynarray(size_t n, const TypeT& val) {
		element_t* addr = _alloc.data();
		for(; n > 0; --n, ++addr) { fennec::construct(addr, val); }
	}

	template<typename...ArgsT>
	dynarray(size_t n, ArgsT&&...args) {
		element_t* addr = _alloc.data();
		for(; n > 0; --n, ++addr) { fennec::construct(addr, fennec::forward<ArgsT>(args)...); }
	}

	~dynarray() {
		element_t* addr = _alloc.data();
		for(; n > 0; --n, ++addr) { fennec::destruct(addr); }
	}

	size_t size() const {
		return _size;
	}

	size_t capacity() const {
		return _alloc.capacity();
	}

	TypeT& operator[](size_t i) {
		assertd(i < _size, "Array Out of Bounds"); return _alloc.data()[i];
	}

	const TypeT& operator[](size_t i) const {
		assertd(i < _size, "Array Out of Bounds"); return _alloc.data()[i];
	}

	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(
				_alloc.data() + i
			,	_alloc.data() + i + 1
			,	(_size - i) * sizeof(TypeT));
		}

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

	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(
				_alloc.data() + i
			,	_alloc.data() + i + 1
			,	(_size - i) * sizeof(TypeT));
		}

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

	template<typename...ArgsT>
	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(
				_alloc.data() + i
			,	_alloc.data() + i + 1
			,	(_size - i) * sizeof(TypeT));
		}

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

	void push_back(const TypeT& val) {
		dynarray::insert(_size, val);
	}

	void push_back(TypeT&& val) {
		dynarray::insert(_size, fennec::forward<TypeT>(val));
	}

	template<typename...ArgsT> void emplace_back(ArgsT...args) {
		dynarray::emplace(_size, fennec::forward<ArgsT>(args)...);
	}

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

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

}

#endif // FENNEC_CONTAINERS_DYNARRAY_H
