#include "TextureManager.h"

#include "Utilities/Log.h"
#include "Utilities/format.h"

// library includes
//#include "png.h"
#include "physfs.h"
#include "corona.h"
#include "GL/glew.h"

// system includes
#include <fstream>
#include <limits.h>

using namespace std;

namespace BlueCore
{

//----------------------------------------------------------------------------------
TextureManager::TextureManager()
{
	_maxAnisotopy = 128.0;
	_maxTextureSize = 1024 * 16;
	_lodDrop = 0;
	_textureCompression = true;

	glewInit();

	if (GLEW_ARB_texture_compression == false)
		_textureCompression = false;

	clog << ">>> TextureManager constructed ..."<< endline;
	if (_textureCompression)
		clog << "    using texture compression"<< endline;
	else
		clog << "    texture compression not supported!"<< endline;

}

//------------------------------------------------------------------------------
TextureManager::~TextureManager()
{
	clog << ">>> TextureManager destructed ..."<< endline;
}

Texture::~Texture()
{
	glDeleteTextures( 1, &_Id);
	clog << ">>> Texture destructed ..."<< endline;
}
/*
 //------------------------------------------------------------------------------
 void TextureManager::initializeSingleton()
 {

 XmlConfig config("config.xml");

 _lodDrop = 0;

 config.getBool("TextureManager", "TextureCompression", _textureCompression,
 true);

 if (GLEW_ARB_texture_compression == false)
 _textureCompression = false;

 if (_textureCompression )
 clog << "    using texture compression"<< endline;
 else
 clog << "    texture compression not supported!"<< endline;

 config.getUInt("TextureManager", "MaxTextureSize", _maxTextureSize, 8192);

 GLint maxTextureSize;

 glGetIntegerv (GL_MAX_TEXTURE_SIZE, &maxTextureSize );

 if (_maxTextureSize > (unsigned int)maxTextureSize )
 _maxTextureSize = maxTextureSize;

 clog << "    maximum texture size: "<< _maxTextureSize << endline;

 config.getFloat("TextureManager", "MaxAnisotopy", _maxAnisotopy, 128.0);

 float maxAnisotopy = 0.0;

 if (GLEW_EXT_texture_filter_anisotropic )
 glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotopy );

 if (_maxAnisotopy > maxAnisotopy )
 _maxAnisotopy = maxAnisotopy;

 if (_maxAnisotopy > 1.0)
 clog << "    using anisotopic texture filtering, level: "<< _maxAnisotopy
 << endlog;
 else
 clog << "    not using anisotopic texture filtering."<< endlog;
 }
 //------------------------------------------------------------------------------
 void TextureManager::shutdownSingleton()
 {
 clog << ">>> shutdown TextureManager..."<< endline;

 clearTextures();
 }
 */

//------------------------------------------------------------------------------
bool TextureManager::saveToCache(const std::string &filename, int levels)
{
	unsigned char *data = 0;
	GLint internalformat, compressed_size = 0, width = 0, height = 0, level = 0;

	PHYSFS_file *file = PHYSFS_openWrite(filename.c_str() );

	if ( !file)
	{
		return false;
	}

	level = 0;

	while ( !PHYSFS_eof(file) )
	{
		glGetTexLevelParameteriv(GL_TEXTURE_2D, level,
				GL_TEXTURE_INTERNAL_FORMAT, &internalformat);

		glGetTexLevelParameteriv(GL_TEXTURE_2D, level,
				GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB, &compressed_size);

		if (compressed_size == 0)
			break;

		glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &width);

		if (width == 0)
			break;

		glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_HEIGHT,
				&height);

		if (height == 0)
			break;

		glGetTexLevelParameteriv(GL_TEXTURE_2D, level,
				GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB, &compressed_size);

		data = new unsigned char[ compressed_size ];

		glGetCompressedTexImageARB(GL_TEXTURE_2D, level, data);

