/**
 * Copyright (c) 2006-2015 LOVE Development Team
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 **/

#include "DevilHandler.h"

// LOVE
#include "common/Exception.h"
#include "common/math.h"

// DevIL
#include <IL/il.h>

namespace love
{
namespace image
{
namespace magpie
{

static inline void ilxClearErrors()
{
	while (ilGetError() != IL_NO_ERROR);
}

DevilHandler::DevilHandler()
	: mutex(nullptr)
{
	// There should only ever be one DevilHandler object (owned by the Image
	// module), so we can use the global initialization function here.
	ilInit();
	ilEnable(IL_ORIGIN_SET);
	ilOriginFunc(IL_ORIGIN_UPPER_LEFT);
}

DevilHandler::~DevilHandler()
{
	ilShutDown();

	if (mutex)
		delete mutex;
}

bool DevilHandler::canDecode(love::filesystem::FileData * /*data*/)
{
	// DevIL can decode a lot of formats...
	return true;
}

bool DevilHandler::canEncode(ImageData::Format format)
{
	switch (format)
	{
	case ImageData::FORMAT_BMP:
	case ImageData::FORMAT_TGA:
	case ImageData::FORMAT_JPG:
	case ImageData::FORMAT_PNG:
		return true;
	default:
		return false;
	}

	return false;
}

DevilHandler::DecodedImage DevilHandler::decode(love::filesystem::FileData *data)
{
	if (!mutex)
		mutex = love::thread::newMutex();

	love::thread::Lock lock(mutex);

	ILuint image = ilGenImage();
	ilBindImage(image);

	DecodedImage img;

	try
	{
		bool success = ilLoadL(IL_TYPE_UNKNOWN, (void *)data->getData(), (ILuint) data->getSize()) == IL_TRUE;

		if (!success)
			throw love::Exception("Could not decode image!");

		img.width = ilGetInteger(IL_IMAGE_WIDTH);
		img.height = ilGetInteger(IL_IMAGE_HEIGHT);

		// Make sure the image is in RGBA format.
		ilConvertImage(IL_RGBA, IL_UNSIGNED_BYTE);

		// This should always be four.
		int bpp = ilGetInteger(IL_IMAGE_BPP);
		if (bpp != sizeof(pixel))
			throw love::Exception("Could not convert image!");

		img.size = (size_t) ilGetInteger(IL_IMAGE_SIZE_OF_DATA);

		try
		{
			img.data = new ILubyte[img.size];
		}
		catch (std::bad_alloc &)
		{
			throw love::Exception("Out of memory.");
		}

		memcpy(img.data, ilGetData(), img.size);
	}
	catch (std::exception &e)
	{
		// catches love and std exceptions
		ilDeleteImage(image);
		throw love::Exception("%s", e.what());
	}

	ilDeleteImage(image);

	return img;
}

DevilHandler::EncodedImage DevilHandler::encode(const DecodedImage &img, ImageData::Format format)
{
	if (!mutex)
		mutex = love::thread::newMutex();

	love::thread::Lock lock(mutex);

	ILuint tempimage = ilGenImage();
	ilBindImage(tempimage);
	ilxClearErrors();

	EncodedImage encodedimage;

	try
	{
		bool success = ilTexImage(img.width, img.height, 1, sizeof(pixel), IL_RGBA, IL_UNSIGNED_BYTE, img.data) ==  IL_TRUE;

		ILenum err = ilGetError();
		ilxClearErrors();

		if (!success)
		{
			if (err != IL_NO_ERROR)
			{
				switch (err)
				{
				case IL_ILLEGAL_OPERATION:
					throw love::Exception("Illegal operation");
				case IL_INVALID_PARAM:
					throw love::Exception("Invalid parameters");
				case IL_OUT_OF_MEMORY:
					throw love::Exception("Out of memory");
				default:
					throw love::Exception("Unknown error (%d)", (int) err);
				}
			}

			throw love::Exception("Could not create image for the encoding!");
		}

		ilRegisterOrigin(IL_ORIGIN_UPPER_LEFT);

		ILuint ilFormat;
		switch (format)
		{
		case ImageData::FORMAT_BMP:
			ilFormat = IL_BMP;
			break;
		case ImageData::FORMAT_TGA:
			ilFormat = IL_TGA;
			break;
		case ImageData::FORMAT_JPG:
			ilFormat = IL_JPG;
			break;
		case ImageData::FORMAT_PNG:
		default: // PNG is the default format
			ilFormat = IL_PNG;
			break;
		}

		encodedimage.size = ilSaveL(ilFormat, NULL, 0);
		if (!encodedimage.size)
			throw love::Exception("Could not encode image!");

		try
		{
			encodedimage.data = new ILubyte[encodedimage.size];
		}
		catch(std::bad_alloc &)
		{
			throw love::Exception("Out of memory");
		}

		ilSaveL(ilFormat, encodedimage.data, encodedimage.size);
	}
	catch (std::exception &e)
	{
		// Catches love and std exceptions.
		ilDeleteImage(tempimage);
		delete[] encodedimage.data;
		encodedimage.data = 0;
		throw love::Exception("%s", e.what());
	}

	ilDeleteImage(tempimage);

	return encodedimage;
}

} // magpie
} // image
} // love
