#include "imageHDR.h"
#include <string>
#include <math.h>
#include "kernel.h"

#define  MINWIDTH	8	// given minimum width for REL
#define  MAXWIDTH	0x7fff	// given maximum width for RLE

using namespace VRUT;

ImageHDR::ImageHDR(void) : Image(HDRIMAGE), dataFloat(NULL), subWidth(0), subHeight(0)
{
	headerHDR.gamma = 7.0;
	headerHDR.exposure = 1.0;
	headerHDR.format = false;
	resolution.signX = false;
	resolution.signY= false;
	resolution.rotated = false;
	resolution.width = 0;
	resolution.height = 0;
}

ImageHDR::ImageHDR(const ImageHDR & imageHDR) : Image(imageHDR), subWidth(imageHDR.subWidth), subHeight(imageHDR.subHeight), headerHDR(imageHDR.headerHDR), resolution(imageHDR.resolution)
{
	dataFloat=new float[resolution.height*resolution.width*3];
	memcpy(dataFloat, imageHDR.dataFloat, resolution.height*resolution.width*3*sizeof(float));
}

ImageHDR::ImageHDR(const float *data, int width, int height) : Image(HDRIMAGE)
{
	resolution.width=width;
	resolution.height=height;
	dataFloat=new float[resolution.height*resolution.width*3];
	memcpy(dataFloat, data, resolution.height*resolution.width*3*sizeof(float));
	SetSubHeight((GetHeight() / 4));
	SetSubWidth((GetWidth() / 3));
}

ImageHDR::ImageHDR(const char *data, unsigned long size) : Image(HDRIMAGE)
{
	TextParser textParser(data, size);
	ParseImageHDR(textParser);
	cube = TestCross();
}

ImageHDR::~ImageHDR(void)
{
	SAFE_DELETE_ARR(dataFloat);
}
// now just call parsing

bool ImageHDR::Load(const wxString & fname, int cubeSide, int flip)
{
	if (cubeSide>0)
		LOGERROR(wxT("ImageHDR::Load load into cubeSide not implemented"));
	if (flip!=0)
		LOGERROR(wxT("ImageHDR::Load flipping image not implemented"));

	wxFileInputStream fin(fname);
	if (!fin || !fin.IsOk())
		return false;
	TextParser textParser(&fin, 100000, 100);
	ParseImageHDR(textParser);
	cube = TestCross();
	return true;
}

// parse header save the attributes, overwrite the default values
// call parsing of content
// return true if successful
bool ImageHDR::ParseImageHDR(TextParser &textParser)
{
	const char *keywords[] = {"RADIANCE", "FORMAT", "EXPOSURE", "COLORCORR", "SOFTWARE", "PIXASPECT", "VIEW", "PRIMARIES", "GAMMA", "-Y", "+Y", "-X", "+X", "" };
	bool rotated = false; // testing if X is before Y in file
	bool cycle = true;

	while (cycle)
	{
		switch(textParser.KeywordsSwitch(keywords, NULL, " \t\n\r="))
		{
		case 0: // RADIANCE keyword which is at the begging of each hdr file
			break;
		case 1: // FORMAT - followed by "32-bit_rle_rgbe" = true or "32-bit_rle_xyze" = false
			if (textParser.NextKeywordIs("=32-bit_rle_rgbe"))
			{
				headerHDR.format = true;
			}
			else
			{
				headerHDR.format = false;
				textParser.GetString();
			}
			break;
		case 2: // EXPOSURE - followed by float number, white chars may occure, default value 1.0
			if (!textParser.ParseFloat(&headerHDR.exposure," \t\n\r="))
			{
				headerHDR.exposure = 1.0;
				textParser.GetString();
			}
			break;
		case 3: // COLORCORR - TODO did not occurred yet
			textParser.GetString();
			break;
		case 4: // SOFTWARE - followed by name of software
			textParser.GetString();
			break;
		case 5: // PIXASPECt TODO did not occurred yet
			textParser.GetString();
			break;
		case 6: // VIEW - TODO did not occurred yet
			textParser.GetString();
			break;
		case 7: // PRIMARIES - TODO did not occurred yet  (followed by float vector)
			textParser.GetString();
			break;
		case 8: // GAMMA - followed by float number - GAMMA is manually edited in rgb2float()
			if (!textParser.ParseFloat(&headerHDR.gamma," \t\n\r="))
			{
				//headerHDR.gamma = 1.0;
				textParser.GetString();
			}
			break;
		case 9: // "-Y"	followed by resolution
			resolution.signY = false;
			textParser.ParseInt(&resolution.height);
			resolution.rotated = rotated;
			break;
		case 10: // "+Y"	followed by resolution
			resolution.signY = true;
			textParser.ParseInt(&resolution.height);
			resolution.rotated = rotated;
			break;
		case 11: // "-X"	followed by resolution
			resolution.signX = false;
			textParser.ParseInt(&resolution.width);
			rotated = true;
			break;
		case 12: // "+X"	followed by resolution
			resolution.signX = true;
			textParser.ParseInt(&resolution.width);
			rotated = true;
			break;
		default:
			cycle = false;
			break;
		}
	}
	SetSubHeight((GetHeight() / 4));
	SetSubWidth((GetWidth() / 3));
	dataFloat = new float[3*resolution.width*resolution.height];

	if (resolution.rotated || (resolution.signX == false) || resolution.signY)
	{
		// image has been rotated or axes order has been reversed
		LOGWARNING(wxT("ImageHDR::ParseImageHDR texture might be rotated or reversed"));
	}

	if ( (resolution.width>MINWIDTH) || (resolution.width<MAXWIDTH) )
	{
		//RLE is allowed for this width

		if (!ParseNewRLE(textParser))
		{
			SAFE_DELETE_ARR(dataFloat);
		}
	}
	else
	{
		//uncompressed - no tested
		if (!ParseUncompressedData(textParser))
		{
			SAFE_DELETE_ARR(dataFloat);
		}
	}
	return true;
}

