#include <iostream>
#include <OgreMeshManager.h>
#include "Components.h"
#include "GameData.h"
#include "LuaData.h"
#include "EventModule.h"
#include "EventTriggerModule.h"
#include <tracy/Tracy.hpp>

struct TriggerBody {
	void *data;
};
#if 0
struct TriggerBody {
	btPairCachingGhostObject *mBody;
	btCylinderShape *shape;
	Ogre::SceneNode *mSceneNode;
	std::set<const btCollisionObject *> contactBodies;
};
struct DeepPenetrationContactResultCallback : public btManifoldResult {
	DeepPenetrationContactResultCallback(
		const btCollisionObjectWrapper *body0Wrap,
		const btCollisionObjectWrapper *body1Wrap)
		: btManifoldResult(body0Wrap, body1Wrap)
		, mPenetrationDistance(0)
		, mOtherIndex(0)
	{
	}
	float mPenetrationDistance;
	int mOtherIndex;
	btVector3 mNormal, mPoint;
	void reset()
	{
		mPenetrationDistance = 0.0f;
	}
	bool hasHit()
	{
		return mPenetrationDistance < 0.0f;
	}
	virtual void addContactPoint(const btVector3 &normalOnBInWorld,
				     const btVector3 &pointInWorldOnB,
				     btScalar depth)
	{
#ifdef VDEBUG
		std::cout
			<< "contact: " << Ogre::Bullet::convert(pointInWorldOnB)
			<< " " << Ogre::Bullet::convert(normalOnBInWorld)
			<< "\n";
#endif
		if (mPenetrationDistance > depth) { // Has penetration?

			const bool isSwapped =
				m_manifoldPtr->getBody0() !=
				m_body0Wrap->getCollisionObject();
			mPenetrationDistance = depth;
			mOtherIndex = isSwapped ? m_index0 : m_index1;
			mPoint = isSwapped ? (pointInWorldOnB +
					      (normalOnBInWorld * depth)) :
					     pointInWorldOnB;

			mNormal = isSwapped ? normalOnBInWorld * -1 :
					      normalOnBInWorld;
		}
	}
};
#endif

