// Copyright (C) 2015 Jérôme Leclercq
// This file is part of the "Nazara Engine - Utility module"
// For conditions of distribution and use, see copyright notice in Config.hpp

#include <Nazara/Utility/Image.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/Core/ErrorFlags.hpp>
#include <Nazara/Utility/Config.hpp>
#include <Nazara/Utility/PixelFormat.hpp>
#include <cmath>
#include <memory>
#include <stdexcept>
#include <Nazara/Utility/Debug.hpp>

///TODO: Rajouter des warnings (Formats compressés avec les méthodes Copy/Update, tests taille dans Copy)
///TODO: Rendre les méthodes exception-safe (Virer toute cette merde de calcul de pointeurs, faire usage du RAII)
///FIXME: Gérer correctement les formats utilisant moins d'un octet par pixel

namespace
{
	inline unsigned int GetLevelSize(unsigned int size, nzUInt8 level)
	{
		if (size == 0) // Possible dans le cas d'une image invalide
			return 0;

		return std::max(size >> level, 1U);
	}

	inline nzUInt8* GetPixelPtr(nzUInt8* base, nzUInt8 bpp, unsigned int x, unsigned int y, unsigned int z, unsigned int width, unsigned int height)
	{
		return &base[(width*(height*z + y) + x)*bpp];
	}
}

bool NzImageParams::IsValid() const
{
	return true; // Rien à vérifier
}

NzImage::NzImage() :
m_sharedImage(&emptyImage)
{
}

NzImage::NzImage(nzImageType type, nzPixelFormat format, unsigned int width, unsigned int height, unsigned int depth, nzUInt8 levelCount) :
m_sharedImage(&emptyImage)
{
	NzErrorFlags flags(nzErrorFlag_ThrowException);
	Create(type, format, width, height, depth, levelCount);
}

NzImage::NzImage(const NzImage& image) :
NzRefCounted(),
NzResource(),
m_sharedImage(image.m_sharedImage)
{
	if (m_sharedImage != &emptyImage)
		m_sharedImage->refCount++;
}

NzImage::NzImage(SharedImage* sharedImage) :
m_sharedImage(sharedImage)
{
}

NzImage::~NzImage()
{
	Destroy();
}

bool NzImage::Convert(nzPixelFormat format)
{
	#if NAZARA_UTILITY_SAFE
	if (m_sharedImage == &emptyImage)
	{
		NazaraError("Image must be valid");
		return false;
	}

	if (!NzPixelFormat::IsValid(format))
	{
		NazaraError("Invalid pixel format");
		return false;
	}

	if (!NzPixelFormat::IsConversionSupported(m_sharedImage->format, format))
	{
		NazaraError("Conversion from " + NzPixelFormat::ToString(m_sharedImage->format) + " to " + NzPixelFormat::ToString(format) + " is not supported");
		return false;
	}
	#endif

	if (format == m_sharedImage->format)
		return true;

	nzUInt8** levels = new nzUInt8*[m_sharedImage->levelCount];

	unsigned int width = m_sharedImage->width;
	unsigned int height = m_sharedImage->height;

	// Les images 3D et cubemaps sont stockés de la même façon
	unsigned int depth = (m_sharedImage->type == nzImageType_Cubemap) ? 6 : m_sharedImage->depth;

	for (unsigned int i = 0; i < m_sharedImage->levelCount; ++i)
	{
		unsigned int pixelsPerFace = width*height;
		nzUInt8* face = new nzUInt8[pixelsPerFace*depth*NzPixelFormat::GetBytesPerPixel(format)];
		nzUInt8* ptr = face;
		nzUInt8* pixels = m_sharedImage->pixels[i];
		unsigned int srcStride = pixelsPerFace * NzPixelFormat::GetBytesPerPixel(m_sharedImage->format);
		unsigned int dstStride = pixelsPerFace * NzPixelFormat::GetBytesPerPixel(format);

		for (unsigned int d = 0; d < depth; ++d)
		{
			if (!NzPixelFormat::Convert(m_sharedImage->format, format, pixels, &pixels[srcStride], ptr))
			{
				NazaraError("Failed to convert image");

				// Nettoyage de la mémoire
				delete[] face; // Permet une optimisation de boucle (GCC)
				for (unsigned int j = 0; j < i; ++j)
					delete[] levels[j];

				delete[] levels;

				return false;
			}

			pixels += srcStride;
			ptr += dstStride;
		}

		levels[i] = face;

		if (width > 1)
			width >>= 1;

		if (height > 1)
			height >>= 1;

		if (depth > 1 && m_sharedImage->type != nzImageType_Cubemap)
			depth >>= 1;
	}

	SharedImage* newImage = new SharedImage(1, m_sharedImage->type, format, m_sharedImage->levelCount, levels, m_sharedImage->width, m_sharedImage->height, m_sharedImage->depth);

	ReleaseImage();
	m_sharedImage = newImage;

	return true;
}

void NzImage::Copy(const NzImage& source, const NzBoxui& srcBox, const NzVector3ui& dstPos)
{
	#if NAZARA_UTILITY_SAFE
	if (m_sharedImage == &emptyImage)
	{
		NazaraError("Image must be valid");
		return;
	}

	if (!source.IsValid())
	{
		NazaraError("Source image must be valid");
		return;
	}

	if (source.GetFormat() != m_sharedImage->format)
	{
		NazaraError("Source image format does not match destination image format");
		return;
	}
	#endif

	const nzUInt8* srcPtr = source.GetConstPixels(srcBox.x, srcBox.y, srcBox.z);
	#if NAZARA_UTILITY_SAFE
	if (!srcPtr)
	{
		NazaraError("Failed to access pixels");
		return;
	}
	#endif

	nzUInt8 bpp = NzPixelFormat::GetBytesPerPixel(m_sharedImage->format);
	nzUInt8* dstPtr = GetPixelPtr(m_sharedImage->pixels[0], bpp, dstPos.x, dstPos.y, dstPos.z, m_sharedImage->width, m_sharedImage->height);

	Copy(dstPtr, srcPtr, bpp, srcBox.width, srcBox.height, srcBox.depth, m_sharedImage->width, m_sharedImage->height, source.GetWidth(), source.GetHeight());
}

