#include "TextureFormat.h"
#include "../ServiceLocator.h"
#include "../../Main.h"

#if defined(DEATH_TARGET_ANDROID)
#	include <android/api-level.h>
#endif

namespace nCine
{
	TextureFormat::TextureFormat(GLenum internalFormat)
		: internalFormat_(internalFormat), format_(-1), type_(-1), isCompressed_(false)
	{
		findExternalFormat();
		checkFormatSupport();
	}

	TextureFormat::TextureFormat(GLenum internalFormat, GLenum type)
		: internalFormat_(internalFormat), format_(-1), type_(-1), isCompressed_(false)
	{
		findExternalFormat();
		checkFormatSupport();
		// Overriding the type found by `findExternalFormat()`
		type_ = type;
	}

	std::uint32_t TextureFormat::numChannels() const
	{
		switch (format_) {
			case GL_RED:
				return 1;
			case GL_RG:
				return 2;
			case GL_RGB:
				return 3;
			case GL_RGBA:
#if !defined(WITH_OPENGLES)
			case GL_BGRA:
#else
			case GL_BGRA_EXT:
#endif
			default:
				return 4;
		}
	}

	void TextureFormat::bgrFormat()
	{
		if (format_ == GL_RGBA) {
#if !defined(WITH_OPENGLES)
			format_ = GL_BGRA;
#else
			format_ = GL_BGRA_EXT;
#endif
		}
#if !defined(WITH_OPENGLES)
		else if (format_ == GL_RGB) {
			format_ = GL_BGR;
		}
#endif
	}

