//-----------------------------------------------------------------------------
//
// ImageLib Sources
// Copyright (C) 2000-2008 by Denton Woods (this file by thakis / Denton)
// Last modified: 02/09/2009
//
// Filename: src-IL/src/il_hdr.c
//
// Description: Reads/writes a RADIANCE High Dynamic Range Image
//
//-----------------------------------------------------------------------------


#include "il_internal.h"
#ifndef IL_NO_HDR
#include "il_hdr.h"
#include "il_endian.h"


//! Checks if the file specified in FileName is a valid .hdr file.
ILboolean ilIsValidHdr(ILconst_string FileName)
{
	ILHANDLE	HdrFile;
	ILboolean	bHdr = IL_FALSE;

	if (!iCheckExtension(FileName, IL_TEXT("hdr"))) {
		ilSetError(IL_INVALID_EXTENSION);
		return bHdr;
	}

	HdrFile = iopenr(FileName);
	if (HdrFile == NULL) {
		ilSetError(IL_COULD_NOT_OPEN_FILE);
		return bHdr;
	}

	bHdr = ilIsValidHdrF(HdrFile);
	icloser(HdrFile);

	return bHdr;
}


//! Checks if the ILHANDLE contains a valid .hdr file at the current position.
ILboolean ilIsValidHdrF(ILHANDLE File)
{
	ILuint		FirstPos;
	ILboolean	bRet;

	iSetInputFile(File);
	FirstPos = itell();
	bRet = iIsValidHdr();
	iseek(FirstPos, IL_SEEK_SET);

	return bRet;
}


//! Checks if Lump is a valid .hdr lump.
ILboolean ilIsValidHdrL(const void *Lump, ILuint Size)
{
	iSetInputLump(Lump, Size);
	return iIsValidHdr();
}


// Internal function used to get the .hdr header from the current file.
ILboolean iGetHdrHead(HDRHEADER *Header)
{
	ILboolean done = IL_FALSE;
	char a, b;
	char x[3], y[3]; //changed 20050217: added space for the '\0' char
	char buff[81]; // 01-19-2009: Added space for the '\0'.
	ILuint count = 0;

	iread(Header->Signature, 1, 10);

	//skip lines until an empty line is found.
	//this marks the end of header information,
	//the next line contains the image's dimension.

	//TODO: read header contents into variables
	//(EXPOSURE, orientation, xyz correction, ...)

	if (iread(&a, 1, 1) != 1)
		return IL_FALSE;

	while (!done) {
		if (iread(&b, 1, 1) != 1)
			return IL_FALSE;
		if (b == '\n' && a == '\n')
			done = IL_TRUE;
		else
			a = b;
	}

	//read dimensions (note that this assumes a somewhat valid image)
	if (iread(&a, 1, 1) != 1)
		return IL_FALSE;
	while (a != '\n') {
		if (count >= 80) {  // Line shouldn't be this long at all.
			ilSetError(IL_INVALID_FILE_HEADER);
			return IL_FALSE;
		}
		buff[count] = a;
		if (iread(&a, 1, 1) != 1)
			return IL_FALSE;
		++count;
	}
	buff[count] = '\0';

	//note that this is not the 100% correct way to load hdr images:
	//in a perfect world we would check if there's a +/- X/Y in front
	//of width and heigth and mirror + rotate the image after decoding
	//according to that. But HDRShop doesn't do that either (and that's
	//my reference program :) ) and it's just a rotate and a mirror,
	//nothing that really changes the appearance of the loaded image...
	//(The code as it is now assumes that y contains "-Y" and x contains
	//"+X" after the following line)
	
	// The 2 has to be in the %s format specifier to prevent buffer overruns.
	sscanf(buff, "%2s %d %2s %d", y, &Header->Height, x, &Header->Width);

	return IL_TRUE;
}


// Internal function to get the header and check it.
ILboolean iIsValidHdr()
{
	char	Head[10];
	ILint	Read;

	Read = iread(Head, 1, 10);
	iseek(-Read, IL_SEEK_CUR);  // Go ahead and restore to previous state
	if (Read != 10)
		return IL_FALSE;

	return
		strnicmp(Head, "#?RADIANCE", 10) == 0
		|| strnicmp(Head, "#?RGBE", 6) == 0;
}


