#pragma once

#include <psemek/ecs/exceptions.hpp>
#include <psemek/ecs/detail/table.hpp>

#include <typeindex>

namespace psemek::ecs
{

	struct accessor
	{
		/** Create an invalid accessor, i.e. one that doesn't refer to an entity.
		 */
		accessor() = default;

		/** Copy-construct the accessor.
		 */
		accessor(accessor const &) = default;

		/** Copy-assign the accessor.
		 */
		accessor & operator = (accessor const & other) = default;

		/** Check if this accessor is valid, i.e. refers to an entity.
		 *
		 *  @return True if the accessor is valid, false otherwise
		 */
		explicit operator bool() const;

		/** Obtain the component of the specified type of the accessed entity.
		 *
		 *  @tparam Component The type of component to obtain
		 *  @return A pointer to the component of the specified type, or a null
		 *    pointer if the entity doesn't contain a component of this type
		 *  @pre The accessor is valid
		 */
		template <typename Component>
		Component * get_if();

		template <typename Component>
		Component const * get_if() const;

		/** Obtain the component of the specified type of the accessed entity.
		 *
		 *  @tparam Component The type of component to obtain
		 *  @return A reference to the component of the specified type
		 *  @throw component_not_found_exception if the entity doesn't
		 *    contain a component of this type
		 *  @pre The accessor is valid
		 */
		template <typename Component>
		Component & get();

		template <typename Component>
		Component const & get() const;

		/** Check if the entity contains this component type.
		 *
		 *  @tparam Component The type of component to obtain
		 *  @return True if the entity contains a component of the specified
		 *    type, false otherwise
		 *  @pre The accessor is valid
		 */
		template <typename Component>
		bool contains() const;

		/** Check if the entity contains this component type by uuid.
		 *
		 *  @param uuid The uuid of the component
		 *  @return True if the entity contains a component with the specified
		 *    uuid, false otherwise
		 *  @pre The accessor is valid
		 */
		bool contains(util::uuid const & uuid) const;

	private:
		detail::table * table_ = nullptr;
		std::uint32_t row_ = 0;

		friend struct container;

		accessor(detail::table * table, std::uint32_t row);
	};

	inline accessor::operator bool() const
	{
		return table_ != nullptr;
	}

	template <typename Component>
	Component * accessor::get_if()
	{
		return const_cast<Component *>(static_cast<accessor const *>(this)->get_if<Component>());
	}

	template <typename Component>
	Component const * accessor::get_if() const
	{
		util::uuid const uuid = std::remove_const_t<Component>::uuid();

		auto column = table_->column(uuid);
		if (!column)
			return nullptr;

		return reinterpret_cast<Component *>(column->data() + detail::stride<std::remove_const_t<Component>>() * row_);
	}

	template <typename Component>
	Component & accessor::get()
	{
		if (auto ptr = get_if<Component>())
			return *ptr;
		throw component_not_found_exception(typeid(std::remove_const_t<Component>), table_->entity_handles()[row_], table_->describe(row_));
	}

	template <typename Component>
	Component const & accessor::get() const
	{
		if (auto ptr = get_if<Component const>())
			return *ptr;
		throw component_not_found_exception(typeid(std::remove_const_t<Component>), table_->entity_handles()[row_], table_->describe(row_));
	}

	template <typename Component>
	bool accessor::contains() const
	{
		return get_if<Component const>() != nullptr;
	}

	inline bool accessor::contains(util::uuid const & uuid) const
	{
		return table_->column_index(uuid) != std::nullopt;
	}

	inline accessor::accessor(detail::table * table, std::uint32_t row)
		: table_(table)
		, row_(row)
	{}

}
