#include "Game.h"
#include "AnimatedModel.h"
#include "BoneAnimatedModel.h"
#include "planet/PlanetData.h"
#include "utils/Utils.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 NETWORK
#include "network/WebSocketClientBase.h"
#ifdef EMSCRIPTEN
#include "network/WebSocketClientEmscripten.h"
#else
#include "network/WebSocketClient.h"
#endif
#endif

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

#include "network/LocalClient.h"
#include "network/ClientState.h"
#include "GameConstants.h"

namespace ZL
{
#ifdef EMSCRIPTEN
	const char* CONST_ZIP_FILE = "resources.zip";
#else
	//const char* CONST_ZIP_FILE = "C:\\Work\\Projects\\space-game001\\resources.zip";
	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*/) {
		if (s_instance) {
			s_instance->mainThreadHandler.EnqueueMainThreadTask([&]() {
				s_instance->setupPart2();
			});
		}
	}

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

	Game::Game()
		: /*window(nullptr)
		, glContext(nullptr)
		, */newTickCount(0)
		, lastTickCount(0)
		, menuManager(renderer)
		, space(renderer, taskManager, mainThreadHandler, networkClient, menuManager)
	{
	}

	Game::~Game() {
		/*
		if (glContext) {
			SDL_GL_DeleteContext(glContext);
		}
		if (window) {
			SDL_DestroyWindow(window);
		}*/
#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() {
		//glContext = SDL_GL_CreateContext(ZL::Environment::window);
		//glContext = in_glContext;

		Environment::width = Environment::CONST_DEFAULT_WIDTH;
		Environment::height = Environment::CONST_DEFAULT_HEIGHT;
		Environment::computeProjectionDimensions();

		ZL::BindOpenGlFunctions();
		ZL::CheckGlError();
		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", "");
		loadingTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/loading.png", ""));
#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);
		loadingTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/loading.png", CONST_ZIP_FILE));
#endif

		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);