	std::uint32_t TextureFormat::calculateMipSizes(GLenum internalFormat, std::int32_t width, std::int32_t height, std::int32_t mipMapCount, std::uint32_t* mipDataOffsets, std::uint32_t* mipDataSizes)
	{
		std::uint32_t blockWidth = 1; // Compression block width in pixels
		std::uint32_t blockHeight = 1; // Compression block height in pixels
		std::uint32_t bpp = 1; // Bits per pixel
		std::uint32_t blockSize = 0; // Compression block size in byts
		std::uint32_t minDataSize = 1; // Minimum data size in bytes

		switch (internalFormat) {
			case GL_RGBA8:
				bpp = 32;
				break;
			case GL_RGB8:
				bpp = 24;
				break;
			case GL_RG8:
			case GL_RGB565:
			case GL_RGB5_A1:
			case GL_RGBA4:
				bpp = 16;
				break;
			case GL_R8:
				bpp = 8;
				break;
			case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
			case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
				// max(1, width / 4) x max(1, height / 4) x 8(DXT1)
				blockWidth = 4;
				blockHeight = 4;
				bpp = 4;
				minDataSize = 8;
				break;
			case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
			case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
				// max(1, width / 4) x max(1, height / 4) x 16(DXT2-5)
				blockWidth = 4;
				blockHeight = 4;
				bpp = 8;
				minDataSize = 16;
				break;
#if defined(WITH_OPENGLES)
			case GL_ETC1_RGB8_OES:
			case GL_COMPRESSED_RGB8_ETC2:
			case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
			case GL_COMPRESSED_R11_EAC:
				blockWidth = 4;
				blockHeight = 4;
				bpp = 4;
				minDataSize = 8;
				break;
			case GL_COMPRESSED_RGBA8_ETC2_EAC:
			case GL_COMPRESSED_RG11_EAC:
				blockWidth = 4;
				blockHeight = 4;
				bpp = 8;
				minDataSize = 16;
				break;
			case GL_ATC_RGBA_EXPLICIT_ALPHA_AMD:
			case GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD:
				// ((width_in_texels+3)/4) * ((height_in_texels+3)/4) * 16
				blockWidth = 4;
				blockHeight = 4;
				bpp = 8;
				minDataSize = 16;
				break;
			case GL_ATC_RGB_AMD:
				// ((width_in_texels+3)/4) * ((height_in_texels+3)/4) * 8
				blockWidth = 4;
				blockHeight = 4;
				bpp = 4;
				minDataSize = 8;
				break;
			case GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG:
			case GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG:
				blockWidth = 8;
				blockHeight = 4;
				bpp = 2;
				minDataSize = 2 * 2 * ((blockWidth * blockHeight * bpp) / 8);
				break;
			case GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
			case GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG:
				blockWidth = 4;
				blockHeight = 4;
				bpp = 4;
				minDataSize = 2 * 2 * ((blockWidth * blockHeight * bpp) / 8);
				break;
#	if (!defined(DEATH_TARGET_ANDROID) && defined(WITH_OPENGLES)) || (defined(DEATH_TARGET_ANDROID) && __ANDROID_API__ >= 21)
			case GL_COMPRESSED_RGBA_ASTC_4x4_KHR:
				blockWidth = 4;
				blockHeight = 4;
				blockSize = 16;
				minDataSize = 16;
				break;
			case GL_COMPRESSED_RGBA_ASTC_5x4_KHR:
				blockWidth = 5;
				blockHeight = 4;
				blockSize = 16;
				minDataSize = 16;
				break;
			case GL_COMPRESSED_RGBA_ASTC_5x5_KHR:
				blockWidth = 5;
				blockHeight = 5;
				blockSize = 16;
				minDataSize = 16;
				break;
			case GL_COMPRESSED_RGBA_ASTC_6x5_KHR:
				blockWidth = 6;
				blockHeight = 5;
				blockSize = 16;
				minDataSize = 16;
				break;
			case GL_COMPRESSED_RGBA_ASTC_6x6_KHR:
				blockWidth = 6;
				blockHeight = 6;
				blockSize = 16;
				minDataSize = 16;
				break;
			case GL_COMPRESSED_RGBA_ASTC_8x5_KHR:
				blockWidth = 8;
				blockHeight = 5;
				blockSize = 16;
				minDataSize = 16;
				break;
			case GL_COMPRESSED_RGBA_ASTC_8x6_KHR:
				blockWidth = 8;
				blockHeight = 6;
				blockSize = 16;
				minDataSize = 16;
				break;
			case GL_COMPRESSED_RGBA_ASTC_8x8_KHR:
				blockWidth = 8;
				blockHeight = 8;
				blockSize = 16;
				minDataSize = 16;
				break;
			case GL_COMPRESSED_RGBA_ASTC_10x5_KHR:
				blockWidth = 10;
				blockHeight = 5;
				blockSize = 16;
				minDataSize = 16;
				break;
			case GL_COMPRESSED_RGBA_ASTC_10x6_KHR:
				blockWidth = 10;
				blockHeight = 6;
				blockSize = 16;
				minDataSize = 16;
				break;
			case GL_COMPRESSED_RGBA_ASTC_10x8_KHR:
				blockWidth = 10;
				blockHeight = 8;
				blockSize = 16;
				minDataSize = 16;
				break;
			case GL_COMPRESSED_RGBA_ASTC_10x10_KHR:
				blockWidth = 10;
				blockHeight = 10;
				blockSize = 16;
				minDataSize = 16;
				break;
			case GL_COMPRESSED_RGBA_ASTC_12x10_KHR:
				blockWidth = 12;
				blockHeight = 10;
				blockSize = 16;
				minDataSize = 16;
				break;
			case GL_COMPRESSED_RGBA_ASTC_12x12_KHR:
				blockWidth = 12;
				blockHeight = 12;
				blockSize = 16;
				minDataSize = 16;
				break;
#	endif
#endif
			default:
				LOGF("MIP maps not supported for internal format: 0x{:x}", internalFormat);
				break;
		}

		std::int32_t levelWidth = width;
		std::int32_t levelHeight = height;
		std::uint32_t dataSizesSum = 0;

		DEATH_ASSERT(mipDataOffsets);
		DEATH_ASSERT(mipDataSizes);

		for (std::int32_t i = 0; i < mipMapCount; i++) {
			mipDataOffsets[i] = dataSizesSum;
			mipDataSizes[i] = (blockSize > 0)
				? (levelWidth / blockWidth) * (levelHeight / blockHeight) * blockSize
				: (levelWidth / blockWidth) * (levelHeight / blockHeight) * ((blockWidth * blockHeight * bpp) / 8);

			// Clamping to the minimum valid size
			if (mipDataSizes[i] < minDataSize) {
				mipDataSizes[i] = minDataSize;
			}

			levelWidth /= 2;
			levelHeight /= 2;
			dataSizesSum += mipDataSizes[i];
		}

		return dataSizesSum;
	}

