#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"

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 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() {
		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;
				if (useWorldSpace) {
					Vector3f rel = particle.position - Environment::shipState.position;
					posCam = Environment::inverseShipMatrix * rel;
				}
				else {
					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;
			if (useWorldSpace) {
				Vector3f rel = particle.position - Environment::shipState.position;
				posCam = Environment::inverseShipMatrix * rel;
			}
			else {
				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::draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight) {
		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!");
		}

		prepareDrawData();

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

		sparkQuad.data.PositionData = drawPositions;
		sparkQuad.data.TexCoordData = drawTexCoords;
		sparkQuad.RefreshVBO();

		static const std::string vPositionName = "vPosition";
		static const std::string vTexCoordName = "vTexCoord";
		static const std::string textureUniformName = "Texture";

		renderer.shaderManager.PushShader(shaderProgramName);
		renderer.RenderUniform1i(textureUniformName, 0);
		renderer.EnableVertexAttribArray(vPositionName);
		renderer.EnableVertexAttribArray(vTexCoordName);

		//float aspectRatio = static_cast<float>(screenWidth) / static_cast<float>(screenHeight);
		//renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, aspectRatio, Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);

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

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

		renderer.PushMatrix();
		//renderer.LoadIdentity();
		//renderer.TranslateMatrix({ 0, 0, -1.0f * zoom });

		renderer.DrawVertexRenderStruct(sparkQuad);

		renderer.PopMatrix();
		//renderer.PopProjectionMatrix();

		glDisable(GL_BLEND);

		renderer.DisableVertexAttribArray(vPositionName);
		renderer.DisableVertexAttribArray(vTexCoordName);
		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 >= emissionRate) {
			emit();
			lastEmissionTime = currentTime;
			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() {
		if (!configured) {
			throw std::runtime_error("Failed to load spark emitter config file 4!");
		}

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

		for (int i = 0; i < emissionPoints.size(); ++i) {
			bool particleFound = false;

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

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

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

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

		if (emitted) {
			drawDataDirty = true;
		}
	}

	void SparkEmitter::setEmissionPoints(const std::vector<Vector3f>& positions) {
		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 {
				std::cerr << "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!");
		}


		std::cout << "Working with shaders 2" << std::endl;
		/*
		if (j.contains("vertexShader") && j.contains("fragmentShader")
			&& j["vertexShader"].is_string() && j["fragmentShader"].is_string()) {
			std::string v = j["vertexShader"].get<std::string>();
			std::string f = j["fragmentShader"].get<std::string>();
			std::cout << "Loading shaders - vertex: " << v << ", fragment: " << f << std::endl;

			try {
				renderer.shaderManager.AddShaderFromFiles(shaderProgramName, v, f, zipFile.c_str());
				std::cout << "Shaders loaded successfully" << std::endl;
			}
			catch (const std::exception& e) {
				std::cerr << "Shader load error: " << e.what() << std::endl;
				throw std::runtime_error("Failed to load spark emitter config file 19!");
			}
		}*/

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

} // namespace ZL