/*********************************************************************
*	Textures Library
*	SOURCE FILE
*	Autor: Michal Jirous
*	Datum: 10.10.2008
*	Soubor: textureslib.cpp
*	Popis: Knihovna texture. Dokaze nacitat nejruznejsi formaty obrazku
*			a vytvaret z nich textury. Dale umoznuje nacitat soubory
*			typu WAD3, coz je databaze textur, kde kazda textura ma
*			identifikacni jmeno.
**********************************************************************/

#include "textureslib.h"
#include <fstream>
#include <SDL/SDL_image.h>

//#define SAFE
//#define PHOBIA_CONSOLE

#ifdef PHOBIA_CONSOLE
#include "sys_console.h"
using namespace systemConsole;
#define PRINT_ERROR(error)	console << error << endline
#else
#include <iostream>
#define PRINT_ERROR(error)	cerr << error << endl
#endif

using namespace std;
TexturesLibrary textureLibrary;

/*********************************
*	TEXTUROVACI JEDNOTKA
**********************************/

/* pocatecni inicializace promennych */
void TextureElement::init()
{
	texture_id = 0;
	shared_index = 0;
	additInfo = NULL;
	animNext = NULL;
	switcher = NULL;
}

TextureElement::TextureElement()
{
	init();
}

TextureElement::TextureElement( std::string key )
{
	init();
	this->key = key;
}

TextureElement::~TextureElement()
{
	if( additInfo )
		delete additInfo;
}

//dodatecne informace
AdditionalTexInfo::AdditionalTexInfo()
{
	wrap_s = wrap_t = DEFAULT_WRAP;
	compilationTypeMag = DEFAULT_MAG_COMPILATION;
	compilationTypeMin = DEFAULT_MIN_COMPILATION;
}

/*! Funce nastavi promennou filename a #ImageInfo::type, kam se dosadi hodnota z vyctu #picture_types podle pripony souboru.
*	@param sFilename Cesta k souboru obrazku.
*/
void ImageInfo::setFilename( std::string sFilename )
{
	size_t index = sFilename.find_last_of( '.' ) + 1;
	if( index < sFilename.length() && index > 0 )
	{
		string ext = sFilename.substr( index );
		if( ext == "wad" )
			type = PICTURE_WAD3;
		else if( ext == "tga" )
			type = PICTURE_TGA;
		else if( ext == "raw" )
			type = PICTURE_RAW;
		else
			type = PICTURE_DEFAULT;
	}
	filename = sFilename;
}

std::string ImageInfo::getFilename()
{ 
	return filename; 
}

//////////////////////////////////////////////////////////////
//
//	KNIHOVNA TEXTUR
//
//////////////////////////////////////////////////////////////

TexturesLibrary::TexturesLibrary()
{
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glPixelStorei( GL_PACK_ALIGNMENT, 1 );
}


/*! Funkce nacte jmena a offsety textur ulozenych v souboru typu WAD3. Nejprve se nacte hlavicka, cimz se zjisti pocet zaznamu a offset k nim.
*	Pak uz se pouze ve smycce nacitaji jednotlive zaznamy a ukladaji do databaze tak, ze se jeste kontroluji mozne navaznosti animace. */
bool TexturesLibrary::loadWAD3(const char *filename)
{
	ifstream fin( filename, ios::in | ios::binary );
	if( !fin.is_open() )
	{
		PRINT_ERROR( "Error: WAD3 File '" << filename << "' not found" );
		return false;
	}

	//TODO porovnani jestli jde o WAD3	

	//nacteni hlavicky souboru
	Wad3Header header;
	fin.read( (char*)&header, sizeof(Wad3Header) );

	//presun na seznam inforamci o ulozenych texturach
	fin.seekg( header.infotableofs );

	//postupne nacitani vlastosti textur
	Wad3FileInfo file;
	TextureElement *texture;
	for( int i = 0; i < header.numlumps; i++ )
	{
		//nacteni jedne z LUMP
		fin.read( (char*)&file, sizeof(file) );	
		if( file.compression )	//pouze bez komprese
			continue;

		//prevod na velka pismena kvuli synchronizaci s modulem nacitani map
		std::string name = file.name;	//nyni chceme lower case
		for( size_t i = 0; i < name.length(); i++ )
			name.at(i) = tolower( name.at(i) );

		texture = addEntry( name );	//vytvoreni zaznamu v databazi
		if( texture )
		{
			//nastaveni parametru
			texture->image.fileoffset = file.filepos;
			texture->image.setFilename( filename );
			//texture->image.type = PICTURE_WAD3;		//timto se rozlisi typ pro pozdejsi nacitani
		}
	}

	fin.close();

	console << "WAD3 database file '" << filename << "' was successfully loaded" << endline;
	return true;
}

/*! 	Nacte pixely. Funkce vola TextureLibrary::loadPixels( ImageInfo &image ), kde za parametr image dosazuje promennou image vlastniho parametru element.
*	@param element Vstupni vlastnosti obrazku ( v promenne image by mela byt nastavena cesta k souboru, typ a offset). 
*	@return Vysledne pixely.
*/
unsigned char* TexturesLibrary::loadPixels( TextureElement *element )
{
	if( element )
		return loadPixels( element->image );
	return NULL;
}

//funkce spocita velikost zapsanych dat pixelu dle sirky a vysky
inline int mipmaps_size( int w, int h )
{
	return ( w*h + w*h/4 + w*h/16 +w*h/64 );
}

