#include <iostream>
#include <Ogre.h>
#include <OgreMeshLodGenerator.h>
#include <OgreCompositorManager.h>
#include <OgreMaterialManager.h>
#include "water.h"

/* TODO: use blender glb model for water shape.
 * TODO: Finish main shader
 * TODO: Add caustics
 * TODO: Add below shader */

static const uint32_t SUBMERGED_MASK = 0x0F0;
static const uint32_t SURFACE_MASK = 0x00F;
static const uint32_t WATER_MASK = 0xF00;
Water::Water()
	: RenderTargetListener()
	, mWaterNode(nullptr)
	, mScnMgr(nullptr)
	, mWindow(nullptr)
	, mCameraNode(nullptr)
	, mWaterPlane(Ogre::Vector3::UNIT_Y, 0)
	, mReflectionPlane(Ogre::Vector3(0.0, 1.0, 0.0),
			   0.0f /* water height */)
	, mReflectionClipPlaneAbove(Ogre::Vector3(0.0, 1.0, 0.0),
				    0.0f /* water height */ - 2.0f)
	, mReflectionClipPlaneBelow(Ogre::Vector3(0.0, -1.0, 0.0),
				    -(0.0f /* water height */ + 2.0))
	, mRefractionClipPlaneAbove(Ogre::Vector3(0.0, -1.0, 0.0),
				    -(0.0f /* water height */ + 2.0))
	, mRefractionClipPlaneBelow(Ogre::Vector3(0.0, 1.0, 0.0),
				    0.0f /* water height */ - 2.0)
	, mAbove(true)
#if 0
	, mWaterBody(nullptr)
	, mDynWorld(nullptr)
#endif
{
}

Water::~Water()
{
	if (mWaterNode)
		mScnMgr->destroySceneNode(mWaterNode);
	if (mReflectionTexture)
		mReflectionTexture->removeAllListeners();
#if 0
	if (mWaterBody) {
		if (mWaterBody->getWorldArrayIndex() >= 0)
			if (mDynWorld)
				mDynWorld->getBtWorld()->removeCollisionObject(
					mWaterBody);
		delete mWaterBody;
		mWaterBody = nullptr;
	}
#endif
}

void Water::create_cameras()
{
	mReflectionTexture->addListener(this);
	mReflectionCamera = mScnMgr->createCamera("ReflectionCamera");
	mCamera->getParentSceneNode()->attachObject(mReflectionCamera);
	mReflectionCamera->setAspectRatio(mCamera->getAspectRatio());
	mReflectionCamera->setNearClipDistance(mCamera->getNearClipDistance());
	mReflectionCamera->setFarClipDistance(mCamera->getFarClipDistance());
	mReflectionCamera->enableCustomNearClipPlane(mReflectionClipPlaneAbove);
	mReflectionCamera->enableReflection(mReflectionPlane);

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

	mRefractionCamera = mScnMgr->createCamera("RefractionCamera");
	mCamera->getParentSceneNode()->attachObject(mRefractionCamera);
	mRefractionCamera->setAspectRatio(mCamera->getAspectRatio());
	mRefractionCamera->setNearClipDistance(mCamera->getNearClipDistance());
	mRefractionCamera->setFarClipDistance(mCamera->getFarClipDistance());
	mRefractionCamera->enableCustomNearClipPlane(mRefractionClipPlaneAbove);

	Ogre::Viewport *refractionViewport = mReflectionTexture->addViewport(
		mRefractionCamera, 1, 0.5, 0, 0.5f, 1.0f);
	refractionViewport->setClearEveryFrame(true);
	refractionViewport->setBackgroundColour(
		Ogre::ColourValue(0.0, 0.5, 1.0, 1.0));
	refractionViewport->setOverlaysEnabled(false);
	refractionViewport->setSkiesEnabled(false);
	refractionViewport->setAutoUpdated(false);
	mViewports.push_back(refractionViewport);
}

void Water::create_textures()
{
	Ogre::TexturePtr reflectionTexture =
		Ogre::TextureManager::getSingleton().createManual(
			"ReflectionRefractionTexture",
			Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
			Ogre::TEX_TYPE_2D, 512, 512, 0, Ogre::PF_R8G8B8A8,
			Ogre::TU_RENDERTARGET);

	mReflectionTexture = reflectionTexture->getBuffer()->getRenderTarget();
	mReflectionTexture->setAutoUpdated(false);
}

