#pragma once

#include <psemek/math/vector.hpp>
#include <psemek/math/point.hpp>
#include <psemek/math/quaternion.hpp>
#include <psemek/math/matrix.hpp>

#include <psemek/util/pimpl.hpp>

#include <variant>
#include <cstdint>

namespace psemek::phys3d
{

	struct ball
	{
		float radius;
	};

	struct box
	{
		math::vector<float, 3> dimensions;
	};

	struct half_space
	{
		// p * normal >= value
		math::vector<float, 3> normal;
		float value;
	};

	struct material
	{
		float density;
		float elasticity;
		float friction;
	};

	struct object_state
	{
		math::point<float, 3> position;
		math::quaternion<float> rotation = math::quaternion<float>::identity();

		math::vector<float, 3> velocity = {0.f, 0.f, 0.f};
		math::vector<float, 3> angular_velocity = {0.f, 0.f, 0.f};
	};

	struct engine
	{
		engine();
		~engine();

		static constexpr std::uint32_t null = static_cast<std::uint32_t>(-1);

		// Shape management

		using shape_handle = std::uint32_t;

		shape_handle add_shape(ball const & s);
		shape_handle add_shape(box const & s);
		shape_handle add_shape(half_space const & s);

		using any_shape = std::variant<ball, box, half_space>;
		using any_shape_ptr = std::variant<ball const *, box const *, half_space const *>;

		any_shape_ptr get_shape(shape_handle h) const;

		void remove_shape(shape_handle h);

		// Material management

		using material_handle = std::uint32_t;

		material_handle add_material(material const & m);

		material const & get_material(material_handle h) const;

		void remove_material(material_handle h);

		// Object management

		using object_handle = std::uint32_t;

		object_handle add_object(shape_handle shape, material_handle material, object_state const & s);

		shape_handle get_object_shape(object_handle h) const;
		material_handle get_object_material(object_handle h) const;

		object_state const & get_object_state(object_handle h) const;
		object_state & get_object_state(object_handle h);

		float get_object_mass(object_handle h) const;
		math::matrix<float, 3, 3> get_object_inertia(object_handle h) const;

		void remove_object(object_handle h);

		object_handle object_count() const;
		bool is_object(object_handle h) const;

		void add_impulse(object_handle h, math::vector<float, 3> const & impulse);

		// Force management

		void set_gravity(math::vector<float, 3> const & g);

		// Update loop

		void update(float dt);

	private:
		psemek_declare_pimpl
	};

}
