/**
 *******************************************************************************
 * @file ewolTexture.cpp
 * @brief ewol Texture loading system (sources)
 * @author Edouard DUPIN
 * @date 28/10/2011
 * @par Project
 * ewol
 *
 * @par Copyright
 * Copyright 2011 Edouard DUPIN, all right reserved
 *
 * This software is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY.
 *
 * Licence summary : 
 *    You can modify and redistribute the sources code and binaries.
 *    You can send me the bug-fix
 *
 * Term of the licence in in the file licence.txt.
 *
 *******************************************************************************
 */



#include <ewolTexture.h>

extern "C"
{
#pragma pack(push,1)
	typedef struct
	{
		int16_t bfType;
		int32_t bfSize;
		int32_t bfReserved;
		int32_t bfOffBits;
	} bitmapFileHeader_ts;
	
	typedef struct
	{
		int32_t biSize;
		int32_t biWidth;
		int32_t biHeight;
		int16_t biPlanes;
		int16_t biBitCount;
		int32_t biCompression;
		int32_t biSizeImage;
		int32_t biXPelsPerMeter;
		int32_t biYPelsPerMeter;
		int32_t biClrUsed;
		int32_t biClrImportant;
	} bitmapInfoHeader_ts;
#pragma pack(pop)

	typedef enum {
		BITS_16_R5G6B5,
		BITS_16_X1R5G5B5,
		BITS_24_R8G8B8,
		BITS_32_X8R8G8B8,
		BITS_32_A8R8G8B8
	} modeBitmap_te;
};


#undef __class__
#define __class__	"ewol::Bitmap"