void Water::init()
{
	int i;
	float w = 1000.0f;
	// mWindow->addListener(this);
	create_cameras();
#if 0
	Ogre::MeshPtr water_plane =
		Ogre::MeshManager::getSingleton().createPlane(
			"water",
			Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
			mWaterPlane, w, w, 100, 100, true, 1, 100, 100,
			Ogre::Vector3::UNIT_Z);
#endif
	if (!Ogre::MeshLodGenerator::getSingletonPtr())
		new Ogre::MeshLodGenerator();

#if 0
	Ogre::LodConfig lod_config(water_plane);
	lod_config.createGeneratedLodLevel(w, 0.5f);
	lod_config.createGeneratedLodLevel(w * 2.0, 0.25f);
	Ogre::MeshLodGenerator::getSingleton().generateLodLevels(lod_config);
#endif

	Ogre::Vector3 positions[] = { { 0, 0, 0 },  { 0, 0, -1 }, { 0, 0, 1 },
				      { -1, 0, 0 }, { 1, 0, 0 },  { -1, 0, -1 },
				      { -1, 0, 1 }, { 1, 0, -1 }, { 1, 0, 1 } };
	mWaterNode = mScnMgr->getRootSceneNode()->createChildSceneNode("Water");

	auto mat =
		Ogre::MaterialManager::getSingleton().getByName("Water/Above");
	mat->load();
	mat->setReceiveShadows(false);
	auto mat2 =
		Ogre::MaterialManager::getSingleton().getByName("Water/Below");
	mat2->load();
	mat2->setReceiveShadows(false);
#if 0

	for (i = 0; i < (int)sizeof(positions) / (int)sizeof(positions[0]);
	     i++) {
		Ogre::Entity *water_lod1 = mScnMgr->createEntity(
			"Water" + Ogre::StringConverter::toString(i), "water");
		water_lod1->setVisibilityFlags(WATER_MASK);
		water_lod1->setCastShadows(true);
		water_lod1->setMaterialName("Water/Above");
		water_lod1->setMaterial(mat);
		Ogre::SceneNode *node_w = mWaterNode->createChildSceneNode(
			"Water" + Ogre::StringConverter::toString(i),
			positions[i] * w, Ogre::Quaternion::IDENTITY);
		node_w->attachObject(water_lod1);
		water_lod1->setVisibilityFlags(WATER_MASK);
		mWaterMeshes.push_back(water_lod1);
	}
#endif
	Ogre::Entity *water_ent = mScnMgr->createEntity("Ocean", "sea.glb");
	water_ent->setVisibilityFlags(WATER_MASK);
	water_ent->setCastShadows(true);
	water_ent->setMaterialName("Water/Above");
	water_ent->setMaterial(mat);
	mWaterNode->attachObject(water_ent);
	mWaterMeshes.push_back(water_ent);
#if 0
	mDynWorld->attachCollisionObject(mWaterBody, water_ent, 1, 0x7FFFFFFF);
#endif
}

void Water::createWater(Ogre::RenderWindow *window, Ogre::Camera *camera /*,
			Ogre::Bullet::DynamicsWorld *world */)
{
	int i;
	mCamera = camera;
	mScnMgr = camera->getSceneManager();
	mCameraNode = camera->getParentSceneNode();
	Ogre::Viewport *viewport = camera->getViewport();
	mWindow = window;
	mCameraPosition = mCameraNode->getPosition();
	create_textures();
#if 0
	mDynWorld = world;
	mWaterBody = new btGhostObject;
	btBoxShape *boxShape = new btBoxShape(btVector3(1000, 1000, 1000));
	btCompoundShape *shape = new btCompoundShape;
	shape->addChildShape(
		btTransform(btQuaternion(), btVector3(0, -1000, 0)), boxShape);
	mWaterBody->setCollisionShape(shape);
	mWaterBody->setCollisionFlags(
		mWaterBody->getCollisionFlags() |
		btCollisionObject::CF_NO_CONTACT_RESPONSE |
		btCollisionObject::CF_STATIC_OBJECT);
#endif
}

void Water::updateWater(float delta)
{
	int i;
	Ogre::Vector3 mCameraPos = mCameraNode->_getDerivedPosition();
	Ogre::Vector3 waterPos = mWaterNode->_getDerivedPosition();
	mCameraPos.y = 0;
	waterPos.y = 0;

	Ogre::Vector3 d = mCameraPos - waterPos;
	// Ogre::Vector3 waterPosition = mCameraPos;
	// mWaterNode->setPosition(waterPosition);
	if (d.squaredLength() < 100.0f * 100.0f)
		mWaterNode->translate(d * 3.0f * delta);
	else
		mWaterNode->translate(d);
	if (mAbove) {
		if (mCameraNode->getPosition().y < 0) {
			mAbove = false;
			for (i = 0; i < mWaterMeshes.size(); i++)
				mWaterMeshes[i]->setMaterialName("Water/Below");
		}
	} else {
		if (mCameraNode->getPosition().y > 0) {
			mAbove = true;
			for (i = 0; i < mWaterMeshes.size(); i++)
				mWaterMeshes[i]->setMaterialName("Water/Above");
		}
	}
	for (i = 0; i < mWaterMeshes.size(); i++)
		mWaterMeshes[i]->setVisible(false);
	for (i = 0; i < mViewports.size(); i++)
		mViewports[i]->update();
	for (i = 0; i < mWaterMeshes.size(); i++)
		mWaterMeshes[i]->setVisible(true);
#if 0
	mOverlaps.clear();
	for (i = 0; i < mWaterBody->getNumOverlappingObjects(); i++) {
		btCollisionObject *sb = mWaterBody->getOverlappingObject(i);
		mOverlaps.insert(sb);
	}
#endif
}

void Water::preRenderTargetUpdate(const Ogre::RenderTargetEvent &evt)
{
	int i;
	if (evt.source == mReflectionTexture) {
		for (i = 0; i < mWaterMeshes.size(); i++)
			mWaterMeshes[i]->setVisible(false);
		if (evt.source == mReflectionTexture)
			mInRefTexUpdate = true;
	} else {
		for (i = 0; i < mWaterMeshes.size(); i++)
			mWaterMeshes[i]->setVisible(true);
		mInRefTexUpdate = false;
	}
}

void Water::postRenderTargetUpdate(const Ogre::RenderTargetEvent &evt)
{
	int i;
	for (i = 0; i < mWaterMeshes.size(); i++)
		mWaterMeshes[i]->setVisible(true);
	mInRefTexUpdate = false;
}

void Water::add_submerged_entity(Ogre::Entity *ent)
{
	ent->setVisibilityFlags(SUBMERGED_MASK);
}

void Water::add_surface_entity(Ogre::Entity *ent)
{
	ent->setVisibilityFlags(SURFACE_MASK);
}
void Water::dump_textures()
{
	mReflectionTexture->writeContentsToFile("Reflection.png");
}
