#pragma once

#include <psemek/ecs/detail/stride.hpp>
#include <psemek/ecs/detail/describe.hpp>
#include <psemek/util/uuid.hpp>
#include <psemek/util/assert.hpp>

#include <memory>
#include <typeindex>

namespace psemek::ecs::detail
{

	struct column
	{
		std::uint8_t * data()
		{
			return data_;
		}

		std::uint8_t const * data() const
		{
			return data_;
		}

		std::size_t stride() const
		{
			return stride_;
		}

		util::uuid const & uuid() const
		{
			return uuid_;
		}

		std::type_index const & type() const
		{
			return type_;
		}

		bool copy_constructible() const
		{
			return copy_constructible_;
		}

		virtual void push_row() = 0;
		virtual void emplace_rows(column * from, std::size_t from_row, std::size_t count) = 0;
		virtual void insert_rows(column const * from, std::size_t from_row, std::size_t count) = 0;
		virtual void swap_rows(std::size_t row1, std::size_t row2) = 0;
		virtual void pop_row() = 0;
		virtual void clear() = 0;

		virtual std::unique_ptr<column> clone() const = 0;

		virtual std::size_t memory_usage() const = 0;

		virtual std::string describe() const = 0;
		virtual std::string describe(std::uint32_t row) const = 0;

		virtual ~column() = default;

	protected:
		std::uint8_t * data_ = nullptr;
		std::size_t stride_;
		util::uuid uuid_;
		std::type_index type_;
		bool copy_constructible_;

		column(std::size_t stride, util::uuid const & uuid, std::type_index const & type, bool copy_constructible)
			: stride_(stride)
			, uuid_(uuid)
			, type_(type)
			, copy_constructible_(copy_constructible)
		{}
	};

	template <typename Component, bool Empty = detail::is_empty_v<Component>>
	struct column_impl
		: column
	{
		column_impl();

		void push_row() override;
		void emplace_rows(column * from, std::size_t from_row, std::size_t count) override;
		void insert_rows(column const * from, std::size_t from_row, std::size_t count) override;
		void swap_rows(std::size_t row1, std::size_t row2) override;
		void pop_row() override;
		void clear() override;

		std::unique_ptr<column> clone() const override;

		std::size_t memory_usage() const override;

		std::string describe() const override;
		std::string describe(std::uint32_t row) const override;

		~column_impl() override;

	private:
		std::size_t capacity_ = 0;
		std::size_t row_count_ = 0;

		void allocate(std::size_t min_capacity);
	};

	template <typename Component>
	struct column_impl<Component, true>
		: column
	{
		column_impl();

		void push_row() override;
		void emplace_rows(column * from, std::size_t from_row, std::size_t count) override;
		void insert_rows(column const * from, std::size_t from_row, std::size_t count) override;
		void swap_rows(std::size_t row1, std::size_t row2) override;
		void pop_row() override;
		void clear() override;

		std::unique_ptr<column> clone() const override;

		std::size_t memory_usage() const override;

		std::string describe() const override;
		std::string describe(std::uint32_t row) const override;

		~column_impl() override;
	};

	template <typename Component, bool Empty>
	column_impl<Component, Empty>::column_impl()
		: column(detail::stride<Component>(), Component::uuid(), typeid(Component), std::is_copy_constructible_v<Component>)
	{}

	template <typename Component, bool Empty>
	void column_impl<Component, Empty>::push_row()
	{
		allocate(row_count_ + 1);

		new (reinterpret_cast<Component *>(data_) + row_count_) Component{};
		++row_count_;
	}

	template <typename Component, bool Empty>
	void column_impl<Component, Empty>::emplace_rows(column * from, std::size_t from_row, std::size_t count)
	{
		allocate(row_count_ + count);

		auto src = reinterpret_cast<Component *>(from->data()) + from_row;
		auto dst = reinterpret_cast<Component *>(data_) + row_count_;
		auto src_end = src + count;
		while (src != src_end)
		{
			new (dst) Component{std::move(*src)};
			++src;
			++dst;
		}

		row_count_ += count;
	}

	template <typename Component, bool Empty>
	void column_impl<Component, Empty>::insert_rows(column const * from, std::size_t from_row, std::size_t count)
	{
		if constexpr (std::is_copy_constructible_v<Component>)
		{
			allocate(row_count_ + count);

			auto src = reinterpret_cast<Component const *>(from->data()) + from_row;
			auto dst = reinterpret_cast<Component *>(data_) + row_count_;
			auto src_end = src + count;
			while (src != src_end)
			{
				new (dst) Component{*src};
				++src;
				++dst;
			}

			row_count_ += count;
		}
		else
		{
			assert(false);
		}
	}

