#include "render/TextureManager.h"
#include "render/OpenGlExtensions.h"
#ifdef PNG_ENABLED
#include "png.h"
#endif
#include <iostream>

namespace ZL
{

	Texture::Texture(const TextureDataStruct& texData) {
		width = texData.width;
		height = texData.height;
		glGenTextures(1, &texID);
		glBindTexture(GL_TEXTURE_2D, texID);

		GLint internalFormat;
		GLenum externalFormat;

		switch (texData.format) {
		case TextureDataStruct::R8:
			// Для WebGL 1.0 лучше использовать GL_LUMINANCE вместо GL_RED
			// Если используешь WebGL 2.0, можно оставить GL_RED
#if defined(EMSCRIPTEN) || defined(__ANDROID__)
			internalFormat = GL_LUMINANCE;
			externalFormat = GL_LUMINANCE;
#else
			internalFormat = GL_RED;
			externalFormat = GL_RED;
#endif
			glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
			// Исправлено: используем GL_TEXTURE_2D
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
			break;

		case TextureDataStruct::RGB:
			internalFormat = GL_RGB;
			externalFormat = GL_RGB;
			glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // RGB может быть не выровнен по 4 байта
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
			break;

		default: // RGBA
			internalFormat = GL_RGBA;
			externalFormat = GL_RGBA;
			glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
			break;
		}

		glTexImage2D(GL_TEXTURE_2D, 0, internalFormat,
			static_cast<GLsizei>(width), static_cast<GLsizei>(height),
			0, externalFormat, GL_UNSIGNED_BYTE, texData.data.data());
		CheckGlError();

		// 3. Фильтрация и Мип-мапы
		// ВНИМАНИЕ: Для шрифтов (NPOT) в WebGL glGenerateMipmap работать НЕ будет!
		if (texData.mipmap == TextureDataStruct::GENERATE && texData.format != TextureDataStruct::R8) {
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
			glGenerateMipmap(GL_TEXTURE_2D);
		}
		else {
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		}
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

		CheckGlError();
		glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
	}

	Texture::Texture(const std::array<TextureDataStruct, 6>& texDataArray)
	{
		width = texDataArray[0].width;
		height = texDataArray[0].height;

		for (size_t i = 1; i < 6; ++i) {
			if (texDataArray[i].width != width || texDataArray[i].height != height) {
				throw std::runtime_error("Cubemap faces must have the same dimensions");
			}
		}

		glGenTextures(1, &texID);

		if (texID == 0)
		{
			throw std::runtime_error("glGenTextures did not work for cubemap");
		}

		glBindTexture(GL_TEXTURE_CUBE_MAP, texID);

		CheckGlError();


		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

#ifndef EMSCRIPTEN
#ifndef __ANDROID__
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
#endif
#endif

		CheckGlError(); 
		
		for (int i = 0; i < 6; ++i)
		{
			GLint internalFormat;
			GLenum format;


			if (texDataArray[i].format == TextureDataStruct::RGB)
			{
				internalFormat = GL_RGB; // internalFormat
				format = GL_RGB;        // format
			}
			else // BS_32BIT
			{
				internalFormat = GL_RGBA; // internalFormat
				format = GL_RGBA;         // format
			}

			glTexImage2D(
				GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 
				0,                            
				internalFormat,                     
				static_cast<GLsizei>(width),
				static_cast<GLsizei>(height),
				0,                           
				format,                           
				GL_UNSIGNED_BYTE,                 
				texDataArray[i].data.data()  
			);
			CheckGlError();
		}

		glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
	}

	Texture::~Texture()
	{
		glDeleteTextures(1, &texID);
		texID = 0;
	}

	GLuint Texture::getTexID()
	{
		return texID;
	}

	size_t Texture::getWidth()
	{
		return width;
	}

	size_t Texture::getHeight()
	{
		return height;
	}



