#include "Game.h"
#include "AnimatedModel.h"
#include "utils/Utils.h"
#include "items/ItemRegistry.h"
#include "render/OpenGlExtensions.h"
#include <iostream>
#include "render/TextureManager.h"
#include "TextModel.h"
#include <random>
#include <cmath>
#include <algorithm>
#include <functional>
#ifdef __ANDROID__
#include <android/log.h>
#endif



#ifdef EMSCRIPTEN
#include <emscripten.h>
#endif

#include "GameConstants.h"

namespace ZL
{
	void set_Texture(Character& npc, const TextureDataStruct& texture)
	{
		auto tt = std::make_shared<Texture>(texture);
		npc.setTexture(tt);
	}
	void set_Texture(Character& npc, const std::string& meshName, const TextureDataStruct& texture)
	{
		auto tt = std::make_shared<Texture>(texture);
		npc.setTexture(meshName, tt);
	}

#ifdef EMSCRIPTEN
	const char* CONST_ZIP_FILE = "resources.zip";
#else
	const char* CONST_ZIP_FILE = "";
#endif

	float x = 0;
	float y = 0;
	float z = 0;

#ifdef EMSCRIPTEN
	Game* Game::s_instance = nullptr;

	void Game::onResourcesZipLoaded(const char* /*filename*/) {
		std::cout << "Resources.zip loaded successfully" << std::endl;
		if (s_instance) {
			s_instance->mainThreadHandler.EnqueueMainThreadTask([&]() {
				s_instance->setupPart2();
				});
		}
	}

	void Game::onResourcesZipError(const char* /*filename*/) {
		std::cout << "Failed to download resources.zip" << std::endl;
	}
#endif

	Game::Game()
		: newTickCount(0)
		, lastTickCount(0)
		, menuManager(renderer)
		, audioPlayer(std::make_unique<AudioPlayerAsync>())
	{
	}

	Game::~Game() {
#ifndef EMSCRIPTEN
		// In Emscripten, SDL must stay alive across context loss/restore cycles
		// so the window remains valid when the game object is re-created.
		SDL_Quit();
#endif
	}

	void Game::setup() {
		Environment::width = Environment::CONST_DEFAULT_WIDTH;
		Environment::height = Environment::CONST_DEFAULT_HEIGHT;
		Environment::computeProjectionDimensions();

		ZL::BindOpenGlFunctions();
		ZL::CheckGlError(__FILE__, __LINE__);
		renderer.InitOpenGL();

#ifdef EMSCRIPTEN
		// These shaders and loading.png are preloaded separately (not from zip),
		// so they are available immediately without waiting for resources.zip.
		renderer.shaderManager.AddShaderFromFiles("defaultColor", "resources/shaders/defaultColor.vertex", "resources/shaders/defaultColor_web.fragment", "");
		renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_web.fragment", "");
#else
		renderer.shaderManager.AddShaderFromFiles("defaultColor", "resources/shaders/defaultColor.vertex", "resources/shaders/defaultColor_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_desktop.fragment", CONST_ZIP_FILE);
#endif
		loadingTexture = renderer.textureManager.LoadFromPng("resources/loading.png", "");

		float minDimension;
		float width = Environment::projectionWidth;
		float height = Environment::projectionHeight;

		if (width >= height)
		{
			minDimension = height;
		}
		else
		{
			minDimension = width;
		}

		loadingMesh.data = CreateRect2D({ 0.0f, 0.0f }, { minDimension * 0.5f, minDimension * 0.5f }, 3);
		loadingMesh.RefreshVBO();

#ifdef EMSCRIPTEN
		// Asynchronously download resources.zip; setupPart2() is called on completion.
		// The loading screen stays visible until the download finishes.
		s_instance = this;
		std::cout << "Load resurces step 1" << std::endl;
		emscripten_async_wget("resources.zip", "resources.zip", onResourcesZipLoaded, onResourcesZipError);
#else
		mainThreadHandler.EnqueueMainThreadTask([this]() {
			std::cout << "Load resurces step 2" << std::endl;
			this->setupPart2();
			std::cout << "Load resurces step 3" << std::endl;
			});
#endif

	}