ECS::EventTriggerModule::EventTriggerModule(flecs::world &ecs)
{
	ecs.module<EventTriggerModule>();
	ecs.component<EventTrigger>();
	ecs.component<EventTriggerData>();
	ecs.component<InTrigger>();
	ecs.component<TriggeredBy>();
	ecs.import <EventModule>();
	ecs.import <LuaModule>();
	ecs.observer<const EngineData, const EventTrigger>("CreateTrigger")
		.event(flecs::OnSet)
		.each([](flecs::entity e, const EngineData &eng,
			 const EventTrigger &trigger) {
            ZoneScoped;
			e.set<EventTriggerData>({});
			e.set<EventData>({});
		});
	ecs.observer<const EventTrigger, EventData>("CreateTriggerEvent")
		.event(flecs::OnSet)
		.each([](flecs::entity e, const EventTrigger &trigger,
			 EventData &evt) {
            ZoneScoped;
            evt.add(e, "actuator_created", e, e);
		});
	ecs.system<const EventTrigger, EventData>("HandleEventSystem")
		.kind(flecs::OnUpdate)
		.each([](flecs::entity e, const EventTrigger &trigger,
			 EventData &evt) {
            ZoneScoped;
            if (e.parent().is_valid() &&
			    e.parent().has<EventData>()) {
				bool added = false;
				for (auto ev : evt.events) {
					e.parent().get_mut<EventData>().add(
						ev.sender, ev.event, ev.e1,
						ev.e2);
					added = true;
				}
				evt.events.clear();
				if (added)
					e.parent().modified<EventData>();
			}
		});
#if 0
	ecs.component<EventTriggerData>();
	ecs.component<TriggerBody>().on_add([](flecs::entity e,
					       TriggerBody &body) {
		bool kinematic = false;
		if (e.get<EventTrigger>().parent) {
			body.mSceneNode =
				e.get<EventTrigger>()
					.parent->createChildSceneNode(
						e.get<EventTrigger>().position,
						Ogre::Quaternion(0, 0, 0, 1));
			kinematic = true;
		} else {
			body.mSceneNode =
				ECS::get<EngineData>()
					.mScnMgr->getRootSceneNode()
					->createChildSceneNode(
						e.get<EventTrigger>().position,
						Ogre::Quaternion(0, 0, 0, 1));
		}
		body.mBody = new btPairCachingGhostObject();
		body.mBody->getWorldTransform().setOrigin(Ogre::Bullet::convert(
			body.mSceneNode->_getDerivedPosition()));
		float h = e.get<EventTrigger>().halfheight;
		float r = e.get<EventTrigger>().radius;
		Ogre::Vector3 position = e.get<EventTrigger>().position;
		body.shape = new btCylinderShape(btVector3(r, h, r));
		body.mBody->setCollisionShape(body.shape);
		int flags = body.mBody->getCollisionFlags();
		flags |= btCollisionObject::CF_NO_CONTACT_RESPONSE;
		if (kinematic)
			flags |= btCollisionObject::CF_STATIC_OBJECT;
		body.mBody->setCollisionFlags(flags);
		ECS::get<EngineData>().mWorld->getBtWorld()->addCollisionObject(
			body.mBody, 16, 0x1);
		struct EntityCollisionListener {
			const Ogre::MovableObject *entity;
			Ogre::Bullet::CollisionListener *listener;
		};
		body.mBody->setUserPointer(
			new EntityCollisionListener{ nullptr, nullptr });
		class CollisionObject {
		protected:
			btCollisionObject *mBtBody;
			btCollisionWorld *mBtWorld;

		public:
			CollisionObject(btCollisionObject *btBody,
					btCollisionWorld *btWorld)
				: mBtBody(btBody)
				, mBtWorld(btWorld)
			{
			}
			virtual ~CollisionObject()
			{
				mBtWorld->removeCollisionObject(mBtBody);
				delete mBtBody->getCollisionShape();
				delete mBtBody;
			}
		};
		auto objWrapper = std::make_shared<CollisionObject>(
			body.mBody,
			ECS::get<EngineData>().mWorld->getBtWorld());
		body.mSceneNode->getUserObjectBindings().setUserAny(
			"BtCollisionObject", objWrapper);
	});
	ecs.component<EventTrigger>().on_set([](flecs::entity e,
						EventTrigger &ev) {
		e.add<TriggerBody>();
		e.set<EventTriggerData>({});
		ECS::get<LuaBase>().mLua->call_handler("actuator_created", e,
						       e);
	});
	ecs.system<const EngineData, const EventTrigger, TriggerBody,
		   EventTriggerData>("CheckCollisions")
		.kind(flecs::OnUpdate)
		.each([](flecs::entity e, const EngineData &eng,
			 const EventTrigger &evt, TriggerBody &body,
			 EventTriggerData &data) {
			btDispatcher *dispatch =
				eng.mWorld->getBtWorld()->getDispatcher();
			btHashedOverlappingPairCache *cache =
				body.mBody->getOverlappingPairCache();
			int i;
			int count = cache->getNumOverlappingPairs();
			std::set<const btCollisionObject *> currentContactBodies;
			if (count > 0) {
				btManifoldArray contacts;
				btBroadphasePairArray &collisionPairs =
					cache->getOverlappingPairArray();
				for (i = 0; i < count; i++) {
					contacts.resize(0);
					if (collisionPairs[i].m_algorithm) {
						collisionPairs[i]
							.m_algorithm
							->getAllContactManifolds(
								contacts);
						OgreAssert(false,
							   "Not implemented");
					} else {
						const btBroadphasePair *collisionPairPtr =
							eng.mWorld->getBtWorld()
								->getBroadphase()
								->getOverlappingPairCache()
								->findPair(
									collisionPairs[i]
										.m_pProxy0,
									collisionPairs[i]
										.m_pProxy1);
						if (collisionPairPtr) {
							const btBroadphasePair
								&collisionPair =
									*collisionPairPtr;
							const btCollisionObject *objA = static_cast<
								btCollisionObject
									*>(
								collisionPair
									.m_pProxy0
									->m_clientObject);
							const btCollisionObject *objB = static_cast<
								btCollisionObject
									*>(
								collisionPair
									.m_pProxy1
									->m_clientObject);
							const btCollisionObject
								*me,
								*other;
							if (objA ==
							    static_cast<
								    btCollisionObject
									    *>(
								    body.mBody)) {
								me = objA;
								other = objB;
							} else {
								me = objB;
								other = objA;
							}
							const btCollisionShape *my_shape =
								me->getCollisionShape();
							const btCollisionShape *other_shape =
								other->getCollisionShape();
							btCollisionObjectWrapper obA(
								NULL, my_shape,
								body.mBody,
								body.mBody
									->getWorldTransform(),
								-1, i);
							btCollisionObjectWrapper obB(
								NULL,
								other_shape,
								other,
								other->getWorldTransform(),
								-1, 0);
							btCollisionAlgorithm *algorithm =
								dispatch->findAlgorithm(
									&obA,
									&obB,
									NULL,
									BT_CONTACT_POINT_ALGORITHMS);
							DeepPenetrationContactResultCallback
								contactPointResult(
									&obA,
									&obB);

							algorithm->processCollision(
								&obA, &obB,
								eng.mWorld
									->getBtWorld()
									->getDispatchInfo(),
								&contactPointResult);
							algorithm
								->~btCollisionAlgorithm();
							dispatch->freeCollisionAlgorithm(
								algorithm);
							if (contactPointResult
								    .hasHit()) {
								currentContactBodies
									.insert(other);
								if (body.contactBodies
									    .find(other) ==
								    body.contactBodies
									    .end()) {
									body.contactBodies
										.insert(other);
									OgreAssert(
										ECS::get<
											Body2Entity>()
												.entities
												.find(const_cast<
													btCollisionObject
														*>(
													other)) !=
											ECS::get<
												Body2Entity>()
												.entities
												.end(),
										"No body to entity mapping");
									flecs::entity obj_e =
										ECS::get<
											Body2Entity>()
											.entities
											.at(const_cast<
												btCollisionObject
													*>(
												other));
									ECS::get<
										LuaBase>()
										.mLua
										->call_handler(
											evt.event +
												"_enter",
											e,
											obj_e);
									obj_e.add<
										InTrigger>(
										e);
									e.add<TriggeredBy>(
										obj_e);
								}
							}
						}
					}
				}
			}
			std::set<const btCollisionObject *>::iterator it =
				body.contactBodies.begin();
			while (it != body.contactBodies.end()) {
				if (currentContactBodies.find(*it) ==
				    currentContactBodies.end()) {
					flecs::entity obj_e =
						ECS::get<Body2Entity>()
							.entities
							.at(const_cast<
								btCollisionObject
									*>(
								*it));
					ECS::get<LuaBase>().mLua->call_handler(
						evt.event + "_exit", e, obj_e);
					obj_e.remove<InTrigger>(e);
					e.remove<TriggeredBy>(obj_e);
					std::cout << "body exited" << std::endl;
					it = body.contactBodies.erase(it);
					if (it == body.contactBodies.end())
						break;
				}
				it++;
			}
		});
#endif
}