	template <typename Component, bool Empty>
	void column_impl<Component, Empty>::swap_rows(std::size_t row1, std::size_t row2)
	{
		auto data = reinterpret_cast<Component *>(data_);
		std::swap(data[row1], data[row2]);
	}

	template <typename Component, bool Empty>
	void column_impl<Component, Empty>::pop_row()
	{
		(reinterpret_cast<Component *>(data_) + row_count_ - 1)->~Component();
		--row_count_;
	}

	template <typename Component, bool Empty>
	void column_impl<Component, Empty>::clear()
	{
		auto data = reinterpret_cast<Component *>(data_);
		for (auto p = data; p < data + row_count_; ++p)
			p->~Component();
		row_count_ = 0;
	}

	template <typename Component, bool Empty>
	std::unique_ptr<column> column_impl<Component, Empty>::clone() const
	{
		return std::make_unique<column_impl<Component, Empty>>();
	}

	template <typename Component, bool Empty>
	std::size_t column_impl<Component, Empty>::memory_usage() const
	{
		return sizeof(Component) * row_count_;
	}

	template <typename Component, bool Empty>
	std::string column_impl<Component, Empty>::describe() const
	{
		return detail::describe<Component>();
	}

	template <typename Component, bool Empty>
	std::string column_impl<Component, Empty>::describe(std::uint32_t row) const
	{
		return detail::describe(reinterpret_cast<Component const *>(data_)[row]);
	}

	template <typename Component, bool Empty>
	column_impl<Component, Empty>::~column_impl()
	{
		clear();
		::operator delete [] (data_, std::align_val_t(alignof(Component)));
		data_ = nullptr;
		row_count_ = 0;
	}

	template <typename Component, bool Empty>
	void column_impl<Component, Empty>::allocate(std::size_t min_capacity)
	{
		if (capacity_ >= min_capacity)
			return;

		std::size_t new_capacity = std::max<std::size_t>(64, capacity_);
		while (new_capacity < min_capacity)
			new_capacity *= 2;

		auto new_data = new (std::align_val_t(alignof(Component))) std::uint8_t[new_capacity * sizeof(Component)];

		auto old_begin = reinterpret_cast<Component *>(data_);
		auto old_end = old_begin + row_count_;
		auto new_begin = reinterpret_cast<Component *>(new_data);
		for (; old_begin != old_end; ++old_begin, ++new_begin)
		{
			new (new_begin) Component{std::move(*old_begin)};
			old_begin->~Component();
		}

		::operator delete [] (data_, std::align_val_t(alignof(Component)));
		data_ = new_data;
		capacity_ = new_capacity;
	}

	template <typename Component>
	column_impl<Component, true>::column_impl()
		: column(detail::stride<Component>(), Component::uuid(), typeid(Component), std::is_copy_constructible_v<Component>)
	{
		data_ = reinterpret_cast<std::uint8_t *>(new Component[1]);
	}

	template <typename Component>
	void column_impl<Component, true>::push_row()
	{}

	template <typename Component>
	void column_impl<Component, true>::emplace_rows(column *, std::size_t, std::size_t)
	{}

	template <typename Component>
	void column_impl<Component, true>::insert_rows(column const *, std::size_t, std::size_t)
	{}

	template <typename Component>
	void column_impl<Component, true>::swap_rows(std::size_t, std::size_t)
	{}

	template <typename Component>
	void column_impl<Component, true>::pop_row()
	{}

	template <typename Component>
	void column_impl<Component, true>::clear()
	{}

	template <typename Component>
	std::unique_ptr<column> column_impl<Component, true>::clone() const
	{
		return std::make_unique<column_impl<Component, true>>();
	}

	template <typename Component>
	std::size_t column_impl<Component, true>::memory_usage() const
	{
		return sizeof(Component);
	}

	template <typename Component>
	std::string column_impl<Component, true>::describe() const
	{
		return detail::describe<Component>();
	}

	template <typename Component>
	std::string column_impl<Component, true>::describe(std::uint32_t) const
	{
		return detail::describe(*reinterpret_cast<Component const *>(data_));
	}

	template <typename Component>
	column_impl<Component, true>::~column_impl()
	{
		delete [] reinterpret_cast<Component *>(data_);
		data_ = nullptr;
	}


}