// Internal function used to check if the HEADER is a valid .hdr header.
ILboolean iCheckHdr(HDRHEADER *Header)
{
	return
		strnicmp(Header->Signature, "#?RADIANCE", 10) == 0
		|| strnicmp(Header->Signature, "#?RGBE", 6) == 0;
}


//! Reads a .hdr file
ILboolean ilLoadHdr(ILconst_string FileName)
{
	ILHANDLE	HdrFile;
	ILboolean	bHdr = IL_FALSE;

	HdrFile = iopenr(FileName);
	if (HdrFile == NULL) {
		ilSetError(IL_COULD_NOT_OPEN_FILE);
		return bHdr;
	}

	bHdr = ilLoadHdrF(HdrFile);
	icloser(HdrFile);

	return bHdr;
}


//! Reads an already-opened .hdr file
ILboolean ilLoadHdrF(ILHANDLE File)
{
	ILuint		FirstPos;
	ILboolean	bRet;

	iSetInputFile(File);
	FirstPos = itell();
	bRet = iLoadHdrInternal();
	iseek(FirstPos, IL_SEEK_SET);

	return bRet;
}


//! Reads from a memory "lump" that contains a .hdr
ILboolean ilLoadHdrL(const void *Lump, ILuint Size)
{
	iSetInputLump(Lump, Size);
	return iLoadHdrInternal();
}


// Internal function used to load the .hdr.
ILboolean iLoadHdrInternal()
{
	HDRHEADER	Header;
	ILfloat *data;
	ILubyte *scanline;
	ILuint i, j, e, r, g, b;

	if (iCurImage == NULL) {
		ilSetError(IL_ILLEGAL_OPERATION);
		return IL_FALSE;
	}

	if (!iGetHdrHead(&Header)) {
		ilSetError(IL_INVALID_FILE_HEADER);
		return IL_FALSE;
	}
	if (!iCheckHdr(&Header)) {
		//iseek(-(ILint)sizeof(HDRHEAD), IL_SEEK_CUR);
		ilSetError(IL_INVALID_FILE_HEADER);
		return IL_FALSE;
	}

	// Update the current image with the new dimensions
	if (!ilTexImage(Header.Width, Header.Height, 1, 3, IL_RGB, IL_FLOAT, NULL)) {
		return IL_FALSE;
	}
	iCurImage->Origin = IL_ORIGIN_UPPER_LEFT;

	//read image data
	if (iGetHint(IL_MEM_SPEED_HINT) == IL_FASTEST)
		iPreCache(iCurImage->Width / 8 * iCurImage->Height);

	data = (ILfloat*)iCurImage->Data;
	scanline = (ILubyte*)ialloc(Header.Width*4);
	for (i = 0; i < Header.Height; ++i) {
		ReadScanline(scanline, Header.Width);

		//convert hdrs internal format to floats
		for (j = 0; j < 4*Header.Width; j += 4) {
			ILuint *ee;
			ILfloat t, *ff;
			e = scanline[j + 3];
			r = scanline[j + 0];
			g = scanline[j + 1];
			b = scanline[j + 2];

			//t = (float)pow(2.f, ((ILint)e) - 128);
			if (e != 0)
				e = (e - 1) << 23;
			
			// All this just to avoid stric-aliasing warnings...
			// was: t = *(ILfloat*)&e
			ee = &e;
			ff = (ILfloat*)ee;
			t = *ff;
			
			data[0] = (r/255.0f)*t;
			data[1] = (g/255.0f)*t;
			data[2] = (b/255.0f)*t;
			data += 3;
		}
	}
	iUnCache();
	ifree(scanline);

	return ilFixImage();
}

