// __author__ = Juan Carlos De Abreu (jcabreur@gmail.com)

#include "TextureManagement.h"

BEGIN_TEG_NAMESPACE();

// Standard constructor of texture management
TextureManagement::TextureManagement() {
	// Initialize the local variables
	m_iMode = MODE_MODULATE;
	m_iMethod = PLANAR;
	m_iQuality = FILTER_TRILINEAR;
/*	m_iOptionS = OPTION_REPEAT;
	m_iOptionT = OPTION_REPEAT;
	m_iOptionR = OPTION_REPEAT;
*/
	m_iOptionS = OPTION_CLAMP_TO_EDGE;
	m_iOptionT = OPTION_CLAMP_TO_EDGE;
	m_iOptionR = OPTION_CLAMP_TO_EDGE;

	// Initialize the use of glew
	glewInit();

	// Update the extension supported
	extensionSupported();

	// Initialize the use of FreeImage
	FreeImage_Initialise(FALSE);
}

// Function to set how to wrap the texture.
void TextureManagement::set_option(int option) {
  if (option) m_iOptionS = m_iOptionT = m_iOptionR = OPTION_CLAMP_TO_EDGE;
  else m_iOptionS = m_iOptionT = m_iOptionR = OPTION_REPEAT;
  setOptionS(); setOptionT(); setOptionR();
}

// Destructor of texture management
TextureManagement::~TextureManagement() {
	// Free the resources uses in the textures of the application
/*	for(int i = 0; i < vTextureNames.size(); ++i)
		glDeleteTextures(1, &vTextureNames[i]);
	vTextureNames.clear();
*/
	std::map<std::tstring, GLuint>::iterator it = vTextureNames.begin();
	std::map<std::tstring, GLuint>::iterator end = vTextureNames.end();

	for(; it != end; ++it) {
		glDeleteTextures(1, &it->second);
	}
	vTextureNames.clear();
}

// Method to set the texture environment mode parameter
void TextureManagement::setMode() {
	switch(m_iMode) {
		case MODE_DECAL:	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); break;
		case MODE_BLEND:	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND); break;
		case MODE_REPLACE:	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); break;
		case MODE_COMBINE:	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); break;		
		case MODE_MODULATE:	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); break;
	}
}

// Method to obtain the max quality supported by the hardware
GLfloat TextureManagement::maxQuality() {
	GLfloat maxFilter;
	glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxFilter);
	return maxFilter;
}

// Method to set the filter to use by the texture
void TextureManagement::setQuality() {
	switch(m_iQuality) {
		case FILTER_LINEAR:
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); break;

		case FILTER_BILINEARR:
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); break;

		case FILTER_TRILINEAR:
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); break;

		case FILTER_ANISOTROPY_2X:
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 2.0f); break;

		case FILTER_ANISOTROPY_4X:
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 4.0f); break;

		case FILTER_ANISOTROPY_8X:
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f); break;

		case FILTER_ANISOTROPY_16X:
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16.0f); break;
	}
}

// Method to set generation of texture coordinates
void TextureManagement::setMethod() {
	switch(m_iMethod) {
		case SPHERE_MAP:
			// Enable the generation of texture coordinates
			glEnable(GL_TEXTURE_GEN_S);	glEnable(GL_TEXTURE_GEN_T);
			
			// Define the type of generation texture coordinate
			glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
			glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
			break;

		case OBJECT_LINEAR:
			// Enable the generation of texture coordinates
			glEnable(GL_TEXTURE_GEN_S);	glEnable(GL_TEXTURE_GEN_T);
			
			// Define the type of generation texture coordinate
			glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); 
			glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
			break;

		case EYE_LINEAR:
			// Enable the generation of texture coordinates
			glEnable(GL_TEXTURE_GEN_S);	glEnable(GL_TEXTURE_GEN_T);
			
			// Define the type of generation texture coordinate
			glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); 
			glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
			break;

		default:
			// Disable the generation of texture coordinates
			glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T);
			break;
		}
}

// Method to set wrap parameter of the coordinate s
void TextureManagement::setOptionS() {
	switch(m_iOptionS) {
		case OPTION_REPEAT:
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); break;

		case OPTION_CLAMP:
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); break;

		case OPTION_CLAMP_TO_EDGE:
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); break;

		case OPTION_CLAMP_TO_BORDER:
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); break;
	}
}

// Method to set wrap parameter of the coordinate t
void TextureManagement::setOptionT() {
	switch(m_iOptionT) {
		case OPTION_REPEAT:
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); break;

		case OPTION_CLAMP:
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); break;
				
		case OPTION_CLAMP_TO_EDGE:
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); break;

		case OPTION_CLAMP_TO_BORDER:
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); break;
	}	
}

// Method to set wrap parameter of the coordinate r
void TextureManagement::setOptionR() {
	switch(m_iOptionR) {
		case OPTION_REPEAT:
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_REPEAT); break;

		case OPTION_CLAMP:
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP); break;
				
		case OPTION_CLAMP_TO_EDGE:
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); break;

		case OPTION_CLAMP_TO_BORDER:
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER); break;
	}	
}