//funkce nacte obrazek ze souboru typu WAD3
unsigned char* TexturesLibrary::loadPixelsFromWAD3MipMap( SDL_RWops *rw, ImageInfo &image )
{
	//nacteni hlavicky obrazku
	Wad3MipMap header;	
	SDL_RWread( rw, (char*)&header,  sizeof(Wad3MipMap), 1 );
	
	//vytvoreni pole pixelu
	unsigned char *pixels = new unsigned char[header.width*header.height];
	
	if( !pixels )
	{
		PRINT_ERROR( "Memory could not be allocated for image " << image.getFilename() );
		return NULL;
	}

	//posun na pozici nejvetsi mipmapy
	SDL_RWseek( rw,  image.fileoffset + header.offsets[0], 0 );

	//nacteni indexu do palety
	SDL_RWread( rw, (char*)pixels, 1, header.width*header.height );	//nacteme si data
	
	//vypocitame offset pallety a posuneme se na ni
	int pallete_offset = sizeof(Wad3MipMap) + mipmaps_size(header.width,header.height);
	SDL_RWseek( rw,  image.fileoffset + pallete_offset, 0 );
	
	//zjistime velikost palety
	short palletesize = 0;
	SDL_RWread( rw, (char*)&palletesize, sizeof(short), 1  );

	//nacteme paletu, ktera je RGB = 3 Byty
	unsigned char *pallete = new unsigned char[palletesize*3];
	SDL_RWread( rw, (char*)pallete, 3, palletesize );

	//nakonec musime pomoci indexu a palety vytvorit pixely
	unsigned char* result = new unsigned char[header.width*header.height*3];	
	//vypocitame vysledne pixely
	for( unsigned int i = 0; i < header.width*header.height; i++ )
	{
            
#ifdef SAFE
	memcpy_s( &result[i*3], 3, &pallete[ pixels[i]*3 ], 3 );	
#else
        memcpy( &result[i*3], &pallete[ pixels[i]*3 ], 3 );
#endif
	}

	//uklid
	delete [] pallete;
	delete [] pixels;

	//nastaveni parametru
	image.width = header.width;
	image.height = header.height;
	image.bpp = 24;

	return result;
}

