#pragma once
// #include "Engine.h"


// #include "Jolt/Physics/Body/BodyCreationSettings.h"
// #include <Jolt/Physics/Collision/Shape/SphereShape.h>
// #include <Jolt/Physics/Body/BodyCreationSettings.h>
// #include "Jolt/Physics/Collision/Shape/BoxShape.h"
// #include "Jolt/Physics/Collision/Shape/SphereShape.h"
// #include "Jolt/Physics/Collision/Shape/CapsuleShape.h"


// using namespace JPH;
// Layer that objects can be in, determines which other objects it can collide with
// Typically you at least want to have 1 layer for moving bodies and 1 layer for static bodies, but you can have more
// layers if you want. E.g. you could have a layer for high detail collision (which is not used by the physics simulation
// but only if you do collision testing).
namespace Layers {
	static constexpr JPH::ObjectLayer NON_MOVING = 0;
	static constexpr JPH::ObjectLayer MOVING = 1;
	static constexpr JPH::ObjectLayer NUM_LAYERS = 2;
};

/// Class that determines if two object layers can collide
class ObjectLayerPairFilterImpl : public JPH::ObjectLayerPairFilter {
	public:
		virtual bool ShouldCollide(JPH::ObjectLayer inObject1, JPH::ObjectLayer inObject2) const override;
};

// Each broadphase layer results in a separate bounding volume tree in the broad phase. You at least want to have
// a layer for non-moving and moving objects to avoid having to update a tree full of static objects every frame.
// You can have a 1-on-1 mapping between object layers and broadphase layers (like in this case) but if you have
// many object layers you'll be creating many broad phase trees, which is not efficient. If you want to fine tune
// your broadphase layers define JPH_TRACK_BROADPHASE_STATS and look at the stats reported on the TTY.
namespace BroadPhaseLayers {
	static constexpr JPH::BroadPhaseLayer NON_MOVING(0);
	static constexpr JPH::BroadPhaseLayer MOVING(1);
	static constexpr JPH::uint NUM_LAYERS(2);
};


// BroadPhaseLayerInterface implementation
// This defines a mapping between object and broadphase layers.
class BPLayerInterfaceImpl final : public JPH::BroadPhaseLayerInterface {
	public:
		BPLayerInterfaceImpl();

		virtual JPH::uint GetNumBroadPhaseLayers() const override;

		virtual JPH::BroadPhaseLayer GetBroadPhaseLayer(JPH::ObjectLayer inLayer) const override;

		#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
		virtual const char* GetBroadPhaseLayerName(JPH::BroadPhaseLayer inLayer) const override;
		#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED

	private:
		JPH::BroadPhaseLayer mObjectToBroadPhase[Layers::NUM_LAYERS];
};

/// Class that determines if an object layer can collide with a broadphase layer
class ObjectVsBroadPhaseLayerFilterImpl : public JPH::ObjectVsBroadPhaseLayerFilter {
	public:
		virtual bool ShouldCollide(JPH::ObjectLayer inLayer1, JPH::BroadPhaseLayer inLayer2) const override;
};

// using namespace std;

// An example contact listener
class MyContactListener : public JPH::ContactListener {
	public:
		// See: ContactListener
		virtual JPH::ValidateResult OnContactValidate(const JPH::Body& inBody1, const JPH::Body& inBody2,
			JPH::RVec3Arg inBaseOffset,
			const JPH::CollideShapeResult& inCollisionResult) override;

		virtual void OnContactAdded(const JPH::Body& inBody1, const JPH::Body& inBody2,
			const JPH::ContactManifold& inManifold,
			JPH::ContactSettings& ioSettings) override;

		virtual void OnContactPersisted(const JPH::Body& inBody1, const JPH::Body& inBody2,
			const JPH::ContactManifold& inManifold,
			JPH::ContactSettings& ioSettings) override;

		virtual void OnContactRemoved(const JPH::SubShapeIDPair& inSubShapePair) override;
};


struct RayCastResClosest {
	bool hit = false;
	float dist;
	glm::vec3 pos;
	glm::vec3 norm = glm::vec3(0, 0, 0);
	JPH::BodyID body_id;
};

// An example activation listener
class MyBodyActivationListener : public JPH::BodyActivationListener {
	public:
		virtual void OnBodyActivated(const JPH::BodyID& inBodyID, JPH::uint64 inBodyUserData) override;

		virtual void OnBodyDeactivated(const JPH::BodyID& inBodyID, JPH::uint64 inBodyUserData) override;
};

class Physics {
	private:
		void set_up_ray_cast(
			JPH::RRayCast& r_ray_cast,
			JPH::RayCastSettings& ray_cast_settings,
			glm::vec3 origin,
			glm::vec3 dir
		);

		JPH::uint step = 0;

	public:
		Physics();
		void tick();
		JPH::TempAllocatorImpl* temp_allocator;
		JPH::JobSystemThreadPool* job_system;

		BPLayerInterfaceImpl broad_phase_layer_interface;
		ObjectVsBroadPhaseLayerFilterImpl object_vs_broadphase_layer_filter;
		ObjectLayerPairFilterImpl object_vs_object_layer_filter;
		JPH::PhysicsSystem* physics_system;
		MyBodyActivationListener* body_activation_listener;
		MyContactListener* contact_listener;
		JPH::BodyInterface* body_interface;

		RayCastResClosest shape_cast_closest(
			JPH::Ref<JPH::Shape> shape,
			glm::vec3 origin,
			JPH::BodyFilter& inBodyFilter,
			JPH::BroadPhaseLayerFilter inBroadPhaseLayerFilter = {},
			JPH::ObjectLayerFilter inObjectLayerFilter = {},
			JPH::ShapeFilter inShapeFilter = {}
		);

		RayCastResClosest RayCastClosest(
			glm::vec3 origin,
			glm::vec3 dir,
			JPH::BodyFilter& inBodyFilter,
			JPH::BroadPhaseLayerFilter inBroadPhaseLayerFilter = {},
			JPH::ObjectLayerFilter inObjectLayerFilter = {},
			JPH::ShapeFilter inShapeFilter = {}
		);
};
