#include "TextureLoaderPvr.h"
#include "../../Main.h"

#include <Base/Memory.h>

using namespace Death::IO;
using namespace Death::Memory;

namespace nCine
{
	TextureLoaderPvr::TextureLoaderPvr(std::unique_ptr<Stream> fileHandle)
		: ITextureLoader(std::move(fileHandle))
	{
		Pvr3Header header;

		if (!fileHandle_->IsValid()) {
			return;
		}

		const bool headerRead = readHeader(header);
		DEATH_ASSERT(headerRead, "PVR header cannot be read", );
		const bool formatParsed = parseFormat(header);
		DEATH_ASSERT(formatParsed, "PVR format cannot be parsed", );

		hasLoaded_ = true;
	}

	bool TextureLoaderPvr::readHeader(Pvr3Header& header)
	{
		// PVR3 header is 52 bytes long
		fileHandle_->Read(&header, 52);

		// Checking for the header presence ("PVR"03)
		DEATH_ASSERT(AsLE(header.version) == 0x03525650, "Invalid PVR3 signature", false);

		headerSize_ = 52 + AsLE(header.metaDataSize);
		width_ = AsLE(header.width);
		height_ = AsLE(header.height);
		mipMapCount_ = header.numMipmaps;

		if (mipMapCount_ == 0) {
			mipMapCount_ = 1;
		}
		return true;
	}