bool NzImage::Create(nzImageType type, nzPixelFormat format, unsigned int width, unsigned int height, unsigned int depth, nzUInt8 levelCount)
{
	Destroy();

	#if NAZARA_UTILITY_SAFE
	if (!NzPixelFormat::IsValid(format))
	{
		NazaraError("Invalid pixel format");
		return false;
	}

	if (width == 0)
	{
		NazaraError("Width must be at least 1 (0)");
		return false;
	}

	if (height == 0)
	{
		NazaraError("Height must be at least 1 (0)");
		return false;
	}

	if (depth == 0)
	{
		NazaraError("Depth must be at least 1 (0)");
		return false;
	}

	switch (type)
	{
		case nzImageType_1D:
			if (height > 1)
			{
				NazaraError("1D textures must be 1 tall");
				return false;
			}

			if (depth > 1)
			{
				NazaraError("1D textures must be 1 deep");
				return false;
			}
			break;

		case nzImageType_1D_Array:
		case nzImageType_2D:
			if (depth > 1)
			{
				NazaraError("2D textures must be 1 deep");
				return false;
			}
			break;

		case nzImageType_2D_Array:
		case nzImageType_3D:
			break;

		case nzImageType_Cubemap:
			if (depth > 1)
			{
				NazaraError("Cubemaps must be 1 deep");
				return false;
			}

			if (width != height)
			{
				NazaraError("Cubemaps must have square dimensions");
				return false;
			}
			break;

		default:
			NazaraInternalError("Image type not handled");
			return false;
	}
	#endif

	levelCount = std::min(levelCount, GetMaxLevel(type, width, height, depth));

	nzUInt8** levels = new nzUInt8*[levelCount];

	unsigned int w = width;
	unsigned int h = height;
	unsigned int d = (type == nzImageType_Cubemap) ? 6 : depth;

	for (unsigned int i = 0; i < levelCount; ++i)
	{
		// Cette allocation est protégée car sa taille dépend directement de paramètres utilisateurs
		try
		{
			levels[i] = new nzUInt8[w * h * d * NzPixelFormat::GetBytesPerPixel(format)];

			if (w > 1)
				w >>= 1;

			if (h > 1)
				h >>= 1;

			if (d > 1 && type != nzImageType_Cubemap)
				d >>= 1;
		}
		catch (const std::exception& e)
		{
			NazaraError("Failed to allocate image's level " + NzString::Number(i) + " (" + NzString(e.what()) + ')');

			// Nettoyage
			delete[] levels[i]; // Permet une optimisation de boucle (GCC)
			for (unsigned int j = 0; j < i; ++j)
				delete[] levels[j];

			delete[] levels;

			return false;
		}
	}

	m_sharedImage = new SharedImage(1, type, format, levelCount, levels, width, height, depth);

	NotifyCreated();
	return true;
}

void NzImage::Destroy()
{
	if (m_sharedImage != &emptyImage)
	{
		NotifyDestroy();
		ReleaseImage();
	}
}

bool NzImage::Fill(const NzColor& color)
{
	#if NAZARA_UTILITY_SAFE
	if (m_sharedImage == &emptyImage)
	{
		NazaraError("Image must be valid");
		return false;
	}

	if (NzPixelFormat::IsCompressed(m_sharedImage->format))
	{
		NazaraError("Cannot access pixels from compressed image");
		return false;
	}
	#endif

	nzUInt8 bpp = NzPixelFormat::GetBytesPerPixel(m_sharedImage->format);
	std::unique_ptr<nzUInt8[]> colorBuffer(new nzUInt8[bpp]);
	if (!NzPixelFormat::Convert(nzPixelFormat_RGBA8, m_sharedImage->format, &color.r, colorBuffer.get()))
	{
		NazaraError("Failed to convert RGBA8 to " + NzPixelFormat::ToString(m_sharedImage->format));
		return false;
	}

	nzUInt8** levels = new nzUInt8*[m_sharedImage->levelCount];

	unsigned int width = m_sharedImage->width;
	unsigned int height = m_sharedImage->height;

	// Les images 3D et cubemaps sont stockés de la même façon
	unsigned int depth = (m_sharedImage->type == nzImageType_Cubemap) ? 6 : m_sharedImage->depth;

	for (unsigned int i = 0; i < m_sharedImage->levelCount; ++i)
	{
		unsigned int size = width*height*depth*bpp;
		nzUInt8* face = new nzUInt8[size];
		nzUInt8* ptr = face;
		nzUInt8* end = &ptr[size];

		while (ptr < end)
		{
			std::memcpy(ptr, colorBuffer.get(), bpp);
			ptr += bpp;
		}

		levels[i] = face;

		if (width > 1U)
			width >>= 1;

		if (height > 1U)
			height >>= 1;

		if (depth > 1U && m_sharedImage->type != nzImageType_Cubemap)
			depth >>= 1;
	}

	SharedImage* newImage = new SharedImage(1, m_sharedImage->type, m_sharedImage->format, m_sharedImage->levelCount, levels, m_sharedImage->width, m_sharedImage->height, m_sharedImage->depth);

	ReleaseImage();
	m_sharedImage = newImage;

	return true;
}