#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);
#endif

		menuManager.setupMenu();

		menuManager.onMainMenuEntered = [this]() {
			if (networkClient) {
				networkClient->Disconnect();
				networkClient.reset();
			}
		};

		menuManager.onSingleplayerPressed = [this](const std::string& nickname, int shipType) {
			Environment::shipState.nickname = nickname;
			Environment::shipState.shipType = shipType;

			if (Environment::shipState.shipType == 1)
			{
				menuManager.uiManager.findButton("shootButton")->state = ButtonState::Disabled;
				menuManager.uiManager.findButton("shootButton2")->state = ButtonState::Disabled;
			}
			else
			{
				menuManager.uiManager.findButton("shootButton")->state = ButtonState::Normal;
				menuManager.uiManager.findButton("shootButton2")->state = ButtonState::Normal;
			}

			auto localClient = new LocalClient;
			ClientState st = Environment::shipState;
			st.id = localClient->GetClientId();
			localClient->setLocalPlayerState(st);

			networkClient = std::unique_ptr<INetworkClient>(localClient);
			networkClient->Connect("", 0);

			space.resetPlayerState();
			lastTickCount = 0;
			};

		menuManager.onMultiplayerPressed = [this](const std::string& nickname, int shipType) {
			Environment::shipState.nickname = nickname;
			Environment::shipState.shipType = shipType;

#ifdef EMSCRIPTEN
			networkClient = std::make_unique<WebSocketClientEmscripten>();
			networkClient->Connect("localhost", 8081);
#else
			networkClient = std::make_unique<WebSocketClient>(taskManager.getIOContext());
			networkClient->Connect("localhost", 8081);
#endif

			if (networkClient) {
				std::string joinMsg = std::string("JOIN:") + nickname + ":" + std::to_string(shipType);
				networkClient->Send(joinMsg);
				std::cerr << "Sent JOIN: " << joinMsg << std::endl;
			}

			space.boxCoordsArr.clear();
			space.boxRenderArr.clear();
			space.boxAlive.clear();
			space.serverBoxesApplied = false;

			space.resetPlayerState();
			connectingStartTicks = SDL_GetTicks();
			lastTickCount = 0;
			};


		space.setup();

		loadingCompleted = true;
	}


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

		renderer.shaderManager.PushShader(defaultShaderName);
		renderer.RenderUniform1i(textureUniformName, 0);
		glEnable(GL_BLEND);
		menuManager.uiManager.draw(renderer);
		glDisable(GL_BLEND);
		renderer.shaderManager.PopShader();
		CheckGlError();
	}

	void Game::drawUnderMainMenu()
	{
		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
		glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

	}

	void Game::drawScene() {
		glViewport(0, 0, Environment::width, Environment::height);
		if (!loadingCompleted) {
			drawLoading();
		}
		else
		{

			if (menuManager.shouldRenderSpace()) {
				space.drawScene();
			}
			else
			{
				drawUnderMainMenu();
			}
			drawUI();
		}
		CheckGlError();
	}

	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();
	}

	int64_t Game::getSyncTimeMs() {
		int64_t localNow = std::chrono::duration_cast<std::chrono::milliseconds>(
			std::chrono::system_clock::now().time_since_epoch()).count();
		if(networkClient)
		{
			return localNow + networkClient->getTimeOffset();
		}
		else
		{
			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;
			if (delta > CONST_MAX_TIME_INTERVAL)
			{
				//throw std::runtime_error("Synchronization is lost");
			}

			if (menuManager.shouldRenderSpace()) {
				space.processTickCount(newTickCount, delta);
			}
			menuManager.uiManager.update(static_cast<float>(delta));
			lastTickCount = newTickCount;
		}
	}

	void Game::render() {
		//SDL_GL_MakeCurrent(ZL::Environment::window, glContext);
		ZL::CheckGlError();

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

		//processTickCount();
		drawScene();
		processTickCount();
		//std::this_thread::sleep_for(std::chrono::milliseconds(50));

		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

#ifdef __ANDROID__
			if (event.type == SDL_FINGERDOWN) {
				int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
				int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
				handleDown(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
			}
			else if (event.type == SDL_FINGERUP) {
				int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
				int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
				handleUp(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
			}
			else if (event.type == SDL_FINGERMOTION) {
				int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
				int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
				handleMotion(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
			}
#else
			// Emscripten on mobile browser: handle real touch events with per-finger IDs.
			// SDL_HINT_TOUCH_MOUSE_EVENTS="0" is set in main.cpp so these don't
			// also fire SDL_MOUSEBUTTONDOWN, preventing double-processing.
#ifdef EMSCRIPTEN
			if (event.type == SDL_FINGERDOWN) {
				int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
				int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
				handleDown(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
			}
			else if (event.type == SDL_FINGERUP) {
				int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
				int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
				handleUp(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
			}
			else if (event.type == SDL_FINGERMOTION) {
				int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
				int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
				handleMotion(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
			}
#endif

			if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
				// Преобразуем экранные пиксели в проекционные единицы
				int mx = static_cast<int>((float)event.button.x / Environment::width * Environment::projectionWidth);
				int my = static_cast<int>((float)event.button.y / Environment::height * Environment::projectionHeight);

				if (event.type == SDL_MOUSEBUTTONDOWN) handleDown(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
				else handleUp(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
				//std::cout << "Mouse button " << (event.type == SDL_MOUSEBUTTONDOWN ? "down" : "up") << ": x=" << mx << " y=" << my << std::endl;
			}
			else if (event.type == SDL_MOUSEMOTION) {
				int mx = static_cast<int>((float)event.motion.x / Environment::width * Environment::projectionWidth);
				int my = static_cast<int>((float)event.motion.y / Environment::height * Environment::projectionHeight);
				handleMotion(ZL::UiManager::MOUSE_FINGER_ID, 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) {
				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;
				}
			}
#endif
		}
		render();
		
		if (networkClient) {
//#ifndef NETWORK
			auto localClient = dynamic_cast<ZL::LocalClient*>(networkClient.get());
			if (localClient) {
				localClient->setLocalPlayerState(Environment::shipState);
			}
//#endif
			networkClient->Poll();

			if (menuManager.getState() == GameState::Connecting) {
				if (networkClient->IsConnected()) {
					menuManager.notifyConnected();
					// Enable/disable shoot buttons based on ship type
					if (Environment::shipState.shipType == 1) {
						if (auto b = menuManager.uiManager.findButton("shootButton"))  b->state = ButtonState::Disabled;
						if (auto b = menuManager.uiManager.findButton("shootButton2")) b->state = ButtonState::Disabled;
					} else {
						if (auto b = menuManager.uiManager.findButton("shootButton"))  b->state = ButtonState::Normal;
						if (auto b = menuManager.uiManager.findButton("shootButton2")) b->state = ButtonState::Normal;
					}
				} else if (SDL_GetTicks() - connectingStartTicks > CONNECTING_TIMEOUT_MS) {
					menuManager.notifyConnectionFailed();
				}
			}
#ifdef NETWORK
			auto* wsBase = dynamic_cast<ZL::WebSocketClientBase*>(networkClient.get());
			if (wsBase) {
				auto spawns = wsBase->getPendingSpawns();
				for (auto& st : spawns) {
					if (st.id == wsBase->getClientId()) {
						// применяем к локальному кораблю
						ZL::Environment::shipState.position = st.position;
						ZL::Environment::shipState.rotation = st.rotation;

						// обнуляем движение чтобы не было рывков
						ZL::Environment::shipState.currentAngularVelocity = Eigen::Vector3f::Zero();
						ZL::Environment::shipState.velocity = 0.0f;
						ZL::Environment::shipState.selectedVelocity = 0;
						ZL::Environment::shipState.discreteMag = 0.0f;
						ZL::Environment::shipState.discreteAngle = -1;

						std::cout << "Game: Applied SPAWN at "
							<< st.position.x() << ", "
							<< st.position.y() << ", "
							<< st.position.z() << std::endl;
					}
				}
			}
#endif
		}
		mainThreadHandler.processMainThreadTasks();

		if (menuManager.shouldRenderSpace()) {
			space.update();
		}
	}

	void Game::handleDown(int64_t fingerId, int mx, int my)
	{
		int uiX = mx;
		int uiY = Environment::projectionHeight - my;

		menuManager.uiManager.onTouchDown(fingerId, uiX, uiY);

		if (!menuManager.uiManager.isUiInteractionForFinger(fingerId)) {
			if (menuManager.shouldRenderSpace()) {
				space.handleDown(mx, my);
			}
		}
	}

	void Game::handleUp(int64_t fingerId, int mx, int my)
	{
		int uiX = mx;
		int uiY = Environment::projectionHeight - my;

		// Check BEFORE onTouchUp erases the finger from the map.
		// If this finger started on a UI element, don't notify space —
		// otherwise space would think the ship-control finger was released.
		bool wasUiInteraction = menuManager.uiManager.isUiInteractionForFinger(fingerId);
		menuManager.uiManager.onTouchUp(fingerId, uiX, uiY);

		if (!wasUiInteraction) {
			if (menuManager.shouldRenderSpace()) {
				space.handleUp(mx, my);
			}
		}
	}

	void Game::handleMotion(int64_t fingerId, int mx, int my)
	{
		int uiX = mx;
		int uiY = Environment::projectionHeight - my;

		// Check before onTouchMove so the "started on UI" state is preserved
		// regardless of what onTouchMove does internally.
		bool wasUiInteraction = menuManager.uiManager.isUiInteractionForFinger(fingerId);
		menuManager.uiManager.onTouchMove(fingerId, uiX, uiY);

		if (!wasUiInteraction) {
			if (menuManager.shouldRenderSpace()) {
				space.handleMotion(mx, my);
			}
		}
	}


}  // namespace ZL
