#include "SparkEmitter.h"
#include <random>
#include <cmath>
//#include "renderer/OpenGlExtensions.h"
#include <fstream>
#include "external/nlohmann/json.hpp"
#include <iostream>
#include "Environment.h"
#include <stdexcept>
#include "utils/Utils.h"
#include "GameConstants.h"

namespace ZL {

	using json = nlohmann::json;

	SparkEmitter::SparkEmitter()
		: emissionRate(100.0f), isActive(true), drawDataDirty(true), maxParticles(200),
		shaderProgramName("default"), particleSize(0.04f), biasX(0.3f), configured(false),
		useWorldSpace(false) {
		particles.resize(maxParticles);
		drawPositions.reserve(maxParticles * 6);
		drawTexCoords.reserve(maxParticles * 6);
		lastEmissionTime = std::chrono::steady_clock::now();

		sparkQuad.data = VertexDataStruct();
	}

	SparkEmitter::SparkEmitter(const SparkEmitter& copyFrom)
		: particles(copyFrom.particles), emissionPoints(copyFrom.emissionPoints),
		lastEmissionTime(copyFrom.lastEmissionTime), emissionRate(copyFrom.emissionRate),
		isActive(copyFrom.isActive), drawPositions(copyFrom.drawPositions),
		drawTexCoords(copyFrom.drawTexCoords), drawDataDirty(true),
		texture(copyFrom.texture),
		maxParticles(copyFrom.maxParticles), particleSize(copyFrom.particleSize),
		biasX(copyFrom.biasX), speedRange(copyFrom.speedRange),
		zSpeedRange(copyFrom.zSpeedRange),
		scaleRange(copyFrom.scaleRange),
		lifeTimeRange(copyFrom.lifeTimeRange),
		shaderProgramName(copyFrom.shaderProgramName),
		configured(copyFrom.configured), useWorldSpace(copyFrom.useWorldSpace)
	{
		// Each copy gets its own GPU buffers; only copy CPU-side data
		sparkQuad.data = copyFrom.sparkQuad.data;
	}

	
	SparkEmitter::SparkEmitter(const std::vector<Vector3f>& positions, float rate)
		: emissionPoints(positions), emissionRate(rate), isActive(true),
		drawDataDirty(true), maxParticles(positions.size() * 100),
		shaderProgramName("default"), particleSize(0.04f), biasX(0.3f), configured(false),
		useWorldSpace(false) {
		particles.resize(maxParticles);
		drawPositions.reserve(maxParticles * 6);
		drawTexCoords.reserve(maxParticles * 6);
		lastEmissionTime = std::chrono::steady_clock::now();

		sparkQuad.data = VertexDataStruct();
	}

	SparkEmitter::SparkEmitter(const std::vector<Vector3f>& positions,
		std::shared_ptr<Texture> tex,
		float rate)
		: emissionPoints(positions), texture(tex), emissionRate(rate),
		isActive(true), drawDataDirty(true), maxParticles(positions.size() * 100),
		shaderProgramName("default"), particleSize(0.04f), biasX(0.3f), configured(false),
		useWorldSpace(false) {
		particles.resize(maxParticles);
		drawPositions.reserve(maxParticles * 6);
		drawTexCoords.reserve(maxParticles * 6);
		lastEmissionTime = std::chrono::steady_clock::now();

		sparkQuad.data = VertexDataStruct();
	}

	void SparkEmitter::setTexture(std::shared_ptr<Texture> tex) {
		texture = tex;
	}