/*! Nacte pixely z objektu SDL_Surface, kde jsou ulozeny informace o pixelech, rozmerech i bitove hloubce.
*	@param surface Objekt SDL_Surface, ze ktereho funkce vztahne pixely. 
*	@param image Informace o objektu, ktere funkce ve svem prubehu nastavi.
*	@return Vysledne pixely.
*/
unsigned char* TexturesLibrary::loadPixelsFromSDLSurface( SDL_Surface *surface, ImageInfo &image )
{
	Uint32 saved_flags;	// Pomocne pro ulozeni flagu
	Uint8  saved_alpha;
	SDL_Surface *tmp_img;	// Obrazek pro vytvoreni textury

	// Vytvori prazdny RGB surface
	tmp_img = SDL_CreateRGBSurface(
			SDL_SWSURFACE,		// Softwarovy surface
			surface->w, surface->h,	// Sirka, vyska
			32,			// Barevna hloubka
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
			0x000000FF,		// OpenGL RGBA maska
			0x0000FF00,
			0x00FF0000,
			0xFF000000
#else
			0xFF000000,
			0x00FF0000,
			0x0000FF00,
			0x000000FF
#endif
			);

	if(tmp_img == NULL)
	{
		PRINT_ERROR( "Could not create copy of SDL_Surface for image " << image.getFilename() );
		return NULL;
	}
	// Ulozi atributy alfa blendingu
	saved_flags = surface->flags & (SDL_SRCALPHA|SDL_RLEACCELOK);
	saved_alpha = surface->format->alpha;

	if((saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA)
		SDL_SetAlpha(surface, 0, 0);

	// Zkopiruje surface do obrazku pro texturu
	SDL_Rect area;
	area.x = 0;
	area.y = 0;
	area.w = surface->w;
	area.h = surface->h;
	SDL_BlitSurface(surface, &area, tmp_img, &area);

	// Obnovi atributy alfa blendingu
	if((saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA)
		SDL_SetAlpha(surface, saved_flags, saved_alpha);

	//Vlastni kod nastavuje parametry
	image.width = tmp_img->w;
	image.height = tmp_img->h;
	image.bpp = 32;
	unsigned int size = image.width*image.height*image.bpp/8;
	unsigned char* result = new unsigned char[size];
	//nakonec zkoporujeme pixely do vysledku
#ifdef SAFE
	memcpy_s( result, size, tmp_img->pixels, size );
#else
	memcpy( result, tmp_img->pixels, size );
#endif
	
	SDL_FreeSurface(tmp_img);
	return result;
}


//Funkce nacita obrazky pomoci SDL_image knihovny, takze podporovane formaty zalezi pouze na teto knihovne
unsigned char* TexturesLibrary::loadPixelsFromDefaultType( SDL_RWops *rw, ImageInfo &image )			//nacte pixely dle parametru v ImageInfo
{
    if( !rw )
        return NULL;
    unsigned char* pixels = NULL;
    SDL_Surface *pImage;

	//ve specifikaci knihovny je uvedeno, ze TGA obrazek nerozlisi pri pouziti SDL_RWops
	//takze je nutne to osetrit touto cestou
    if( image.type == PICTURE_TGA )
		pImage = IMG_LoadTGA_RW( rw );
    else
		pImage = IMG_Load_RW( rw, 0 );
    if( pImage )
    {
		//nacteme pixely
		pixels = loadPixelsFromSDLSurface( pImage, image );

		SDL_FreeSurface( pImage );	
    }
    return pixels;
}


/*! 	Nacte pixely podle parametru image. Dle promenne ImageInfo::filename parametru image se vytvori objekt SDL_RWops, ktery se predava funkci
*	TextureLibrary::loadPixelsRW( rw, image ) spolecne s parametrem image.
*	@param image Vstupni parametry obrazku (Mel by mit nastavenou cestu k souboru, typ a offset). 
*	@return Vysledne pixely. 
*/
unsigned char* TexturesLibrary::loadPixels( ImageInfo &image )
{
	unsigned char* pixels = NULL;
	SDL_RWops *rw = SDL_RWFromFile( image.getFilename().c_str(), "r" );	//vytvorime RWops
	if( rw )
	{
		SDL_RWseek( rw, image.fileoffset, 0 );	//posuneme se na cilovou pozici
			pixels = loadPixelsRW( rw, image );
		SDL_RWclose( rw );
	}

	return pixels;
}

/*!	Nacita pixely z pozice definovane parametrem typu SDL_Rwops. Vysledne informace o obrazku (vyska, sirka, bitova hloubka) se ukladaji do parametru image.
*	V prubehu teto funkce se testuje typ obrazku dle promenne ImageInfo::type parametru image a volaji se prislusne vnitrni funkce. Pokud je hodnota rovna
*	picture_types::PICTURE_WAD3, tak
*	se vola funkce TextureLibrary::loadPixelsFromWAD3MipMap( rw, image ), pokud rovna picture_types::PICTURE_RAW, tak funkce TextureLibrary::loadPixelsFromRAW( rw, *	image ). Jinak se pokracuje volanim TextureLibrary::loadPixelsFromDefaultType( rw, image ).
*	@param rw Objekt typu SDL_RWops, ktery je ukazatelem do pameti nebo souboru, kde jsou ulozena cilova data pro nacitani
*	@param image Objekt, kam se ulozi vysledne parametry obrazku.
*	@return Vysledne pixely.
*/
unsigned char* TexturesLibrary::loadPixelsRW( SDL_RWops *rw,  ImageInfo &image )
{
	if( !rw )
		return NULL;
	unsigned char* pixels = NULL;

	//rozlisujeme nacitani podle techto typu obrazku
	switch( image.type )
	{
		case PICTURE_WAD3:
			pixels = loadPixelsFromWAD3MipMap( rw, image );
			break;
		case PICTURE_RAW:
			pixels = loadPixelsFromRAW( rw, image );
			break;
		default:
			pixels = loadPixelsFromDefaultType( rw, image );
	    
	}
        
	return pixels;
}

//nacteni obrazku z nejjednodussiho formatu 3 udaje o parametrech a vlastni pixely
unsigned char* TexturesLibrary::loadPixelsFromRAW( SDL_RWops *rw, ImageInfo &image )
{
	unsigned char* pixels = NULL;

	if( rw )
	{	
		//nacteni parametru
		SDL_RWread( rw, (char*)&image.width, sizeof(int), 1 );
		SDL_RWread( rw, (char*)&image.height, sizeof(int), 1 );
		SDL_RWread( rw, (char*)&image.bpp, sizeof(int), 1 );

		unsigned int size = image.width * image.height * image.bpp;
		pixels = new unsigned char[size];
		if(pixels == 0)
		{
			PRINT_ERROR( "Error: Memory allocation for texture '" << image.getFilename() << "' failed." );
			return NULL;
		}

		// load the texture
		SDL_RWread(rw, pixels, size, 1);

		image.bpp *= 8;
	}
	return pixels;
}

/*! Funkce prohodi radky obrazku a tim zmeni bod [0,0]. Pouziva pomocne pole o velikosti celeho radku k docasnemu ulozeni radku pri prohazovani.
*	@param pixels Pixely, kde se maji radky prohodit.
*	@param image Vlastnosti obrazku, podle kterych k prohazovani dochazi (sirka, vyska, bitova hloubka).
*/
void TexturesLibrary::swapRows( unsigned char* pixels, ImageInfo &image )
{
	if( pixels )
	{
		unsigned int bytesPerRow = (image.width*image.bpp/8);	//pocet Bytu na radek
		
		//ukazatel na cilovy horni radek
		unsigned char* topIndex = &pixels[ bytesPerRow*(image.height-1)];

		//ukazatel na cilovy dolni radek
		unsigned char* bottomIndex = &pixels[ 0 ];
		unsigned char* tmpField = new unsigned char[bytesPerRow];	//pomocne pole pro kopirovani
		for( unsigned int i = 0; i < image.height / 2; i++ )
		{
			//prochazime polovinu poctu radku a prohazujeme je
#ifdef SAFE
			memcpy_s( tmpField, bytesPerRow, topIndex, bytesPerRow );
			memcpy_s( topIndex,  bytesPerRow,bottomIndex, bytesPerRow );
			memcpy_s( bottomIndex,  bytesPerRow,tmpField, bytesPerRow );
#else
			memcpy( tmpField, topIndex, bytesPerRow );
			memcpy( topIndex, bottomIndex, bytesPerRow );
			memcpy( bottomIndex, tmpField, bytesPerRow );
#endif
			//posun indexu o radek
			bottomIndex += bytesPerRow;
			topIndex -= bytesPerRow;
		}

		delete [] tmpField;
	}
}

/*! Funkce vraci ukazatel na texturovaci jednotku dle klice. Knihovna textur pouziva k ukladani strukturu std::map, takze vyhledavani se provadi pomoci
*	funkci find. Diky tomuto zpusobu je slozitost teto operace O( log( n ) ), a proto je relativne rychla.
*	@param key Klic textury, podle ktereho se vyhledava.
*	@return Ukazatel na prvek databaze s pozadovanym klicem nebo NULL v pripade nenalezeni.
*/
TextureElement *TexturesLibrary::getTexture( std::string key )
{
	if( m_TexturesMap.empty() )
		return NULL;
	map<string,TextureElement>::iterator iter = m_TexturesMap.find( key );
	if( iter != m_TexturesMap.end() )
		return &iter->second;
	
	for( size_t i = 0; i != key.length(); ++i )
	{
		char &c = key[i];
		if( isalpha(c) )
			c = tolower( c );
	}

	iter = m_TexturesMap.find( key );
	if( iter != m_TexturesMap.end() )
		return &iter->second;

	return NULL;

}

/*! Funkce vraci ukazatel na texturovaci jednotku podle OpenGL ID textury. V tomto pripade se musi sekvence prochazet cela databaze a v pripade obrovske databaze
*	je tato operace velice pomala - slozitost O(n). Proto se nedoporucuje tuto funkci pouzivat.
*	@param texture_id OpenGL ID textury, ktera ma byt nalezena.
*	@return Ukazatel na prvek databaze s pozadovanym klicem nebo NULL v pripade nenalezeni.
*	@warning Slozitost je rovna O( n )!!!!
*/
//Funkce vraci ukazatel na texturovaci jednotku dle ID textury
TextureElement *TexturesLibrary::getTexture( unsigned int texture_id )
{
	if( m_TexturesMap.empty() )
		return NULL;
	for( map<string,TextureElement>::iterator iter = m_TexturesMap.begin(); iter != m_TexturesMap.end(); iter++ )
	{
		if( iter->second.texture_id == texture_id )
			return &iter->second;	
	}

	return NULL;
}

//Funkce nacte texturu do pameti
TextureElement * TexturesLibrary::loadTexture( TextureElement *element )
{
	//nacteme pixely ze souboru
	if( element )
	{
		unsigned char* pixels = loadPixels( element );	//nacteni pixelu
		//musime prohodit radky
		swapRows( pixels, element->image );

		//konverze textur, ktere maji byt pruhledne
		if(!element->key.empty() && element->key.at(0) == '{')	
			pixels = make_it_transparent( pixels, element );
		element = createTexture( element, pixels );	//vytvoreni textury
		delete [] pixels;
	}
	return element;
}

/*! Vytvari novou texturu na zaklade predanych parametru. Nejprve se testuje bitova hloubka #ImageInfo::bpp. Pokud je mensi nez 24, tak se pixely mazou a funkce konci *	chybou. V opacnem pripade se dale testuji dodatecne informace o texture TextureElement::additInfo. Pokud existuji, tak se berou parametry z tohoto objektu. 
*	V opacnem pripade se nastavi standardni parametry #DEFAULT_WRAP, #DEFAULT_MIN_COMPILATION, #DEFAULT_MAG_COMPILATION. Pote se vytvori textura volanim
*	gluBuild2DMipmaps.
*	@param element Vlastnosti obrazku a cil ulozeni OpenGL ID textury.
*	@param pixels Pixely obrazku, ze ktereho se ma textura vyrobit.
*	@return Ukazatel na prvek textury. Stejny jako vstupni parametr element. V pripade chyby NULL.
*/
TextureElement * TexturesLibrary::createTexture( TextureElement *element, unsigned char* pixels )
{
	if( pixels && element )
	{
		if( element->image.bpp < 24 || element->shared_index > 0 || element->texture_id != 0 )
		{
			PRINT_ERROR( "Error while loading texture '" << element->key << "': Already loaded or has bpp less than 24." );
			//delete [] pixels;
			return NULL;
		}

		int iType = 1;
		switch(element->image.bpp)
		{
			case 32:
				iType = GL_RGBA;
				break;
			case 24:
			default:
				iType = GL_RGB;
		}

		//int iComponents = element->image.bpp / 8;

		glGenTextures( 1, &element->texture_id );
		glBindTexture(GL_TEXTURE_2D, element->texture_id);			// Bind Our Texture
		

		int wrap_s, wrap_t, mincomp, maxcomp;

		if( element->additInfo )
		{
			wrap_s =  element->additInfo->wrap_s;
			wrap_t =  element->additInfo->wrap_t;
			mincomp =  element->additInfo->compilationTypeMin;
			maxcomp =  element->additInfo->compilationTypeMag;
		}
		else
		{
			//standardni parametry
			wrap_s = wrap_t = DEFAULT_WRAP;
			mincomp = DEFAULT_MIN_COMPILATION;
			maxcomp = DEFAULT_MAG_COMPILATION;
		}
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mincomp );	// Linear Filtered
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, maxcomp );	// Linear Filtered

		glTexParameteri( GL_TEXTURE_2D,
						 GL_TEXTURE_WRAP_S,
						 wrap_s );
		glTexParameteri( GL_TEXTURE_2D,
						 GL_TEXTURE_WRAP_T,
						 wrap_t );
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
		glPixelStorei( GL_PACK_ALIGNMENT, 1 );
		gluBuild2DMipmaps( GL_TEXTURE_2D, element->image.bpp>>3, element->image.width, element->image.height, iType, GL_UNSIGNED_BYTE, pixels);

		element->shared_index = 1;
		return element;
	}
	else
		return NULL;
}