	TextureDataStruct CreateTextureDataFromBmp24(const std::string& fullFileName, const std::string& ZIPFileName)
	{

		TextureDataStruct texData;
		std::vector<char> fileArr;

		fileArr = !ZIPFileName.empty() ? readFileFromZIP(fullFileName, ZIPFileName) : readFile(fullFileName);

		size_t fileSize = fileArr.size();

		if (fileSize < 22)
		{
			throw std::runtime_error("File is too short or not correct!");
		}

		//This refers to BITMAPV5HEADER
		texData.width = *reinterpret_cast<uint32_t*>(&fileArr[18]);
		texData.height = *reinterpret_cast<uint32_t*>(&fileArr[22]);

		texData.format = TextureDataStruct::RGB;

		size_t dataSize = texData.width * texData.height  * 3;

		texData.data.resize(dataSize);

		size_t pos = *reinterpret_cast<uint32_t*>(&fileArr[10]);
		size_t x = 0;

		for (size_t i = 0; i < texData.width; i++)
			for (size_t j = 0; j < texData.height; j++)
			{

				if (pos + 3 > fileSize)
				{
					throw std::runtime_error("File is too short!");
				}
					

				x = (i * texData.height + j) + (i * texData.height + j) + (i * texData.height + j);

				texData.data[x + 2] = fileArr[pos++];
				texData.data[x + 1] = fileArr[pos++];
				texData.data[x + 0] = fileArr[pos++];

			}

		return texData;
	}

	TextureDataStruct CreateTextureDataFromBmp32(const std::string& fullFileName, const std::string& ZIPFileName)
	{

		TextureDataStruct texData;
		std::vector<char> fileArr;
		
		fileArr = !ZIPFileName.empty() ? readFileFromZIP(fullFileName, ZIPFileName) : readFile(fullFileName);

		size_t fileSize = fileArr.size();

		if (fileSize < 22)
		{
			throw std::runtime_error("File is too short or not correct!");
		}

		//This refers to BITMAPV5HEADER
		texData.width = *reinterpret_cast<uint32_t*>(&fileArr[18]);
		texData.height = *reinterpret_cast<uint32_t*>(&fileArr[22]);

		texData.format = TextureDataStruct::RGBA;

		size_t dataSize = texData.width * texData.height * 4;

		texData.data.resize(dataSize);

		size_t pos = *reinterpret_cast<uint32_t*>(&fileArr[10]);
		size_t x = 0;

		for (size_t i = 0; i < texData.width; i++)
			for (size_t j = 0; j < texData.height; j++)
			{

				if (pos + 4 > fileSize)
				{
					throw std::runtime_error("File is too short!");
				}


				x = (i * texData.height + j) + (i * texData.height + j) + (i * texData.height + j) + (i * texData.height + j);

				texData.data[x + 2] = fileArr[pos++];
				texData.data[x + 1] = fileArr[pos++];
				texData.data[x + 0] = fileArr[pos++];
				texData.data[x + 3] = fileArr[pos++];

			}

		return texData;
	}

#ifdef PNG_ENABLED

	struct png_data_t {
		const char* data;
		size_t size;
		size_t offset;
	};

	void user_read_data(png_structp png_ptr, png_bytep out_ptr, png_size_t bytes_to_read) {
		png_data_t* data = (png_data_t*)png_get_io_ptr(png_ptr);

		if (data->offset + bytes_to_read > data->size) {
			png_error(png_ptr, "PNG Read Error: Attempted to read past end of data buffer.");
			bytes_to_read = data->size - data->offset; 
		}

		std::memcpy(out_ptr, data->data + data->offset, bytes_to_read);

		data->offset += bytes_to_read;
	}

	void user_warning_fn(png_structp png_ptr, png_const_charp warning_msg) {
		std::cout << "PNG Warning: " << warning_msg << std::endl;
	}


	void user_error_fn(png_structp png_ptr, png_const_charp error_msg) {

		std::cout << "PNG Error: " << error_msg << std::endl;
		longjmp(png_jmpbuf(png_ptr), 1);
	}