void ReadScanline(ILubyte *scanline, ILuint w) {
	ILubyte *runner;
	ILuint r, g, b, e, read, shift;

	r = igetc();
	g = igetc();
	b = igetc();
	e = igetc();

	//check if the scanline is in the new format
	//if so, e, r, g, g are stored separated and are
	//rle-compressed independently.
	if (r == 2 && g == 2) {
		ILuint length = (b << 8) | e;
		ILuint j, t, k;
		if (length > w)
			length = w; //fix broken files
		for (k = 0; k < 4; ++k) {
			runner = scanline + k;
			j = 0;
			while (j < length) {
				t = igetc();
				if (t > 128) { //Run?
					ILubyte val = igetc();
					t &= 127;
					//copy current byte
					while (t > 0 && j < length) {
						*runner = val;
						runner += 4;
						--t;
						++j;
					}
				}
				else { //No Run.
					//read new bytes
					while (t > 0 && j < length) {
						*runner = igetc();
						runner += 4;
						--t;
						++j;
					}
				}
			}
		}
		return; //done decoding a scanline in separated format
	}

	//if we come here, we are dealing with old-style scanlines
	shift = 0;
	read = 0;
	runner = scanline;
	while (read < w) {
		if (read != 0) {
			r = igetc();
			g = igetc();
			b = igetc();
			e = igetc();
		}

		//if all three mantissas are 1, then this is a rle
		//count dword
		if (r == 1 && g == 1 && b == 1) {
			ILuint length = e;
			ILuint j;
			for (j = length << shift; j > 0 && read < w; --j) {
				memcpy(runner, runner - 4, 4);
				runner += 4;
				++read;
			}
			//if more than one rle count dword is read
			//consecutively, they are higher order bytes
			//of the first read value. shift keeps track of
			//that.
			shift += 8;
		}
		else {
			runner[0] = r;
			runner[1] = g;
			runner[2] = b;
			runner[3] = e;

			shift = 0;
			runner += 4;
			++read;
		}
	}
}



//! Writes a Hdr file
ILboolean ilSaveHdr(const ILstring FileName)
{
	ILHANDLE	HdrFile;
	ILuint		HdrSize;

	if (ilGetBoolean(IL_FILE_MODE) == IL_FALSE) {
		if (iFileExists(FileName)) {
			ilSetError(IL_FILE_ALREADY_EXISTS);
			return IL_FALSE;
		}
	}

	HdrFile = iopenw(FileName);
	if (HdrFile == NULL) {
		ilSetError(IL_COULD_NOT_OPEN_FILE);
		return IL_FALSE;
	}

	HdrSize = ilSaveHdrF(HdrFile);
	iclosew(HdrFile);

	if (HdrSize == 0)
		return IL_FALSE;
	return IL_TRUE;
}


//! Writes a Hdr to an already-opened file
ILuint ilSaveHdrF(ILHANDLE File)
{
	ILuint Pos;
	iSetOutputFile(File);
	Pos = itellw();
	if (iSaveHdrInternal() == IL_FALSE)
		return 0;  // Error occurred
	return itellw() - Pos;  // Return the number of bytes written.
}


//! Writes a Hdr to a memory "lump"
ILuint ilSaveHdrL(void *Lump, ILuint Size)
{
	ILuint Pos;
	iSetOutputLump(Lump, Size);
	Pos = itellw();
	if (iSaveHdrInternal() == IL_FALSE)
		return 0;  // Error occurred
	return itellw() - Pos;  // Return the number of bytes written.
}


//
// Much of the saving code is based on the code by Bruce Walter,
//  available at http://www.graphics.cornell.edu/online/formats/rgbe/.
//
// The actual source code file is
//  http://www.graphics.cornell.edu/online/formats/rgbe/rgbe.c
//


/* standard conversion from float pixels to rgbe pixels */
/* note: you can remove the "inline"s if your compiler complains about it */
//static INLINE void 
static void 
float2rgbe(unsigned char rgbe[4], float red, float green, float blue)
{
  float v;
  int e;

  v = red;
  if (green > v) v = green;
  if (blue > v) v = blue;
  if (v < 1e-32) {
    rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
  }
  else {
    v = (float)(frexp(v,&e) * 256.0/v);
    rgbe[0] = (unsigned char) (red * v);
    rgbe[1] = (unsigned char) (green * v);
    rgbe[2] = (unsigned char) (blue * v);
    rgbe[3] = (unsigned char) (e + 128);
  }
}