/*! Funkce zjisti ukazatel na prvek databaze dle parametru key a vola funkci TextureLibrary::applyTexture( TextureElement *element ).
*	Pri nacitani se testuje hodnota promenne #TextureElement::texture_id. Pokud neni rovno nule, tak to znamena, ze neni ulozena v pameti graficke karty,
*	takze se musi nejdrive nacist. Pokud neni rovno nule, tak se pouze zvysi promenna #TextureElement::shared_index. Soucasti teto funkce je i nacitani ostatnich
*	textur ve shluku animaci.
*	@param key Klic textury, kterou chceme pouzit.
*	@return TextureElement s klicem, ktery byl zadan parametrem, pokud probehla operace bez chyb. V opacnem pripade NULL.
*/
TextureElement *TexturesLibrary::applyTexture( std::string key )
{
	return applyTexture( getTexture( key ) );
}

/*! Funkce nacita textury dle parametru. Pri nacitani se testuje hodnota promenne #TextureElement::texture_id. Pokud neni rovno nule, tak to znamena, ze neni ulozena v *	pameti graficke karty, takze se musi nejdrive nacist. Pokud neni rovno nule, tak se pouze zvysi promenna #TextureElement::shared_index. Soucasti teto funkce je
*	i nacitani ostatnich textur ve shluku animaci.
*	@param element Prvek s informacemi o texture, ktery chceme pouzit.
*	@return TextureElement, ktery byl zadan parametrem, pokud probehla operace bez chyb. V opacnem pripade NULL.
*/
TextureElement *TexturesLibrary::applyTexture( TextureElement *element )
{
	//zde nacteme texturu do pameti
	if( element )
	{

		//pro animovane textury je treba nacist vsechny soucasti animace
		if( element->switcher && element->switcher != element )	//animace po prepnuti
		{
			if( !proceedTextureAnimationLoad( element->switcher ) )
				return NULL;
		}
	
		//i zakladni animace
		if( !proceedTextureAnimationLoad( element ) )
			return NULL;
	}
	return element;
}