bool NzImage::Fill(const NzColor& color, const NzBoxui& box)
{
	#if NAZARA_UTILITY_SAFE
	if (m_sharedImage == &emptyImage)
	{
		NazaraError("Image must be valid");
		return false;
	}

	if (!box.IsValid())
	{
		NazaraError("Invalid rectangle");
		return false;
	}

	if (box.x+box.width > m_sharedImage->width || box.y+box.height > m_sharedImage->height || box.z+box.depth > m_sharedImage->depth)
	{
		NazaraError("Box dimensions are out of bounds");
		return false;
	}
	#endif

	EnsureOwnership();

	nzUInt8 bpp = NzPixelFormat::GetBytesPerPixel(m_sharedImage->format);
	std::unique_ptr<nzUInt8[]> colorBuffer(new nzUInt8[bpp]);
	if (!NzPixelFormat::Convert(nzPixelFormat_RGBA8, m_sharedImage->format, &color.r, colorBuffer.get()))
	{
		NazaraError("Failed to convert RGBA8 to " + NzPixelFormat::ToString(m_sharedImage->format));
		return false;
	}

	///FIXME: L'algorithme a du mal avec un bpp non multiple de 2
	nzUInt8* dstPixels = GetPixelPtr(m_sharedImage->pixels[0], bpp, box.x, box.y, box.z, m_sharedImage->width, m_sharedImage->height);
	unsigned int srcStride = box.width * bpp;
	unsigned int dstStride = m_sharedImage->width * bpp;
	unsigned int faceSize = dstStride * m_sharedImage->height;
	for (unsigned int z = 0; z < box.depth; ++z)
	{
		nzUInt8* facePixels = dstPixels;
		for (unsigned int y = 0; y < box.height; ++y)
		{
			nzUInt8* start = facePixels;
			nzUInt8* end = facePixels + srcStride;
			while (start < end)
			{
				std::memcpy(start, colorBuffer.get(), bpp);
				start += bpp;
			}

			facePixels += dstStride;
		}

		dstPixels += faceSize;
	}

	return true;
}

bool NzImage::Fill(const NzColor& color, const NzRectui& rect, unsigned int z)
{
	#if NAZARA_UTILITY_SAFE
	if (m_sharedImage == &emptyImage)
	{
		NazaraError("Image must be valid");
		return false;
	}

	if (!rect.IsValid())
	{
		NazaraError("Invalid rectangle");
		return false;
	}

	if (rect.x + rect.width > m_sharedImage->width || rect.y + rect.height > m_sharedImage->height)
	{
		NazaraError("Rectangle dimensions are out of bounds");
		return false;
	}

	unsigned int depth = (m_sharedImage->type == nzImageType_Cubemap) ? 6 : m_sharedImage->depth;
	if (z >= depth)
	{
		NazaraError("Z value exceeds depth (" + NzString::Number(z) + " >= " + NzString::Number(depth) + ')');
		return false;
	}
	#endif

	EnsureOwnership();

	nzUInt8 bpp = NzPixelFormat::GetBytesPerPixel(m_sharedImage->format);
	std::unique_ptr<nzUInt8[]> colorBuffer(new nzUInt8[bpp]);
	if (!NzPixelFormat::Convert(nzPixelFormat_RGBA8, m_sharedImage->format, &color.r, colorBuffer.get()))
	{
		NazaraError("Failed to convert RGBA8 to " + NzPixelFormat::ToString(m_sharedImage->format));
		return false;
	}

	///FIXME: L'algorithme a du mal avec un bpp non multiple de 2
	nzUInt8* dstPixels = GetPixelPtr(m_sharedImage->pixels[0], bpp, rect.x, rect.y, z, m_sharedImage->width, m_sharedImage->height);
	unsigned int srcStride = rect.width * bpp;
	unsigned int dstStride = m_sharedImage->width * bpp;
	for (unsigned int y = 0; y < rect.height; ++y)
	{
		nzUInt8* start = dstPixels;
		nzUInt8* end = dstPixels + srcStride;
		while (start < end)
		{
			std::memcpy(start, colorBuffer.get(), bpp);
			start += bpp;
		}

		dstPixels += dstStride;
	}

	return true;
}

bool NzImage::FlipHorizontally()
{
	#if NAZARA_UTILITY_SAFE
	if (m_sharedImage == &emptyImage)
	{
		NazaraError("Image must be valid");
		return false;
	}
	#endif

	EnsureOwnership();

	unsigned int width = m_sharedImage->width;
	unsigned int height = m_sharedImage->height;
	unsigned int depth = (m_sharedImage->type == nzImageType_Cubemap) ? 6 : m_sharedImage->depth;
	for (unsigned int level = 0; level < m_sharedImage->levelCount; ++level)
	{
		if (!NzPixelFormat::Flip(nzPixelFlipping_Horizontally, m_sharedImage->format, width, height, depth, m_sharedImage->pixels[level], m_sharedImage->pixels[level]))
		{
			NazaraError("Failed to flip image");
			return false;
		}

		if (width > 1U)
			width >>= 1;

		if (height > 1U)
			height >>= 1;

		if (depth > 1U && m_sharedImage->type != nzImageType_Cubemap)
			depth >>= 1;
	}

	return true;
}