	void TextureFormat::findExternalFormat()
	{
		bool found = false;

		if (!found)
			found = integerFormat();
		if (!found)
			found = nonIntegerFormat();
		if (!found)
			found = floatFormat();
		if (!found)
			found = compressedFormat();
#if defined(WITH_OPENGLES)
		if (!found)
			found = oesCompressedFormat();
#endif

		FATAL_ASSERT_MSG(found, "Unknown internal format: 0x{:x}", internalFormat_);

		//LOGD("Internal format: 0x{:x} - type: 0x{:x}", internalFormat_, type_);
	}

	bool TextureFormat::integerFormat()
	{
		bool found = true;

		switch (internalFormat_) {
			case GL_RGBA8:
			case 4:
				format_ = GL_RGBA;
				break;
			case GL_RGB8:
			case 3:
				format_ = GL_RGB;
				break;
			case GL_RG8:
			case 2:
				format_ = GL_RG;
				break;
			case GL_R8:
			case 1:
				format_ = GL_RED;
				break;
			case GL_DEPTH_COMPONENT16:
			case GL_DEPTH_COMPONENT24:
				format_ = GL_DEPTH_COMPONENT;
				break;
			default:
				found = false;
				break;
		}

		if (found) {
			type_ = GL_UNSIGNED_BYTE;
		}

		return found;
	}

	bool TextureFormat::nonIntegerFormat()
	{
		bool found = true;

		switch (internalFormat_) {
			case GL_RGB5_A1:
				format_ = GL_RGBA;
				type_ = GL_UNSIGNED_SHORT_5_5_5_1;
				break;
			case GL_RGBA4:
				format_ = GL_RGBA;
				type_ = GL_UNSIGNED_SHORT_4_4_4_4;
				break;
			case GL_RGB565:
				format_ = GL_RGB;
				type_ = GL_UNSIGNED_SHORT_5_6_5;
				break;
			default:
				found = false;
				break;
		}

		return found;
	}

	bool TextureFormat::floatFormat()
	{
		bool found = true;

		switch (internalFormat_) {
			case GL_RGBA16F:
			case GL_RGBA32F:
				format_ = GL_RGBA;
				break;
			case GL_RGB16F:
			case GL_RGB32F:
				format_ = GL_RGB;
				break;
			case GL_DEPTH_COMPONENT32F:
				format_ = GL_DEPTH_COMPONENT;
				break;
			default:
				found = false;
				break;
		}

		if (found) {
			type_ = GL_FLOAT;
		}

		return found;
	}

	bool TextureFormat::compressedFormat()
	{
		bool found = true;

		switch (internalFormat_) {
			case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
			case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
			case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
				format_ = GL_RGBA;
				break;
			case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
				format_ = GL_RGB;
				break;
			default:
				found = false;
				break;
		}

		if (found) {
			type_ = GL_UNSIGNED_BYTE;
			isCompressed_ = true;
		}

		return found;
	}

#if defined(WITH_OPENGLES)
	bool TextureFormat::oesCompressedFormat()
	{
		bool found = true;

		switch (internalFormat_) {
			case GL_ATC_RGBA_EXPLICIT_ALPHA_AMD:
			case GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD:
			case GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG:
			case GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG:
			case GL_COMPRESSED_RGBA8_ETC2_EAC:
			case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
#	if (!defined(DEATH_TARGET_ANDROID) && defined(WITH_OPENGLES)) || (defined(DEATH_TARGET_ANDROID) && __ANDROID_API__ >= 21)
			case GL_COMPRESSED_RGBA_ASTC_4x4_KHR:
			case GL_COMPRESSED_RGBA_ASTC_5x4_KHR:
			case GL_COMPRESSED_RGBA_ASTC_5x5_KHR:
			case GL_COMPRESSED_RGBA_ASTC_6x5_KHR:
			case GL_COMPRESSED_RGBA_ASTC_6x6_KHR:
			case GL_COMPRESSED_RGBA_ASTC_8x5_KHR:
			case GL_COMPRESSED_RGBA_ASTC_8x6_KHR:
			case GL_COMPRESSED_RGBA_ASTC_8x8_KHR:
			case GL_COMPRESSED_RGBA_ASTC_10x5_KHR:
			case GL_COMPRESSED_RGBA_ASTC_10x6_KHR:
			case GL_COMPRESSED_RGBA_ASTC_10x8_KHR:
			case GL_COMPRESSED_RGBA_ASTC_10x10_KHR:
			case GL_COMPRESSED_RGBA_ASTC_12x10_KHR:
			case GL_COMPRESSED_RGBA_ASTC_12x12_KHR:
#	endif
				format_ = GL_RGBA;
				break;
			case GL_ETC1_RGB8_OES:
			case GL_COMPRESSED_RGB8_ETC2:
			case GL_ATC_RGB_AMD:
			case GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG:
			case GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
				format_ = GL_RGB;
				break;
			case GL_COMPRESSED_RG11_EAC:
				format_ = GL_RG;
				break;
			case GL_COMPRESSED_R11_EAC:
				format_ = GL_RED;
				break;
			default:
				found = false;
				break;
		}

		if (found) {
			type_ = GL_UNSIGNED_BYTE;
			isCompressed_ = true;
		}

		return found;
	}
#endif