class Bitmap
{
	private:
		modeBitmap_te       m_dataMode;
		int32_t             m_width;
		int32_t             m_height;
		int32_t             m_size;
		uint8_t *           m_data;
		uint8_t *           m_dataGenerate;
		bitmapFileHeader_ts m_FileHeader;
		bitmapInfoHeader_ts m_InfoHeader;
	public:
		Bitmap(etk::File & fileName) : m_data(NULL), m_dataGenerate(NULL)
		{
			m_dataMode = BITS_16_R5G6B5;
			m_width = 0;
			m_height = 0;
			m_size = 0;

			FILE *File=NULL;
			// Get the fileSize ...
			if (fileName.Size() < (int32_t)(sizeof(bitmapFileHeader_ts) + sizeof(bitmapInfoHeader_ts) ) ) {
				EWOL_ERROR("not enought data in the file named=\"" << fileName << "\"");
				return;
			}
			File=fopen(fileName.GetCompleateName().c_str(),"rb");
			if(NULL == File) {
				EWOL_ERROR("Can not find the file name=\"" << fileName << "\"");
				return;
			}
			// get the data : 
			if (fread(&m_FileHeader,sizeof(bitmapFileHeader_ts),1,File) != 1) {
				EWOL_ERROR("error loading file header");
				fclose(File);
				return;
			}
			if (fread(&m_InfoHeader,sizeof(bitmapInfoHeader_ts),1,File) != 1) {
				EWOL_ERROR("error loading file header");
				fclose(File);
				return;
			}
			// TODO : do otherwise ...
			fseek(File,m_FileHeader.bfOffBits,SEEK_SET);
			if(ferror(File)) {
				EWOL_ERROR("error with the 'bfOffBits' in the file named=\"" << fileName << "\"");
				fclose(File);
				return;
			}
			// Check the header error : 
			if (m_FileHeader.bfType != 0x4D42) {
				EWOL_ERROR("the file=\"" << fileName << "\" is not a bitmap file ...");
				fclose(File);
				return;
			}
			if (m_FileHeader.bfReserved != 0x00000000) {
				EWOL_ERROR("the bfReserved feald is not at 0 ==> not supported format ...");
				fclose(File);
				return;
			}
			if(    m_InfoHeader.biBitCount == 16
			    && m_InfoHeader.biCompression == 0)
			{
				m_dataMode = BITS_16_X1R5G5B5;
			} else if(    m_InfoHeader.biBitCount == 16
			         && m_InfoHeader.biCompression == 3)
			{
				m_dataMode = BITS_16_R5G6B5;
			} else if(    m_InfoHeader.biBitCount == 24
			           && m_InfoHeader.biCompression == 0)
			{
				m_dataMode = BITS_24_R8G8B8;
			} else if(    m_InfoHeader.biBitCount == 32
			           && m_InfoHeader.biCompression == 3)
			{
				m_dataMode = BITS_32_X8R8G8B8;
			} else if(    m_InfoHeader.biBitCount == 32
			           && m_InfoHeader.biCompression == 0)
			{
				m_dataMode = BITS_32_A8R8G8B8;
			} else {
				EWOL_ERROR("the biBitCount & biCompression fealds are unknow ==> not supported format ...");
				fclose(File);
				return;
			}
			m_width = m_InfoHeader.biWidth;
			m_height = m_InfoHeader.biHeight;
			
			if(0 != m_InfoHeader.biSizeImage)
			{
				m_data=new uint8_t[m_InfoHeader.biSizeImage];
				if (fread(m_data,m_InfoHeader.biSizeImage,1,File) != 1){
					EWOL_CRITICAL("Can not read the file with the good size...");
				}
				// allocate the destination data ...
				m_dataGenerate=new uint8_t[m_width*m_height*4];
			}
			fclose(File);
			// need now to generate RGBA data ...
			switch(m_dataMode)
			{
				case BITS_16_R5G6B5:
					{
						uint16_t * pointer = (uint16_t*)m_data;
						for(int32_t yyy=0; yyy<m_height; yyy++) {
							for(int32_t xxx=0; xxx<m_width; xxx++) {
								m_dataGenerate[4*((m_height-yyy-1) * m_width + xxx ) + 0] = (int8_t)((*pointer & 0xF800) >> 8);
								m_dataGenerate[4*((m_height-yyy-1) * m_width + xxx ) + 1] = (int8_t)((*pointer & 0x07E0) >> 3);
								m_dataGenerate[4*((m_height-yyy-1) * m_width + xxx ) + 2] = (int8_t)(*pointer << 3);
								m_dataGenerate[4*((m_height-yyy-1) * m_width + xxx ) + 3] = 0xFF;
								pointer++;
							}
						}
					}
					break;
				case BITS_16_X1R5G5B5:
					{
						uint16_t * pointer = (uint16_t*)m_data;
						for(int32_t yyy=0; yyy<m_height; yyy++) {
							for(int32_t xxx=0; xxx<m_width; xxx++) {
								m_dataGenerate[4*((m_height-yyy-1) * m_width + xxx ) + 0] = (int8_t)((*pointer & 0x7C00) >> 7);
								m_dataGenerate[4*((m_height-yyy-1) * m_width + xxx ) + 1] = (int8_t)((*pointer & 0x03E0) >> 2);
								m_dataGenerate[4*((m_height-yyy-1) * m_width + xxx ) + 2] = (int8_t)(*pointer << 3);
								m_dataGenerate[4*((m_height-yyy-1) * m_width + xxx ) + 3] = 0xFF;
								pointer++;
							}
						}
					}
					break;
				case BITS_24_R8G8B8:
					{
						uint8_t * pointer = m_data;
						for(int32_t yyy=0; yyy<m_height; yyy++) {
							for(int32_t xxx=0; xxx<m_width; xxx++) {
								m_dataGenerate[4*((m_height-yyy-1) * m_width + xxx ) + 0] = *pointer++;
								m_dataGenerate[4*((m_height-yyy-1) * m_width + xxx ) + 1] = *pointer++;
								m_dataGenerate[4*((m_height-yyy-1) * m_width + xxx ) + 2] = *pointer++;
								m_dataGenerate[4*((m_height-yyy-1) * m_width + xxx ) + 3] = 0xFF;
							}
						}
					}
					break;
				case BITS_32_X8R8G8B8:
					{
						uint8_t * pointer = m_data;
						for(int32_t yyy=0; yyy<m_height; yyy++) {
							for(int32_t xxx=0; xxx<m_width; xxx++) {
								pointer++;
								m_dataGenerate[4*((m_height-yyy-1) * m_width + xxx ) + 0] = *pointer++;
								m_dataGenerate[4*((m_height-yyy-1) * m_width + xxx ) + 1] = *pointer++;
								m_dataGenerate[4*((m_height-yyy-1) * m_width + xxx ) + 2] = *pointer++;
								m_dataGenerate[4*((m_height-yyy-1) * m_width + xxx ) + 3] = 0xFF;
							}
						}
					}
					break;
				case BITS_32_A8R8G8B8:
					{
						uint8_t * pointer = m_data;
						for(int32_t yyy=0; yyy<m_height; yyy++) {
							for(int32_t xxx=0; xxx<m_width; xxx++) {
								m_dataGenerate[4*((m_height-yyy-1) * m_width + xxx ) + 0] = *pointer++;
								m_dataGenerate[4*((m_height-yyy-1) * m_width + xxx ) + 1] = *pointer++;
								m_dataGenerate[4*((m_height-yyy-1) * m_width + xxx ) + 2] = *pointer++;
								m_dataGenerate[4*((m_height-yyy-1) * m_width + xxx ) + 3] = *pointer++;
							}
						}
					}
					break;
				default:
					EWOL_DEBUG("        mode = ERROR");
					break;
			}
		}
		