bool NzImage::FlipVertically()
{
	#if NAZARA_UTILITY_SAFE
	if (m_sharedImage == &emptyImage)
	{
		NazaraError("Image must be valid");
		return false;
	}

	if (NzPixelFormat::IsCompressed(m_sharedImage->format))
	{
		NazaraError("Cannot flip compressed image");
		return false;
	}
	#endif

	EnsureOwnership();

	unsigned int width = m_sharedImage->width;
	unsigned int height = m_sharedImage->height;
	unsigned int depth = (m_sharedImage->type == nzImageType_Cubemap) ? 6 : m_sharedImage->depth;
	for (unsigned int level = 0; level < m_sharedImage->levelCount; ++level)
	{
		if (!NzPixelFormat::Flip(nzPixelFlipping_Vertically, m_sharedImage->format, width, height, depth, m_sharedImage->pixels[level], m_sharedImage->pixels[level]))
		{
			NazaraError("Failed to flip image");
			return false;
		}

		if (width > 1U)
			width >>= 1;

		if (height > 1U)
			height >>= 1;

		if (depth > 1U && m_sharedImage->type != nzImageType_Cubemap)
			depth >>= 1;
	}

	return true;
}

const nzUInt8* NzImage::GetConstPixels(unsigned int x, unsigned int y, unsigned int z, nzUInt8 level) const
{
	#if NAZARA_UTILITY_SAFE
	if (m_sharedImage == &emptyImage)
	{
		NazaraError("Image must be valid");
		return nullptr;
	}

	if (level >= m_sharedImage->levelCount)
	{
		NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_sharedImage->levelCount) + ')');
		return nullptr;
	}
	#endif

	unsigned int width = GetLevelSize(m_sharedImage->width, level);
	#if NAZARA_UTILITY_SAFE
	if (x >= width)
	{
		NazaraError("X value exceeds width (" + NzString::Number(x) + " >= " + NzString::Number(width) + ')');
		return nullptr;
	}
	#endif

	unsigned int height = GetLevelSize(m_sharedImage->height, level);
	#if NAZARA_UTILITY_SAFE
	if (y >= height)
	{
		NazaraError("Y value exceeds height (" + NzString::Number(y) + " >= " + NzString::Number(height) + ')');
		return nullptr;
	}

	unsigned int depth = (m_sharedImage->type == nzImageType_Cubemap) ? 6 : GetLevelSize(m_sharedImage->depth, level);
	if (z >= depth)
	{
		NazaraError("Z value exceeds depth (" + NzString::Number(z) + " >= " + NzString::Number(depth) + ')');
		return nullptr;
	}
	#endif

	return GetPixelPtr(m_sharedImage->pixels[level], NzPixelFormat::GetBytesPerPixel(m_sharedImage->format), x, y, z, width, height);
}

unsigned int NzImage::GetDepth(nzUInt8 level) const
{
	#if NAZARA_UTILITY_SAFE
	if (level >= m_sharedImage->levelCount)
	{
		NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_sharedImage->levelCount) + ')');
		return 0;
	}
	#endif

	return GetLevelSize(m_sharedImage->depth, level);
}

nzPixelFormat NzImage::GetFormat() const
{
	return m_sharedImage->format;
}

unsigned int NzImage::GetHeight(nzUInt8 level) const
{
	#if NAZARA_UTILITY_SAFE
	if (level >= m_sharedImage->levelCount)
	{
		NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_sharedImage->levelCount) + ')');
		return 0;
	}
	#endif

	return GetLevelSize(m_sharedImage->height, level);
}

nzUInt8 NzImage::GetLevelCount() const
{
	return m_sharedImage->levelCount;
}

nzUInt8 NzImage::GetMaxLevel() const
{
	return GetMaxLevel(m_sharedImage->type, m_sharedImage->width, m_sharedImage->height, m_sharedImage->depth);
}

unsigned int NzImage::GetMemoryUsage() const
{
	unsigned int width = m_sharedImage->width;
	unsigned int height = m_sharedImage->height;
	unsigned int depth = m_sharedImage->depth;

	unsigned int size = 0;
	for (unsigned int i = 0; i < m_sharedImage->levelCount; ++i)
	{
		size += width * height * depth;

		if (width > 1)
			width >>= 1;

		if (height > 1)
			height >>= 1;

		if (depth > 1)
			depth >>= 1;
	}

	if (m_sharedImage->type == nzImageType_Cubemap)
		size *= 6;

	return size * NzPixelFormat::GetBytesPerPixel(m_sharedImage->format);
}

unsigned int NzImage::GetMemoryUsage(nzUInt8 level) const
{
	#if NAZARA_UTILITY_SAFE
	if (level >= m_sharedImage->levelCount)
	{
		NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_sharedImage->levelCount) + ')');
		return 0;
	}
	#endif

	return (GetLevelSize(m_sharedImage->width, level)) *
	       (GetLevelSize(m_sharedImage->height, level)) *
	       ((m_sharedImage->type == nzImageType_Cubemap) ? 6 : GetLevelSize(m_sharedImage->depth, level)) *
	       NzPixelFormat::GetBytesPerPixel(m_sharedImage->format);
}

NzColor NzImage::GetPixelColor(unsigned int x, unsigned int y, unsigned int z) const
{
	#if NAZARA_UTILITY_SAFE
	if (m_sharedImage == &emptyImage)
	{
		NazaraError("Image must be valid");
		return NzColor();
	}

	if (NzPixelFormat::IsCompressed(m_sharedImage->format))
	{
		NazaraError("Cannot access pixels from compressed image");
		return NzColor();
	}

	if (x >= m_sharedImage->width)
	{
		NazaraError("X value exceeds width (" + NzString::Number(x) + " >= " + NzString::Number(m_sharedImage->width) + ')');
		return NzColor();
	}

	if (y >= m_sharedImage->height)
	{
		NazaraError("Y value exceeds height (" + NzString::Number(y) + " >= " + NzString::Number(m_sharedImage->height) + ')');
		return NzColor();
	}

	unsigned int depth = (m_sharedImage->type == nzImageType_Cubemap) ? 6 : m_sharedImage->depth;
	if (z >= depth)
	{
		NazaraError("Z value exceeds depth (" + NzString::Number(z) + " >= " + NzString::Number(depth) + ')');
		return NzColor();
	}
	#endif

	const nzUInt8* pixel = GetPixelPtr(m_sharedImage->pixels[0], NzPixelFormat::GetBytesPerPixel(m_sharedImage->format), x, y, z, m_sharedImage->width, m_sharedImage->height);

	NzColor color;
	if (!NzPixelFormat::Convert(m_sharedImage->format, nzPixelFormat_RGBA8, pixel, &color.r))
		NazaraError("Failed to convert image's format to RGBA8");

	return color;
}