	void SparkEmitter::prepareDrawData(bool withRotation) {
		if (!drawDataDirty) return;

		drawPositions.clear();
		drawTexCoords.clear();

		if (getActiveParticleCount() == 0) {
			drawDataDirty = false;
			return;
		}

		std::vector<std::pair<const SparkParticle*, float>> sortedParticles;
		sortedParticles.reserve(getActiveParticleCount());

		for (const auto& particle : particles) {
			if (particle.active) {
				Vector3f posCam;

				posCam = particle.position;

				sortedParticles.push_back({ &particle, posCam(2) });
			}
		}

		std::sort(sortedParticles.begin(), sortedParticles.end(),
			[](const auto& a, const auto& b) {
				return a.second < b.second;
			});

		for (const auto& [particlePtr, depth] : sortedParticles) {
			const auto& particle = *particlePtr;
			Vector3f posCam;
			posCam = particle.position;
		

			float size = particleSize * particle.scale;

			
				drawPositions.push_back({ posCam(0) - size, posCam(1) - size, posCam(2) });
				drawTexCoords.push_back({ 0.0f, 0.0f });

				drawPositions.push_back({ posCam(0) - size, posCam(1) + size, posCam(2) });
				drawTexCoords.push_back({ 0.0f, 1.0f });

				drawPositions.push_back({ posCam(0) + size, posCam(1) + size, posCam(2) });
				drawTexCoords.push_back({ 1.0f, 1.0f });

				drawPositions.push_back({ posCam(0) - size, posCam(1) - size, posCam(2) });
				drawTexCoords.push_back({ 0.0f, 0.0f });

				drawPositions.push_back({ posCam(0) + size, posCam(1) + size, posCam(2) });
				drawTexCoords.push_back({ 1.0f, 1.0f });

				drawPositions.push_back({ posCam(0) + size, posCam(1) - size, posCam(2) });
				drawTexCoords.push_back({ 1.0f, 0.0f });

		
		}

		drawDataDirty = false;
	}

	 void SparkEmitter::prepareForDraw(bool withRotation) {
		if (!configured) return;
		
			prepareDrawData(withRotation);
		
			if (!drawPositions.empty()) {
			sparkQuad.data.PositionData = drawPositions;
			sparkQuad.data.TexCoordData = drawTexCoords;
			sparkQuad.RefreshVBO();
		}
	}