		~Bitmap(void)
		{
			if (NULL != m_data) {
				delete(m_data);
			}
			if (NULL != m_dataGenerate) {
				delete(m_dataGenerate);
			}
		}
		
		bool LoadOK(void) { if (NULL != m_dataGenerate) { return true; } else { return false; } };
		
		int32_t Width(void)  { return m_width; };
		int32_t Height(void) { return m_height; };
		uint8_t * Data(void)   { return m_dataGenerate; };
		
		void Display(void)
		{
			if (NULL == m_data) {
				EWOL_ERROR("Might loading error of this Bitmap ...");
				return;
			}
			EWOL_DEBUG(" -----------------------------------------------------------");
			if (false) {
				EWOL_DEBUG("Display caracteristic of the bitmap : ");
				EWOL_DEBUG("    Header of file :");
				EWOL_DEBUG("        bfType    =" << m_FileHeader.bfType << "      19778 : must always be set to 'BM' to declare that this is a .bmp-file.");
				EWOL_DEBUG("        bfSize    =" << m_FileHeader.bfSize << "      specifies the size of the file in bytes.");
				EWOL_DEBUG("        bfReserved=" << m_FileHeader.bfReserved << "      must always be set to zero.");
				EWOL_DEBUG("        bfOffBits =" << m_FileHeader.bfOffBits << "      1078 : specifies the offset from the beginning of the file to the bitmap data.");
				EWOL_DEBUG("    info header of file :");
				EWOL_DEBUG("        biSize         =" << m_InfoHeader.biSize << "      specifies the size of the BITMAPINFOHEADER structure, in bytes.");
				EWOL_DEBUG("        biWidth        =" << m_InfoHeader.biWidth << "      specifies the width of the image, in pixels.");
				EWOL_DEBUG("        biHeight       =" << m_InfoHeader.biHeight << "      specifies the height of the image, in pixels.");
				EWOL_DEBUG("        biPlanes       =" << m_InfoHeader.biPlanes << "      specifies the number of planes of the target device, must be set to zero.");
				EWOL_DEBUG("        biBitCount     =" << m_InfoHeader.biBitCount << "      specifies the number of bits per pixel.");
				EWOL_DEBUG("        biCompression  =" << m_InfoHeader.biCompression << "      Specifies the type of compression, usually set to zero (no compression).");
				EWOL_DEBUG("        biSizeImage    =" << m_InfoHeader.biSizeImage << "      specifies the size of the image data, in bytes. If there is no compression, it is valid to set this member to zero.");
				EWOL_DEBUG("        biXPelsPerMeter=" << m_InfoHeader.biXPelsPerMeter << "      specifies the the horizontal pixels per meter on the designated targer device, usually set to zero.");
				EWOL_DEBUG("        biYPelsPerMeter=" << m_InfoHeader.biYPelsPerMeter << "      specifies the the vertical pixels per meter on the designated targer device, usually set to zero.");
				EWOL_DEBUG("        biClrUsed      =" << m_InfoHeader.biClrUsed << "      speglTexImage2Dcifies the number of colors used in the bitmap, if set to zero the number of colors is calculated using the biBitCount member.");
				EWOL_DEBUG("        biClrImportant =" << m_InfoHeader.biClrImportant << "      specifies the number of color that are 'important' for the bitmap, if set to zero, all colors are important.");
			}
			EWOL_DEBUG("Bitmap : " << m_width << "x" << m_height);
			switch(m_dataMode)
			{
				case BITS_16_R5G6B5:
					EWOL_DEBUG("        mode = 16 bits R5G6B5");
					break;
				case BITS_16_X1R5G5B5:
					EWOL_DEBUG("        mode = 16 bits X1R5G5B5");
					break;
				case BITS_24_R8G8B8:
					EWOL_DEBUG("        mode = 24 bits R8G8B8");
					break;
				case BITS_32_X8R8G8B8:
					EWOL_DEBUG("        mode = 32 bits X8R8G8B8");
					break;
				case BITS_32_A8R8G8B8:
					EWOL_DEBUG("        mode = 32 bits A8R8G8B8");
					break;
				default:
					EWOL_DEBUG("        mode = ERROR");
					break;
			}
		}
};