nzUInt8* NzImage::GetPixels(unsigned int x, unsigned int y, unsigned int z, nzUInt8 level)
{
	#if NAZARA_UTILITY_SAFE
	if (m_sharedImage == &emptyImage)
	{
		NazaraError("Image must be valid");
		return nullptr;
	}

	if (level >= m_sharedImage->levelCount)
	{
		NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_sharedImage->levelCount) + ')');
		return nullptr;
	}
	#endif

	unsigned int width = GetLevelSize(m_sharedImage->width, level);
	#if NAZARA_UTILITY_SAFE
	if (x >= width)
	{
		NazaraError("X value exceeds width (" + NzString::Number(x) + " >= " + NzString::Number(width) + ')');
		return nullptr;
	}
	#endif

	unsigned int height = GetLevelSize(m_sharedImage->height, level);
	#if NAZARA_UTILITY_SAFE
	if (y >= height)
	{
		NazaraError("Y value exceeds height (" + NzString::Number(y) + " >= " + NzString::Number(height) + ')');
		return nullptr;
	}

	unsigned int depth = (m_sharedImage->type == nzImageType_Cubemap) ? 6 : GetLevelSize(m_sharedImage->depth, level);
	if (z >= depth)
	{
		NazaraError("Z value exceeds depth (" + NzString::Number(z) + " >= " + NzString::Number(depth) + ')');
		return nullptr;
	}

	if (level >= m_sharedImage->levelCount)
	{
		NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_sharedImage->levelCount) + ')');
		return nullptr;
	}
	#endif

	EnsureOwnership();

	return GetPixelPtr(m_sharedImage->pixels[level], NzPixelFormat::GetBytesPerPixel(m_sharedImage->format), x, y, z, width, height);
}

NzVector3ui NzImage::GetSize(nzUInt8 level) const
{
	#if NAZARA_UTILITY_SAFE
	if (level >= m_sharedImage->levelCount)
	{
		NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_sharedImage->levelCount) + ')');
		return NzVector3ui::Zero();
	}
	#endif

	return NzVector3ui(GetLevelSize(m_sharedImage->width, level), GetLevelSize(m_sharedImage->height, level), GetLevelSize(m_sharedImage->depth, level));
}

nzImageType	NzImage::GetType() const
{
	return m_sharedImage->type;
}

unsigned int NzImage::GetWidth(nzUInt8 level) const
{
	#if NAZARA_UTILITY_SAFE
	if (level >= m_sharedImage->levelCount)
	{
		NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_sharedImage->levelCount) + ')');
		return 0;
	}
	#endif

	return GetLevelSize(m_sharedImage->width, level);
}

bool NzImage::IsValid() const
{
	return m_sharedImage != &emptyImage;
}

bool NzImage::LoadFromFile(const NzString& filePath, const NzImageParams& params)
{
	return NzImageLoader::LoadFromFile(this, filePath, params);
}

bool NzImage::LoadFromMemory(const void* data, std::size_t size, const NzImageParams& params)
{
	return NzImageLoader::LoadFromMemory(this, data, size, params);
}

bool NzImage::LoadFromStream(NzInputStream& stream, const NzImageParams& params)
{
	return NzImageLoader::LoadFromStream(this, stream, params);
}

// LoadArray
bool NzImage::LoadArrayFromFile(const NzString& filePath, const NzImageParams& imageParams, const NzVector2ui& atlasSize)
{
	NzImage image;
	if (!image.LoadFromFile(filePath, imageParams))
	{
		NazaraError("Failed to load image");
		return false;
	}

	return LoadArrayFromImage(image, atlasSize);
}

bool NzImage::LoadArrayFromImage(const NzImage& image, const NzVector2ui& atlasSize)
{
	#if NAZARA_UTILITY_SAFE
	if (!image.IsValid())
	{
		NazaraError("Image must be valid");
		return false;
	}

	if (atlasSize.x == 0)
	{
		NazaraError("Atlas width must be over zero");
		return false;
	}

	if (atlasSize.y == 0)
	{
		NazaraError("Atlas height must be over zero");
		return false;
	}
	#endif

	nzImageType type = image.GetType();

	#if NAZARA_UTILITY_SAFE
	if (type != nzImageType_1D && type != nzImageType_2D)
	{
		NazaraError("Image type not handled (0x" + NzString::Number(type, 16) + ')');
		return false;
	}
	#endif

	NzVector2ui imageSize(image.GetWidth(), image.GetHeight());

	if (imageSize.x % atlasSize.x != 0)
	{
		NazaraWarning("Image width is not divisible by atlas width (" + NzString::Number(imageSize.x) + " mod " + NzString::Number(atlasSize.x) + " != 0)");
	}

	if (imageSize.y % atlasSize.y != 0)
	{
		NazaraWarning("Image height is not divisible by atlas height (" + NzString::Number(imageSize.y) + " mod " + NzString::Number(atlasSize.y) + " != 0)");
	}

	NzVector2ui faceSize = imageSize/atlasSize;

	unsigned int layerCount = atlasSize.x*atlasSize.y;

	// Selon le type de l'image de base, on va créer un array d'images 2D ou 1D
	if (type == nzImageType_2D)
		Create(nzImageType_2D_Array, image.GetFormat(), faceSize.x, faceSize.y, layerCount);
	else
		Create(nzImageType_1D_Array, image.GetFormat(), faceSize.x, layerCount);

	unsigned int layer = 0;
	for (unsigned int j = 0; j < atlasSize.y; ++j)
		for (unsigned int i = 0; i < atlasSize.x; ++i)
			Copy(image, NzRectui(i*faceSize.x, j*faceSize.y, faceSize.x, faceSize.y), NzVector3ui(0, 0, layer++));

	return true;
}