	void SparkEmitter::draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight)
	{
		draw(renderer, zoom, screenWidth, screenHeight, true);
	}

	void SparkEmitter::draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight, bool withRotation) {
		if (!configured) {
			throw std::runtime_error("Failed to load spark emitter config file 1!");
		}

		if (getActiveParticleCount() == 0) {
			return;
		}

		if (!texture) {
			throw std::runtime_error("Failed to load spark emitter config file 2!");
		}

		if (drawPositions.empty()) {
			return;
		}

		renderer.shaderManager.PushShader(shaderProgramName);
		renderer.RenderUniform1i(textureUniformName, 0);
		renderer.SetMatrix();

		glBindTexture(GL_TEXTURE_2D, texture->getTexID());

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

		//renderer.PushMatrix();
		renderer.DrawVertexRenderStruct(sparkQuad);
		//renderer.PopMatrix();

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

	void SparkEmitter::update(float deltaTimeMs) {
		if (!configured) {
			throw std::runtime_error("Failed to load spark emitter config file 3!");
		}

		auto currentTime = std::chrono::steady_clock::now();
		auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
			currentTime - lastEmissionTime).count();

		if (isActive && elapsed >= static_cast<long long>(emissionRate)) {
			int emitCount = static_cast<int>(elapsed / emissionRate);
			float elapsedF = static_cast<float>(elapsed);
			for (int e = 0; e < emitCount; ++e) {
				// e=0 — самая старая эмиссия, e=emitCount-1 — самая свежая
				float ageMs = static_cast<float>(emitCount - 1 - e) * emissionRate;
				// lerpT=0 → текущая позиция (новейшая), lerpT=1 → предыдущая (самая старая)
				float lerpT = (elapsedF > 0.0f) ? min(ageMs / elapsedF, 1.0f) : 0.0f;
				emit(ageMs, lerpT);
			}
			lastEmissionTime += std::chrono::milliseconds(
				static_cast<long long>(emitCount * emissionRate));
			
			drawDataDirty = true;
		}

		bool anyChanged = false;
		for (auto& particle : particles) {
			if (particle.active) {
				Vector3f oldPosition = particle.position;
				float oldScale = particle.scale;

				particle.position(0) += particle.velocity(0) * deltaTimeMs / 1000.0f;
				particle.position(1) += particle.velocity(1) * deltaTimeMs / 1000.0f;
				particle.position(2) += particle.velocity(2) * deltaTimeMs / 1000.0f;

				particle.lifeTime += deltaTimeMs;

				if (particle.lifeTime >= particle.maxLifeTime) {
					particle.active = false;
					anyChanged = true;
				}
				else {
					float lifeRatio = particle.lifeTime / particle.maxLifeTime;
					particle.scale = 1.0f - lifeRatio * 0.8f;

					if (oldPosition(0) != particle.position(0) ||
						oldPosition(1) != particle.position(1) ||
						oldPosition(2) != particle.position(2) ||
						oldScale != particle.scale) {
						anyChanged = true;
					}
				}
			}
		}

		if (anyChanged) {
			drawDataDirty = true;
		}
	}

	void SparkEmitter::emit(float ageMs, float lerpT) {
		if (!configured) {
			throw std::runtime_error("Failed to load spark emitter config file 4!");
		}

		if (emissionPoints.empty()) return;
		bool emitted = false;

		auto applyAge = [](SparkParticle& particle, float age) {
			if (age <= 0.0f) return;
			particle.position(0) += particle.velocity(0) * age / 1000.0f;
			particle.position(1) += particle.velocity(1) * age / 1000.0f;
			particle.position(2) += particle.velocity(2) * age / 1000.0f;
			particle.lifeTime = age;
			if (particle.lifeTime >= particle.maxLifeTime) {
				particle.active = false;
			} else {
				float lifeRatio = particle.lifeTime / particle.maxLifeTime;
				particle.scale = 1.0f - lifeRatio * 0.8f;
			}
		};

		// Вычисляем стартовую позицию с интерполяцией между prev и current
		// lerpT=0 → текущая позиция (новейшая эмиссия), lerpT=1 → предыдущая (самая старая)
		bool canInterp = (prevEmissionPoints.size() == emissionPoints.size());

		for (int i = 0; i < (int)emissionPoints.size(); ++i) {
			Vector3f birthPos = emissionPoints[i];
			if (canInterp && lerpT > 0.0f) {
				birthPos = emissionPoints[i] * (1.0f - lerpT) + prevEmissionPoints[i] * lerpT;
			}

			bool particleFound = false;

			for (auto& particle : particles) {
				if (!particle.active) {
					initParticle(particle, i);
					particle.active = true;
					particle.lifeTime = 0;
					particle.position = birthPos;
					particle.emitterIndex = i;
					applyAge(particle, ageMs);
					particleFound = true;
					emitted = true;
					break;
				}
			}

			if (!particleFound && !particles.empty()) {
				size_t oldestIndex = 0;
				float maxLifeRatio = 0;

				for (size_t j = 0; j < particles.size(); ++j) {
					if (particles[j].lifeTime > maxLifeRatio) {
						maxLifeRatio = particles[j].lifeTime;
						oldestIndex = j;
					}
				}

				initParticle(particles[oldestIndex], i);
				particles[oldestIndex].active = true;
				particles[oldestIndex].lifeTime = 0;
				particles[oldestIndex].position = birthPos;
				particles[oldestIndex].emitterIndex = i;
				applyAge(particles[oldestIndex], ageMs);
				emitted = true;
			}
		}

		if (emitted) {
			drawDataDirty = true;
		}
	}

	void SparkEmitter::setEmissionPoints(const std::vector<Vector3f>& positions) {
		prevEmissionPoints = emissionPoints;
		emissionPoints = positions;
		drawDataDirty = true;
	}

	void SparkEmitter::resetEmissionPoints(const std::vector<Vector3f>& positions)
	{
		prevEmissionPoints.clear();
		emissionPoints = positions;
		drawDataDirty = true;
	}

	void SparkEmitter::initParticle(SparkParticle& particle, int emitterIndex) {
		if (!configured) {
			throw std::runtime_error("Failed to load spark emitter config file 5!");
		}

		particle.velocity = getRandomVelocity(emitterIndex);
		static std::random_device rd;
		static std::mt19937 gen(rd());
		std::uniform_real_distribution<float> scaleDist(scaleRange.min, scaleRange.max);
		particle.scale = scaleDist(gen);

		std::uniform_real_distribution<float> lifeDist(lifeTimeRange.min, lifeTimeRange.max);
		particle.maxLifeTime = lifeDist(gen);
		particle.emitterIndex = emitterIndex;
	}

	Vector3f SparkEmitter::getRandomVelocity(int emitterIndex) {
		if (!configured) {
			throw std::runtime_error("Failed to load spark emitter config file 6!");
		}

		static std::random_device rd;
		static std::mt19937 gen(rd());
		std::uniform_real_distribution<float> angleDist(0.0f, 2.0f * static_cast<float>(M_PI));
		std::uniform_real_distribution<float> speedDist(speedRange.min, speedRange.max);
		std::uniform_real_distribution<float> zSpeedDist(zSpeedRange.min, zSpeedRange.max);

		float angle = angleDist(gen);
		float speed = speedDist(gen);
		float zSpeed = zSpeedDist(gen);

		// Теперь biasX берется из JSON
		if (emitterIndex == 0) {
			return Vector3f{
				cosf(angle) * speed - biasX,
				sinf(angle) * speed,
				zSpeed
			};
		}
		else {
			return Vector3f{
				cosf(angle) * speed + biasX,
				sinf(angle) * speed,
				zSpeed
			};
		}
	}

	const std::vector<SparkParticle>& SparkEmitter::getParticles() const {
		return particles;
	}

	size_t SparkEmitter::getActiveParticleCount() const {
		size_t count = 0;
		for (const auto& particle : particles) {
			if (particle.active) {
				count++;
			}
		}
		return count;
	}

	bool SparkEmitter::loadFromJsonFile(const std::string& path, Renderer& renderer, const std::string& zipFile) {
		std::cout << "Loading spark config from: " << path << std::endl;

		std::string content;
		try {
			if (zipFile.empty()) {
				content = readTextFile(path);
			}
			else {
				auto buf = readFileFromZIP(path, zipFile);
				if (buf.empty()) {
					std::cerr << "Failed to read JSON from zip: " << path << " in " << zipFile << std::endl;
					throw std::runtime_error("Failed to load spark emitter config file 7!");
				}
				content.assign(buf.begin(), buf.end());
			}
		}
		catch (const std::exception& e) {
			std::cerr << "Failed to open JSON file: " << path << " : " << e.what() << std::endl;
			throw std::runtime_error("Failed to load spark emitter config file 8!");
		}

		json j;
		try {
			j = json::parse(content);
			std::cout << "JSON parsed successfully" << std::endl;
		}
		catch (const std::exception& e) {
			std::cerr << "JSON parse error: " << e.what() << std::endl;
			throw std::runtime_error("Failed to load spark emitter config file 9!");
		}
		std::cout << "JSON content: " << j.dump(2) << std::endl;

		auto requireKey = [&](const std::string& key) {
			if (!j.contains(key)) {
				std::cerr << "Missing required key in spark JSON: " << key << std::endl;
				throw std::runtime_error("Failed to load spark emitter config file 9!");
			}
			};

		requireKey("emissionPoints");
		requireKey("texture");
		requireKey("speedRange");
		requireKey("zSpeedRange");
		requireKey("scaleRange");
		requireKey("lifeTimeRange");
		requireKey("emissionRate");
		requireKey("maxParticles");
		requireKey("particleSize");
		requireKey("biasX");
		requireKey("shaderProgramName");

		emissionRate = j["emissionRate"].get<float>();

		maxParticles = j["maxParticles"].get<int>();
		particles.resize(maxParticles);
		drawPositions.reserve(maxParticles * 6);
		drawTexCoords.reserve(maxParticles * 6);
		std::cout << "Max particles: " << maxParticles << std::endl;

		particleSize = j["particleSize"].get<float>();
		std::cout << "Particle size: " << particleSize << std::endl;

		biasX = j["biasX"].get<float>();
		std::cout << "Bias X: " << biasX << std::endl;

		// emissionPoints
		std::vector<Vector3f> points;
		if (j.contains("emissionPoints") && j["emissionPoints"].is_array()) {
			for (const auto& el : j["emissionPoints"]) {
				if (el.contains("position") && el["position"].is_array()) {
					auto arr = el["position"];
					points.push_back(Vector3f{ arr[0].get<float>(), arr[1].get<float>(), arr[2].get<float>() });
					std::cout << "Fixed point: [" << arr[0] << ", " << arr[1] << ", " << arr[2] << "]" << std::endl;
				}
				else if (el.contains("positionRange") && el["positionRange"].is_object()) {
					auto pr = el["positionRange"];
					auto minArr = pr["min"];
					auto maxArr = pr["max"];
					int count = 1;
					if (el.contains("count")) count = el["count"].get<int>();

					std::random_device rd;
					std::mt19937 gen(rd());
					std::uniform_real_distribution<float> dx(minArr[0].get<float>(), maxArr[0].get<float>());
					std::uniform_real_distribution<float> dy(minArr[1].get<float>(), maxArr[1].get<float>());
					std::uniform_real_distribution<float> dz(minArr[2].get<float>(), maxArr[2].get<float>());

					for (int k = 0; k < count; ++k) {
						Vector3f randomPoint{ dx(gen), dy(gen), dz(gen) };
						points.push_back(randomPoint);
						std::cout << "Random point " << k + 1 << ": [" << randomPoint(0)
							<< ", " << randomPoint(1) << ", " << randomPoint(2) << "]" << std::endl;
					}
				}
			}
			if (!points.empty()) {
				setEmissionPoints(points);
				std::cout << "Total emission points: " << emissionPoints.size() << std::endl;
			}
			else {
				setEmissionPoints({});
				std::cout << "Emission points parsed but empty" << std::endl;
				//throw std::runtime_error("Failed to load spark emitter config file 10!");
			}
		}
		else {
			std::cerr << "emissionPoints is not array" << std::endl;
			throw std::runtime_error("Failed to load spark emitter config file 11!");
		}

		// Ranges
		if (j.contains("speedRange") && j["speedRange"].is_array()) {
			auto a = j["speedRange"];
			speedRange.min = a[0].get<float>();
			speedRange.max = a[1].get<float>();
			std::cout << "Speed range: [" << speedRange.min << ", " << speedRange.max << "]" << std::endl;
		}
		else {
			std::cerr << "speedRange missing or invalid" << std::endl;
			throw std::runtime_error("Failed to load spark emitter config file 12!");
		}

		if (j.contains("zSpeedRange") && j["zSpeedRange"].is_array()) {
			auto a = j["zSpeedRange"];
			zSpeedRange.min = a[0].get<float>();
			zSpeedRange.max = a[1].get<float>();
			std::cout << "Z speed range: [" << zSpeedRange.min << ", " << zSpeedRange.max << "]" << std::endl;
		}
		else {
			std::cerr << "zSpeedRange missing or invalid" << std::endl;
			throw std::runtime_error("Failed to load spark emitter config file 13!");
		}

		if (j.contains("scaleRange") && j["scaleRange"].is_array()) {
			auto a = j["scaleRange"];
			scaleRange.min = a[0].get<float>();
			scaleRange.max = a[1].get<float>();
			std::cout << "Scale range: [" << scaleRange.min << ", " << scaleRange.max << "]" << std::endl;
		}
		else {
			std::cerr << "scaleRange missing or invalid" << std::endl;
			throw std::runtime_error("Failed to load spark emitter config file 14!");
		}

		if (j.contains("lifeTimeRange") && j["lifeTimeRange"].is_array()) {
			auto a = j["lifeTimeRange"];
			lifeTimeRange.min = a[0].get<float>();
			lifeTimeRange.max = a[1].get<float>();
			std::cout << "Life time range: [" << lifeTimeRange.min << ", " << lifeTimeRange.max << "]" << std::endl;
		}
		else {
			std::cerr << "lifeTimeRange missing or invalid" << std::endl;
			throw std::runtime_error("Failed to load spark emitter config file 15!");
		}

		// texture
		if (j.contains("texture") && j["texture"].is_string()) {
			std::string texPath = j["texture"].get<std::string>();
			std::cout << "Loading texture: " << texPath << " From zip file: " << zipFile << std::endl;

			try {
				std::cout << "Loading texture step 1" << std::endl;
				auto texData = CreateTextureDataFromPng(texPath.c_str(), zipFile.c_str());
				std::cout << "Loading texture step 2" << std::endl; 
				texture = std::make_shared<Texture>(texData);
				std::cout << "Texture loaded successfully, ID: " << texture->getTexID() << std::endl;
			}
			catch (const std::exception& e) {
				std::cout << "Texture load error: " << e.what() << std::endl;
				std::cerr << "Texture load error: " << e.what() << std::endl;
				throw std::runtime_error("Failed to load spark emitter config file 16!");
			}
		}
		else {
			std::cerr << "No texture specified or invalid type in JSON" << std::endl;
			throw std::runtime_error("Failed to load spark emitter config file 17!");
		}

		std::cout << "Working with shaders 1" << std::endl;
		// shaders
		if (j.contains("shaderProgramName") && j["shaderProgramName"].is_string()) {
			shaderProgramName = j["shaderProgramName"].get<std::string>();
			std::cout << "Shader program name: " << shaderProgramName << std::endl;
		}
		else {
			std::cerr << "shaderProgramName missing or invalid" << std::endl;
			throw std::runtime_error("Failed to load spark emitter config file 18!");
		}

		drawDataDirty = true;
		configured = true;
		std::cout << "SparkEmitter configuration loaded successfully!" << std::endl;
		return true;
	}

} // namespace ZL