/*! Funkce nacita textury dle SDL_RWops a typu cilovych dat. O animace se tato funkce nestara. Vytvari novy objekt TextureElement a vola funkci
*	TextureLibrary::applyTexture( TextureElement *, SDL_RWops * )
*	@param rw Objekt SDL_RWops ukazujici na data obrazku.
*	@param type Typ obrazku z vyctu #picture_types.
*	@return TextureElement, ktery byl vytvoren, pokud probehla operace bez chyb. V opacnem pripade NULL.
*/
TextureElement *TexturesLibrary::applyTexture( SDL_RWops *rw, int type )
{
	TextureElement * newTexture = new TextureElement();
        newTexture->image.type = type;
	if( applyTexture( newTexture, rw ) )
		return newTexture;
	
	delete newTexture;
	return NULL;
}

/*! Funkce nacita textury dle SDL_RWops a vlastnosti o obrazku v TextureElement parametru. O animace se tato funkce nestara. Vola funkci 
*	loadPixelsRW( SDL_RWops*, ImageInfo&). Pote prohazuje radky a nakonec provadi pruhlednost vybranych pixelu funkci. Nakonec vytvari texturu a maze nactene pixely.
*	@param element Vlastnosti obrazku. Hlavne #TextureElement::image, kde je ulozen typ obrazku.
*	@param rw Objekt SDL_RWops ukazujici na data obrazku.
*	@return TextureElement, ktery byl dan parametrem, pokud probehla operace bez chyb. V opacnem pripade NULL.
*/
TextureElement *TexturesLibrary::applyTexture( TextureElement *element, SDL_RWops *rw)
{
//TODO test uziti teto textury
	if( element )
	{
		unsigned char *pixels = loadPixelsRW( rw, element->image );	//nacteni pixelu
		if( pixels )
		{
			//vytvoreni textury
			//musime prohodit radky
			swapRows( pixels, element->image );

			//konverze textur, ktere maji byt pruhledne
			if(!element->key.empty() && element->key.at(0) == '{')	
				pixels = make_it_transparent( pixels, element );
			element = createTexture( element, pixels );
			delete [] pixels;
		}
	}

	return element;
}

//funkce nacte vsechny textury v aktualnim spojovem seznamu animace
bool TexturesLibrary::proceedTextureAnimationLoad( TextureElement *element )
{
	TextureElement *tmp = element;

	do
	{
		//pokud uz je textura nactena, tak se pouze zvysi index a nemusi se znova nacitat
		if(tmp->texture_id > 0 )
			tmp->shared_index++;
		else
		{
			//v opacnem pripade se musi nacist
			tmp = loadTexture( tmp );
			if( !tmp )
			{
				//pokud se nepovede nacist, tak se musi uvolnit cela animace
				unloadTexture( element );
				return false;
			}
		}
	}
	while( (tmp = tmp->animNext) && tmp != element );	//prochazime cely cyklus animace
	return true;
}

