#include "PlanetObject.h"
#include <random>
#include <cmath>
#include "render/OpenGlExtensions.h"
#include "Environment.h"
#include "StoneObject.h"
#include "utils/TaskManager.h"
#include "TextModel.h"
#include "GameConstants.h"

namespace ZL {

	extern float x;

#if defined EMSCRIPTEN || defined __ANDROID__
	using std::min;
	using std::max;
#endif

	extern const char* CONST_ZIP_FILE;

	Matrix3f GetRotationForTriangle(const Triangle& tri) {


		Vector3f vA = tri.data[0];
		Vector3f vB = tri.data[1];
		Vector3f vC = tri.data[2];

		// 1. Вычисляем ось X (горизонталь).
		Vector3f x_axis = (vC - vB).normalized();


		// 2. Вычисляем нормаль (ось Z).
		// Порядок cross product (AB x AC) определит "лицевую" сторону.
		Vector3f edge1 = vB - vA;
		Vector3f edge2 = vC - vA;
		Vector3f z_axis = edge1.cross(edge2).normalized();

		// 3. Вычисляем ось Y (вертикаль).
		// В ортонормированном базисе Y всегда перпендикулярна Z и X.
		Vector3f y_axis = z_axis.cross(x_axis).normalized();

		// 4. Формируем прямую матрицу поворота (Rotation/World Matrix).
		Matrix3f rot;

		// Столбец 0: Ось X
		rot.data()[0] = x_axis.data()[0];
		rot.data()[3] = x_axis.data()[1];
		rot.data()[6] = x_axis.data()[2];

		// Столбец 1: Ось Y
		rot.data()[1] = y_axis.data()[0];
		rot.data()[4] = y_axis.data()[1];
		rot.data()[7] = y_axis.data()[2];

		// Столбец 2: Ось Z
		rot.data()[2] = z_axis.data()[0];
		rot.data()[5] = z_axis.data()[1];
		rot.data()[8] = z_axis.data()[2];

		return rot;
	}

	PlanetObject::PlanetObject(Renderer& iRenderer, TaskManager& iTaskManager, MainThreadHandler& iMainThreadHandler)
		: renderer(iRenderer),
		taskManager(iTaskManager),
		mainThreadHandler(iMainThreadHandler)
	{
	}

	void PlanetObject::init() {
		// 1. Инициализируем данные (генерация мешей)
		planetData.init();

		// 2. Забираем данные для VBO
		// Берем максимальный LOD для начальной отрисовки
		planetRenderStruct.data = planetData.getLodLevel().vertexData;

		//planetRenderStruct.data.PositionData.resize(9);
		planetRenderStruct.RefreshVBO();


		sandTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/sand2.png", CONST_ZIP_FILE));
		stoneTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/rockdark3.png", CONST_ZIP_FILE));

		// Атмосфера
		planetAtmosphereRenderStruct.data = planetData.getAtmosphereLod().vertexData;
		if (planetAtmosphereRenderStruct.data.PositionData.size() > 0)
		{
			planetAtmosphereRenderStruct.RefreshVBO();
		}

		planetStones = CreateStoneGroupData(778, planetData.getLodLevel());

		//stonesToRender = planetStones.inflate(planetStones.allInstances.size());
		stonesToRender.resize(planetStones.allInstances.size());
		planetStones.initStatuses();

		stoneToBake = planetStones.inflateOne(0, 0.75);

		/*
		campPlatform.data = LoadFromTextFile02("resources/platform1.txt", CONST_ZIP_FILE);
		campPlatform.RefreshVBO();

		campPlatformTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/platform_base.png", CONST_ZIP_FILE));
		*/
	}


