#include "irrTypes.h"
#include "COpenGLTexture.h"
#include "os.h"
#include "CColorConverter.h"

#include "IrrCompileConfig.h"
#ifdef _IRR_COMPILE_WITH_OPENGL_

#include "glext.h"

namespace irr
{
namespace video  
{

//! constructor
COpenGLTexture::COpenGLTexture(IImage* image, bool generateMipLevels)
: Pitch(0), ImageSize(0,0), hasMipMaps(generateMipLevels),
ImageData(0), TextureName(0)
{
	#ifdef _DEBUG
    setDebugName("COpenGLTexture");
	#endif

	#ifdef LINUX
		hasMipMaps = false;
	#endif

	if (image)
	{
		getImageData(image);

		if (ImageData)
		{
			glGenTextures(1, &TextureName);
			copyTexture();
		}		
	}
}


//! destructor
COpenGLTexture::~COpenGLTexture()
{
	delete [] ImageData;
	glDeleteTextures(1, &TextureName);
}


void COpenGLTexture::getImageData(IImage* image)
{
	ImageSize = image->getDimension();
	OriginalSize = ImageSize;

	core::dimension2d<s32> nImageSize;
	nImageSize.Width = getTextureSizeFromSurfaceSize(ImageSize.Width);
	nImageSize.Height = getTextureSizeFromSurfaceSize(ImageSize.Height);

	if (!nImageSize.Width || !nImageSize.Height ||
		!ImageSize.Width || !ImageSize.Height)
	{
		os::Printer::log("Could not create OpenGL Texture.", ELL_ERROR);
		return;
	}

	ImageData = new s32[nImageSize.Width * nImageSize.Height];

	if (nImageSize == ImageSize)
	{
		if (image->getColorFormat() == ECF_A8R8G8B8)
		{
			s32 s = nImageSize.Width * nImageSize.Height;
			s32 *t = (s32*)image->lock();
			for (s32 i=0; i<s; ++i)
				ImageData[i] = t[i];
			image->unlock();
		}
		else
		{
			// slow converting
			for (s32 x=0; x<ImageSize.Width; ++x)
				for (s32 y=0; y<ImageSize.Height; ++y)
					ImageData[y*nImageSize.Width + x] = image->getPixel(x,y).color;
		}
	}
	else
	{
		// scale texture

		s32* source = (s32*)image->lock();

		f32 sourceXStep = (f32)ImageSize.Width / (f32)nImageSize.Width;
		f32 sourceYStep = (f32)ImageSize.Height / (f32)nImageSize.Height;
		f32 sy;

		if (image->getColorFormat() == ECF_A8R8G8B8)
		{
			// copy texture scaling
			for (s32 x=0; x<nImageSize.Width; ++x)
			{
				sy = 0.0f;

				for (s32 y=0; y<nImageSize.Height; ++y)
				{
					ImageData[(s32)(y*nImageSize.Width + x)] = source[(s32)(((s32)sy)*ImageSize.Width + x*sourceXStep)];
					sy+=sourceYStep;
				}
			}
		}
		else
		{
			// convert texture scaling, slow
			for (s32 x=0; x<nImageSize.Width; ++x)
			{
				sy = 0.0f;

				for (s32 y=0; y<nImageSize.Height; ++y)
				{
					ImageData[(s32)(y*nImageSize.Width + x)] = 
						image->getPixel((s32)(x*sourceXStep), (s32)sy).color;

					sy+=sourceYStep;
				}
			}
		}
	}		

	ImageSize = nImageSize;
}


//! test if an error occurred, prints the problem, and returns 
//! true if an error happened
inline bool COpenGLTexture::testError()
{
	#ifdef _DEBUG
	GLenum g = glGetError();
	switch(g)
	{
	case GL_NO_ERROR:
		return false;
	case GL_INVALID_ENUM:
		os::Printer::log("GL_INVALID_ENUM", ELL_ERROR); break;
	case GL_INVALID_VALUE:
		os::Printer::log("GL_INVALID_VALUE", ELL_ERROR); break;
	case GL_INVALID_OPERATION:
		os::Printer::log("GL_INVALID_OPERATION", ELL_ERROR); break;
	case GL_STACK_OVERFLOW:
		os::Printer::log("GL_STACK_OVERFLOW", ELL_ERROR); break;
	case GL_STACK_UNDERFLOW:
		os::Printer::log("GL_STACK_UNDERFLOW", ELL_ERROR); break;
	case GL_OUT_OF_MEMORY:
		os::Printer::log("GL_OUT_OF_MEMORY", ELL_ERROR); break;
	};

	return true;
#endif
	return false;	
}


//! copies the the texture into an open gl texture.
void COpenGLTexture::copyTexture()
{
	glBindTexture(GL_TEXTURE_2D, TextureName);
	if (testError())
		os::Printer::log("Could not bind Texture", ELL_ERROR);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ImageSize.Width, 
		ImageSize.Height, 0, GL_BGRA_EXT , GL_UNSIGNED_BYTE, ImageData);
	if (testError())
		os::Printer::log("Could not glTexImage2D", ELL_ERROR);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	if (hasMipMaps)
	{
		s32 ret = 0;
		
		// TODO: make this work in linux too
		#ifdef _WIN32
		ret = gluBuild2DMipmaps(GL_TEXTURE_2D, 4, ImageSize.Width, ImageSize.Height,
 		            GL_BGRA_EXT, GL_UNSIGNED_BYTE, ImageData);
		#endif

		if (ret)
		{
			#ifdef _WIN32
			os::Printer::log("Could not create OpenGL texture mip maps.",
				(c8*)gluErrorString(ret), ELL_ERROR);
			#else
			os::Printer::log("Could not create OpenGL texture mip maps.", ELL_ERROR);
			#endif
		}
		else
		{
			glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST );
			glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
		}
	}	
}


//! returns the size of a texture which would be the optimize size for rendering it
inline s32 COpenGLTexture::getTextureSizeFromSurfaceSize(s32 size)
{
	s32 ts = 0x01;
	while(ts < size)
		ts <<= 1;

	return ts;
}


//! lock function
void* COpenGLTexture::lock()
{
	return ImageData;
}



//! unlock function
void COpenGLTexture::unlock()
{
	copyTexture();
}


//! Returns original size of the texture.
const core::dimension2d<s32>& COpenGLTexture::getOriginalSize()
{
	return OriginalSize;
}


//! Returns (=size) of the texture.
const core::dimension2d<s32>& COpenGLTexture::getSize()
{
	return ImageSize;
}


//! returns driver type of texture (=the driver, who created the texture)
EDriverType COpenGLTexture::getDriverType()
{
	return EDT_OPENGL;
}



//! returns color format of texture
ECOLOR_FORMAT COpenGLTexture::getColorFormat()
{
	return ECF_A8R8G8B8;
}



//! returns pitch of texture (in bytes)
s32 COpenGLTexture::getPitch()
{
	return ImageSize.Width * 4;
}



//! return open gl texture name
GLuint COpenGLTexture::getOpenGLTextureName()
{
	return TextureName;
}


} // end namespace video
} // end namespace irr

#endif // _IRR_COMPILE_WITH_OPENGL_