		PHYSFS_write(file, &internalformat, sizeof (internalformat ), 1);
		PHYSFS_write(file, &level, sizeof (level ), 1);
		PHYSFS_write(file, &width, sizeof (width ), 1);
		PHYSFS_write(file, &height, sizeof (height ), 1);
		PHYSFS_write(file, &compressed_size, sizeof (compressed_size ), 1);
		PHYSFS_write(file, data, compressed_size, 1);

		delete [] data;

		level += 1;

		if ( (width == 1 ) && (height == 1 ))
			break;

		if (level >= levels)
			break;
	}

	PHYSFS_close(file);

	return true;
}

//------------------------------------------------------------------------------
bool TextureManager::loadFromCache(const std::string &filename,
		unsigned int &max_width, unsigned int &max_height)
{
	unsigned char *data = 0;
	GLuint internalformat, compressed_size, width, height, level;

	max_width = 0;
	max_height = 0;

	PHYSFS_file *file = PHYSFS_openRead(filename.c_str() );

	if ( !file)
	{
		return false;
	}

	while ( !PHYSFS_eof(file) )
	{
		PHYSFS_read(file, &internalformat, sizeof (internalformat ), 1);
		PHYSFS_read(file, &level, sizeof (level ), 1);
		PHYSFS_read(file, &width, sizeof (width ), 1);
		PHYSFS_read(file, &height, sizeof (height ), 1);
		PHYSFS_read(file, &compressed_size, sizeof (compressed_size ), 1);

		if ( !data)
			data = new unsigned char[ compressed_size ];

		PHYSFS_read(file, data, compressed_size, 1);

		glCompressedTexImage2DARB(GL_TEXTURE_2D, level, internalformat, width,
				height, 0, compressed_size, data);

		max_width = max(width, max_width);
		max_height = max(height, max_height);

		if (width == 1 && height == 1)
			break;
	}

	delete [] data;

	PHYSFS_close(file);

	return true;
}