bool NzImage::LoadArrayFromMemory(const void* data, std::size_t size, const NzImageParams& imageParams, const NzVector2ui& atlasSize)
{
	NzImage image;
	if (!image.LoadFromMemory(data, size, imageParams))
	{
		NazaraError("Failed to load image");
		return false;
	}

	return LoadArrayFromImage(image, atlasSize);
}

bool NzImage::LoadArrayFromStream(NzInputStream& stream, const NzImageParams& imageParams, const NzVector2ui& atlasSize)
{
	NzImage image;
	if (!image.LoadFromStream(stream, imageParams))
	{
		NazaraError("Failed to load image");
		return false;
	}

	return LoadArrayFromImage(image, atlasSize);
}

bool NzImage::LoadCubemapFromFile(const NzString& filePath, const NzImageParams& imageParams, const NzCubemapParams& cubemapParams)
{
	NzImage image;
	if (!image.LoadFromFile(filePath, imageParams))
	{
		NazaraError("Failed to load image");
		return false;
	}

	return LoadCubemapFromImage(image, cubemapParams);
}

bool NzImage::LoadCubemapFromImage(const NzImage& image, const NzCubemapParams& params)
{
	#if NAZARA_UTILITY_SAFE
	if (!image.IsValid())
	{
		NazaraError("Image must be valid");
		return false;
	}

	nzImageType type = image.GetType();
	if (type != nzImageType_2D)
	{
		NazaraError("Image type not handled (0x" + NzString::Number(type, 16) + ')');
		return false;
	}
	#endif

	unsigned int width = image.GetWidth();
	unsigned int height = image.GetHeight();
	unsigned int faceSize = (params.faceSize == 0) ? std::max(width, height)/4 : params.faceSize;

	// Sans cette vérification, celles des rectangles pourrait réussir via un overflow
	if (width < faceSize || height < faceSize)
	{
		NazaraError("Image is too small for this face size");
		return false;
	}

	// Calcul et vérification des surfaces
	unsigned limitX = width - faceSize;
	unsigned limitY = height - faceSize;

	NzVector2ui backPos = params.backPosition * faceSize;
	if (backPos.x > limitX || backPos.y > limitY)
	{
		NazaraError("Back rectangle is out of image");
		return false;
	}

	NzVector2ui downPos = params.downPosition * faceSize;
	if (downPos.x > limitX || downPos.y > limitY)
	{
		NazaraError("Down rectangle is out of image");
		return false;
	}

	NzVector2ui forwardPos = params.forwardPosition * faceSize;
	if (forwardPos.x > limitX || forwardPos.y > limitY)
	{
		NazaraError("Forward rectangle is out of image");
		return false;
	}

	NzVector2ui leftPos = params.leftPosition * faceSize;
	if (leftPos.x > limitX || leftPos.y > limitY)
	{
		NazaraError("Left rectangle is out of image");
		return false;
	}

	NzVector2ui rightPos = params.rightPosition * faceSize;
	if (rightPos.x > limitX || rightPos.y > limitY)
	{
		NazaraError("Right rectangle is out of image");
		return false;
	}

	NzVector2ui upPos = params.upPosition * faceSize;
	if (upPos.x > limitX || upPos.y > limitY)
	{
		NazaraError("Up rectangle is out of image");
		return false;
	}

	Create(nzImageType_Cubemap, image.GetFormat(), faceSize, faceSize);

	#ifdef NAZARA_DEBUG
	// Les paramètres sont valides, que Create ne fonctionne pas relèverait d'un bug
	if (m_sharedImage == &emptyImage)
	{
		NazaraInternalError("Failed to create cubemap");
		return false;
	}
	#endif

	Copy(image, NzRectui(backPos.x, backPos.y, faceSize, faceSize), NzVector3ui(0, 0, nzCubemapFace_NegativeZ));
	Copy(image, NzRectui(downPos.x, downPos.y, faceSize, faceSize), NzVector3ui(0, 0, nzCubemapFace_NegativeY));
	Copy(image, NzRectui(forwardPos.x, forwardPos.y, faceSize, faceSize), NzVector3ui(0, 0, nzCubemapFace_PositiveZ));
	Copy(image, NzRectui(leftPos.x, leftPos.y, faceSize, faceSize), NzVector3ui(0, 0, nzCubemapFace_NegativeX));
	Copy(image, NzRectui(rightPos.x, rightPos.y, faceSize, faceSize), NzVector3ui(0, 0, nzCubemapFace_PositiveX));
	Copy(image, NzRectui(upPos.x, upPos.y, faceSize, faceSize), NzVector3ui(0, 0, nzCubemapFace_PositiveY));

	return true;
}