#ifndef VRUT_HPC
void ImageHDR::BuildOneCubeSide(float * data, const TEXTURE_SAMPLER_TYPE buildAsType, GLenum cubeSide, bool use_glu_for_mipmaps) const
{
	if (buildAsType == TEXCUBE)
	{
		if (cubeSide == GL_TEXTURE_CUBE_MAP_NEGATIVE_Z)
		{
			int w = subWidth * 3;
			int end = w * subHeight - 3;
			for (int i = 0; i < (subHeight/2); i++)
			{
				int a = i * w;
				for (int j = 0; j < subWidth; j++)
				{
					int b = j * 3;
					float f0 = data[a + b];
					float f1 = data[a + b + 1];
					float f2 = data[a + b + 2];
					data[a + b]		= data[end - a - b];
					data[a + b + 1] = data[end - a - b + 1];
					data[a + b + 2] = data[end - a - b + 2];
					data[end - a - b]	  = f0;
					data[end - a - b + 1] = f1;
					data[end - a - b + 2] = f2;
				}
			}
		}

		if (use_glu_for_mipmaps)
			gluBuild2DMipmaps(cubeSide, 3, subWidth, subHeight, GL_RGB, GL_FLOAT, data);
		else
			glTexImage2D(cubeSide, 0, GL_RGB, subWidth, subHeight, 0, GL_RGB, GL_FLOAT, data);
	}
	else
	{
		if (use_glu_for_mipmaps)
			gluBuild2DMipmaps(GL_TEXTURE_2D, 3, GetWidth(), GetHeight(), GL_RGB, GL_FLOAT, data);
		else
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, GetWidth(), GetHeight(), 0, GL_RGB, GL_FLOAT, data);
	}
}


void ImageHDR::UpdateOglTexture(GLuint tex_object, const TEXTURE_SAMPLER_TYPE buildAsType, bool create_mipmaps) const
{
	// TODO Does anyone have a better idea where to place this (OGL context has to be created and active by the time we call this) ?
	// TODO Since this function is in VRUT's core and GLEW is a static library, initializing GLEW in module does not mean GLEW is
	//		initialized here. It means GLEW must be initialized from VRUT's core.
	if (!glGenerateMipmapEXT)
		KERNEL->InitGlew();

	if (IsOk())
	{
		int texTarget = (buildAsType==TEXCUBE)? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
		glEnable(texTarget);
		glBindTexture( texTarget, tex_object);
		glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );

		// resize of the texture (if there is not enough memory on GPU) is not implemented  - TODO

		bool use_glu_for_mipmaps = create_mipmaps && !(GLEW_EXT_framebuffer_object);

		GLenum cubesides[6] = {GL_TEXTURE_CUBE_MAP_POSITIVE_X,GL_TEXTURE_CUBE_MAP_NEGATIVE_X,GL_TEXTURE_CUBE_MAP_POSITIVE_Y,GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,GL_TEXTURE_CUBE_MAP_POSITIVE_Z,GL_TEXTURE_CUBE_MAP_NEGATIVE_Z};
		if (buildAsType == TEXCUBE)
		{
			if (cube)
			{
				float * subDataFloat = new float[subHeight*subWidth*3];
				int coordsX[] = {2, 0, 1, 1, 1, 1};  // specific order is necessary for correct position of each face
				int coordsY[] = {1, 1, 0, 2, 1, 3};

				for (int i = 0; i < 6; i++)
				{
					int baseX = coordsX[i]*subWidth * 3;
					int baseY = coordsY[i]*subHeight * resolution.width * 3;

					for (int xx = 0; xx < subWidth; xx++)
					{
						int a = xx * 3;
						for (int yy = 0; yy < subHeight; yy++)
						{
							int b = yy*subWidth*3;
							int index =  resolution.width*yy*3 + baseY + baseX + a;
							subDataFloat[a + b] = dataFloat[index];
							subDataFloat[a + b + 1] = dataFloat[index+1];
							subDataFloat[a + b + 2] = dataFloat[index+2];
						}
					}
					BuildOneCubeSide(subDataFloat, buildAsType, cubesides[i], use_glu_for_mipmaps);
				}
				if (create_mipmaps && !use_glu_for_mipmaps)
					glGenerateMipmapEXT(GL_TEXTURE_CUBE_MAP);
			}
			else
			{
				BuildOneCubeSide(dataFloat, buildAsType, GL_TEXTURE_2D, use_glu_for_mipmaps);
				if (create_mipmaps && !use_glu_for_mipmaps)
					glGenerateMipmapEXT(GL_TEXTURE_2D);
			}
		}
		else
		{
			BuildOneCubeSide(dataFloat, buildAsType, GL_TEXTURE_2D, use_glu_for_mipmaps);
			if (create_mipmaps && !use_glu_for_mipmaps)
				glGenerateMipmapEXT(GL_TEXTURE_2D);
		}
	}
}
#endif