/*! Nektere textury obsahuji cistou modrou nebo bilou barvu a to znamena, ze takove pixely
*	maji byt pruhledne. V tom pripade se musi vytvorit nove pole pixelu ve velikosti RGBA a vysledny alfa kanal
*	dopocitat. Pixely barvy (0,0,255) se zmeni na (0,0,0,0), barvy (255,255,255) na (255,255,255,255).
*	@param pixels Vstupni pixely, nad kterymi se provadi test.
*	@param image Parametry pixelu obrazku (dulezite jsou #ImageInfo::width, ImageInfo::height, ImageInfo::bpp ).
*	@return Nove pixely v pripade uspechu. Jinak vstupni pixely.
*/
unsigned char* TexturesLibrary::make_it_transparent( unsigned char* pixels, TextureElement *current )
{
	ImageInfo &image = current->image;
	if( image.bpp < 24 )
		return pixels;
	int iBytesPerPixel = image.bpp / 8;
	unsigned char* result = new unsigned char[ image.height*image.width*4 ];	//RGBA
	if( !result )
	{
		PRINT_ERROR( "Error: Memory could not be allocated for transparency operation. Size:" << image.height*image.width*4  );
		return pixels;
	}

	bool toRed = false;
	if( current->key.find("{blood") != -1 )
		toRed = true;
	bool clamp = false;

	for( unsigned int i = 0; i < image.height * image.width; i++ )
	{
#ifdef SAFE
		memcpy_s( &result[i*4], iBytesPerPixel, &pixels[ i*iBytesPerPixel ], iBytesPerPixel );	//prvni 3 zkopirujeme
#else
		memcpy( &result[i*4], &pixels[ i*iBytesPerPixel ], iBytesPerPixel );	//prvni 3 zkopirujeme
#endif

		if( iBytesPerPixel == 3 )
			result[i*4+3] = 0xFF;	//primarne se nastavuje 0% pruhlednost

		if( result[i*4+2] == 0xFF && result[i*4] == 0x00 && result[i*4+1] == 0x00 )	//modra barva
		{
			result[i*4+2] = 0x00;	//nastavime modrou na 0
			result[i*4+3] = 0x00;	//nastavime pruhlednost na 100%
		}

		//100% bila barva = pruhlednost
		else if(  result[i*4] == 0xFF && result[i*4+1] == 0xFF && result[i*4+2] == 0xFF && !toRed )
		{

			//if( toRed )
			//{
			//	result[i*4+1] = 0x00;	//nastavime modrou na 0
			//	result[i*4+2] = 0x00;	//nastavime pruhlednost na 100%
			//}
			//bila mista v texture
			result[i*4+3] = 0x00;	//nastavime pruhlednost na 100%
			clamp = true;
		}
		else if( toRed )
		{
			if( result[i*4] == 0xFF && result[i*4+1] == 0xFF && result[i*4+2] == 0xFF )
				result[i*4+3] = 0x00;	//nastavime pruhlednost na 100%
			if( result[i*4] != 0xFF && result[i*4+1] != 0xFF && result[i*4+2] != 0xFF )
			{
				result[i*4+1] = 0xFF - result[i*4+1];
				result[i*4+1] = 0x00;	//nastavime modrou na 0
				result[i*4+2] = 0x00;	//nastavime pruhlednost na 100%
			}
		}

	}
	if( clamp )
		setTextureWraps( current, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE );
	//puvodni pixely se odstrani
	delete [] pixels;

	image.bpp = 32;	//nyni mame RGBA
	return result;
}

/*! Prirazuje dodatecne informace o texture objektu TextureElement z parametru. Pokud prvek tento objekt jeste nema, tak ho vytvori. Ve vsech pripadech nastavuje
*	vlastnosti.
*	@param element Prvek, kam se maji udaje priradit.
*	@param wrap_s Vlastnost WRAP ve smeru S.
*	@param wrap_t Vlastnost WRAP ve smeru T.
*/
void TexturesLibrary::setTextureWraps( TextureElement *element, int wrap_s, int wrap_t )
{
	if( !element->additInfo )
		element->additInfo = new AdditionalTexInfo();

	if( element )
	{
		element->additInfo->wrap_s = wrap_s;
		element->additInfo->wrap_t = wrap_t;
	}
}

/*! Prirazuje dodatecne informace o texture objektu TextureElement z parametru. Pokud prvek tento objekt jeste nema, tak ho vytvori. Ve vsech pripadech nastavuje
*	vlastnosti.
*	@param element Prvek, kam se maji udaje priradit.
*	@param mag Nastaveni MAG filtru. Moznosti: GL_LINEAR, GL_NEAREST.
*	@param min Nastaveni MIN filtru. Moznosti: GL_LINEAR, GL_NEAREST, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR,
*		GL_LINEAR_MIPMAP_LINEAR.
*/
void TexturesLibrary::setTextureCompilations( TextureElement *element, int mag, int min )
{
	if( !element->additInfo )
		element->additInfo = new AdditionalTexInfo();

	if( element )
	{
		element->additInfo->compilationTypeMag = mag;
		element->additInfo->compilationTypeMin = min;
	}
}

/*! Vlozeni zaznamu do databaze s klicem zadanym v parametru. Podle hodnoty parametru checkForAnim se urcuje, zda se ma provadet test na spojeni prvku do animace.
	@param key Klic noveho zaznamu.
	@param checkForAnim Urcuje, zda se ma provadet shlukovani tohoto prvku do animace.
	@return Ukazatel na novy prvek pri uspesnem vlozeni, NULL v pripade chyby.
*/

