#include "EditorSkyboxSystem.hpp"
#include "../components/Skybox.hpp"
#include "../components/Sun.hpp"
#include <OgreLogManager.h>
#include <OgreManualObject.h>
#include <OgreMaterialManager.h>
#include <OgreTechnique.h>
#include <OgrePass.h>
#include <OgreGpuProgramParams.h>

EditorSkyboxSystem::EditorSkyboxSystem(flecs::world &world,
				       Ogre::SceneManager *sceneMgr)
	: m_world(world)
	, m_sceneMgr(sceneMgr)
	, m_query(world.query<SkyboxComponent>())
{
}

EditorSkyboxSystem::~EditorSkyboxSystem() = default;

void EditorSkyboxSystem::update(Ogre::Camera *camera)
{
	m_query.each([&](flecs::entity entity, SkyboxComponent &skybox) {
		if (!skybox.enabled) {
			if (skybox.sceneNode) {
				skybox.sceneNode->setVisible(false);
			}
			return;
		}

		if (skybox.dirty || !skybox.manualObject) {
			rebuildSkybox(entity, skybox);
			skybox.dirty = false;
		}

		if (!skybox.sceneNode || !camera)
			return;

		// Follow camera position
		skybox.sceneNode->setPosition(camera->getDerivedPosition());

		// Get sun data from same entity or use defaults
		float sunElev = 1.0f;
		Ogre::Vector3 sunDir(0.0f, -1.0f, 0.0f);

		if (entity.has<SunComponent>()) {
			const SunComponent &sun = entity.get<SunComponent>();
			{
				float sunAngle = (sun.timeOfDay - 6.0f) / 24.0f * 2.0f *
						 M_PI;
				sunElev = std::sin(sunAngle);
				float tiltRad = sun.orbitTilt * M_PI / 180.0f;
				sunDir.x = -std::cos(sunAngle);
				sunDir.y = -sunElev;
				sunDir.z = -std::sin(sunAngle) * std::sin(tiltRad);
				sunDir.normalise();
			}
		}

		updateShaderParams(skybox, sunElev, sunDir);
	});
}

void EditorSkyboxSystem::rebuildSkybox(flecs::entity entity,
				       SkyboxComponent &skybox)
{
	cleanupSkybox(skybox);

	Ogre::String baseName = "Skybox_" +
				Ogre::StringConverter::toString(entity.id());

	// Create scene node at origin (will follow camera)
	skybox.sceneNode =
		m_sceneMgr->getRootSceneNode()->createChildSceneNode(
			baseName + "Node");

	// Create manual object cube
	skybox.manualObject =
		m_sceneMgr->createManualObject(baseName + "Mesh");
	createCube(skybox.manualObject, skybox.size);

	// Get material instance
	Ogre::MaterialPtr material =
		Ogre::MaterialManager::getSingleton()
			.getByName("Skybox/Dynamic",
				   Ogre::ResourceGroupManager::
					   DEFAULT_RESOURCE_GROUP_NAME);

	if (material) {
		skybox.manualObject->setMaterialName(0, "Skybox/Dynamic");
	}

	skybox.manualObject->setRenderQueueGroup(
		Ogre::RENDER_QUEUE_SKIES_EARLY);

	skybox.sceneNode->attachObject(skybox.manualObject);

	Ogre::LogManager::getSingleton().logMessage(
		"SkyboxSystem: Created skybox for entity " +
		Ogre::StringConverter::toString(entity.id()) +
		", size=" + Ogre::StringConverter::toString(skybox.size));
}

void EditorSkyboxSystem::cleanupSkybox(SkyboxComponent &skybox)
{
	if (skybox.manualObject) {
		if (skybox.sceneNode) {
			skybox.sceneNode->detachObject(skybox.manualObject);
		}
		m_sceneMgr->destroyManualObject(skybox.manualObject);
		skybox.manualObject = nullptr;
	}
	if (skybox.sceneNode) {
		m_sceneMgr->destroySceneNode(skybox.sceneNode);
		skybox.sceneNode = nullptr;
	}
}

