#pragma once

#include <optional>
#include <variant>
#include <vector>

#include "api/BoxCollider.h"
#include "api/CircleCollider.h"
#include "api/Event.h"
#include "api/Metadata.h"
#include "api/Rigidbody.h"
#include "api/Transform.h"
#include "api/Vector2.h"

#include "Collider.h"
#include "System.h"

namespace crepe {

//! A system responsible for detecting and handling collisions between colliders.
class CollisionSystem : public System {
public:
	using System::System;

private:
	//! A variant type that can hold either a BoxCollider or a CircleCollider.
	using collider_variant = std::variant<std::reference_wrapper<BoxCollider>,
										  std::reference_wrapper<CircleCollider>>;

	//! Enum representing the types of collider pairs for collision detection.
	enum class CollisionInternalType {
		BOX_BOX,
		CIRCLE_CIRCLE,
		BOX_CIRCLE,
		CIRCLE_BOX,
	};

	/**
		* \brief A structure to store the collision data of a single collider.
		*
		* This structure all components and id that are for needed within this system when calculating or handeling collisions.
		* The transform and rigidbody are mostly needed for location and rotation.
		* In rigidbody additional info is written about what the body of the object is,
		* and how it should respond on a collision.
		*/
	struct CollisionInternal {
		game_object_id_t id = 0;
		collider_variant collider;
		Transform & transform;
		Rigidbody & rigidbody;
	};

	//! Enum representing movement directions during collision resolution.
	enum class Direction {
		//! No movement required.
		NONE,
		//! Movement in the X direction.
		X_DIRECTION,
		//! Movement in the Y direction.
		Y_DIRECTION,
		//! Movement in both X and Y directions.
		BOTH
	};

public:
	/**
		* \brief Structure representing detailed collision information between two colliders.
		*
		* Includes information about the colliding objects and the resolution data for handling the collision.
		*/
	struct CollisionInfo {
		Collider & this_collider;
		Transform & this_transform;
		Rigidbody & this_rigidbody;
		Metadata & this_metadata;
		Collider & other_collider;
		Transform & other_transform;
		Rigidbody & other_rigidbody;
		Metadata & other_metadata;
		//! The resolution vector for the collision.
		vec2 resolution;
		//! The direction of movement for resolving the collision.
		Direction resolution_direction = Direction::NONE;
	};

public:
	//! Updates the collision system by checking for collisions between colliders and handling them.
	void update() override;

private:
	/**
		* \brief Determines the type of collider pair from two colliders.
		*
		* Uses std::holds_alternative to identify the types of the provided colliders.
		*
		* \param collider1 First collider variant (BoxCollider or CircleCollider).
		* \param collider2 Second collider variant (BoxCollider or CircleCollider).
		* \return The combined type of the two colliders.
		*/
	CollisionInternalType get_collider_type(const collider_variant & collider1,
											const collider_variant & collider2) const;

	/**
		* \brief Calculates the current position of a collider.
		*
		* Combines the Collider offset, Transform position, and Rigidbody offset to compute the position of the collider.
		*
		* \param collider_offset The offset of the collider.
		* \param transform The Transform of the associated game object.
		* \param rigidbody The Rigidbody of the associated game object.
		* \return The calculated position of the collider.
		*/
	vec2 get_current_position(const vec2 & collider_offset, const Transform & transform,
							  const Rigidbody & rigidbody) const;

private:
	/**
		* \brief Handles collision resolution between two colliders.
		*
		* Processes collision data and adjusts objects to resolve collisions and/or calls the user oncollision script function.
		*
		* \param data1 Collision data for the first collider.
		* \param data2 Collision data for the second collider.
		*/
	void collision_handler_request(CollisionInternal & data1, CollisionInternal & data2);

	/**
		* \brief Resolves collision between two colliders and calculates the movement required.
		*
		* Determines the displacement and direction needed to separate colliders based on their types.
		*
		* \param data1 Collision data for the first collider.
		* \param data2 Collision data for the second collider.
		* \param type The type of collider pair.
		* \return A pair containing the resolution vector and direction for the first collider.
		*/
	std::pair<vec2, Direction> collision_handler(CollisionInternal & data1,
												 CollisionInternal & data2,
												 CollisionInternalType type);

	/**
		* \brief Calculates the resolution vector for two BoxColliders.
		*
		* Computes the displacement required to separate two overlapping BoxColliders.
		*
		* \param box_collider1 The first BoxCollider.
		* \param box_collider2 The second BoxCollider.
		* \param position1 The position of the first BoxCollider.
		* \param position2 The position of the second BoxCollider.
		* \return The resolution vector for the collision.
		*/
	vec2 get_box_box_resolution(const BoxCollider & box_collider1,
								const BoxCollider & box_collider2, const vec2 & position1,
								const vec2 & position2) const;

	/**
		* \brief Calculates the resolution vector for two CircleCollider.
		*
		* Computes the displacement required to separate two overlapping CircleCollider.
		*
		* \param circle_collider1 The first CircleCollider.
		* \param circle_collider2 The second CircleCollider.
		* \param final_position1 The position of the first CircleCollider.
		* \param final_position2 The position of the second CircleCollider.
		* \return The resolution vector for the collision.
		*/
	vec2 get_circle_circle_resolution(const CircleCollider & circle_collider1,
									  const CircleCollider & circle_collider2,
									  const vec2 & final_position1,
									  const vec2 & final_position2) const;