TextureElement *TexturesLibrary::addEntry( std::string key, bool checkForAnim )
{
	if( !key.empty() )
	{
		pair<map<string,TextureElement>::iterator,bool> retValue;

		/*if( key == "+0CRETE_SWTCH1A" || key == "+0crete_swtch1a" || key == "+ACRETE_SWTCH1A" || key == "+acrete_swtch1a" )
			cout << "x";*/
		for( size_t i = 0; i != key.length(); ++i )
		{
			char &c = key[i];
			if( isalpha(c) )
				c = tolower( c );
		}

		

		//vlozeni klice do databaze
		retValue = m_TexturesMap.insert( make_pair( key, TextureElement(key) ) );
		
		//druhy parametr udava, zda probehlo vlozeni v poradku
		if( retValue.second )
		{
			//nastaveni pocatecnich parametru
			//kazda textura ma na zacatku jako nasledovnika animace a prepinace sebe sama
			retValue.first->second.animNext = &retValue.first->second;
			retValue.first->second.switcher = &retValue.first->second;
			retValue.first->second.material = determineMaterialType( key );	//nastaveni materialu
			if( checkForAnim )
				checkAnimRelations( &retValue.first->second );
			return &retValue.first->second;
		}
	}
	
	return NULL;
}

/*! Vlozeni zaznamu do databaze s klicem a cestou k souboru obrazku zadanych parametry. Podle hodnoty parametru checkForAnim se urcuje, zda se ma provadet test na spojeni prvku *	do animace.
*	@param key Klic noveho zaznamu.
*	@param filename Cesta k souboru obrazku.
*	@param checkForAnim Urcuje, zda se ma provadet shlukovani tohoto prvku do animace.
*	@return Ukazatel na novy prvek pri uspesnem vlozeni, NULL v pripade chyby.
*/
TextureElement *TexturesLibrary::addEntry( std::string key, const char* filename, bool checkForAnim )
{
	std::string s = filename;
	return addEntry( key, s, checkForAnim ); 
}



/*! Vlozeni zaznamu do databaze s klicem a cestou k souboru obrazku zadanych parametry. Podle hodnoty parametru checkForAnim se urcuje, zda se ma provadet test na spojeni prvku *	do animace.
*	@param key Klic noveho zaznamu.
*	@param filename Cesta k souboru obrazku.
*	@param checkForAnim Urcuje, zda se ma provadet shlukovani tohoto prvku do animace.
*	@return Ukazatel na novy prvek pri uspesnem vlozeni, NULL v pripade chyby.
*/
TextureElement *TexturesLibrary::addEntry( std::string key, std::string filename, bool checkForAnim )
{
	TextureElement *newOne = addEntry( key, checkForAnim );
	if( newOne )
	{
		newOne->image.setFilename( filename );
	}
	return newOne;
}

/*! Funkce uvolnuje texturu dle parametru. Jelikoz se v tomto pripade musi najit prvek databaze podle OpenGL ID textury, tak se cela knihovna prochazi sekvencne, coz
*	je casove narocne. Slozitost je zde rovna O( n ). Proto se nedoporucuje tuto funkci pouzivat. Po nalezeni se snizuje hodnota #TextureElement::shared_index o
*	jedna u vsech textur ve shluku animace.
*	@param iTextureID OpenGL ID textury.
*	@warning Slozitost O( n )!!!!
*/
void TexturesLibrary::unloadTexture( unsigned int iTextureID )
{
	unloadTexture( getTexture( iTextureID ) );
}

/*! Funkce uvolnuje texturu dle parametru. Vyhledavani prvku databaze probiha pomoci funkce find tridy std::map. Slozitost je zde rovna O( log( n) ) a je casove vyhodna.
*	Po nalezeni se snizuje hodnota #TextureElement::shared_index o jedna u vsech textur ve shluku animace.
*	@param key Klic textury.
*/
void TexturesLibrary::unloadTexture( std::string key )
{
	unloadTexture( getTexture( key ) );
}

//snizeni indexu sdileni textury u vsech textur v sekvenci animace
void TexturesLibrary::proceedTextureAnimationUnload( TextureElement *element )
{
	if( element )
	{
		TextureElement *tmp = element;
		do
		{
			if( tmp->shared_index > 0 )
				tmp->shared_index--;
		
		}
		while( (tmp = tmp->animNext) && tmp != element );

	}
}

/*! Funkce uvolnuje texturu dle parametru. Snizuje se hodnota #TextureElement::shared_index o jedna.
*	@param element Element, ktery se ma uvolnit.
*/
void TexturesLibrary::unloadTexture( TextureElement *element )
{
	if( element )
	{
		proceedTextureAnimationUnload( element );
		if( element->switcher != element )
			proceedTextureAnimationUnload( element->switcher );
	}
}

/*! Funkce testuje vyuziti vsech textur ulozenych v databazi. Pokud je #TextureElement::shared_index polozky roven nule, tak se textura smaze, ale prvek zustava
*	v databazi. Nuluje se #TextureElement::texture_id.
*/
void TexturesLibrary::checkTexturesUsage()
{
	if( m_TexturesMap.empty() )
		return;
	for( map<string,TextureElement>::iterator iter = m_TexturesMap.begin(); iter != m_TexturesMap.end(); iter++ )
		if( iter->second.shared_index == 0 && iter->second.texture_id != 0 )
		{
			glDeleteTextures( 1, &iter->second.texture_id );
			iter->second.texture_id = 0;
		}
}


//vytvareni propojovani textur pri animaci. Tato funkce obsluhuje pouze pocatecni textury obou animaci
void TexturesLibrary::setStartingTextures( TextureElement *current, std::string sSwitcherPrefix, std::string sNextPrefix, std::string sRemainPart )
{
	TextureElement *tmp = getTexture( sSwitcherPrefix + sRemainPart );
	if( tmp )
	{
		current->switcher = tmp;
		tmp->switcher = current;
		
		//musime zpopirovat udaje o prepinaci v cele druhe animaci
		TextureElement *last = tmp->animNext;
		while( last && last != tmp )
		{
			last->switcher = tmp->switcher;
			last = last->animNext;
		}
	}

	//a potom nasleduje obsluha vlastni animace
	tmp = getTexture( sNextPrefix + sRemainPart );
	if( tmp )
		relateNext( current, tmp );	//vztahy v me animaci
}