	void Game::setupPart2()
	{

#ifdef EMSCRIPTEN
		renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/defaultAtmosphere.vertex", "resources/shaders/defaultAtmosphere_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/planet_bake.vertex", "resources/shaders/planet_bake_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/planet_stone.vertex", "resources/shaders/planet_stone_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("spark", "resources/shaders/spark.vertex", "resources/shaders/spark_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("skinning", "resources/shaders/skinning.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("fog", "resources/shaders/fog.vertex", "resources/shaders/fog_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("fog_skinning", "resources/shaders/fog_skinning.vertex", "resources/shaders/fog_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("darklands_fog", "resources/shaders/darklands_fog.vertex", "resources/shaders/darklands_fog_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("darklands_fog_skinning", "resources/shaders/darklands_fog_skinning.vertex", "resources/shaders/darklands_fog_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("darklands_flash", "resources/shaders/default.vertex", "resources/shaders/darklands_flash_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("cutsceneFade", "resources/shaders/default.vertex", "resources/shaders/cutscene_fade_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("cutsceneBlack", "resources/shaders/default.vertex", "resources/shaders/cutscene_black_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("shadow_depth", "resources/shaders/shadow_depth.vertex", "resources/shaders/shadow_depth_web.fragment", CONST_ZIP_FILE);
		std::cout << "Load resurces step 12x1" << std::endl;
		renderer.shaderManager.AddShaderFromFiles("shadow_depth_skinning", "resources/shaders/shadow_depth_skinning.vertex", "resources/shaders/shadow_depth_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("default_shadow", "resources/shaders/default_shadow.vertex", "resources/shaders/default_shadow_web.fragment", CONST_ZIP_FILE);
		std::cout << "Load resurces step 12x3" << std::endl;
		renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_web.fragment", CONST_ZIP_FILE);
		std::cout << "Load resurces step 12x4" << std::endl;
		renderer.shaderManager.AddShaderFromFiles("fog_shadow", "resources/shaders/fog_shadow.vertex", "resources/shaders/fog_shadow_web.fragment", CONST_ZIP_FILE);
		std::cout << "Load resurces step 12x5" << std::endl;
		renderer.shaderManager.AddShaderFromFiles("fog_skinning_shadow", "resources/shaders/fog_skinning_shadow.vertex", "resources/shaders/fog_shadow_web.fragment", CONST_ZIP_FILE);

#else
		renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/defaultAtmosphere.vertex", "resources/shaders/defaultAtmosphere_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/planet_bake.vertex", "resources/shaders/planet_bake_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/planet_stone.vertex", "resources/shaders/planet_stone_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("spark", "resources/shaders/spark.vertex", "resources/shaders/spark_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("skinning", "resources/shaders/skinning.vertex", "resources/shaders/default_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("fog", "resources/shaders/fog.vertex", "resources/shaders/fog_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("fog_skinning", "resources/shaders/fog_skinning.vertex", "resources/shaders/fog_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("darklands_fog", "resources/shaders/darklands_fog.vertex", "resources/shaders/darklands_fog_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("darklands_fog_skinning", "resources/shaders/darklands_fog_skinning.vertex", "resources/shaders/darklands_fog_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("darklands_flash", "resources/shaders/default.vertex", "resources/shaders/darklands_flash_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("cutsceneFade", "resources/shaders/default.vertex", "resources/shaders/cutscene_fade_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("cutsceneBlack", "resources/shaders/default.vertex", "resources/shaders/cutscene_black_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("shadow_depth", "resources/shaders/shadow_depth.vertex", "resources/shaders/shadow_depth_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("shadow_depth_skinning", "resources/shaders/shadow_depth_skinning.vertex", "resources/shaders/shadow_depth_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("default_shadow", "resources/shaders/default_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("fog_shadow", "resources/shaders/fog_shadow.vertex", "resources/shaders/fog_shadow_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("fog_skinning_shadow", "resources/shaders/fog_skinning_shadow.vertex", "resources/shaders/fog_shadow_desktop.fragment", CONST_ZIP_FILE);

#endif

		std::cout << "Load resurces step 4" << std::endl;

		ItemRegistry::instance().loadFromJson("resources/config2/items.json", CONST_ZIP_FILE);

		globalFloats["player_hp"] = 200;

		LocationSetup uniInteriorParams;
		uniInteriorParams.gameObjectsJsonPath = "resources/config2/gameobjects_uni_interior.json";
		uniInteriorParams.npcsJsonPath = "resources/config2/npcs_uni_interior.json";
		uniInteriorParams.dialoguesJsonPath = "resources/dialogue/uni_interior_dialogues.json";

		/*
		uniInteriorParams.navigationJsonPaths = {
			"resources/navigation/uni_interior0_all_locked.txt",
			"resources/navigation/uni_interior0_hall.txt",
			"resources/navigation/uni_interior0_lr_hall.txt",
			"resources/navigation/uni_interior_basic0_objects_opendoors.txt",
			"resources/navigation/uni_interior0_aiperi_lr_tr_hall.txt",
			"resources/navigation/uni_interior1_night.txt",	
			"resources/navigation/uni_interior1_darklands_open.txt", //6
			"resources/navigation/uni_interior2_locked.txt",
			"resources/navigation/uni_interior2_unlocked_s1.txt",
			"resources/navigation/uni_interior2_unlocked_s2.txt",
			"resources/navigation/uni_interior2_unlocked_s3.txt",
			"resources/navigation/uni_interior2_unlocked_n2.txt",
			"resources/navigation/uni_interior2_unlocked_n3.txt",
			"resources/navigation/uni_interior2_unlocked_hall.txt"
		};*/


		uniInteriorParams.navigationJsonPaths = {
			"resources/navigation/uni_interior0_all_locked.json",
			"resources/navigation/uni_interior0_hall.json",
			"resources/navigation/uni_interior0_lr_hall.json",
			"resources/navigation/uni_interior_basic0_objects_opendoors.json",
			"resources/navigation/uni_interior0_aiperi_lr_tr_hall.json",
			"resources/navigation/uni_interior1_night.json",
			"resources/navigation/uni_interior1_darklands_open.json", //6
			"resources/navigation/uni_interior2_locked.json",
			"resources/navigation/uni_interior2_unlocked_s1.json",
			"resources/navigation/uni_interior2_unlocked_s2.json",
			"resources/navigation/uni_interior2_unlocked_s3.json",
			"resources/navigation/uni_interior2_unlocked_n2.json",
			"resources/navigation/uni_interior2_unlocked_n3.json",
			"resources/navigation/uni_interior2_unlocked_hall.json"
		};


		uniInteriorParams.teleportsJsonPath = "resources/config2/teleports_uni_interior.json";
		uniInteriorParams.triggerZonesJsonPath = "resources/config2/trigger_zones_uni_interior.json";
		uniInteriorParams.scriptPath = "resources/start_uni_interior.lua";
		uniInteriorParams.interactiveObjectsJsonPath = "resources/config2/interactive_objects_uni_interior.json";
		uniInteriorParams.playerPosition = Eigen::Vector3f(0.942694, 0, -9.63104);

		locations["uni_interior"] = std::make_shared<Location>(renderer, inventory);
		locations["uni_interior"]->setup(uniInteriorParams);
		locations["uni_interior"]->scriptEngine.setGlobalStore(&globalInts);
		locations["uni_interior"]->scriptEngine.setGlobalFloatStore(&globalFloats);
		locations["uni_interior"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); };
		if (locations["uni_interior"]->player)
			locations["uni_interior"]->player->onDeathAnimComplete = [this]() { startDarklandsTransition(); };

		LocationSetup uniExteriorParams = uniInteriorParams;
		uniExteriorParams.gameObjectsJsonPath = "resources/config2/gameobjects_uni_exterior.json";
		uniExteriorParams.interactiveObjectsJsonPath = "resources/config2/interactive_objects_uni_exterior.json";
		//uniExteriorParams.navigationJsonPaths = {"resources/config2/navigation2.json"};
		uniExteriorParams.navigationJsonPaths = { "resources/w/nav_uni_exterior.txt" };
		uniExteriorParams.teleportsJsonPath = "resources/config2/teleports_uni_exterior.json";
		uniExteriorParams.triggerZonesJsonPath = "resources/config2/trigger_zones_uni_exterior.json";
		uniExteriorParams.scriptPath = "resources/start_uni_exterior.lua";
		uniExteriorParams.playerPosition = Eigen::Vector3f(5, 0, -18.4);
		uniExteriorParams.npcsJsonPath = "resources/config2/npcs_uni_exterior.json";
		uniExteriorParams.dialoguesJsonPath = "resources/dialogue/uni_exterior_dialogues.json";

		locations["uni_exterior"] = std::make_shared<Location>(renderer, inventory);
		locations["uni_exterior"]->setup(uniExteriorParams);
		locations["uni_exterior"]->scriptEngine.setGlobalStore(&globalInts);
		locations["uni_exterior"]->scriptEngine.setGlobalFloatStore(&globalFloats);
		locations["uni_exterior"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); };
		if (locations["uni_exterior"]->player)
			locations["uni_exterior"]->player->onDeathAnimComplete = [this]() { startDarklandsTransition(); };

		LocationSetup params_dorm;
		params_dorm.gameObjectsJsonPath = "resources/config2/gameobjects_dorm.json";
		params_dorm.npcsJsonPath = "resources/config2/npcs_dorm.json";
		params_dorm.dialoguesJsonPath = "resources/dialogue/dorm_dialogues.json";
		//params_dorm.navigationJsonPaths = {"resources/config2/navigation_dorm.json", "resources/config2/navigation_dorm_tutorial001.json" };
		params_dorm.navigationJsonPaths = { "resources/w/nav_dorm.txt", "resources/w/nav_dorm.txt" };

		params_dorm.teleportsJsonPath = "resources/config2/teleports_dorm.json";
		params_dorm.triggerZonesJsonPath = "resources/config2/trigger_zones_dorm.json";
		params_dorm.scriptPath = "resources/start_dorm.lua";
		params_dorm.interactiveObjectsJsonPath = "resources/config2/interactive_objects_dorm.json";
		params_dorm.playerPosition = Eigen::Vector3f(6.76345, 0, -14.6022);

		locations["location_dorm"] = std::make_shared<Location>(renderer, inventory);
		locations["location_dorm"]->setup(params_dorm);
		locations["location_dorm"]->scriptEngine.setGlobalStore(&globalInts);
		locations["location_dorm"]->scriptEngine.setGlobalFloatStore(&globalFloats);
		locations["location_dorm"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); };
		if (locations["location_dorm"]->player)
			locations["location_dorm"]->player->onDeathAnimComplete = [this]() { startDarklandsTransition(); };

		// Teleport callbacks: destination name and position come from the teleport zone data.
		auto teleportCallback = [this](const std::string& destName, const Eigen::Vector3f& destPos, float destRotY) {
				std::cout << "[TELEPORT] " << " -> " << destName << std::endl;
				auto it = locations.find(destName);
				if (it == locations.end()) {
					std::cerr << "[TELEPORT] Unknown destination location: " << destName << std::endl;
					return;
				}
				if (currentLocation)
					currentLocation->scriptEngine.callLocationExitCallback();
				currentLocation = it->second;
				if (currentLocation->player) {
					currentLocation->player->position = destPos;
					currentLocation->player->setTarget(destPos);
					currentLocation->player->facingAngle = destRotY;
					currentLocation->player->targetFacingAngle = destRotY;
				}
				currentLocation->cameraAzimuth = destRotY;
				currentLocation->scriptEngine.callLocationEnterCallback();
			};

		locations["uni_exterior"]->onTeleport = teleportCallback;
		locations["uni_interior"]->onTeleport = teleportCallback;
		locations["location_dorm"]->onTeleport = teleportCallback;

		currentLocation = locations["location_dorm"];
		currentLocation->scriptEngine.callLocationEnterCallback();

		std::cout << "Load resurces step 5" << std::endl;
		/*
		std::cout << "Load resurces step 12" << std::endl;

		// Shadow mapping shaders
#ifdef EMSCRIPTEN
		renderer.shaderManager.AddShaderFromFiles("shadow_depth", "resources/shaders/shadow_depth.vertex", "resources/shaders/shadow_depth_web.fragment", CONST_ZIP_FILE);
		std::cout << "Load resurces step 12x1" << std::endl;
		renderer.shaderManager.AddShaderFromFiles("shadow_depth_skinning", "resources/shaders/shadow_depth_skinning.vertex", "resources/shaders/shadow_depth_web.fragment", CONST_ZIP_FILE);
		std::cout << "Load resurces step 12x2" << std::endl;
		renderer.shaderManager.AddShaderFromFiles("default_shadow", "resources/shaders/default_shadow.vertex", "resources/shaders/default_shadow_web.fragment", CONST_ZIP_FILE);
		std::cout << "Load resurces step 12x3" << std::endl;
		renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_web.fragment", CONST_ZIP_FILE);
		std::cout << "Load resurces step 12x4" << std::endl;
		renderer.shaderManager.AddShaderFromFiles("fog_shadow", "resources/shaders/fog_shadow.vertex", "resources/shaders/fog_shadow_web.fragment", CONST_ZIP_FILE);
		std::cout << "Load resurces step 12x5" << std::endl;
		renderer.shaderManager.AddShaderFromFiles("fog_skinning_shadow", "resources/shaders/fog_skinning_shadow.vertex", "resources/shaders/fog_shadow_web.fragment", CONST_ZIP_FILE);
		std::cout << "Load resurces step 12x6" << std::endl;
		renderer.shaderManager.AddShaderFromFiles("cutsceneFade", "resources/shaders/default.vertex", "resources/shaders/cutscene_fade_web.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("cutsceneBlack", "resources/shaders/default.vertex", "resources/shaders/cutscene_black_web.fragment", CONST_ZIP_FILE);


#else
		renderer.shaderManager.AddShaderFromFiles("shadow_depth", "resources/shaders/shadow_depth.vertex", "resources/shaders/shadow_depth_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("shadow_depth_skinning", "resources/shaders/shadow_depth_skinning.vertex", "resources/shaders/shadow_depth_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("default_shadow", "resources/shaders/default_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("fog_shadow", "resources/shaders/fog_shadow.vertex", "resources/shaders/fog_shadow_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("fog_skinning_shadow", "resources/shaders/fog_skinning_shadow.vertex", "resources/shaders/fog_shadow_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("cutsceneFade", "resources/shaders/default.vertex", "resources/shaders/cutscene_fade_desktop.fragment", CONST_ZIP_FILE);
		renderer.shaderManager.AddShaderFromFiles("cutsceneBlack", "resources/shaders/default.vertex", "resources/shaders/cutscene_black_desktop.fragment", CONST_ZIP_FILE);
#endif*/

		std::cout << "Load resurces step 13" << std::endl;

		try {
			menuManager.setup(inventory, CONST_ZIP_FILE);
			std::cout << "UI loaded successfully" << std::endl;
		}
		catch (const std::exception& e) {
			std::cerr << "Failed to load UI: " << e.what() << std::endl;
		}

		loadingCompleted = true;

		if (audioPlayer->init()) {
			audioPlayer->setMusicVolume(100);
			audioPlayer->setSoundVolume(80);
			std::cout << "Audio initialized successfully" << std::endl;
		}
		else {
			std::cout << "Audio initialization failed" << std::endl;
		}

	}

	void Game::drawUI()
	{
		glClear(GL_DEPTH_BUFFER_BIT);

		glDisable(GL_DEPTH_TEST);
		glDepthMask(GL_FALSE);

		renderer.shaderManager.PushShader(defaultShaderName);
		renderer.RenderUniform1i(textureUniformName, 0);
		glEnable(GL_BLEND);

		menuManager.uiManager.draw(renderer);
		if (currentLocation)
		{
			currentLocation->dialogueSystem.draw(renderer);
		}

		glDisable(GL_BLEND);
		renderer.shaderManager.PopShader();

		glDepthMask(GL_TRUE);
		glEnable(GL_DEPTH_TEST);

		CheckGlError(__FILE__, __LINE__);
	}



	void Game::drawScene() {
		glViewport(0, 0, Environment::width, Environment::height);
		if (!loadingCompleted) {
			drawLoading();
		}
		else
		{
			if (currentLocation)
			{
				// Sync global darklands flag so Location's raycast and draw functions see it.
				currentLocation->isDarklands = isDarklands;

				if (isDarklands) {
					currentLocation->drawGameDarklands();
					CheckGlError(__FILE__, __LINE__);
				}
				else if (currentLocation->shadowMap) {
					CheckGlError(__FILE__, __LINE__);
					currentLocation->drawShadowDepthPass();
					CheckGlError(__FILE__, __LINE__);
					currentLocation->drawGameWithShadows();
					CheckGlError(__FILE__, __LINE__);
				}
				else {
					currentLocation->drawGame();
					CheckGlError(__FILE__, __LINE__);
				}
			}
			else
			{
				// ??? Main menu???
			}
			drawUI();
			drawDarklandsFlash();
		}
		CheckGlError(__FILE__, __LINE__);
	}

	void Game::drawLoading()
	{
		glClear(GL_DEPTH_BUFFER_BIT);

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

		float width = Environment::projectionWidth;
		float height = Environment::projectionHeight;

		renderer.PushProjectionMatrix(
			-width * 0.5f, width * 0.5f,
			-height * 0.5f, height * 0.5f,
			-10, 10);

		renderer.PushMatrix();
		renderer.LoadIdentity();

		glBindTexture(GL_TEXTURE_2D, loadingTexture->getTexID());
		renderer.DrawVertexRenderStruct(loadingMesh);

		renderer.PopMatrix();
		renderer.PopProjectionMatrix();
		renderer.shaderManager.PopShader();
		CheckGlError(__FILE__, __LINE__);
	}


	int64_t Game::getSyncTimeMs() {
		int64_t localNow = std::chrono::duration_cast<std::chrono::milliseconds>(
			std::chrono::system_clock::now().time_since_epoch()).count();
		return localNow;
	}


	void Game::processTickCount() {

		if (lastTickCount == 0) {
			lastTickCount = getSyncTimeMs();

			lastTickCount = (lastTickCount / 50) * 50;

			return;
		}

		newTickCount = getSyncTimeMs();

		newTickCount = (newTickCount / 50) * 50;

		if (newTickCount - lastTickCount > CONST_TIMER_INTERVAL) {

			int64_t delta = newTickCount - lastTickCount;

			lastTickCount = newTickCount;

			updateDarklandsFlash(delta);

			if (currentLocation)
			{
				currentLocation->update(delta);
			}

		}
	}

	void Game::render() {
		ZL::CheckGlError(__FILE__, __LINE__);

		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		drawScene();
		processTickCount();

		SDL_GL_SwapWindow(ZL::Environment::window);
	}

	void Game::update() {
		SDL_Event event;
		while (SDL_PollEvent(&event)) {
			if (event.type == SDL_QUIT) {
				Environment::exitGameLoop = true;
			}


			if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
				// Обновляем размеры и сбрасываем кеш текстов, т.к. меши хранятся в пикселях
				Environment::width = event.window.data1;
				Environment::height = event.window.data2;
				Environment::computeProjectionDimensions();
				//menuManager.uiManager.updateAllLayouts();
				std::cout << "Window resized: " << Environment::width << "x" << Environment::height << std::endl;

				//space.clearTextRendererCache();
			}

#ifdef __ANDROID__
			if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_AC_BACK) {
				Environment::exitGameLoop = true;
			}
#endif

			// Touch events (real fingers on Android and on mobile browsers via Emscripten).
			// SDL_HINT_TOUCH_MOUSE_EVENTS="0" is set in main.cpp so these do not
			// also fire SDL_MOUSEBUTTONDOWN, preventing double-processing.
			if (event.type == SDL_FINGERDOWN || event.type == SDL_FINGERUP || event.type == SDL_FINGERMOTION) {
				int eventX = static_cast<int>(event.tfinger.x * Environment::width);
				int eventY = static_cast<int>(event.tfinger.y * Environment::height);
				int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
				int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
				int64_t fingerId = static_cast<int64_t>(event.tfinger.fingerId);

				if (event.type == SDL_FINGERDOWN) {
					onPointerDown(fingerId, eventX, eventY, mx, my);
				}
				else if (event.type == SDL_FINGERUP) {
					onPointerUp(fingerId, eventX, eventY, mx, my);
				}
				else {
					onPointerMotion(fingerId, eventX, eventY, mx, my);
				}
				continue;
			}

			// Mouse-left maps to a single virtual touch with MOUSE_FINGER_ID.
			// Right mouse is intentionally unused now — camera rotation is on hold-and-drag.
			if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
				if (event.button.button == SDL_BUTTON_LEFT) {
					int eventX = event.button.x;
					int eventY = event.button.y;
					int mx = static_cast<int>((float)eventX / Environment::width * Environment::projectionWidth);
					int my = static_cast<int>((float)eventY / Environment::height * Environment::projectionHeight);

					if (event.type == SDL_MOUSEBUTTONDOWN) {
						onPointerDown(ZL::UiManager::MOUSE_FINGER_ID, eventX, eventY, mx, my);
					}
					else {
						onPointerUp(ZL::UiManager::MOUSE_FINGER_ID, eventX, eventY, mx, my);
					}
				}
				else if (event.button.button == SDL_BUTTON_RIGHT
				         && event.type == SDL_MOUSEBUTTONUP
				         && navigationEditorMode
				         && currentLocation) {
					currentLocation->navigationEditorHandleRightClick();
				}
			}
			else if (event.type == SDL_MOUSEMOTION) {
				int eventX = event.motion.x;
				int eventY = event.motion.y;
				int mx = static_cast<int>((float)eventX / Environment::width * Environment::projectionWidth);
				int my = static_cast<int>((float)eventY / Environment::height * Environment::projectionHeight);
				onPointerMotion(ZL::UiManager::MOUSE_FINGER_ID, eventX, eventY, mx, my);
			}

			if (event.type == SDL_MOUSEWHEEL) {
				static const float zoomstep = 2.0f;
				if (event.wheel.y > 0) {
					Environment::zoom -= zoomstep;
				}
				else if (event.wheel.y < 0) {
					Environment::zoom += zoomstep;
				}
				if (Environment::zoom < zoomstep) {
					Environment::zoom = zoomstep;
				}
			}

			if (event.type == SDL_KEYDOWN && event.key.repeat == 0) {
				switch (event.key.keysym.sym) {
				case SDLK_0:
					startDarklandsTransition();
					break;
				case SDLK_1:
					//if (audioPlayer) audioPlayer->playSoundAsync("audio/background.wav");
					break;
				case SDLK_2:
					//if (audioPlayer) audioPlayer->playMusicAsync("audio/lullaby-music-vol20-186394--online-audio-convert.com.ogg");
					break;
				case SDLK_3:
					//if (audioPlayer) audioPlayer->stopMusicAsync();
					break;
				case SDLK_f:
					//currentLocation->dialogueSystem.startDialogue("test_choice_dialogue");
					break;

				case SDLK_e:
					currentLocation->dialogueSystem.startCutscene("test_cutscene_01"); //.startDialogue("test_cutscene_pan_dialogue");
					break;

				case SDLK_n:
					navigationEditorMode = !navigationEditorMode;
					if (currentLocation) {
						currentLocation->navigationEditorMode = navigationEditorMode;
						if (navigationEditorMode) {
							currentLocation->navigationEditorBuildNavMeshes();
						}
					}
					std::cout << "[NAV_EDITOR] Mode: " << (navigationEditorMode ? "ON" : "OFF") << std::endl;
					break;

				case SDLK_o:
					//y = y + 0.002;
					//currentLocation->player->hp = 200;
					//currentLocation->npcs[0]->walkSpeed += 0.01f;
					//std::cout << "Walk speed: " << currentLocation->npcs[0]->walkSpeed << std::endl;
					break;

				case SDLK_k:
					//y = y - 0.002;
					std::cout << "Player pos: " << currentLocation->player->position.transpose() << std::endl;
					//currentLocation->npcs[0]->walkSpeed -= 0.01f;
					//std::cout << "Walk speed: " << currentLocation->npcs[0]->walkSpeed << std::endl;
					break;

				case SDLK_p:
					currentLocation = locations["uni_interior"];
					currentLocation->player->position = Eigen::Vector3f(-0.0189243, 0, -13.4314);
					currentLocation->player->setTarget(currentLocation->player->position);
					//std::cout << "Switched to location " << ((currentLocation == locations["location1"]) ? "1" : "2") << std::endl;
					break;

				case SDLK_l:
					x = x - 0.002;
					break;

				case SDLK_c:
					std::cout << "SLOW-MO activated!" << std::endl;
					activateSlowMoEffect();
					break;

				case SDLK_b:
					if (navigationEditorMode && currentLocation) {
						currentLocation->navigationEditorSave();
					}
					break;

				case SDLK_j:
					if (navigationEditorMode && currentLocation) {
						currentLocation->gameObjectEditorPlaceTree();
					} else {
						menuManager.toggleQuestJournal();
					}
					break;

				case SDLK_v:
					if (navigationEditorMode && currentLocation) {
						currentLocation->gameObjectEditorSave();
					}
					break;

				case SDLK_RETURN:

				default:
					break;
				}
			}

			// Обработка ввода текста
			if (event.type == SDL_KEYDOWN) {
				if (event.key.keysym.sym == SDLK_BACKSPACE) {
					//menuManager.uiManager.onKeyBackspace();
				}
			}

			if (event.type == SDL_TEXTINPUT) {
				// Пропускаем ctrl+c и другие команды
				if ((event.text.text[0] & 0x80) == 0) { // ASCII символы
					//menuManager.uiManager.onKeyPress((unsigned char)event.text.text[0]);
				}
			}

			if (event.type == SDL_KEYUP) {
				if (event.key.keysym.sym == SDLK_r) {
					std::cout << "Camera position: x=" << x << " y=" << y << " z=" << z << std::endl;
				}
			}

			if (event.type == SDL_KEYUP) {

			}
		}
		render();

		mainThreadHandler.processMainThreadTasks();

	}

	int Game::countNonUiPointers() const
	{
		int n = 0;
		for (const auto& kv : activePointers) {
			if (!kv.second.capturedByUi) ++n;
		}
		return n;
	}

	void Game::activateSlowMoEffect() {
		if (!currentLocation) return;

		if (currentLocation->player) {
			currentLocation->player->slowMoTimeRemaining = currentLocation->player->slowMoActiveTime;
		}

		for (auto& npc : currentLocation->npcs) {
			if (npc) {
				npc->slowMoTimeRemaining = npc->slowMoActiveTime;
			}
		}
	}

	void Game::enterCameraDragMode(int eventX, int eventY)
	{
		cameraDragging = true;
		if (currentLocation) {
			currentLocation->cameraDragging = true;
			// Anchor on the *current* position, not the original press, so the
			// camera doesn't snap by however far the finger drifted before the
			// movement threshold was crossed.
			currentLocation->lastMouseX = eventX;
			currentLocation->lastMouseY = eventY;
		}
	}

	void Game::exitCameraDragMode()
	{
		cameraDragging = false;
		if (currentLocation) {
			currentLocation->cameraDragging = false;
		}
	}

	void Game::startPinch()
	{
		// Pick the first two non-UI pointers as pinch anchors.
		bool foundA = false, foundB = false;
		for (auto& kv : activePointers) {
			if (kv.second.capturedByUi) continue;
			if (!foundA) {
				pinchFingerA = kv.first;
				foundA = true;
			}
			else if (!foundB) {
				pinchFingerB = kv.first;
				foundB = true;
				break;
			}
		}
		if (!foundA || !foundB) return;

		const PointerState& a = activePointers[pinchFingerA];
		const PointerState& b = activePointers[pinchFingerB];
		float dx = static_cast<float>(a.eventX - b.eventX);
		float dy = static_cast<float>(a.eventY - b.eventY);
		pinchStartDistance = std::sqrt(dx * dx + dy * dy);
		pinchStartZoom = Environment::zoom;
		pinchActive = true;
	}

	void Game::updatePinchZoom()
	{
		auto itA = activePointers.find(pinchFingerA);
		auto itB = activePointers.find(pinchFingerB);
		if (itA == activePointers.end() || itB == activePointers.end()) return;
		if (pinchStartDistance <= 1.0f) return;

		float dx = static_cast<float>(itA->second.eventX - itB->second.eventX);
		float dy = static_cast<float>(itA->second.eventY - itB->second.eventY);
		float dist = std::sqrt(dx * dx + dy * dy);
		if (dist <= 1.0f) return;

		float newZoom = pinchStartZoom * (pinchStartDistance / dist);
		// Match the wheel-zoom lower bound so both gestures clamp the same way.
		static const float zoomMin = 2.0f;
		if (newZoom < zoomMin) newZoom = zoomMin;
		Environment::zoom = newZoom;
	}

	void Game::endPinch()
	{
		pinchActive = false;
		pinchStartDistance = 0.0f;
		pinchStartZoom = 0.0f;
	}

	void Game::onPointerDown(int64_t fingerId, int eventX, int eventY, int mx, int my)
	{
		PointerState st;
		st.eventX = st.downEventX = eventX;
		st.eventY = st.downEventY = eventY;
		st.mx = st.downMx = mx;
		st.my = st.downMy = my;

		const int uiX = mx;
		const int uiY = Environment::projectionHeight - my;
		menuManager.uiManager.onTouchDown(fingerId, uiX, uiY);
		st.capturedByUi = menuManager.uiManager.isUiInteractionForFinger(fingerId);

		activePointers[fingerId] = st;

		if (st.capturedByUi) {
			// UI button / slider / text-field grabbed this press; don't drive
			// gameplay or pinch from it.
			return;
		}

		if (countNonUiPointers() >= 2) {
			// Second finger landed on the gameplay area: switch to pinch-zoom
			// and abandon any in-flight tap/camera-drag.
			if (cameraDragging) {
				exitCameraDragMode();
			}
			hasPrimaryPointer = false;
			if (!pinchActive) {
				startPinch();
			}
		}
		else {
			hasPrimaryPointer = true;
			primaryPointerId = fingerId;
		}
	}

	void Game::onPointerUp(int64_t fingerId, int eventX, int eventY, int mx, int my)
	{
		const int uiX = mx;
		const int uiY = Environment::projectionHeight - my;
		menuManager.uiManager.onTouchUp(fingerId, uiX, uiY);

		auto it = activePointers.find(fingerId);
		if (it == activePointers.end()) return;
		PointerState st = it->second;
		activePointers.erase(it);

		// Pinch ends as soon as either anchor is lifted. The remaining finger,
		// if any, is *not* promoted back to a primary tap — the user was mid-
		// gesture, not starting a fresh press.
		if (pinchActive && (fingerId == pinchFingerA || fingerId == pinchFingerB)) {
			endPinch();
			return;
		}

		if (!hasPrimaryPointer || fingerId != primaryPointerId) {
			return;
		}

		const bool wasDragging = cameraDragging;
		if (cameraDragging) {
			exitCameraDragMode();
		}
		hasPrimaryPointer = false;

		if (st.capturedByUi || wasDragging) {
			// UI handled the press, or it was a camera-rotation drag — no tap action.
			return;
		}

		// Tap → walk-to / interact, using the original press coords so the target
		// isn't shifted by tiny finger drift before release.
		if (currentLocation) {
			currentLocation->handleDown(fingerId, st.downEventX, st.downEventY, st.downMx, st.downMy);
		}
	}

	void Game::onPointerMotion(int64_t fingerId, int eventX, int eventY, int mx, int my)
	{
		const int uiX = mx;
		const int uiY = Environment::projectionHeight - my;
		menuManager.uiManager.onTouchMove(fingerId, uiX, uiY);

		auto it = activePointers.find(fingerId);
		if (it != activePointers.end()) {
			it->second.eventX = eventX;
			it->second.eventY = eventY;
			it->second.mx = mx;
			it->second.my = my;
		}

		if (pinchActive) {
			updatePinchZoom();
			return;
		}


		// Only the primary, non-UI-captured pointer can promote itself into a
		// camera-rotation drag once it crosses the movement threshold.
		if (hasPrimaryPointer && fingerId == primaryPointerId && it != activePointers.end()
			&& !it->second.capturedByUi && !cameraDragging) {
			int dx = mx - it->second.downMx;
			int dy = my - it->second.downMy;
			if (dx * dx + dy * dy >= CAMERA_DRAG_PIXEL_THRESHOLD * CAMERA_DRAG_PIXEL_THRESHOLD) {
				enterCameraDragMode(eventX, eventY);
			}
		}


		if (currentLocation) {
			// Forwarded for dialogue hover and (when cameraDragging) camera rotation.
			currentLocation->handleMotion(fingerId, eventX, eventY, mx, my);
		}
	}

	bool Game::startDarklandsTransition()
	{
		if (darklandsFlashActive) return false;
		darklandsFlashActive = true;
		darklandsFlashFadingIn = true;
		darklandsFlashAlpha = 0.0f;
		return true;
	}

	void Game::updateDarklandsFlash(int64_t deltaMs)
	{
		if (!darklandsFlashActive) return;
		static constexpr float kFadeDurationMs = 500.0f;
		const float step = static_cast<float>(deltaMs) / kFadeDurationMs;

		if (darklandsFlashFadingIn) {
			darklandsFlashAlpha = min(darklandsFlashAlpha + step, 1.0f);
			if (darklandsFlashAlpha >= 1.0f) {
				isDarklands = !isDarklands;
				if (currentLocation) {
					if (isDarklands)
						currentLocation->scriptEngine.callDarklandsEnterCallback();
					else
						currentLocation->scriptEngine.callDarklandsExitCallback();
				}
				darklandsFlashFadingIn = false;
			}
		} else {
			darklandsFlashAlpha = max(darklandsFlashAlpha - step, 0.0f);
			if (darklandsFlashAlpha <= 0.0f) {
				darklandsFlashAlpha = 0.0f;
				darklandsFlashActive = false;
			}
		}
	}

	void Game::drawDarklandsFlash()
	{
		if (darklandsFlashAlpha <= 0.001f) return;

		const float W = static_cast<float>(Environment::projectionWidth);
		const float H = static_cast<float>(Environment::projectionHeight);

		if (darklandsFlashQuadW != W || darklandsFlashQuadH != H) {
			darklandsFlashQuadW = W;
			darklandsFlashQuadH = H;
			VertexDataStruct data;
			data.PositionData = {
				{0.f, 0.f, 0.f}, {0.f, H, 0.f}, {W, H, 0.f},
				{W,   H, 0.f},   {W,  0.f, 0.f}, {0.f, 0.f, 0.f}
			};
			data.TexCoordData = {
				{0.f, 0.f}, {0.f, 1.f}, {1.f, 1.f},
				{1.f, 1.f}, {1.f, 0.f}, {0.f, 0.f}
			};
			darklandsFlashQuad.data = std::move(data);
			darklandsFlashQuad.RefreshVBO();
		}

		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		renderer.shaderManager.PushShader("darklands_flash");
		renderer.PushProjectionMatrix(0.0f, W, 0.0f, H, -10.0f, 10.0f);
		renderer.PushMatrix();
		renderer.LoadIdentity();

		renderer.RenderUniform1f("uAlpha", darklandsFlashAlpha);
		renderer.DrawVertexRenderStruct(darklandsFlashQuad);

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

		glDisable(GL_BLEND);
	}

}  // namespace ZL