typedef struct {
  ILuint	valid;            /* indicate which fields are valid */
  ILbyte	programtype[16]; /* listed at beginning of file to identify it 
                         * after "#?".  defaults to "RGBE" */ 
  ILfloat	gamma;          /* image has already been gamma corrected with 
                         * given gamma.  defaults to 1.0 (no correction) */
  ILfloat	exposure;       /* a value of 1.0 in an image corresponds to
			 * <exposure> watts/steradian/m^2. 
			 * defaults to 1.0 */
} rgbe_header_info;

/* flags indicating which fields in an rgbe_header_info are valid */
#define RGBE_VALID_PROGRAMTYPE 0x01
#define RGBE_VALID_GAMMA       0x02
#define RGBE_VALID_EXPOSURE    0x04

/* offsets to red, green, and blue components in a data (float) pixel */
#define RGBE_DATA_RED    0
#define RGBE_DATA_GREEN  1
#define RGBE_DATA_BLUE   2
/* number of floats per pixel */
#define RGBE_DATA_SIZE   3


/* default minimal header. modify if you want more information in header */
ILboolean RGBE_WriteHeader(ILuint width, ILuint height, rgbe_header_info *info)
{
	char *programtype = "RGBE";

	if (info && (info->valid & RGBE_VALID_PROGRAMTYPE))
		programtype = info->programtype;
	if (ilprintf("#?%s\n",programtype) < 0)
		return IL_FALSE;
	/* The #? is to identify file type, the programtype is optional. */
	if (info && (info->valid & RGBE_VALID_GAMMA)) {
		if (ilprintf("GAMMA=%g\n",info->gamma) < 0)
		  return IL_FALSE;
	}
	if (info && (info->valid & RGBE_VALID_EXPOSURE)) {
		if (ilprintf("EXPOSURE=%g\n",info->exposure) < 0)
		  return IL_FALSE;
	}
	if (ilprintf("FORMAT=32-bit_rle_rgbe\n\n") < 0)
		return IL_FALSE;
	if (ilprintf("-Y %d +X %d\n", height, width) < 0)
		return IL_FALSE;
	return IL_TRUE;
}


/* simple write routine that does not use run length encoding */
/* These routines can be made faster by allocating a larger buffer and
   fread-ing and iwrite-ing the data in larger chunks */
int RGBE_WritePixels(float *data, int numpixels)
{
	unsigned char rgbe[4];

	while (numpixels-- > 0) {
		float2rgbe(rgbe,data[RGBE_DATA_RED],data[RGBE_DATA_GREEN],data[RGBE_DATA_BLUE]);
		data += RGBE_DATA_SIZE;
		if (iwrite(rgbe, sizeof(rgbe), 1) < 1)
			return IL_FALSE;
	}
	return IL_TRUE;
}


/* The code below is only needed for the run-length encoded files. */
/* Run length encoding adds considerable complexity but does */
/* save some space.  For each scanline, each channel (r,g,b,e) is */
/* encoded separately for better compression. */

ILboolean RGBE_WriteBytes_RLE(ILubyte *data, ILuint numbytes)
{
#define MINRUNLENGTH 4
	ILuint	cur, beg_run, run_count, old_run_count, nonrun_count;
	ILubyte	buf[2];

	cur = 0;
	while (cur < numbytes) {
		beg_run = cur;
		/* find next run of length at least 4 if one exists */
		run_count = old_run_count = 0;
		while((run_count < MINRUNLENGTH) && (beg_run < numbytes)) {
			beg_run += run_count;
			old_run_count = run_count;
			run_count = 1;
			// 01-25-2009: Moved test for beg_run + run_count first so that it is
			//  tested first.  This keeps it from going out of bounds by 1.
			while((beg_run + run_count < numbytes) && (run_count < 127) && 
				(data[beg_run] == data[beg_run + run_count]))
			run_count++;
		}
		/* if data before next big run is a short run then write it as such */
		if ((old_run_count > 1)&&(old_run_count == beg_run - cur)) {
			buf[0] = 128 + old_run_count;   /*write short run*/
			buf[1] = data[cur];
			if (iwrite(buf,sizeof(buf[0])*2,1) < 1)
				return IL_FALSE;
			cur = beg_run;
		}
		/* write out bytes until we reach the start of the next run */
		while(cur < beg_run) {
			nonrun_count = beg_run - cur;
			if (nonrun_count > 128) 
				nonrun_count = 128;
			buf[0] = nonrun_count;
			if (iwrite(buf,sizeof(buf[0]),1) < 1)
				return IL_FALSE;
			if (iwrite(&data[cur],sizeof(data[0])*nonrun_count,1) < 1)
				return IL_FALSE;
			cur += nonrun_count;
		}
		/* write out next run if one was found */
		if (run_count >= MINRUNLENGTH) {
			buf[0] = 128 + run_count;
			buf[1] = data[beg_run];
			if (iwrite(buf,sizeof(buf[0])*2,1) < 1)
				return IL_FALSE;
			cur += run_count;
		}
	}
	return IL_TRUE;
#undef MINRUNLENGTH
}