	void TextureFormat::checkFormatSupport() const
	{
		const IGfxCapabilities& gfxCaps = theServiceLocator().GetGfxCapabilities();

		switch (internalFormat_) {
			case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
			case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
			case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
			{
				const bool hasS3tc = gfxCaps.HasExtension(IGfxCapabilities::GLExtensions::EXT_TEXTURE_COMPRESSION_S3TC);
				FATAL_ASSERT_MSG(hasS3tc, "GL_EXT_texture_compression_s3tc not available");
				break;
			}
#if defined(WITH_OPENGLES)
			case GL_ETC1_RGB8_OES:
			{
				const bool hasEct1 = gfxCaps.HasExtension(IGfxCapabilities::GLExtensions::OES_COMPRESSED_ETC1_RGB8_TEXTURE);
				FATAL_ASSERT_MSG(hasEct1, "GL_OES_compressed_etc1_rgb8_texture not available");
				break;
			}
			case GL_ATC_RGB_AMD:
			case GL_ATC_RGBA_EXPLICIT_ALPHA_AMD:
			case GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD:
			{
				const bool hasAtc = gfxCaps.HasExtension(IGfxCapabilities::GLExtensions::AMD_COMPRESSED_ATC_TEXTURE);
				FATAL_ASSERT_MSG(hasAtc, "GL_AMD_compressed_ATC_texture not available");
				break;
			}
			case GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG:
			case GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG:
			case GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
			case GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG:
			{
				const bool hasPvr = gfxCaps.HasExtension(IGfxCapabilities::GLExtensions::IMG_TEXTURE_COMPRESSION_PVRTC);
				FATAL_ASSERT_MSG(hasPvr, "GL_IMG_texture_compression_pvrtc not available");
				break;
			}
#	if (!defined(DEATH_TARGET_ANDROID) && defined(WITH_OPENGLES)) || (defined(DEATH_TARGET_ANDROID) && __ANDROID_API__ >= 21)
			case GL_COMPRESSED_RGBA_ASTC_4x4_KHR:
			case GL_COMPRESSED_RGBA_ASTC_5x4_KHR:
			case GL_COMPRESSED_RGBA_ASTC_5x5_KHR:
			case GL_COMPRESSED_RGBA_ASTC_6x5_KHR:
			case GL_COMPRESSED_RGBA_ASTC_6x6_KHR:
			case GL_COMPRESSED_RGBA_ASTC_8x5_KHR:
			case GL_COMPRESSED_RGBA_ASTC_8x6_KHR:
			case GL_COMPRESSED_RGBA_ASTC_8x8_KHR:
			case GL_COMPRESSED_RGBA_ASTC_10x5_KHR:
			case GL_COMPRESSED_RGBA_ASTC_10x6_KHR:
			case GL_COMPRESSED_RGBA_ASTC_10x8_KHR:
			case GL_COMPRESSED_RGBA_ASTC_10x10_KHR:
			case GL_COMPRESSED_RGBA_ASTC_12x10_KHR:
			case GL_COMPRESSED_RGBA_ASTC_12x12_KHR:
			{
				const bool hasAstc = gfxCaps.HasExtension(IGfxCapabilities::GLExtensions::KHR_TEXTURE_COMPRESSION_ASTC_LDR);
				FATAL_ASSERT_MSG(hasAstc, "GL_KHR_texture_compression_astc_ldr not available");
				break;
			}
#	endif
#endif
		}
	}

}