bool ImageHDR::SetTransparencyAndSetAlphaUsage(bool testPixels)
{
	return true;
}


int ImageHDR::GetWidth() const
{
	return resolution.width;
}

int ImageHDR::GetHeight() const
{
	return resolution.height;
}

float * ImageHDR::GetData() const
{
	return dataFloat;
}

bool ImageHDR::IsOk() const
{
	return (dataFloat != (float *)NULL);
}

bool ImageHDR::Save(wxString filename) const
{
	return true;
}

bool ImageHDR::ParseUncompressedData(TextParser &textParser) // not tested
{
	unsigned char *dataChar = new unsigned char[4*resolution.width*resolution.height];
	/*unsigned char myB[4];
	int numPixels = resolution.width*resolution.height;
	while ((numPixels--) > 0) {
		textParser.GetBinData(myB, 4);
	}*/
	textParser.GetBinData(dataChar, 4*resolution.width*resolution.height);
	bool ret=Rgb2Float(dataChar);
	SAFE_DELETE_ARR(dataChar);
	return ret;
}
bool ImageHDR::ParseOldRLE(TextParser &textParser) // not tested / it has to be called from ParseNewRLE(), but we need 4 bytes, that are used for testing in ParseNewRLE()
{
	unsigned char *dataChar = new unsigned char[4*resolution.width*resolution.height];
	int lineNumber;
	for (lineNumber = 0; lineNumber < resolution.height; lineNumber++)
	{
		int i;
		int rshift = 0;
		int length = resolution.width;
		char myB[4];

		while (length > 0) {
			int index = lineNumber*resolution.width*4+(resolution.width-length)*4;
			textParser.GetBinData(myB, 4);
			dataChar[index] = myB[0];
			dataChar[index+1] = myB[1];
			dataChar[index+2] = myB[2];
			dataChar[index+3] = myB[3];

			if ((dataChar[index] == 1) && (dataChar[index+1] == 1) && (dataChar[index+2] == 1))
			{
				for (i = (dataChar[index+3] << rshift); i > 0; i--) {
					memcpy(&dataChar[index], &dataChar[index-4], 4);
					length--;
				}
				rshift += 8;
			}
			else
			{
				length--;
				rshift = 0;
			}
		}
	}
	bool ret=Rgb2Float(dataChar);
	SAFE_DELETE_ARR(dataChar);
	return ret;
}

