#include <iostream>
#include <Ogre.h>
#include <OgreColourValue.h>
#include <OgreShaderSubRenderState.h>
#include <OgreShaderGenerator.h>
#include <OgreRTShaderSystem.h>
#include <BulletCollision/NarrowPhaseCollision/btGjkEpaPenetrationDepthSolver.h>
#include <BulletCollision/NarrowPhaseCollision/btGjkPairDetector.h>
#include <BulletCollision/NarrowPhaseCollision/btPointCollector.h>
#include "GameData.h"
#include "Components.h"
#include "WaterModule.h"
#include <tracy/Tracy.hpp>
namespace ECS
{
#if 0
class WaterPhysicsAction : public btActionInterface {
	btPairCachingGhostObject *mWaterBody;
	btManifoldArray mManifoldArray;

public:
	btVector3 mShapeAabbMin, mShapeAabbMax;
	btGjkEpaPenetrationDepthSolver *gjk_epa_pen_solver;
	btVoronoiSimplexSolver *gjk_simplex_solver;
	struct contact {
		const btCollisionObject *body;
		float mDistance;
		btVector3 mNormal;
		btVector3 mPoint;
		bool mHasResult;
	};
	std::vector<struct contact> mContacts;
	std::set<const btCollisionObject *> mInWater;
	WaterPhysicsAction(btPairCachingGhostObject *waterBody)
		: btActionInterface()
		, mWaterBody(waterBody)
		, mShapeAabbMin(0, 0, 0)
		, mShapeAabbMax(0, 0, 0)
		, gjk_epa_pen_solver(OGRE_NEW btGjkEpaPenetrationDepthSolver)
		, gjk_simplex_solver(OGRE_NEW btVoronoiSimplexSolver)
	{
		mManifoldArray.reserve(160);
		mManifoldArray.resize(0);
		setupBody();
	}
	~WaterPhysicsAction()
	{
	}
	void updateAction(btCollisionWorld *collisionWorld,
			  btScalar deltaTimeStep) override;
	void debugDraw(btIDebugDraw *debugDrawer) override;
	void setupBody();
	bool isInWater(const btCollisionObject *body) const
	{
		return (mInWater.find(body) != mInWater.end());
	}
};
#endif
static const uint32_t WATER_MASK = 0xF00;
static bool debugEnabled = false;
WaterModule::WaterModule(flecs::world &ecs)
{
	ecs.module<WaterModule>();
	ecs.component<InWater>();
	ecs.component<WaterReady>().add(flecs::Singleton);
	ecs.component<WaterAlmostReady>().add(flecs::Singleton);
	ecs.component<GroundCheckReady>().add(flecs::Singleton);
	ecs.component<WaterSurface>()
		.on_add([](flecs::entity e, WaterSurface &water) {
			water.mAbove = false;
			water.mDepthMaterial = nullptr;
			water.mDepthTech = nullptr;
			water.mInRefTexUpdate = false;
			water.mReflectionCamera = nullptr;
			water.mReflectionCameraNode = nullptr;
			water.mReflectionDepthCamera = nullptr;
			water.mReflectionTexture = nullptr;
			water.mRefractionCamera = nullptr;
			water.mRefractionCameraNode = nullptr;
			water.mRefractionDepthCamera = nullptr;
			water.mWaterEnt = nullptr;
			water.mWaterNode = nullptr;

			const EngineData &eng = ECS::get<EngineData>();
			const Camera &camera = ECS::get<Camera>();

			std::cout << "Water setup\n";
			water.mDepthMaterial =
				Ogre::MaterialManager::getSingleton().getByName(
					"Water/Depth");
			OgreAssert(water.mDepthMaterial,
				   "Water Depth material not found.");
			water.mDepthMaterial->load();
			water.mDepthTech =
				water.mDepthMaterial->getBestTechnique();
			OgreAssert(water.mDepthTech, "Bad material technique");
			water.mAbove = false;
			water.mInRefTexUpdate = false;
			water.mRenderTargetListener.mSurface = &water;
			const Ogre::String renderTargetName =
				"ReflectionRefractionTexture";
			water.mWaterPlane =
				Ogre::Plane(Ogre::Vector3::UNIT_Y, 0);
			water.mReflectionPlane =
				Ogre::Plane(Ogre::Vector3(0.0, 1.0, 0.0),
					    0.5f /* water height */);
			float h = 0.0f;
			water.mReflectionClipPlaneAbove =
				Ogre::Plane(Ogre::Vector3(0.0, 1.0, 0.0), -h);
			water.mReflectionClipPlaneBelow =
				Ogre::Plane(Ogre::Vector3(0.0, -1.0, 0.0), h);
			water.mRefractionClipPlaneAbove =
				Ogre::Plane(Ogre::Vector3(0.0, -1.0, 0.0), h);
			water.mRefractionClipPlaneBelow =
				Ogre::Plane(Ogre::Vector3(0.0, 1.0, 0.0), -h);
			Ogre::TexturePtr reflectionTexture =
				Ogre::TextureManager::getSingleton().createManual(
					renderTargetName,
					Ogre::ResourceGroupManager::
						DEFAULT_RESOURCE_GROUP_NAME,
					Ogre::TEX_TYPE_2D, 512, 512, 0,
					Ogre::PF_R8G8B8A8,
					Ogre::TU_RENDERTARGET);
			Ogre::MaterialPtr debug_mat =
				Ogre::MaterialManager::getSingleton().getByName(
					"Water/Debug", "Water");
			if (!debug_mat) {
				debug_mat =
					Ogre::MaterialManager::getSingleton()
						.create("Water/Debug", "Water");
				Ogre::Technique *tech =
					debug_mat->getTechnique(0);
				Ogre::Pass *pass = tech->getPass(0);
				pass->setLightingEnabled(false);
				pass->setAmbient(Ogre::ColourValue(1, 1, 1, 1));
				pass->setDiffuse(Ogre::ColourValue(0.0f, 0.2f,
								   0.5f, 1.0f));
				pass->setDepthCheckEnabled(false);
				pass->setDepthWriteEnabled(false);
				pass->setAlphaRejectFunction(
					Ogre::CMPF_ALWAYS_PASS);
				pass->setCullingMode(Ogre::CULL_NONE);
				pass->setManualCullingMode(
					Ogre::MANUAL_CULL_NONE);
				Ogre::TextureUnitState *texture_unit =
					pass->createTextureUnitState();
				texture_unit->setTextureName(
					"ReflectionRefractionTexture");
				Ogre::Sampler::UVWAddressingMode uvw;
				uvw.u = Ogre::TextureUnitState::TAM_MIRROR;
				uvw.v = Ogre::TextureUnitState::TAM_MIRROR;
				uvw.w = Ogre::TextureUnitState::TAM_MIRROR;
				texture_unit->setTextureAddressingMode(uvw);
				texture_unit->setTextureFiltering(
					Ogre::FT_MIN, Ogre::FO_LINEAR);
				texture_unit->setTextureFiltering(
					Ogre::FT_MAG, Ogre::FO_LINEAR);
				texture_unit->setTextureFiltering(
					Ogre::FT_MIP, Ogre::FO_LINEAR);
			}
			// create a frosted screen in front of the camera, using our dynamic texture to "thaw" certain areas
			if (debugEnabled) {
				Ogre::Entity *ent = eng.mScnMgr->createEntity(
					"WaterDebugPlane",
					Ogre::SceneManager::PT_PLANE);
				ent->setMaterialName("Water/Debug", "Water");
				ent->setVisibilityFlags(WATER_MASK);
				Ogre::SceneNode *node =
					camera.mCameraNode
						->createChildSceneNode();
				node->setPosition(-150, 60, -400);
				node->attachObject(ent);
			}

			water.mReflectionTexture =
				reflectionTexture->getBuffer()
					->getRenderTarget();
			water.mReflectionTexture->setAutoUpdated(false);
			water.mWaterNode =
				eng.mScnMgr->getRootSceneNode()
					->createChildSceneNode("Water");
			Ogre::MaterialPtr mat =
				Ogre::MaterialManager::getSingleton().getByName(
					"Water/Above", "Water");
			if (!mat) {
				mat = Ogre::MaterialManager::getSingleton()
					      .create("Water/Above", "Water");
				Ogre::Technique *tech = mat->getTechnique(0);
				Ogre::Pass *pass = tech->getPass(0);
				pass->setLightingEnabled(true);
				pass->setAmbient(Ogre::ColourValue(1, 1, 1, 1));
				pass->setDiffuse(Ogre::ColourValue(0.0f, 0.2f,
								   0.5f, 1.0f));
				pass->setCullingMode(Ogre::CULL_NONE);
				pass->setManualCullingMode(
					Ogre::MANUAL_CULL_NONE);
				pass->setVertexProgram("Water/water_vp");
				pass->setFragmentProgram("Water/water_fp");
				Ogre::TextureUnitState *texture_unit =
					pass->createTextureUnitState();
				texture_unit->setTextureName(
					"ReflectionRefractionTexture");
				Ogre::Sampler::UVWAddressingMode uvw;
				uvw.u = Ogre::TextureUnitState::TAM_MIRROR;
				uvw.v = Ogre::TextureUnitState::TAM_MIRROR;
				uvw.w = Ogre::TextureUnitState::TAM_MIRROR;
				texture_unit->setTextureAddressingMode(uvw);
				texture_unit->setTextureFiltering(
					Ogre::FT_MIN, Ogre::FO_LINEAR);
				texture_unit->setTextureFiltering(
					Ogre::FT_MAG, Ogre::FO_LINEAR);
				texture_unit->setTextureFiltering(
					Ogre::FT_MIP, Ogre::FO_LINEAR);
				Ogre::TextureUnitState *texture_unit2 =
					pass->createTextureUnitState();
				texture_unit2->setTextureName("waves2.png");
				texture_unit2->setTextureAddressingMode(uvw);
				texture_unit2->setTextureFiltering(
					Ogre::FT_MIN, Ogre::FO_LINEAR);
				texture_unit2->setTextureFiltering(
					Ogre::FT_MAG, Ogre::FO_LINEAR);
				texture_unit2->setTextureFiltering(
					Ogre::FT_MIP, Ogre::FO_LINEAR);
			}
			mat->load();
			mat->setReceiveShadows(false);
			/*
				auto mat2 =
					Ogre::MaterialManager::getSingleton()
						.getByName("Water/Below");
				mat2->load();
				mat2->setReceiveShadows(false);
                */
			Ogre::Vector3 cameraPosition =
				camera.mCameraNode->getPosition();
			water.mWaterEnt =
				eng.mScnMgr->createEntity("Ocean", "sea.glb");
			water.mWaterEnt->setVisibilityFlags(WATER_MASK);
			water.mWaterEnt->setCastShadows(true);
			//				water.mWaterEnt->setMaterialName("Water/Above");
			water.mWaterEnt->setMaterial(mat);
			water.mWaterNode->attachObject(water.mWaterEnt);
			// mDynWorld->attachCollisionObject(
			//	mWaterBody, water_ent, 1, 0x7FFFFFFF);
			water.mReflectionTexture->addListener(
				&water.mRenderTargetListener);
			water.mReflectionCamera =
				eng.mScnMgr->createCamera("ReflectionCamera");
			water.mReflectionDepthCamera =
				eng.mScnMgr->createCamera(
					"ReflectionDepthCamera");
			water.mReflectionCameraNode =
				camera.mCamera->getParentSceneNode()
					->createChildSceneNode(
						"ReflectionCameraNode");
			water.mReflectionCameraNode->setPosition(
				Ogre::Vector3(0, 1.0f, 0));
			water.mReflectionCameraNode->attachObject(
				water.mReflectionCamera);
			water.mReflectionCameraNode->attachObject(
				water.mReflectionDepthCamera);
			water.mReflectionCamera->setAspectRatio(
				camera.mCamera->getAspectRatio());
			water.mReflectionCamera->setNearClipDistance(
				camera.mCamera->getNearClipDistance());
			water.mReflectionCamera->setFarClipDistance(
				Ogre::Math::Clamp(
					camera.mCamera->getFarClipDistance(),
					10.0f, 10.0f));
			water.mReflectionCamera->enableCustomNearClipPlane(
				water.mReflectionClipPlaneAbove);
			water.mReflectionCamera->enableReflection(
				water.mReflectionPlane);

			water.mReflectionDepthCamera->setAspectRatio(
				camera.mCamera->getAspectRatio());
			water.mReflectionDepthCamera->setNearClipDistance(
				camera.mCamera->getNearClipDistance());
			water.mReflectionDepthCamera->setFarClipDistance(
				camera.mCamera->getFarClipDistance());
			water.mReflectionDepthCamera->enableCustomNearClipPlane(
				water.mReflectionClipPlaneAbove);
			water.mReflectionDepthCamera->enableReflection(
				water.mReflectionPlane);

			Ogre::Viewport *reflectionViewport =
				water.mReflectionTexture->addViewport(
					water.mReflectionCamera, 0, 0, 0, 0.5f,
					0.5f);
			reflectionViewport->setClearEveryFrame(true);
			reflectionViewport->setBackgroundColour(
				Ogre::ColourValue(0.0, 0.0, 1.0, 1.0));
			reflectionViewport->setOverlaysEnabled(false);
			reflectionViewport->setSkiesEnabled(true);
			reflectionViewport->setAutoUpdated(false);
			reflectionViewport->setVisibilityMask(~WATER_MASK);
			water.mViewports[0] = reflectionViewport;

			Ogre::Viewport *reflectionDepthViewport =
				water.mReflectionTexture->addViewport(
					water.mReflectionDepthCamera, 2, 0,
					0.5f, 0.5f, 0.5f);
			reflectionDepthViewport->setClearEveryFrame(true);
			reflectionDepthViewport->setBackgroundColour(
				Ogre::ColourValue(0.0, 0.0, 0.0, 0.0));
			reflectionDepthViewport->setOverlaysEnabled(false);
			reflectionDepthViewport->setSkiesEnabled(true);
			reflectionDepthViewport->setAutoUpdated(false);
			reflectionDepthViewport->setVisibilityMask(~WATER_MASK);
			water.mViewports[2] = reflectionDepthViewport;

			water.mRefractionCamera =
				eng.mScnMgr->createCamera("RefractionCamera");
			water.mRefractionDepthCamera =
				eng.mScnMgr->createCamera(
					"RefractionDepthCamera");
			water.mRefractionCameraNode =
				camera.mCamera->getParentSceneNode()
					->createChildSceneNode(
						"RefractionCameraNode");
			water.mRefractionCameraNode->attachObject(
				water.mRefractionCamera);
			water.mRefractionCameraNode->attachObject(
				water.mRefractionDepthCamera);
			water.mRefractionCamera->setAspectRatio(
				camera.mCamera->getAspectRatio());
			water.mRefractionCamera->setNearClipDistance(
				camera.mCamera->getNearClipDistance());
			water.mRefractionCamera->setFarClipDistance(
				Ogre::Math::Clamp(
					camera.mCamera->getFarClipDistance(),
					1.0f, 10.0f));
			water.mRefractionCamera->enableCustomNearClipPlane(
				water.mRefractionClipPlaneAbove);

			water.mRefractionDepthCamera->setAspectRatio(
				camera.mCamera->getAspectRatio());
			water.mRefractionDepthCamera->setNearClipDistance(
				camera.mCamera->getNearClipDistance());
			water.mRefractionDepthCamera->setFarClipDistance(
				camera.mCamera->getFarClipDistance());
			water.mRefractionDepthCamera->enableCustomNearClipPlane(
				water.mRefractionClipPlaneAbove);

			Ogre::Viewport *refractionViewport =
				water.mReflectionTexture->addViewport(
					water.mRefractionCamera, 1, 0.5, 0,
					0.5f, 0.5f);
			refractionViewport->setClearEveryFrame(true);
			refractionViewport->setBackgroundColour(
				Ogre::ColourValue(0.0, 0.5, 1.0, 1.0));
			refractionViewport->setOverlaysEnabled(false);
			refractionViewport->setSkiesEnabled(false);
			refractionViewport->setAutoUpdated(false);
			refractionViewport->setVisibilityMask(~WATER_MASK);
			water.mViewports[1] = refractionViewport;

			Ogre::Viewport *refractionDepthViewport =
				water.mReflectionTexture->addViewport(
					water.mRefractionDepthCamera, 3, 0.5,
					0.5, 0.5f, 0.5f);
			refractionDepthViewport->setClearEveryFrame(true);
			refractionDepthViewport->setBackgroundColour(
				Ogre::ColourValue(0.0, 0.0, 0.0, 0.0));
			refractionDepthViewport->setOverlaysEnabled(false);
			refractionDepthViewport->setSkiesEnabled(false);
			refractionDepthViewport->setAutoUpdated(false);
			refractionDepthViewport->setVisibilityMask(~WATER_MASK);
			water.mViewports[3] = refractionDepthViewport;

			water.mRenderTargetListener.mInDepth = false;
			eng.mScnMgr->getRenderQueue()->setRenderableListener(
				&water.mRenderTargetListener);
			std::cout << "Water setup done\n";
			ECS::get().add<WaterAlmostReady>();
		})
		.on_remove([](flecs::entity e, WaterSurface &water) {
			const Ogre::String renderTargetName =
				"ReflectionRefractionTexture";
			ECS::get<EngineData>()
				.mScnMgr->getRenderQueue()
				->setRenderableListener(nullptr);
			water.mReflectionTexture->removeAllViewports();
			ECS::get<EngineData>().mScnMgr->destroyCamera(
				water.mRefractionCamera);
			ECS::get<EngineData>().mScnMgr->destroyCamera(
				water.mRefractionDepthCamera);
			ECS::get<EngineData>().mScnMgr->destroyCamera(
				water.mReflectionCamera);
			ECS::get<EngineData>().mScnMgr->destroyCamera(
				water.mReflectionDepthCamera);
			Ogre::TextureManager::getSingleton().remove(
				renderTargetName);
			water.mWaterNode->destroyAllChildrenAndObjects();
			ECS::get<EngineData>().mScnMgr->destroySceneNode(
				water.mWaterNode);
			Ogre::MaterialManager::getSingleton().remove(
				"Water/Depth");
			Ogre::MaterialManager::getSingleton().remove(
				"Water/Above");
		})
		.add(flecs::Singleton);
    ecs.system<const EngineData, const Camera, WaterSurface>(
           "UpdateWaterPosition")
		.kind(flecs::OnUpdate)
		.with<WaterReady>()
        .interval(0.3f)
		.each([](const EngineData &eng, const Camera &camera,
			 WaterSurface &water) {
            ZoneScopedN("UpdateWaterPosition");
            float delta = eng.delta;
            Ogre::Vector3 mCameraPos =
                camera.mCameraNode->_getDerivedPosition();
            Ogre::Vector3 waterPos =
                water.mWaterNode->_getDerivedPosition();
            mCameraPos.y = 0;
            waterPos.y = 0;
            Ogre::Vector3 d = mCameraPos - waterPos;
            if (d.squaredLength() < 100.0f * 100.0f)
                water.mWaterNode->translate(d * 3.0f * delta);
            else
                water.mWaterNode->translate(d);
        });
    ecs.system<WaterSurface>("UpdateWater")
        .kind(flecs::OnUpdate)
        .with<WaterReady>()
        .each([](WaterSurface &water) {
            static int updateViewport = 0;
            ZoneScopedN("UpdateWaterRender");
            water.mViewports[updateViewport++]->update();
            if (updateViewport > 1)
                updateViewport = 0;
		});
}
struct shapeParams {
	float offsetX, offsetY, offsetZ;
	float boxX, boxY, boxZ;
};
static struct shapeParams childShapes[] = {
	{ 0.0f, 0.0f, 0.0f, 100.0f, 100.0f, 100.0f },
};
void WaterSurface::RenderTextureListener::preRenderTargetUpdate(
	const Ogre::RenderTargetEvent &evt)
{
	int i;
	if (evt.source == mSurface->mReflectionTexture) {
		mSurface->mWaterEnt->setVisible(false);
		if (evt.source == mSurface->mReflectionTexture)
			mSurface->mInRefTexUpdate = true;
	} else {
		mSurface->mWaterEnt->setVisible(true);
		mSurface->mInRefTexUpdate = false;
	}
}
void WaterSurface::RenderTextureListener::postRenderTargetUpdate(
	const Ogre::RenderTargetEvent &evt)
{
	int i;
	mSurface->mWaterEnt->setVisible(true);
	mSurface->mInRefTexUpdate = false;
}
bool WaterSurface::RenderTextureListener::renderableQueued(
	Ogre::Renderable *rend, Ogre::uint8 groupID, ushort priority,
	Ogre::Technique **ppTech, Ogre::RenderQueue *pQueue)
{
	if (mInDepth)
		*ppTech = mSurface->mDepthTech;
	return true;
}
}