	/**
		* \brief Calculates the resolution vector for two CircleCollider.
		*
		* Computes the displacement required to separate two overlapping CircleCollider.
		*
		* \param circle_collider The first CircleCollider.
		* \param box_collider The second CircleCollider.
		* \param circle_position The position of the CircleCollider.
		* \param box_position The position of the BoxCollider.
		* \return The resolution vector for the collision.
		*/
	vec2 get_circle_box_resolution(const CircleCollider & circle_collider,
								   const BoxCollider & box_collider,
								   const vec2 & circle_position,
								   const vec2 & box_position) const;

	/**
		* \brief Determines the appropriate collision handler for a collision.
		*
		* Decides the correct resolution process based on the dynamic or static nature of the colliders involved.
		*
		* \param info Collision information containing data about both colliders.
		*/
	void determine_collision_handler(CollisionInfo & info);

	/**
		* \brief Handles collisions involving static objects.
		*
		* Resolves collisions by adjusting positions and modifying velocities if bounce is enabled.
		*
		* \param info Collision information containing data about both colliders.
		*/
	void static_collision_handler(CollisionInfo & info);

private:
	/**
		* \brief Checks for collisions between colliders.
		*
		* Identifies collisions and generates pairs of colliding objects for further processing.
		*
		* \param colliders A collection of all active colliders.
		* \return A list of collision pairs with their associated data.
		*/
	std::vector<std::pair<CollisionInternal, CollisionInternal>>
	gather_collisions(std::vector<CollisionInternal> & colliders);

	/**
	 * \brief Checks if two collision layers have at least one common layer.
	 *
	 * This function checks if there is any overlapping layer between the two inputs.
	 * It compares each layer from the first input to see
	 * if it exists in the second input. If at least one common layer is found,
	 * the function returns true, indicating that the two colliders share a common
	 * collision layer.
	 *
	 * \param layers1 all collision layers for the first collider.
	 * \param layers2 all collision layers for the second collider.
	 * \return Returns true if there is at least one common layer, false otherwise.
	 */

	bool have_common_layer(const std::set<int> & layers1, const std::set<int> & layers2);

	/**
		* \brief Checks for collision between two colliders.
		*
		* Calls the appropriate collision detection function based on the collider types.
		*
		* \param first_info Collision data for the first collider.
		* \param second_info Collision data for the second collider.
		* \param type The type of collider pair.
		* \return True if a collision is detected, otherwise false.
		*/
	bool get_collision(const CollisionInternal & first_info,
					   const CollisionInternal & second_info,
					   CollisionInternalType type) const;

	/**
		* \brief Detects collisions between two BoxColliders.
		*
		* \param box1 The first BoxCollider.
		* \param box2 The second BoxCollider.
		* \param transform1 Transform of the first object.
		* \param transform2 Transform of the second object.
		* \param rigidbody1 Rigidbody of the first object.
		* \param rigidbody2 Rigidbody of the second object.
		* \return True if a collision is detected, otherwise false.
		*/
	bool get_box_box_collision(const BoxCollider & box1, const BoxCollider & box2,
							   const Transform & transform1, const Transform & transform2,
							   const Rigidbody & rigidbody1,
							   const Rigidbody & rigidbody2) const;

	/**
	 * \brief Check collision for box on circle collider
	 *
	 * \param box1 The BoxCollider
	 * \param circle2 The CircleCollider
	 * \param transform1 Transform of the first object.
	 * \param transform2 Transform of the second object.
	 * \param rigidbody1 Rigidbody of the first object.
	 * \param rigidbody2 Rigidbody of the second object.
	 * \return True if a collision is detected, otherwise false.
	 */
	bool get_box_circle_collision(const BoxCollider & box1, const CircleCollider & circle2,
								  const Transform & transform1, const Transform & transform2,
								  const Rigidbody & rigidbody1,
								  const Rigidbody & rigidbody2) const;

	/**
	 * \brief Check collision for circle on circle collider
	 *
	 * \param circle1 First CircleCollider
	 * \param circle2 Second CircleCollider
	 * \param transform1 Transform of the first object.
	 * \param transform2 Transform of the second object.
	 * \param rigidbody1 Rigidbody of the first object.
	 * \param rigidbody2 Rigidbody of the second object.
	 * \return True if a collision is detected, otherwise false.
	 *
	 * \return status of collision
	 */
	bool get_circle_circle_collision(const CircleCollider & circle1,
									 const CircleCollider & circle2,
									 const Transform & transform1,
									 const Transform & transform2,
									 const Rigidbody & rigidbody1,
									 const Rigidbody & rigidbody2) const;
};

/**
 * \brief Event triggered during a collision between objects.
 */
class CollisionEvent : public Event {
public:
	crepe::CollisionSystem::CollisionInfo info;
	CollisionEvent(const crepe::CollisionSystem::CollisionInfo & collisionInfo)
		: info(collisionInfo) {}
};

} // namespace crepe