bool NzImage::LoadCubemapFromMemory(const void* data, std::size_t size, const NzImageParams& imageParams, const NzCubemapParams& cubemapParams)
{
	NzImage image;
	if (!image.LoadFromMemory(data, size, imageParams))
	{
		NazaraError("Failed to load image");
		return false;
	}

	return LoadCubemapFromImage(image, cubemapParams);
}

bool NzImage::LoadCubemapFromStream(NzInputStream& stream, const NzImageParams& imageParams, const NzCubemapParams& cubemapParams)
{
	NzImage image;
	if (!image.LoadFromStream(stream, imageParams))
	{
		NazaraError("Failed to load image");
		return false;
	}

	return LoadCubemapFromImage(image, cubemapParams);
}

void NzImage::SetLevelCount(nzUInt8 levelCount)
{
	#if NAZARA_UTILITY_SAFE
	if (m_sharedImage == &emptyImage)
	{
		NazaraError("Image must be valid");
		return;
	}

	if (levelCount == 0)
	{
		NazaraError("Level count must be positive");
		return;
	}
	#endif

	levelCount = std::min(levelCount, GetMaxLevel());

	if (m_sharedImage->levelCount == levelCount)
		return;

	EnsureOwnership();

	nzUInt8 oldLevelCount = m_sharedImage->levelCount;
	nzUInt8 maxLevelCount = std::max(levelCount, oldLevelCount);
	m_sharedImage->levelCount = levelCount; // Pour faire fonctionner GetMemoryUsage

	nzUInt8** pixels = new nzUInt8*[levelCount];
	for (unsigned int i = 0; i < maxLevelCount; ++i)
	{
		if (i < oldLevelCount)
			pixels[i] = m_sharedImage->pixels[i];
		else if (i < levelCount)
			pixels[i] = new nzUInt8[GetMemoryUsage(i)];
		else
			delete[] m_sharedImage->pixels[i];
	}

	delete[] m_sharedImage->pixels;

	m_sharedImage->pixels = pixels;
}

bool NzImage::SetPixelColor(const NzColor& color, unsigned int x, unsigned int y, unsigned int z)
{
	#if NAZARA_UTILITY_SAFE
	if (m_sharedImage == &emptyImage)
	{
		NazaraError("Image must be valid");
		return false;
	}

	if (NzPixelFormat::IsCompressed(m_sharedImage->format))
	{
		NazaraError("Cannot access pixels from compressed image");
		return false;
	}

	if (x >= m_sharedImage->width)
	{
		NazaraError("X value exceeds width (" + NzString::Number(x) + " >= " + NzString::Number(m_sharedImage->width) + ')');
		return false;
	}

	if (y >= m_sharedImage->height)
	{
		NazaraError("Y value exceeds height (" + NzString::Number(y) + " >= " + NzString::Number(m_sharedImage->height) + ')');
		return false;
	}

	unsigned int depth = (m_sharedImage->type == nzImageType_Cubemap) ? 6 : m_sharedImage->depth;
	if (z >= depth)
	{
		NazaraError("Z value exceeds depth (" + NzString::Number(z) + " >= " + NzString::Number(depth) + ')');
		return false;
	}
	#endif

	nzUInt8* pixel = GetPixelPtr(m_sharedImage->pixels[0], NzPixelFormat::GetBytesPerPixel(m_sharedImage->format), x, y, z, m_sharedImage->width, m_sharedImage->height);

	if (!NzPixelFormat::Convert(nzPixelFormat_RGBA8, m_sharedImage->format, &color.r, pixel))
	{
		NazaraError("Failed to convert RGBA8 to image's format");
		return false;
	}

	return true;
}

bool NzImage::Update(const nzUInt8* pixels, unsigned int srcWidth, unsigned int srcHeight, nzUInt8 level)
{
	#if NAZARA_UTILITY_SAFE
	if (m_sharedImage == &emptyImage)
	{
		NazaraError("Image must be valid");
		return false;
	}

	if (!pixels)
	{
		NazaraError("Invalid pixel source");
		return false;
	}

	if (level >= m_sharedImage->levelCount)
	{
		NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_sharedImage->levelCount) + ')');
		return false;
	}
	#endif

	EnsureOwnership();

	Copy(m_sharedImage->pixels[level], pixels, NzPixelFormat::GetBytesPerPixel(m_sharedImage->format),
	     GetLevelSize(m_sharedImage->width, level),
	     GetLevelSize(m_sharedImage->height, level),
	     GetLevelSize(m_sharedImage->depth, level),
	     0, 0,
	     srcWidth, srcHeight);

	return true;
}

bool NzImage::Update(const nzUInt8* pixels, const NzBoxui& box, unsigned int srcWidth, unsigned int srcHeight, nzUInt8 level)
{
	#if NAZARA_UTILITY_SAFE
	if (m_sharedImage == &emptyImage)
	{
		NazaraError("Image must be valid");
		return false;
	}

	if (!pixels)
	{
		NazaraError("Invalid pixel source");
		return false;
	}

	if (level >= m_sharedImage->levelCount)
	{
		NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_sharedImage->levelCount) + ')');
		return false;
	}
	#endif

	unsigned int width = GetLevelSize(m_sharedImage->width, level);
	unsigned int height = GetLevelSize(m_sharedImage->height, level);

	#if NAZARA_UTILITY_SAFE
	if (!box.IsValid())
	{
		NazaraError("Invalid box");
		return false;
	}

	unsigned int depth = (m_sharedImage->type == nzImageType_Cubemap) ? 6 : GetLevelSize(m_sharedImage->depth, level);
	if (box.x+box.width > width || box.y+box.height > height || box.z+box.depth > depth ||
	    (m_sharedImage->type == nzImageType_Cubemap && box.depth > 1)) // Nous n'autorisons pas de modifier plus d'une face du cubemap à la fois
	{
		NazaraError("Box dimensions are out of bounds");
		return false;
	}
	#endif

	EnsureOwnership();

	nzUInt8 bpp = NzPixelFormat::GetBytesPerPixel(m_sharedImage->format);
	nzUInt8* dstPixels = GetPixelPtr(m_sharedImage->pixels[level], bpp, box.x, box.y, box.z, width, height);

	Copy(dstPixels, pixels, bpp,
	     box.width, box.height, box.depth,
	     width, height,
	     srcWidth, srcHeight);

	return true;
}

