#pragma once

#include <psemek/phys/common_2d.hpp>

#include <psemek/util/pimpl.hpp>

#include <psemek/math/box.hpp>

#include <memory>
#include <variant>
#include <cstdint>

namespace psemek::phys2d
{

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

		// Shape management

		using shape_handle = std::uint32_t;

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

		std::variant<ball const *, half_space const *, box const *> get_shape(shape_handle handle) const;

		void remove_shape(shape_handle handle);

		// Material management

		using material_handle = std::uint32_t;

		material_handle add_material(material const & m);

		material const & get_material(material_handle handle) const;
		material & get_material(material_handle handle);

		void remove_material(material_handle handle);

		// Group & body management

		using group_handle = std::uint32_t;

		group_handle create_group();
		void remove_group(group_handle handle);

		std::size_t group_size(group_handle handle) const;
		static_state const * group_static_state(group_handle handle) const;
		dynamic_state const * group_dynamic_state(group_handle handle) const;
		static_state * group_static_state(group_handle handle);
		dynamic_state * group_dynamic_state(group_handle handle);

		std::size_t add_object(group_handle group, shape_handle shape, material_handle material, static_state const & ss, dynamic_state const & ds);
		void remove_object(group_handle group, std::size_t index);

		struct object_info
		{
			engine::shape_handle shape;
			engine::material_handle material;
			float inv_mass;
			float inv_inertia;
		};

		object_info const & get_object(group_handle group, std::size_t index);

		// Constraint management

		using constraint_handle = std::uint32_t;

		constraint_handle add_constraint(group_handle g, std::size_t index, fixed_constraint const & c);
		constraint_handle add_constraint(group_handle g1, std::size_t index1, group_handle g2, std::size_t index2, joint_constraint const & c);

		void remove_constraint(constraint_handle handle);

		// Global system properties

		void set_gravity(math::vector<float, 2> const & g);
		void set_air_friction(float friction);

		// Effects

		void explode(math::point<float, 2> const & center, float strength, float attenuation);

		// Main update

		void update(float dt);

	private:
		psemek_declare_pimpl
	};

}