	TextureDataStruct CreateTextureDataFromPng(const std::vector<char>& fileArr)
	{
		TextureDataStruct texData;

		png_data_t png_data = { fileArr.data(), fileArr.size(), 0 };

		png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
		if (!png) {
			throw std::runtime_error("Could not create PNG read structure");
		}

		png_infop info = png_create_info_struct(png);
		if (!info) {
			png_destroy_read_struct(&png, nullptr, nullptr);
			throw std::runtime_error("Could not create PNG info structure");
		}

		if (setjmp(png_jmpbuf(png))) {
			png_destroy_read_struct(&png, &info, nullptr);
			throw std::runtime_error("Error during PNG read (longjmp was executed)");
		}

		png_set_error_fn(png, nullptr, user_error_fn, user_warning_fn);

		png_set_read_fn(png, &png_data, user_read_data);
		
		png_read_info(png, info);

		texData.width = png_get_image_width(png, info);
		texData.height = png_get_image_height(png, info);
		png_byte color_type = png_get_color_type(png, info);
		png_byte bit_depth = png_get_bit_depth(png, info);

		if (bit_depth == 16)
			png_set_strip_16(png);

		if (color_type == PNG_COLOR_TYPE_PALETTE)
			png_set_palette_to_rgb(png);

		if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
			png_set_expand_gray_1_2_4_to_8(png);

		if (png_get_valid(png, info, PNG_INFO_tRNS))
			png_set_tRNS_to_alpha(png);

		if (color_type == PNG_COLOR_TYPE_RGB ||
			color_type == PNG_COLOR_TYPE_GRAY ||
			color_type == PNG_COLOR_TYPE_PALETTE)
			png_set_filler(png, 0xFF, PNG_FILLER_AFTER);

		if (color_type == PNG_COLOR_TYPE_GRAY ||
			color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
			png_set_gray_to_rgb(png);

		png_read_update_info(png, info);
		
		png_bytep* row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * texData.height);
		for (int y = 0; y < texData.height; y++) {
			row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info));
		}

		png_read_image(png, row_pointers);

		bool has_alpha = (color_type & PNG_COLOR_MASK_ALPHA) || (png_get_valid(png, info, PNG_INFO_tRNS));

		size_t dataSize;

		if (has_alpha)
		{
			texData.format = TextureDataStruct::RGBA;
		}
		else
		{
			texData.format = TextureDataStruct::RGB;
		}

		int channels = has_alpha ? 4 : 3;

		dataSize = texData.width * texData.height * channels;
		texData.data.resize(dataSize);


		for (int y = texData.height - 1; y >= 0; y--) {
			png_bytep row = row_pointers[texData.height - 1 - y];
			for (int x = 0; x < texData.width; x++) {
				png_bytep px = &(row[x * 4]);
				texData.data[(y * texData.width + x) * channels + 0] = px[0]; // R
				texData.data[(y * texData.width + x) * channels + 1] = px[1]; // G
				texData.data[(y * texData.width + x) * channels + 2] = px[2]; // B
				if (has_alpha) {
					texData.data[(y * texData.width + x) * channels + 3] = px[3]; // A
				}
			}
			free(row_pointers[texData.height - 1 - y]);
		}
		free(row_pointers);
		// ==================================================

		png_destroy_read_struct(&png, &info, nullptr);

		return texData;
	}


	TextureDataStruct CreateTextureDataFromPng(const std::string& fullFileName, const std::string& ZIPFileName)
	{
		std::vector<char> fileArr;

		fileArr = !ZIPFileName.empty() ? readFileFromZIP(fullFileName, ZIPFileName) : readFile(fullFileName);

		if (fileArr.empty()) {
			throw std::runtime_error("Could not read file data into memory");
		}

		return CreateTextureDataFromPng(fileArr);
	}


#endif


}