void EditorSkyboxSystem::createCube(Ogre::ManualObject *obj, float size)
{
	float h = size * 0.5f;

	obj->begin("Skybox/Dynamic",
		   Ogre::RenderOperation::OT_TRIANGLE_LIST);

	// Front face (+Z)
	obj->position(-h, -h, h);
	obj->position(h, -h, h);
	obj->position(h, h, h);
	obj->position(-h, h, h);
	obj->triangle(0, 2, 1);
	obj->triangle(0, 3, 2);

	// Back face (-Z)
	obj->position(h, -h, -h);
	obj->position(-h, -h, -h);
	obj->position(-h, h, -h);
	obj->position(h, h, -h);
	obj->triangle(4, 6, 5);
	obj->triangle(4, 7, 6);

	// Left face (-X)
	obj->position(-h, -h, -h);
	obj->position(-h, -h, h);
	obj->position(-h, h, h);
	obj->position(-h, h, -h);
	obj->triangle(8, 10, 9);
	obj->triangle(8, 11, 10);

	// Right face (+X)
	obj->position(h, -h, h);
	obj->position(h, -h, -h);
	obj->position(h, h, -h);
	obj->position(h, h, h);
	obj->triangle(12, 14, 13);
	obj->triangle(12, 15, 14);

	// Top face (+Y)
	obj->position(-h, h, h);
	obj->position(h, h, h);
	obj->position(h, h, -h);
	obj->position(-h, h, -h);
	obj->triangle(16, 18, 17);
	obj->triangle(16, 19, 18);

	// Bottom face (-Y)
	obj->position(-h, -h, -h);
	obj->position(h, -h, -h);
	obj->position(h, -h, h);
	obj->position(-h, -h, h);
	obj->triangle(20, 22, 21);
	obj->triangle(20, 23, 22);

	obj->end();
}

void EditorSkyboxSystem::updateShaderParams(SkyboxComponent &skybox,
					    float sunElev,
					    const Ogre::Vector3 &sunDir)
{
	if (!skybox.manualObject)
		return;

	Ogre::MaterialPtr material =
		Ogre::MaterialManager::getSingleton()
			.getByName("Skybox/Dynamic",
				   Ogre::ResourceGroupManager::
					   DEFAULT_RESOURCE_GROUP_NAME);
	if (!material)
		return;

	Ogre::Pass *pass = material->getTechnique(0)->getPass(0);
	Ogre::GpuProgramParametersSharedPtr fpParams =
		pass->getFragmentProgramParameters();
	if (!fpParams)
		return;

	fpParams->setNamedConstant("uDayTop",
				   Ogre::Vector3(skybox.dayTopColor.r,
						 skybox.dayTopColor.g,
						 skybox.dayTopColor.b));
	fpParams->setNamedConstant("uDayBottom",
				   Ogre::Vector3(skybox.dayBottomColor.r,
						 skybox.dayBottomColor.g,
						 skybox.dayBottomColor.b));
	fpParams->setNamedConstant("uNightTop",
				   Ogre::Vector3(skybox.nightTopColor.r,
						 skybox.nightTopColor.g,
						 skybox.nightTopColor.b));
	fpParams->setNamedConstant("uNightBottom",
				   Ogre::Vector3(skybox.nightBottomColor.r,
						 skybox.nightBottomColor.g,
						 skybox.nightBottomColor.b));
	fpParams->setNamedConstant("uSunriseColor",
				   Ogre::Vector3(skybox.sunriseColor.r,
						 skybox.sunriseColor.g,
						 skybox.sunriseColor.b));
	fpParams->setNamedConstant("uSunsetColor",
				   Ogre::Vector3(skybox.sunsetColor.r,
						 skybox.sunsetColor.g,
						 skybox.sunsetColor.b));
	fpParams->setNamedConstant("uSunDir", sunDir);
	fpParams->setNamedConstant("uSunElev", sunElev);
	fpParams->setNamedConstant("uSunSize", skybox.sunSize);
	fpParams->setNamedConstant("uMoonSize", skybox.moonSize);
	fpParams->setNamedConstant("uStarsEnabled",
				   skybox.starsEnabled ? 1.0f : 0.0f);
}
