#ifndef PHYSICS_HPP
#define PHYSICS_HPP

#include "logging.hpp"

#include <Jolt/Jolt.h>
#include <Jolt/Physics/Body/BodyInterface.h>
#include <Jolt/RegisterTypes.h>
#include <Jolt/Core/Factory.h>
#include <Jolt/Core/TempAllocator.h>
#include <Jolt/Core/JobSystemThreadPool.h>
#include <Jolt/Physics/PhysicsSettings.h>
#include <Jolt/Physics/PhysicsSystem.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/Body/BodyActivationListener.h>

#include <cstdarg>
#include <glm/glm.hpp>

using namespace JPH;
using namespace JPH::literals;

// Callback for traces, connect this to your own trace function if you have one
static void TraceImpl(const char *in_fmt, ...)
{
    // Format the message
    va_list list;
    va_start(list, in_fmt);
    char buffer[1024];
    vsnprintf(buffer, sizeof(buffer), in_fmt, list);
    va_end(list);

    OE_CORE_TRACE("Jolt: {}", buffer);
}

#ifdef JPH_ENABLE_ASSERTS
// Callback for asserts, connect this to your own assert handler if you have one
static bool AssertFailedImpl(const char *in_expression, const char *in_message, const char *in_file, uint in_line)
{
    OE_CORE_ERROR("Jolt: Assert in file: {}:{} :({}) {}", in_file, in_line, in_expression, (in_message != nullptr? in_message : ""));

    // Breakpoint
    return true;
};
#endif // JPH_ENABLE_ASSERTS

// 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 ObjectLayer NON_MOVING = 0;
	static constexpr ObjectLayer MOVING = 1;
	static constexpr ObjectLayer NUM_LAYERS = 2;
};

class ObjectLayerPairFilterImpl : public ObjectLayerPairFilter
{
    public:
        virtual bool ShouldCollide(ObjectLayer object1, ObjectLayer object2) 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 BroadPhaseLayer NON_MOVING(0);
	static constexpr BroadPhaseLayer MOVING(1);
	static constexpr uint NUM_LAYERS(2);
};

inline JPH::Vec3 ToJolt(const glm::vec3& v) { return JPH::Vec3(v.x, v.y, v.z); };

class BPLayerInterfaceImpl final : public BroadPhaseLayerInterface
{
    public:
        BPLayerInterfaceImpl()
        {
            object_to_broad_phase[Layers::NON_MOVING] = BroadPhaseLayers::NON_MOVING;
            object_to_broad_phase[Layers::MOVING] = BroadPhaseLayers::MOVING;
        }

        virtual uint GetNumBroadPhaseLayers() const override { return BroadPhaseLayers::NUM_LAYERS; };

        virtual BroadPhaseLayer GetBroadPhaseLayer(ObjectLayer layer) const override
        {
            JPH_ASSERT(layer < Layers::NUM_LAYERS);
            return object_to_broad_phase[layer];
        }

    private:
        BroadPhaseLayer	object_to_broad_phase[Layers::NUM_LAYERS];
};

class ObjectVsBroadPhaseLayerFilterImpl : public ObjectVsBroadPhaseLayerFilter
{
    public:
        virtual bool ShouldCollide(ObjectLayer layer1, BroadPhaseLayer layer2) const override
        {
            switch (layer1)
            {
                case Layers::NON_MOVING:
                    return layer2 == BroadPhaseLayers::MOVING;
                case Layers::MOVING:
                    return true;
                default:
                    JPH_ASSERT(false);
                    return false;
            }
        }
};

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

		// Allows you to ignore a contact before it is created (using layers to not make objects collide is cheaper!)
		return ValidateResult::AcceptAllContactsForThisBodyPair;
	}

	virtual void			OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override
	{
		OE_CORE_TRACE("Jolt: A contact was added");
	}

	virtual void			OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override
	{
		OE_CORE_TRACE("Jolt: A contact was persisted");
	}

	virtual void			OnContactRemoved(const SubShapeIDPair &inSubShapePair) override
	{
		OE_CORE_TRACE("Jolt: A contact was removed");
	}
};

// An example activation listener
class MyBodyActivationListener : public BodyActivationListener
{
public:
	virtual void		OnBodyActivated(const BodyID &inBodyID, uint64 inBodyUserData) override
	{
		OE_CORE_TRACE("Jolt: A body got activated");
	}

	virtual void		OnBodyDeactivated(const BodyID &inBodyID, uint64 inBodyUserData) override
	{
		OE_CORE_TRACE("Jolt: A body went to sleep");
	}
};

namespace OpenEngine {

    // Consider making this static like the renderer
    class PhysicsEngine
    {
        public:
            PhysicsEngine();
            ~PhysicsEngine();

            // TODO: Temp, make abstract function to create a body
            BodyInterface& GetBodyInterface() { return physics_system.GetBodyInterface(); };
            void Update( float delta_time, int collision_steps);

        private:
            PhysicsSystem physics_system;

            Ref<TempAllocatorImpl> tmp_allocator;
            Ref<JobSystemThreadPool> job_system;

            MyBodyActivationListener body_activation_listener;
            MyContactListener contact_listener;

            BPLayerInterfaceImpl broad_phase_layer_interface;
            ObjectVsBroadPhaseLayerFilterImpl object_vs_broadphase_layer_filter;
            ObjectLayerPairFilterImpl object_vs_object_layer_filter;

    };
}

#endif // PHYSICS_HPP