//funkce spojuje animovane textury k sobe ukazateli
//nelze totiz zarucit, ze budou textury chodit po sobe tak jako pri animaci
void TexturesLibrary::checkAnimRelations( TextureElement *element )
{
	if( !element || element->key.length() < 3 || element->key.at(0) != '+' )
		return;

	char number = element->key.at(1);
	std::string sRemainPart = element->key.substr( 2 );
	TextureElement *tmp = NULL;
	if( number == '0' )
	{
		//hledam texturu s klicem A v druhe animaci a 1 v me vlastni animaci
		setStartingTextures( element, "+a", "+1", sRemainPart );
	}
	else if( number == 'a' )
	{
		//hledam texturu s cislem 0 v druhe animaci a B v me vlastni animaci
		setStartingTextures( element, "+0", "+b", sRemainPart );
	}
	else if( number > '0' && number <= '9' )
	{
		//nejdrive najdeme predchazejici
		char anim = number - 1;
		setBeforeTexture( element, string("+") + anim + sRemainPart );
			
		if( number != '9' )
		{
			//a pak nasledujici
			anim = number + 1;
			setNextTexture( element, string("+") + anim + sRemainPart );
		}
	}
	else if( number > 'a' && number <= 'j' )
	{
		//nejdrive najdeme predchazejici
		char anim = number - 1;
		setBeforeTexture( element, string("+") + anim + sRemainPart );
			
		if( number != 'j' )
		{
			//a pak nasledujici
			anim = number + 1;
			setNextTexture( element, string("+") + anim + sRemainPart );
		}
	}
}

//nastaveni spojeni s predchozi texturou
void TexturesLibrary::setBeforeTexture( TextureElement *current, std::string sBeforeTexture )
{
	TextureElement *tmp = getTexture( sBeforeTexture );
	if( tmp )
	{
		current->animNext = tmp->animNext;
		tmp->animNext = current;
		current->switcher = tmp->switcher;
	}
}

//nastaveni spojeni s nasledujici texturou
void TexturesLibrary::setNextTexture( TextureElement *current, std::string sNextTexture )
{
	TextureElement *tmp = getTexture( sNextTexture );
	if( tmp)
		relateNext( current, tmp );
}

//spojeni s nasledujici texturou a aktualizace cyklu
//kde se meni nasledovnik posledniho prvku v aktualni smycce na prave vkladany prvek
void TexturesLibrary::relateNext( TextureElement *current, TextureElement *next )
{
	if( current && next )
	{
		TextureElement *last = next;
		while( last && last->animNext != next )
		{
			last->switcher = current->switcher;
			last = last->animNext;
		}
	
		if( last )
		{
			last->animNext = current->animNext;
			last->switcher = current->switcher;
			current->animNext = next;
		}
	}
}

/*! Funkce smaze prvek databaze podle parametru. Zaroven i maze texturu z pameti graficke karty.
*	@param element Prvek, ktery se ma zmazat.
*	@return True v pripade uspechu, jinak false.
*/
bool TexturesLibrary::deleteTexture( TextureElement *element )
{
	if( element )
	{
		glDeleteTextures( 1, &element->texture_id );
		m_TexturesMap.erase( element->key );
		return true;
	}
	return false;
}

/*! Funkce smaze prvek databaze podle parametru. Zaroven i maze texturu z pameti graficke karty. Zde se musi hledat sekvencne cilovy prvek, coz odpovida slozitosti O
*	( n ), coz je velice casove narocne pri velike databazi, takze se nedoporucuje tuto funkci pouzivat.
*	@param texture_id OpenGL ID textury, ktera se ma smazat.
*	@return True v pripade uspechu, jinak false.
*	@warning Slozitost O( n )!!!!!
*/
bool TexturesLibrary::deleteTexture( unsigned int texture_id )
{
	if( !deleteTexture( getTexture( texture_id ) ) )
	{
		if( texture_id == 0 )
			return false;

		glDeleteTextures( 1, &texture_id );
	}
	return true;
}

/*! Funkce smaze prvek databaze podle parametru. Zaroven i maze texturu z pameti graficke karty. Zde se hleda funkci find tridy std::map, takze slozitost 
* 	odpovida O( log( n ) ).
*	@param key Klic textury, ktera se ma smazat.
*	@return True v pripade uspechu, jinak false.
*/
bool TexturesLibrary::deleteTexture( std::string key )
{
	return deleteTexture( getTexture( key ) );
}

/*! Funkce smaze vsechny prvky v  databaze podle parametru. Zaroven maze i texturu z pameti graficke karty.
*/
void TexturesLibrary::deleteAll()
{
	if( m_TexturesMap.empty() )
		return;
	for( map<string,TextureElement>::iterator iter = m_TexturesMap.begin(); iter != m_TexturesMap.end(); iter++ )
		if( iter->second.texture_id )
			glDeleteTextures( 1, & iter->second.texture_id );
	m_TexturesMap.clear();
}


int TexturesLibrary::determineMaterialType( std::string texture_name )
{
	if( !texture_name.empty() )
	{
		char lastChar = texture_name.at( texture_name.length() - 1 );
		for( int i = 0; i < TEXTURE_MATERIALS_COUNT;++i )
			if( lastChar == TEXTURE_MATERIALS[i] )
				return i;
	}
	return MATERIAL_CONCRETE;
}