// Method to check the extensions suppoted by the hardware
void TextureManagement::extensionSupported() {
	// Check the extension supported
    m_bSupport[MULTITEXTURE] = GLEW_ARB_multitexture;
	m_bSupport[TEXTURE_FLOAT] = GLEW_ARB_texture_float;
    m_bSupport[TEXTURE_ENV_ADD] = GLEW_ARB_texture_env_add;
    m_bSupport[TEXTURE_ENV_DOT3] = GLEW_ARB_texture_env_dot3;
    m_bSupport[TEXTURE_CUBE_MAP] = GLEW_ARB_texture_cube_map;
    m_bSupport[TEXTURE_RECTANGLE] = GLEW_ARB_texture_rectangle;
    m_bSupport[TEXTURE_COMPRESSION] = GLEW_ARB_texture_compression;
    m_bSupport[TEXTURE_ENV_COMBINE] = GLEW_ARB_texture_env_combine;
    m_bSupport[TEXTURE_BORDER_CLAMP] = GLEW_ARB_texture_border_clamp;
    m_bSupport[TEXTURE_ENV_CROSSBAR] = GLEW_ARB_texture_env_crossbar;
    m_bSupport[TEXTURE_MIRRORED_REPEAT] = GLEW_ARB_texture_mirrored_repeat;
    m_bSupport[TEXTURE_NON_POWER_OF_TWO] = GLEW_ARB_texture_non_power_of_two;
}

// Method to load a image of any format
GLuint TextureManagement::load(const char* lpszPathName) {
	
	// Check if the dont file was loaded sometime before ...
	std::map<std::tstring, GLuint>::iterator it = vTextureNames.find(lpszPathName);
	if(it != vTextureNames.end()) {
		// Return the id of the texture
		return it->second;
	}
	
	// Check the file signature and deduce its format
	FREE_IMAGE_FORMAT fif = FreeImage_GetFileType(lpszPathName, 0);
	
	// if don't have signature
	if(fif == FIF_UNKNOWN) {
		// Try to guess the file format from the file extension
		fif = FreeImage_GetFIFFromFilename(lpszPathName);
	}

	FIBITMAP* bitmap = NULL;

	// Check that the plugin has reading capabilities ...
	if((fif != FIF_UNKNOWN) && FreeImage_FIFSupportsReading(fif)) {
		// Ok, let's load the file
		bitmap = FreeImage_Load(fif, lpszPathName, 0);
	}

	// If the load was successful
	if(bitmap) {
		// Add new texture name id
		//vTextureNames.push_back(0);
		vTextureNames.insert(std::pair<std::tstring, GLuint> (lpszPathName, 0));

		// is obtained the number of textures
		const int nTextures = static_cast<int> (vTextureNames.size()) - 1;

		// Generate a texture name
	//	glGenTextures(1, &vTextureNames[nTextures]);
		glGenTextures(1, &vTextureNames[lpszPathName]);
		
		// Enable the creation of of the named texture
//		glBindTexture(GL_TEXTURE_2D, vTextureNames[nTextures]);
		glBindTexture(GL_TEXTURE_2D, vTextureNames[lpszPathName]);

		//obtain the number of bytes uses per pixel
		GLuint nComponents = (FreeImage_GetBPP(bitmap) + 7) / 8;
		
		// Set the format of the pixel ...
		GLuint format;
		switch(nComponents) {
			case 1 : format = GL_LUMINANCE; break;
			case 2 : break;	// Do anything, maybe report a warning o error
			case 3 : format = GL_BGR; break;
			case 4 : format = GL_BGRA;	break;
			default: break;	// TODO: print a error in the log	
		}

		// Create the 2D mipmap
		gluBuild2DMipmaps(GL_TEXTURE_2D, nComponents, FreeImage_GetWidth(bitmap), FreeImage_GetHeight(bitmap),
						  format, GL_UNSIGNED_BYTE, FreeImage_GetBits(bitmap));

		// Check if is a texture name was generated successful
//		if(glIsTexture(vTextureNames[nTextures]) == GL_FALSE) {
		if(glIsTexture(vTextureNames[lpszPathName]) == GL_FALSE) {
			// If don't was generated the name texture, report the error in the Log
			PRINT_LOG(TYPE_ERROR, "Error ocurr when try to generate the texture name of \"%s\"", lpszPathName);
		}

		setMode();
		setMethod();
		setQuality();
		setOptionS();
		setOptionT();
		setOptionR();

		//se desactiva la unidad de textura que se esta utilizando
		glBindTexture(GL_TEXTURE_2D, 0);
		
		// Disable the use of Texture 2D
		glDisable(GL_TEXTURE_2D);
		
		// Unload the image file
		FreeImage_Unload(bitmap);

		// Return the id of the texture name
//		return vTextureNames[nTextures];
		return vTextureNames[lpszPathName];
	}

	// TODO: check the information in the log of FreeImage!

	// If don't load, report the error in the Log
	PRINT_LOG(TYPE_ERROR, "Error ocurr when try to load the texture \"%s\"", lpszPathName);

	return 1;	// Error loading the texture
}

END_TEG_NAMESPACE();