// Internal function used to save the Hdr.
ILboolean iSaveHdrInternal()
{
	ILimage *TempImage;
	rgbe_header_info stHeader;
	unsigned char rgbe[4];
	ILubyte		*buffer;
	ILfloat		*data;
	ILuint		i;
	ILboolean	bRet;

	if (iCurImage == NULL) {
		ilSetError(IL_ILLEGAL_OPERATION);
		return IL_FALSE;
	}

	stHeader.exposure = 0;
	stHeader.gamma = 0;
	stHeader.programtype[0] = 0;
	stHeader.valid = 0;

	if (iCurImage->Format != IL_UNSIGNED_BYTE) {
		TempImage = iConvertImage(iCurImage, IL_RGB, IL_FLOAT);
		if (TempImage == NULL)
			return IL_FALSE;
	}
	else
		TempImage = iCurImage;

	if (!RGBE_WriteHeader(TempImage->Width, TempImage->Height, &stHeader))
		return IL_FALSE;

	if (TempImage->Origin == IL_ORIGIN_LOWER_LEFT)
		iFlipBuffer(TempImage->Data, TempImage->Depth, TempImage->Bps, TempImage->Height);
	data = (ILfloat*)TempImage->Data;

	if ((TempImage->Width < 8)||(TempImage->Width > 0x7fff)) {
		/* run length encoding is not allowed so write flat*/
		bRet = RGBE_WritePixels(data,TempImage->Width*TempImage->Height);
		if (iCurImage != TempImage)
			ilCloseImage(TempImage);
		return bRet;
	}
	buffer = (ILubyte*)ialloc(sizeof(ILubyte)*4*TempImage->Width);
	if (buffer == NULL) {
		/* no buffer space so write flat */
		bRet = RGBE_WritePixels(data,TempImage->Width*TempImage->Height);
		if (iCurImage != TempImage)
			ilCloseImage(TempImage);
		return bRet;
	}

	while(TempImage->Height-- > 0) {
		rgbe[0] = 2;
		rgbe[1] = 2;
		rgbe[2] = TempImage->Width >> 8;
		rgbe[3] = TempImage->Width & 0xFF;
		if (iwrite(rgbe, sizeof(rgbe), 1) < 1) {
			free(buffer);
			if (iCurImage != TempImage)
				ilCloseImage(TempImage);
			return IL_FALSE;
		}

		for(i=0;i<TempImage->Width;i++) {
			float2rgbe(rgbe,data[RGBE_DATA_RED],data[RGBE_DATA_GREEN],data[RGBE_DATA_BLUE]);
			buffer[i] = rgbe[0];
			buffer[i+TempImage->Width] = rgbe[1];
			buffer[i+2*TempImage->Width] = rgbe[2];
			buffer[i+3*TempImage->Width] = rgbe[3];
			data += RGBE_DATA_SIZE;
		}
		/* write out each of the four channels separately run length encoded */
		/* first red, then green, then blue, then exponent */
		for(i=0;i<4;i++) {
			if (RGBE_WriteBytes_RLE(&buffer[i*TempImage->Width],TempImage->Width) != IL_TRUE) {
				ifree(buffer);
				if (iCurImage != TempImage)
					ilCloseImage(TempImage);
				return IL_FALSE;
			}
		}
	}
	ifree(buffer);

	if (iCurImage != TempImage)
		ilCloseImage(TempImage);
	return IL_TRUE;
}


#endif//IL_NO_HDR