	void PlanetObject::update(float deltaTimeMs) {

		// 1. Проверка порога движения (оптимизация из текущего кода)
		float movementThreshold = 1.0f;
		if ((Environment::shipState.position - lastUpdatePos).squaredNorm() < movementThreshold * movementThreshold
			&& !triangleIndicesToDraw.empty()) {
			//processMainThreadTasks(); // Все равно обрабатываем очередь OpenGL задач
			return;
		}
		lastUpdatePos = Environment::shipState.position;

		// 2. Получаем список видимых треугольников
		auto newIndices = planetData.getTrianglesUnderCameraNew2(Environment::shipState.position);
		std::sort(newIndices.begin(), newIndices.end());

		// 3. Анализируем, что нужно загрузить
		for (int triIdx : newIndices) {
			if (planetStones.statuses[triIdx] == ChunkStatus::Empty) {
				// Помечаем, чтобы не спамить задачами
				planetStones.statuses[triIdx] = ChunkStatus::Generating;

				// Отправляем тяжелую математику в TaskManager
				taskManager.EnqueueBackgroundTask([this, triIdx]() {
					// Выполняется в фоновом потоке: только генерация геометрии в RAM
					float scaleModifier = 1.0f;
					VertexDataStruct generatedData = planetStones.inflateOneDataOnly(triIdx, scaleModifier);

					// Передаем задачу на загрузку в GPU в очередь главного потока
					this->mainThreadHandler.EnqueueMainThreadTask([this, triIdx, data = std::move(generatedData)]() mutable {
						// Проверяем актуальность: если треугольник всё еще в списке видимых
						auto it = std::find(triangleIndicesToDraw.begin(), triangleIndicesToDraw.end(), triIdx);
						if (it != triangleIndicesToDraw.end()) {
							stonesToRender[triIdx].data = std::move(data);
							stonesToRender[triIdx].RefreshVBO(); // OpenGL вызов
							planetStones.statuses[triIdx] = ChunkStatus::Live;
						}
						else {
							// Если уже не нужен — просто сбрасываем статус
							planetStones.statuses[triIdx] = ChunkStatus::Empty;
						}
						});
					});
			}
		}

		// 4. Очистка (Unload)
		// Если статус Live, но треугольника нет в новых индексах — освобождаем GPU память
		for (size_t i = 0; i < planetStones.statuses.size(); ++i) {
			if (planetStones.statuses[i] == ChunkStatus::Live) {
				bool isVisible = std::binary_search(newIndices.begin(), newIndices.end(), (int)i);
				if (!isVisible) {
					// Очищаем данные и VBO (деструкторы shared_ptr в VertexRenderStruct сделают glDeleteBuffers)
					stonesToRender[i].data.PositionData.clear();
					stonesToRender[i].vao.reset();
					stonesToRender[i].positionVBO.reset();
					stonesToRender[i].normalVBO.reset();
					stonesToRender[i].tangentVBO.reset();
					stonesToRender[i].binormalVBO.reset();
					stonesToRender[i].colorVBO.reset();
					stonesToRender[i].texCoordVBO.reset();

					planetStones.statuses[i] = ChunkStatus::Empty;
				}
			}
		}

		triangleIndicesToDraw = std::move(newIndices);

		// 5. Выполняем одну задачу из очереди OpenGL (RefreshVBO и т.д.)
		//processMainThreadTasks();
	}


	void PlanetObject::bakeStoneTexture(Renderer& renderer) {
		glViewport(0, 0, 512, 512);
		glClearColor(0,0,0, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


		static const std::string planetBakeShaderName = "planetBake";

		renderer.shaderManager.PushShader(planetBakeShaderName);
		renderer.RenderUniform1i(textureUniformName, 0);

		Triangle tr = planetData.getLodLevel().triangles[0];

		// 1. Получаем матрицу вращения (оси в столбцах)
		Matrix3f mr = GetRotationForTriangle(tr);

		// 2. Трансформируем вершины в локальное пространство экрана, чтобы найти габариты
		// Используем MultMatrixVector(Matrix, Vector). 
		// Если ваша функция считает V * M, то передайте Inverse(mr).
		Vector3f rA = mr * tr.data[0];
		Vector3f rB = mr * tr.data[1];
		Vector3f rC = mr * tr.data[2];

		// 3. Вычисляем реальные границы треугольника после поворота
		float minX = min(rA(0), min(rB(0), rC(0)));
		float maxX = max(rA(0), max(rB(0), rC(0)));
		float minY = min(rA(1), min(rB(1), rC(1)));
		float maxY = max(rA(1), max(rB(1), rC(1)));


		// Находим центр и размеры
		float width = maxX - minX;
		float height = maxY - minY;
		float centerX = (minX + maxX) * 0.5f;
		float centerY = (minY + maxY) * 0.5f;

		//width = width * 0.995;
		//height = height * 0.995;

		renderer.PushProjectionMatrix(
			centerX - width*0.5, centerX + width * 0.5,
			centerY - height * 0.5, centerY + height * 0.5,
			150, 200000);

		renderer.PushMatrix();
		renderer.LoadIdentity();
		// Сдвигаем камеру по Z
		renderer.TranslateMatrix(Vector3f{ 0, 0, -45000 });

		// Применяем вращение
		renderer.RotateMatrix(mr);
		
		
		// Извлекаем нормаль треугольника (это 3-й столбец нашей матрицы вращения)
		Vector3f planeNormal = mr.col(2);

		renderer.RenderUniform3fv("uPlanePoint", tr.data[0].data());
		renderer.RenderUniform3fv("uPlaneNormal", planeNormal.data());
		renderer.RenderUniform1f("uMaxHeight", StoneParams::BASE_SCALE * 1.1f); // Соответствует BASE_SCALE + perturbation
		
		
		glActiveTexture(GL_TEXTURE0);

		glEnable(GL_CULL_FACE);
		glCullFace(GL_BACK); // Отсекаем задние грани
		if (stoneToBake.data.PositionData.size() > 0)
		{

			glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID());
			renderer.DrawVertexRenderStruct(stoneToBake);
			CheckGlError();
		}
		glDisable(GL_CULL_FACE); // Не забываем выключить, чтобы не сломать остальной рендер
		renderer.PopMatrix();
		renderer.PopProjectionMatrix();
		renderer.shaderManager.PopShader();
		CheckGlError();
	}