#include <GL/gl.h>
#include <GL/glu.h>

class LoadedTexture
{
	public:
		etk::File m_filename;
		int32_t   m_nbTimeLoaded;
		int32_t   m_imageSize; // must be x=y ...
		uint32_t  m_openGlTextureID;
};

etk::VectorType<LoadedTexture*> listLoadedTexture;


#undef __class__
#define __class__	"ewol"

int32_t ewol::LoadTexture(etk::File fileName)
{
	if (listLoadedTexture.Size()!=0) {
		for (int32_t iii=0; iii<listLoadedTexture.Size(); iii++) {
			if (listLoadedTexture[iii]->m_filename == fileName) {
				listLoadedTexture[iii]->m_nbTimeLoaded++;
				return listLoadedTexture[iii]->m_openGlTextureID;
			}
		}
	}
	etk::String fileExtention = fileName.GetExtention();
	if (fileExtention ==  "bmp") {
		if (false == fileName.Exist()) {
			EWOL_ERROR("File does not Exist ... " << fileName);
			return -1;
		}
		Bitmap myBitmap(fileName);
		myBitmap.Display();
		if (myBitmap.LoadOK() == true) {
			if (myBitmap.Width()!= myBitmap.Height()) {
				EWOL_ERROR("Texture can not have Width=" << myBitmap.Width() << "px different of height=" << myBitmap.Height() << "px in file:" << fileName);
				return -1;
			}
			GLuint textureid;
			glGenTextures(1, &textureid);
			glBindTexture(GL_TEXTURE_2D, textureid);
			//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
			//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
			//--- mode nearest
			//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
			//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			//--- Mode linear
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, myBitmap.Width(), myBitmap.Height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, myBitmap.Data());
			LoadedTexture *tmpTex = new LoadedTexture();
			if (NULL != tmpTex) {
				tmpTex->m_filename = fileName;
				tmpTex->m_nbTimeLoaded = 1;
				tmpTex->m_imageSize = myBitmap.Width();
				tmpTex->m_openGlTextureID = textureid;
				listLoadedTexture.PushBack(tmpTex);
			} else {
				EWOL_ERROR("Allocation ERROR... ");
			}
			return textureid;
		} else {
			return -1;
		}
	} else {
		EWOL_ERROR("Extention not managed " << fileName << " Sopported extention : .bmp");
		return -1;
	}
}

void ewol::UnLoadTexture(uint32_t textureID)
{
	for (int32_t iii=0; iii<listLoadedTexture.Size(); iii++) {
		if (listLoadedTexture[iii]->m_openGlTextureID == textureID) {
			listLoadedTexture[iii]->m_nbTimeLoaded--;
			if (0 == listLoadedTexture[iii]->m_nbTimeLoaded) {
				EWOL_DEBUG("Remove openGL texture ID=" << textureID << " file:" << listLoadedTexture[iii]->m_filename);
				glDeleteTextures(1,&listLoadedTexture[iii]->m_openGlTextureID);
				delete(listLoadedTexture[iii]);
				listLoadedTexture[iii] = NULL;
				listLoadedTexture.Erase(iii);
			}
			return;
		}
	}
	EWOL_CRITICAL("Can not find TextureId=" << textureID << " in the list of texture loaded...==> to remove it ...");
}


int32_t ewol::GetTextureSize(uint32_t textureID)
{
	for (int32_t iii=0; iii<listLoadedTexture.Size(); iii++) {
		if (listLoadedTexture[iii]->m_openGlTextureID == textureID) {
			return listLoadedTexture[iii]->m_imageSize;
		}
	}
	EWOL_ERROR("Can not find TextureId=" << textureID << " in the list of texture loaded...");
	return -1;
}