	bool TextureLoaderPvr::parseFormat(const Pvr3Header& header)
	{
		GLenum internalFormat = GL_RGB8; // to suppress uninitialized variable warning

		uint64_t pixelFormat = AsLE(header.pixelFormat);

		// Texture contains compressed data, most significant 4 bytes have been set to zero
		if (pixelFormat < 0x0000000100000000ULL) {
			LOGI("Compressed format: {}", pixelFormat);

			// Parsing the pixel format
			switch (pixelFormat) {
				case FMT_DXT1:
					internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
					break;
				case FMT_DXT3:
					internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
					break;
				case FMT_DXT5:
					internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
					break;
#if defined(WITH_OPENGLES)
				case FMT_ETC1:
					internalFormat = GL_ETC1_RGB8_OES;
					break;
				case FMT_PVRTC_2BPP_RGB:
					internalFormat = GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
					break;
				case FMT_PVRTC_2BPP_RGBA:
					internalFormat = GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
					break;
				case FMT_PVRTC_4BPP_RGB:
					internalFormat = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
					break;
				case FMT_PVRTC_4BPP_RGBA:
					internalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
					break;
				case FMT_PVRTCII_2BPP:
				case FMT_PVRTCII_4BPP:
					LOGF("No support for PVRTC-II compression");
					break;
				case FMT_ETC2_RGB:
					internalFormat = GL_COMPRESSED_RGB8_ETC2;
					break;
				case FMT_ETC2_RGBA:
					internalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC;
					break;
				case FMT_ETC2_RGB_A1:
					internalFormat = GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2;
					break;
				case FMT_EAC_R11:
					internalFormat = GL_COMPRESSED_R11_EAC;
					break;
				case FMT_EAC_RG11:
					internalFormat = GL_COMPRESSED_RG11_EAC;
					break;
#	if (!defined(DEATH_TARGET_ANDROID) && defined(WITH_OPENGLES)) || (defined(DEATH_TARGET_ANDROID) && __ANDROID_API__ >= 21)
				case FMT_ASTC_4x4:
					internalFormat = GL_COMPRESSED_RGBA_ASTC_4x4_KHR;
					break;
				case FMT_ASTC_5x4:
					internalFormat = GL_COMPRESSED_RGBA_ASTC_5x4_KHR;
					break;
				case FMT_ASTC_5x5:
					internalFormat = GL_COMPRESSED_RGBA_ASTC_5x5_KHR;
					break;
				case FMT_ASTC_6x5:
					internalFormat = GL_COMPRESSED_RGBA_ASTC_6x5_KHR;
					break;
				case FMT_ASTC_6x6:
					internalFormat = GL_COMPRESSED_RGBA_ASTC_6x6_KHR;
					break;
				case FMT_ASTC_8x5:
					internalFormat = GL_COMPRESSED_RGBA_ASTC_8x5_KHR;
					break;
				case FMT_ASTC_8x6:
					internalFormat = GL_COMPRESSED_RGBA_ASTC_8x6_KHR;
					break;
				case FMT_ASTC_8x8:
					internalFormat = GL_COMPRESSED_RGBA_ASTC_8x8_KHR;
					break;
				case FMT_ASTC_10x5:
					internalFormat = GL_COMPRESSED_RGBA_ASTC_10x5_KHR;
					break;
				case FMT_ASTC_10x6:
					internalFormat = GL_COMPRESSED_RGBA_ASTC_10x6_KHR;
					break;
				case FMT_ASTC_10x8:
					internalFormat = GL_COMPRESSED_RGBA_ASTC_10x8_KHR;
					break;
				case FMT_ASTC_10x10:
					internalFormat = GL_COMPRESSED_RGBA_ASTC_10x10_KHR;
					break;
				case FMT_ASTC_12x10:
					internalFormat = GL_COMPRESSED_RGBA_ASTC_12x10_KHR;
					break;
				case FMT_ASTC_12x12:
					internalFormat = GL_COMPRESSED_RGBA_ASTC_12x12_KHR;
					break;
#	endif
#endif
				default:
					LOGE("Unsupported PVR3 compressed format: 0x{:x}", pixelFormat);
					return false;
			}

			loadPixels(internalFormat);
		} else {
			// Texture contains uncompressed data
			GLenum type = GL_UNSIGNED_BYTE;

			LOGI("Uncompressed format: {:c}{:c}{:c}{:c} ({}, {}, {}, {})",
				   reinterpret_cast<char*>(&pixelFormat)[0], reinterpret_cast<char*>(&pixelFormat)[1],
				   reinterpret_cast<char*>(&pixelFormat)[2], reinterpret_cast<char*>(&pixelFormat)[3],
				   reinterpret_cast<unsigned char*>(&pixelFormat)[4], reinterpret_cast<unsigned char*>(&pixelFormat)[5],
				   reinterpret_cast<unsigned char*>(&pixelFormat)[6], reinterpret_cast<unsigned char*>(&pixelFormat)[7]);

			switch (pixelFormat) {
				case FMT_BGRA_8888:
#if !defined(WITH_OPENGLES)
					internalFormat = GL_BGRA;
#else
					internalFormat = GL_BGRA_EXT;
#endif
					break;
				case FMT_RGBA_8888:
					internalFormat = GL_RGBA8;
					break;
				case FMT_RGB_888:
					internalFormat = GL_RGB8;
					break;
				case FMT_RGB_565:
					internalFormat = GL_RGB565;
					type = GL_UNSIGNED_SHORT_5_6_5;
					break;
				case FMT_RGBA_5551:
					internalFormat = GL_RGB5_A1;
					type = GL_UNSIGNED_SHORT_5_5_5_1;
					break;
				case FMT_RGBA_4444:
					internalFormat = GL_RGBA4;
					type = GL_UNSIGNED_SHORT_4_4_4_4;
					break;
				case FMT_LA_88:
					internalFormat = GL_RG8;
					break;
				case FMT_L_8:
					internalFormat = GL_R8;
					break;
				case FMT_A_8:
					internalFormat = GL_R8;
					break;
				default:
					LOGE("Unsupported PVR3 uncompressed format: 0x{:x}", pixelFormat);
					return false;
			}

			loadPixels(internalFormat, type);
		}

		if (mipMapCount_ > 1) {
			LOGI("MIP Maps: {}", mipMapCount_);
			mipDataOffsets_ = std::make_unique<std::uint32_t[]>(mipMapCount_);
			mipDataSizes_ = std::make_unique<std::uint32_t[]>(mipMapCount_);
			std::uint32_t dataSizesSum = TextureFormat::calculateMipSizes(internalFormat, width_, height_, mipMapCount_, mipDataOffsets_.get(), mipDataSizes_.get());
			if (dataSizesSum != dataSize_) {
				LOGW("The sum of MIP maps size ({}) is different than texture total data ({})", dataSizesSum, dataSize_);
			}
		}

		return true;
	}
}