//------------------------------------------------------------------------------
Texture *TextureManager::loadTexture(const std::string &filename,
		int mipmapLevel, int compressionLevel)
{
	if (filename.empty() )
		return 0;

	std::string cachename = filename;
	cachename += "_";
	cachename += format("%d", mipmapLevel);
	cachename += "_";
	cachename += format("%d", compressionLevel);
	cachename += ".cache";

	TextureMap::const_iterator result;
	result = textures.find(filename);

	if (result != textures.end() )
	{
		return result->second;
	}

	GLint UnpackAlignment;

	// Set unpack alignment to one byte
	glGetIntegerv(GL_UNPACK_ALIGNMENT, &UnpackAlignment);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	unsigned int id = 0;
	glGenTextures( 1, &id);
	glBindTexture(GL_TEXTURE_2D, id);

	if (mipmapLevel == 0)
	{
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	}
	else
	{
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
				GL_LINEAR_MIPMAP_NEAREST);
	}

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	if (_maxAnisotopy > 0.0)
	{
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT,
				( float ) _maxAnisotopy );
	}

	Texture *t = 0;
	unsigned int width = 0, height = 0;

	// check for cached texture
	if (_textureCompression && (compressionLevel > 0))
	{
		PHYSFS_sint64 mod_file = PHYSFS_getLastModTime(filename.c_str() );
		PHYSFS_sint64 mod_config = PHYSFS_getLastModTime("config.xml");

		PHYSFS_sint64 mod_cache = PHYSFS_getLastModTime(cachename.c_str() );

		if ( (mod_cache > mod_file) && (mod_cache > mod_config))
		{
			if (loadFromCache(cachename, width, height) )
			{
				glPixelStorei(GL_UNPACK_ALIGNMENT, UnpackAlignment);
				clog << ">>> Texture '" << filename << "' loaded from cache ("
						<< width << ", " << height << ")." << endline;
				t = new Texture( id, width, height );
				textures[filename] = t;
				return t;
			}
		}
	}

	//clog << ">>> Texture '" << filename << "': loading from file..." << endline;
	corona::Image *image = corona::OpenImage(filename.c_str(),
			corona::PF_R8G8B8A8);
	if (image)
	{
		int level = 0;
		unsigned int width_scaled = image->getWidth();
		unsigned int height_scaled = image->getHeight();

		corona::FlipImage(image, corona::CA_X);
		// create mipmaps
		while ( 1)
		{
			if ( (width_scaled <= _maxTextureSize ) && (height_scaled
					<= _maxTextureSize ))
			{
				width = max(width_scaled, width);
				height = max(height_scaled, height);

				if (_textureCompression && (compressionLevel > 0))
				{
					glHint(GL_TEXTURE_COMPRESSION_HINT_ARB, GL_NICEST);

					GLuint compression = GL_COMPRESSED_RGBA_ARB;
					if (GLEW_EXT_texture_compression_s3tc)
					{
						if (compressionLevel == 1)
							compression = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
						else if (compressionLevel == 2)
							compression = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
						else
							compression = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
					}

					glTexImage2D(GL_TEXTURE_2D, level, compression,
							width_scaled, height_scaled, 0, GL_RGBA,
							GL_UNSIGNED_BYTE, ( void* ) image->getPixels() );
				}
				else
				{
					glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, width_scaled,
							height_scaled, 0, GL_RGBA, GL_UNSIGNED_BYTE,
							( void* ) image->getPixels() );
				}

				level += 1;
			}

			if ( (width_scaled == 1 ) && (height_scaled == 1 ))
				break;

			if ( (mipmapLevel >= 0 ) && (level >= mipmapLevel ))
				break;

			// rescale image
			int halfwidth = width_scaled > 1 ? width_scaled / 2 : 1;
			int halfheight = height_scaled > 1 ? height_scaled / 2 : 1;
			int idx1 = width_scaled * 4;
			int idx2 = (width_scaled + 1 ) * 4;
			unsigned char *dst = (unsigned char *)image->getPixels();
			unsigned char *src = (unsigned char *)image->getPixels();

			for (int y = 0; y < halfheight; y++)
			{
				for (int x = 0; x < halfwidth; x++)
				{
					for (int k = 0; k < 4; k ++)
					{
						*dst ++ = ( GLubyte ) ( ( ( int ) *src + ( int ) src[4]
								+ ( int ) src[idx1] + ( int ) src[idx2] + 2 )
								>> 2 );
						src ++;
					}

					src += 4;
				}

				src += 4 * width_scaled;
			}

			width_scaled = halfheight;
			height_scaled = halfwidth;
		}

		delete image;

		if (_textureCompression && (compressionLevel > 0))
			saveToCache(cachename, level);

		t = new Texture( id, width, height );
		clog << ">>> Texture '" << filename << "': loaded from file" << endline;
	}
	else
	{
		glDeleteTextures( 1, &id);
		t = new Texture( 0, 0, 0 );
		clog << "!!! Texture '" << filename << "' not found!" << endline;
	}

	glPixelStorei(GL_UNPACK_ALIGNMENT, UnpackAlignment);

	textures[filename] = t;

	return t;
}
/*
 //------------------------------------------------------------------------------
 void TextureManager::releaseTexture ( const Texture *texture )
 {
 Texture *t = (Texture *)texture;
 
 if( t->_RefCount > 1 )
 t->_RefCount--;
 else if( t->_RefCount == 1 )
 {
 glDeleteTextures ( 1, &t->_Id );
 delete t;
 
 TextureMap::iterator i;
 for( i = textures.begin(); i != textures.end(); i++ )
 {
 if( (*i).second == t )
 {
 textures.erase( i );
 break;
 }
 }
 }
 
 }
 */
//------------------------------------------------------------------------------
void TextureManager::TextureDestroySlot(Referenced *referenced)
{
	TextureMap::iterator i;

	for (i = textures.begin(); i != textures.end(); i++)
	{
		Texture *t = (*i).second;
		if (t == (Texture*)referenced)
		{
			textures.erase(i);
			break;
		}
	}
}
}