bool NzImage::Update(const nzUInt8* pixels, const NzRectui& rect, unsigned int z, unsigned int srcWidth, unsigned int srcHeight, nzUInt8 level)
{
	return Update(pixels, NzBoxui(rect.x, rect.y, z, rect.width, rect.height, 1), srcWidth, srcHeight, level);
}

NzImage& NzImage::operator=(const NzImage& image)
{
	ReleaseImage();

	m_sharedImage = image.m_sharedImage;
	if (m_sharedImage != &emptyImage)
		m_sharedImage->refCount++;

	return *this;
}

void NzImage::Copy(nzUInt8* destination, const nzUInt8* source, nzUInt8 bpp, unsigned int width, unsigned int height, unsigned int depth, unsigned int dstWidth, unsigned int dstHeight, unsigned int srcWidth, unsigned int srcHeight)
{
	if (dstWidth == 0)
		dstWidth = width;

	if (dstHeight == 0)
		dstHeight = height;

	if (srcWidth == 0)
		srcWidth = width;

	if (srcHeight == 0)
		srcHeight = height;

	if ((height == 1 || (dstWidth == width && srcWidth == width)) && (depth == 1 || (dstHeight == height && srcHeight == height)))
		std::memcpy(destination, source, width*height*depth*bpp);
	else
	{
		unsigned int lineStride = width * bpp;
		unsigned int dstLineStride = dstWidth * bpp;
		unsigned int dstFaceStride = dstLineStride * dstHeight;
		unsigned int srcLineStride = srcWidth * bpp;
		unsigned int srcFaceStride = srcLineStride * srcHeight;

		for (unsigned int i = 0; i < depth; ++i)
		{
			nzUInt8* dstFacePtr = destination;
			const nzUInt8* srcFacePtr = source;
			for (unsigned int y = 0; y < height; ++y)
			{
				std::memcpy(dstFacePtr, srcFacePtr, lineStride);

				dstFacePtr += dstLineStride;
				srcFacePtr += srcLineStride;
			}

			destination += dstFaceStride;
			source += srcFaceStride;
		}
	}
}

nzUInt8 NzImage::GetMaxLevel(unsigned int width, unsigned int height, unsigned int depth)
{
	// Le niveau maximal est le niveau requis pour la plus grande taille
	return std::max(NzIntegralLog2(std::max({width, height, depth})), 1U);
}

nzUInt8 NzImage::GetMaxLevel(nzImageType type, unsigned int width, unsigned int height, unsigned int depth)
{
	// Pour éviter que la profondeur ne soit comptée dans le calcul des niveaux
	switch (type)
	{
		case nzImageType_1D:
		case nzImageType_1D_Array:
			return GetMaxLevel(width, 1U, 1U);

		case nzImageType_2D:
		case nzImageType_2D_Array:
		case nzImageType_Cubemap:
			return GetMaxLevel(width, height, 1U);

		case nzImageType_3D:
			return GetMaxLevel(width, height, depth);
	}

	NazaraError("Image type not handled (0x" + NzString::Number(type, 16) + ')');
	return 0;

}

void NzImage::EnsureOwnership()
{
	if (m_sharedImage == &emptyImage)
		return;

	if (m_sharedImage->refCount > 1)
	{
		m_sharedImage->refCount--;

		nzUInt8** pixels = new nzUInt8*[m_sharedImage->levelCount];
		for (unsigned int i = 0; i < m_sharedImage->levelCount; ++i)
		{
			unsigned int size = GetMemoryUsage(i);
			pixels[i] = new nzUInt8[size];
			std::memcpy(pixels[i], m_sharedImage->pixels[i], size);
		}

		m_sharedImage = new SharedImage(1, m_sharedImage->type, m_sharedImage->format, m_sharedImage->levelCount, pixels, m_sharedImage->width, m_sharedImage->height, m_sharedImage->depth);
	}
}

void NzImage::ReleaseImage()
{
	if (m_sharedImage == &emptyImage)
		return;

	if (--m_sharedImage->refCount == 0)
	{
		for (unsigned int i = 0; i < m_sharedImage->levelCount; ++i)
			delete[] m_sharedImage->pixels[i];

		delete[] m_sharedImage->pixels;
		delete m_sharedImage;
	}

	m_sharedImage = &emptyImage;
}

bool NzImage::Initialize()
{
	if (!NzImageLibrary::Initialize())
	{
		NazaraError("Failed to initialise library");
		return false;
	}

	if (!NzImageManager::Initialize())
	{
		NazaraError("Failed to initialise manager");
		return false;
	}

	return true;
}

void NzImage::Uninitialize()
{
	NzImageManager::Uninitialize();
	NzImageLibrary::Uninitialize();
}

NzImage::SharedImage NzImage::emptyImage(0, nzImageType_2D, nzPixelFormat_Undefined, 1, nullptr, 0, 0, 0);
NzImageLibrary::LibraryMap NzImage::s_library;
NzImageLoader::LoaderList NzImage::s_loaders;
NzImageManager::ManagerMap NzImage::s_managerMap;
NzImageManager::ManagerParams NzImage::s_managerParameters;