bool ImageHDR::ParseNewRLE(TextParser &textParser)
{
	unsigned char *dataChar = new unsigned char[4*resolution.width*resolution.height];
	int lineNumber = 0;
	unsigned char myB[4];

	for (; lineNumber < resolution.height; ++lineNumber)
	{
		//reads first four bytes
		textParser.GetBinData(myB, 4);
		//RLE begins with 2 bytes equal to 2
		//Since 3rd byte is upper byte of scanline length, it must be >0 (signed)
		if ((myB[0] != 2) || (myB[1] != 2) || (myB[2] & 0x80))
		{
			LOGERROR(wxT("ImageHDR::ParseNewRLE OLD RLE DATA not supported"));
			SAFE_DELETE_ARR(dataChar);
			return false;
		}
		// 3rd byte and 4th byte equals scanline length
		int tmp = int(myB[2]) << 8 | int(myB[3]);
		if ( tmp != resolution.width)
		{
			LOGERROR(wxT("ImageHDR::ParseNewRLE width verification fail"));
			SAFE_DELETE_ARR(dataChar);
			return false;
		}
		unsigned char val=0;
		for (int i = 0; i < 4; ++i)
		{
			for (int j = 0; j < resolution.width; )
			{
				unsigned char code;
				textParser.GetBinData(&code, 1);
				if (code > 128) { // run = same vales
					code &= 127;
					textParser.GetBinData(&val, 1);
					while (code--)
					{
						dataChar[lineNumber*resolution.width*4 + (j++)*4 + i] = val;
					}
				}
				else
				{	// non-run
					while (code--)
					{
						textParser.GetBinData(&val, 1);
						dataChar[lineNumber*resolution.width*4 + (j++)*4 + i] = val;
					}
				}
			}
		}
	}
	if (lineNumber!=resolution.height){
		LOG(wxT("ImageHDR::ParseNewRLE number of processed lines is not corresponding to height of image"));
		SAFE_DELETE_ARR(dataChar);
		return false;
	}
	//convert rgbe to floats
	bool ret=Rgb2Float(dataChar);
	SAFE_DELETE_ARR(dataChar);
	return ret;
}

bool ImageHDR::Rgb2Float(unsigned char *dataChar)
{
	float maxVal = 1.0;
	float r,g,b,f;
	int e, index, indexf;
	float ex = (256.0f*headerHDR.exposure);
	for (int i=0; i<resolution.height; i++){
		int a = i*resolution.width*4;
		int af = i*resolution.width*3;
		for (int j=0; j < resolution.width; j++){
			index = a + j*4;
			indexf = af + j*3;
			e = dataChar[index+3];
			if (e==0){
				r=g=b=0;
			}
			else
			{
				f = pow(2.f,(e-128));
				r = (( dataChar[index]+0.5f ) * f) / ex;
				g = (( dataChar[index+1]+0.5f ) * f) / ex;
				b = (( dataChar[index+2]+0.5f ) * f) / ex;
				maxVal = __max(b,__max(g,__max(r, maxVal)));
			}
			dataFloat[indexf]=r;
			dataFloat[indexf+1]=g;
			dataFloat[indexf+2]=b;
		}
	}
	float lambda = 1/(headerHDR.gamma*1); // value 5.5 is manualy set correction (displayed textures were too dark)

	for (int i = 0; i < resolution.height; i++)
	{
		int a = i*resolution.width*3;
		for (int j = 0; j < resolution.width; j++)
		{
			index = a + j*3;
			//mapped to range [0,1]
			dataFloat[index] /= maxVal;
			dataFloat[index+1] /= maxVal;
			dataFloat[index+2] /= maxVal;
			//gamma correction
			dataFloat[index] = (pow(dataFloat[index],lambda));
			dataFloat[index+1] = (pow(dataFloat[index+1],lambda));
			dataFloat[index+2] = (pow(dataFloat[index+2],lambda));
		}
	}
	return true;
}

bool ImageHDR::TestCross() const
{
	int coordsX[] = {2, 0, 2, 0};
	int coordsY[] = {0, 2, 2, 3};

	bool test = false;
	if (subHeight == subWidth)
	{
		// control of 20 pseudo random pixels from 1st area with another 20 pixels in 4 areas which are the same, if it is cross texture
		for (int i = 0; i < 20; i++)
		{
			int x = (rand() % (subWidth-4) + 2) * 3;
			int y = (rand() % (subHeight-4) + 2) * resolution.width * 3 ;
			int r = dataFloat[y + x];
			int g = dataFloat[y + x+1];
			int b = dataFloat[y + x+2];

			for (int j = 0; j < 4; j++)
			{
				for (int k = 0; k < 20 ; k++){
					int xx = rand() % (subWidth-4) + 2;
					int yy = rand() % (subHeight-4) + 2;
 					int index =  resolution.width*yy*3 + subHeight*coordsY[j]*resolution.width * 3 + coordsX[j]*subWidth*3 + xx*3;
					if ((r == dataFloat[index]) && (g == dataFloat[index+1]) && (b == dataFloat[index+2]))
						test = true;
					else
						return false;
				}
			}
		}
	}
	return test;
}

void ImageHDR::SetSubHeight(int h)
{
	this->subHeight = h;
}

void ImageHDR::SetSubWidth(int w)
{
	this->subWidth = w;
}

Vector4 ImageHDR::GetRGBA(int x, int y)
{
	if (!dataFloat || (x<0) || (x>=resolution.width) || (y<0) || (y>=resolution.height))
		return Vector4();
	return Vector4(dataFloat[(resolution.width*y+x)*3], dataFloat[(resolution.width*y+x)*3+1], dataFloat[(resolution.width*y+x)*3+2], 1.0f);
}
