/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#include "ImageLoaderKtx.hpp"
#include "Base/Logger.hpp"

#if __has_include("ktx.h")
#include <ktx.h>

#include <memory>
#include <vector>
#include <iostream>
#include <stdexcept>

namespace OpenVulkano::Image
{
	namespace
	{
		struct KTXTextureDeleter {
			void operator()(ktxTexture* texture) const {
				if (texture) {
					ktxTexture_Destroy(texture);
				}
			}
		};

		std::unique_ptr<Image> ExtractImage(ktxTexture* texture)
		{
			if (!texture->pData)
			{
				throw std::runtime_error("No image data found in KTX texture.");
			}

			auto width = static_cast<int>(texture->baseWidth);
			auto height = static_cast<int>(texture->baseHeight);

			auto image = std::make_unique<Image>();
			image->resolution.x = width;
			image->resolution.y = height;
			image->resolution.z = 1;

			if (texture->classId == ktxTexture1_c)
			{
				ktxTexture1* tx = reinterpret_cast<ktxTexture1*>(texture);
				DataFormat format = DataFormat::Format::UNDEFINED;
				switch (tx->glInternalformat)
				{
					case 0x8051: /* GL_RGB8_EXT */     format = DataFormat::Format::R8G8B8_UNORM; break;
					case 0x8054: /* GL_RGB16_EXT */    format = DataFormat::Format::R16G16B16_UNORM; break;
					case 0x8057: /* GL_RGB5_A1_EXT */  format = DataFormat::Format::R5G5B5A1_UNORM_PACK16; break;
					case 0x8058: /* GL_RGBA8_EXT */    format = DataFormat::Format::R8G8B8A8_UNORM; break;
					case 0x8059: /* GL_RGB10_A2_EXT */ format = DataFormat::Format::A2R10G10B10_UNORM_PACK32; break;
					case 0x805B: /* GL_RGBA16_EXT */   format = DataFormat::Format::R16G16B16A16_UNORM; break;
					default: throw std::runtime_error("Unhandled texture1 format: " + tx->glInternalformat);
				}
				image->dataFormat = format;
			}
			else if (texture->classId == ktxTexture2_c)
			{
				ktxTexture2* tx = reinterpret_cast<ktxTexture2*>(texture);
				image->dataFormat = reinterpret_cast<DataFormat::Format&>(tx->vkFormat);
			}
			else [[unlikely]]
			{
				throw std::runtime_error("ktxTexture has unhandled classId!");
			}

			image->data = Array<uint8_t>(texture->dataSize);
			memcpy(image->data.Data(), texture->pData, image->data.Size());

			return image;
		}
	}

	std::unique_ptr<Image> ImageLoaderKtx::loadFromFile(const std::string& filePath)
	{
		ktxTexture* tmp = nullptr;
		KTX_error_code error_code =
		ktxTexture_CreateFromNamedFile(filePath.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &tmp);
		std::unique_ptr<ktxTexture, KTXTextureDeleter> texture(tmp);

		if (error_code != KTX_SUCCESS)
		{
			throw std::runtime_error("Failed to load KTX texture: " + std::string(ktxErrorString(error_code)));
		}

		auto result = ExtractImage(texture.get());
		return result;
	}

	std::unique_ptr<Image> ImageLoaderKtx::loadFromMemory(const std::vector<uint8_t>& buffer)
	{
		ktxTexture* tmp = nullptr;
		KTX_error_code error_code =
		ktxTexture_CreateFromMemory(buffer.data(), buffer.size(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &tmp);
		std::unique_ptr<ktxTexture, KTXTextureDeleter> texture(tmp);

		if (error_code != KTX_SUCCESS)
		{
			throw std::runtime_error("Failed to load KTX texture from memory: " + std::string(ktxErrorString(error_code)));
		}

		auto result = ExtractImage(texture.get());
		return result;
	}

	Math::Vector2i ImageLoaderKtx::GetImageDimensions(const std::string& filename)
	{
		ktxTexture* tmp = nullptr;
		KTX_error_code result =
		ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &tmp);
		std::unique_ptr<ktxTexture, KTXTextureDeleter> texture(tmp);

		if (result != KTX_SUCCESS)
		{
			throw std::runtime_error("Failed to load KTX texture for dimensions: "
			                         + std::string(ktxErrorString(result)));
		}

		Math::Vector2i dimensions(texture->baseWidth, texture->baseHeight);
		return dimensions;
	}
}
#else
namespace OpenVulkano::Image
{
	std::unique_ptr<Image> ImageLoaderKtx::loadFromFile(const std::string& filePath)
	{
		Logger::DATA->error("KTX loading not available.");
		return nullptr;
	}

	std::unique_ptr<Image> ImageLoaderKtx::loadFromMemory(const std::vector<uint8_t>& buffer)
	{
		Logger::DATA->error("KTX loading not available.");
		return nullptr;
	}

	Math::Vector2i ImageLoaderKtx::GetImageDimensions(const std::string& filename)
	{
		Logger::DATA->error("KTX loading not available.");
		return {0,0};
	}
}
#endif