	void PlanetObject::draw(Renderer& renderer) {

		{
			if (stoneMapFB == nullptr)
			{
				//stoneMapFB = std::make_unique<FrameBuffer>(512, 512, true);
				stoneMapFB = std::make_unique<FrameBuffer>(512, 512);
			}
			stoneMapFB->Bind();

			bakeStoneTexture(renderer);

			stoneMapFB->Unbind();

			stoneMapFB->GenerateMipmaps();

		}

		glViewport(0, 0, Environment::width, Environment::height);
		//--------------------------


		drawPlanet(renderer);
		drawStones(renderer);
		//drawCamp(renderer);
		glClear(GL_DEPTH_BUFFER_BIT);
		drawAtmosphere(renderer);
	}

	void PlanetObject::drawPlanet(Renderer& renderer)
	{
		static const std::string planetLandShaderName = "planetLand";

		static const std::string textureUniformName = "Texture";

		renderer.shaderManager.PushShader(planetLandShaderName);
		

		float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position);
		auto zRange = planetData.calculateZRange(dist);
		const float currentZNear = zRange.first;
		const float currentZFar = zRange.second;
		
		// 2. Применяем динамическую матрицу проекции
		renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
			static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
			currentZNear, currentZFar);

		renderer.PushMatrix();
		renderer.LoadIdentity();
		renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
		renderer.RotateMatrix(Environment::inverseShipMatrix);

		renderer.TranslateMatrix(-Environment::shipState.position);

		const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix();
		
		renderer.RenderUniform1i(textureUniformName, 0);
		renderer.RenderUniform1i("BakedTexture", 1);

		Triangle tr = planetData.getLodLevel().triangles[0]; // Берем базовый треугольник
		Matrix3f mr = GetRotationForTriangle(tr); // Та же матрица, что и при запекании

		// Позиция камеры (корабля) в мире
		renderer.RenderUniform3fv("uViewPos", Environment::shipState.position.data());

		//renderer.RenderUniform1f("uHeightScale", 0.08f);
		renderer.RenderUniform1f("uHeightScale", 0.0f);

		renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
		renderer.RenderUniform1f("uCurrentZFar", currentZFar);

		// Направление на солнце в мировом пространстве
		Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized();
		renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data());

		// Направление от центра планеты к игроку для расчета дня/ночи
		Vector3f playerDirWorld = Environment::shipState.position.normalized();
		renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data());

		// Тот же фактор освещенности игрока
		float playerLightFactor = playerDirWorld.dot(-sunDirWorld);
		playerLightFactor = max(0.0f, (playerLightFactor + 0.2f) / 1.2f);
		renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor);


		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, stoneMapFB->getTextureID());
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, sandTexture->getTexID());
		//glBindTexture(GL_TEXTURE_2D, stoneMapFB->getTextureID());

		renderer.DrawVertexRenderStruct(planetRenderStruct);
		CheckGlError();

		renderer.PopMatrix();
		renderer.PopProjectionMatrix();
		renderer.shaderManager.PopShader();
		CheckGlError();

		
	}

	void PlanetObject::drawStones(Renderer& renderer)
	{
		static const std::string planetStoneShaderName = "planetStone";

		renderer.shaderManager.PushShader(planetStoneShaderName);
		renderer.RenderUniform1i(textureUniformName, 0);

		float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position);
		auto zRange = planetData.calculateZRange(dist);
		const float currentZNear = zRange.first;
		const float currentZFar = zRange.second;


		// 2. Применяем динамическую матрицу проекции
		renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
			static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
			currentZNear, currentZFar);

		renderer.PushMatrix();
		renderer.LoadIdentity();
		renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
		renderer.RotateMatrix(Environment::inverseShipMatrix);
		renderer.TranslateMatrix(-Environment::shipState.position);

		renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
		renderer.RenderUniform1f("uCurrentZFar", currentZFar);
		renderer.RenderUniform3fv("uViewPos", Environment::shipState.position.data());
		//std::cout << "uViewPos" << Environment::shipState.position << std::endl;
		// PlanetObject.cpp, метод drawStones
		Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized();
		renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data());

		Vector3f playerDirWorld = Environment::shipState.position.normalized();
		float playerLightFactor = max(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f);
		renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor);
		
		glEnable(GL_CULL_FACE);
		glCullFace(GL_BACK);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID());

		/*
		for (int i : triangleIndicesToDraw)
		//for (int i = 0; i < stonesToRender.size(); i++)
		{
			if (stonesToRender[i].data.PositionData.size() > 0)
			{
				renderer.DrawVertexRenderStruct(stonesToRender[i]);
			}
		}*/
		for (int i : triangleIndicesToDraw) {
			// КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ: 
			// Проверяем, что данные не просто существуют, а загружены в GPU
			if (planetStones.statuses[i] == ChunkStatus::Live) {
				// Дополнительная проверка на наличие данных (на всякий случай)
				if (stonesToRender[i].data.PositionData.size() > 0) {
					renderer.DrawVertexRenderStruct(stonesToRender[i]);
				}
			}
			// Если статус Generating или Empty — мы просто пропускаем этот индекс.
			// Камни появятся на экране плавно, как только отработает TaskManager и RefreshVBO.
		}

		CheckGlError();
		glDisable(GL_BLEND);
		glDisable(GL_CULL_FACE);

		renderer.PopMatrix();
		renderer.PopProjectionMatrix();
		renderer.shaderManager.PopShader();
		CheckGlError();

		
	}

	void PlanetObject::drawAtmosphere(Renderer& renderer) {
		static const std::string defaultShaderName = "defaultAtmosphere";
		//glClear(GL_DEPTH_BUFFER_BIT);
		glDepthMask(GL_FALSE);

		renderer.shaderManager.PushShader(defaultShaderName);
		float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position);
		auto zRange = planetData.calculateZRange(dist);
		float currentZNear = zRange.first;
		float currentZFar = zRange.second;

		if (currentZNear < 200)
		{
			currentZNear = 200;
			currentZFar = currentZNear * 100;
		}

		// 2. Применяем динамическую матрицу проекции
		renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
			static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
			currentZNear, currentZFar);

		renderer.PushMatrix();
		renderer.LoadIdentity();
		renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
		renderer.RotateMatrix(Environment::inverseShipMatrix);
		renderer.TranslateMatrix(-Environment::shipState.position);

		const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix();

		Vector3f color = { 0.0, 0.5, 1.0 };

		renderer.RenderUniform3fv("uColor", color.data());

		renderer.RenderUniformMatrix4fv("ModelViewMatrix", false, viewMatrix.data());

		renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);

		// В начале drawAtmosphere или как uniform
		//float time = SDL_GetTicks() / 1000.0f;
		//renderer.RenderUniform1f("uTime", time);

		// В методе PlanetObject::drawAtmosphere
		Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized();

		// Получаем текущую матрицу вида (ModelView без трансляции объекта)
		// В вашем движке это Environment::inverseShipMatrix
		Matrix3f viewMatrix2 = Environment::inverseShipMatrix;

		// Трансформируем вектор света в пространство вида
		Vector3f viewLightDir = viewMatrix2 * worldLightDir;

		// Передаем инвертированный вектор (направление НА источник)
		Vector3f lightToSource = -viewLightDir.normalized();
		renderer.RenderUniform3fv("uLightDirView", lightToSource.data());

		// В методе Game::drawCubemap
		//renderer.RenderUniform1f("uTime", SDL_GetTicks() / 1000.0f);
		// Направление света в мировом пространстве для освещения облаков
		//Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized();
		renderer.RenderUniform3fv("uWorldLightDir", worldLightDir.data());

		// 1. Рассчитываем uPlayerLightFactor (как в Game.cpp)
		Vector3f playerDirWorld = Environment::shipState.position.normalized();
		Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized();

		// Насколько игрок на свету
		float playerLightFactor = playerDirWorld.dot(-sunDirWorld);
		playerLightFactor = max(0.0f, (playerLightFactor + 0.2f) / 1.2f); // Мягкий порог

		// 2. ОБЯЗАТЕЛЬНО передаем в шейдер
		renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor);

		// 3. Убедитесь, что uSkyColor тоже передан (в коде выше его не было)
		renderer.RenderUniform3fv("uSkyColor", color.data());

		//Vector3f playerDirWorld = Environment::shipState.position.normalized();
		renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data());

		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE);// Аддитивное смешивание для эффекта свечения

		renderer.DrawVertexRenderStruct(planetAtmosphereRenderStruct);
		glDisable(GL_BLEND);
		glDepthMask(GL_TRUE);
		renderer.PopMatrix();
		renderer.PopProjectionMatrix();
		renderer.shaderManager.PopShader();
		CheckGlError();

	}

	void PlanetObject::drawCamp(Renderer& renderer)
	{
		renderer.shaderManager.PushShader(defaultShaderName);
		renderer.RenderUniform1i(textureUniformName, 0);

		float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position);
		auto zRange = planetData.calculateZRange(dist);
		const float currentZNear = zRange.first;
		const float currentZFar = zRange.second;


		// 2. Применяем динамическую матрицу проекции
		renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
			static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
			currentZNear, currentZFar);

		renderer.PushMatrix();
		renderer.LoadIdentity();
		renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
		renderer.RotateMatrix(Environment::inverseShipMatrix);
		renderer.TranslateMatrix(-Environment::shipState.position);

		renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
		renderer.RenderUniform1f("uCurrentZFar", currentZFar);
		renderer.RenderUniform3fv("uViewPos", Environment::shipState.position.data());
		//std::cout << "uViewPos" << Environment::shipState.position << std::endl;
		// PlanetObject.cpp, метод drawStones
		Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized();
		renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data());

		Vector3f playerDirWorld = Environment::shipState.position.normalized();
		float playerLightFactor = max(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f);
		renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor);

		glEnable(GL_CULL_FACE);
		glCullFace(GL_BACK);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		for (int i = 0; i < planetData.campObjects.size(); i++)
		{
			
				renderer.PushMatrix();
				for (int j = 0; j < 5; j++)
				{
					renderer.PushMatrix();
					renderer.TranslateMatrix(planetData.campObjects[i].position);
					renderer.RotateMatrix(planetData.campObjects[i].rotation);
					renderer.ScaleMatrix(Vector3f{ 2.0f, 2.0f, 2.0f });
					renderer.TranslateMatrix(planetData.campObjects[i].platformPos[j]);
					glBindTexture(GL_TEXTURE_2D, campPlatformTexture->getTexID());
					renderer.DrawVertexRenderStruct(campPlatform);
					renderer.PopMatrix();
				}
				renderer.PopMatrix();
			
		}

		CheckGlError();
		glDisable(GL_BLEND);
		glDisable(GL_CULL_FACE);

		renderer.PopMatrix();
		renderer.PopProjectionMatrix();
		renderer.shaderManager.PopShader();
		CheckGlError();

		glClear(GL_DEPTH_BUFFER_BIT);
	}

	float PlanetObject::distanceToPlanetSurface(const Vector3f& viewerPosition)
	{
		return planetData.distanceToPlanetSurfaceFast(viewerPosition);
	}

	

} // namespace ZL