/* This file is part of the KDE project
   Copyright (C) 2005 Christoph Hormann <chris_hormann@gmx.de>
   Copyright (C) 2005 Ignacio Castaño <castanyo@yahoo.es>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the Lesser GNU General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.
*/

#include "hdr.h"

#include <QtGui/QImage>
#include <QtCore/QDataStream>

#include <kdebug.h>
#include <kglobal.h>

typedef Q_UINT8 uchar;

namespace {	// Private.

#define MAXLINE		1024
#define MINELEN		8       // minimum scanline length for encoding
#define MAXELEN		0x7fff  // maximum scanline length for encoding

	static inline uchar ClipToByte(float value)
	{
		if (value > 255.0f) return 255;
		//else if (value < 0.0f) return 0;	// we know value is positive.
		return uchar(value);
	}

	// read an old style line from the hdr image file
	// if 'first' is true the first byte is already read
	static bool Read_Old_Line (uchar * image, int width, QDataStream & s)
	{
		int  rshift = 0;
		int  i;

		while (width > 0)
		{
			s >> image[0];
			s >> image[1];
			s >> image[2];
			s >> image[3];

			if (s.atEnd()) return false;

			if ((image[0] == 1) && (image[1] == 1) && (image[2] == 1))
			{
				for (i = image[3] << rshift; i > 0; i--)
				{
					//memcpy(image, image-4, 4);
					(uint &)image[0] = (uint &)image[0-4];
					image += 4;
					width--;
				}
				rshift += 8;
			}
			else
			{
				image += 4;
				width--;
				rshift = 0;
			}
		}
		return true;
	}


	static void RGBE_To_QRgbLine(uchar * image, QRgb * scanline, int width)
	{
		for (int j = 0; j < width; j++)
		{
			// v = ldexp(1.0, int(image[3]) - 128);
			float v;
			int e = int(image[3]) - 128;
			if( e > 0 ) 
			{
				v = float(1 << e);
			}
			else 
			{
				v = 1.0f / float(1 << -e);
			}
			
			scanline[j] = qRgb( ClipToByte(float(image[0]) * v),
								ClipToByte(float(image[1]) * v),
								ClipToByte(float(image[2]) * v) );

			image += 4;
		}
	}

	// Load the HDR image.
	static bool LoadHDR( QDataStream & s, const int width, const int height, QImage & img )
	{
		uchar val, code;

		// Create dst image.
		if( !img.create( width, height, 32 ) )
		{
			return false;
		}

  		QMemArray<uchar> image( width * 4 );
	
		for (int cline = 0; cline < height; cline++)
		{
			QRgb * scanline = (QRgb *) img.scanLine( cline );

			// determine scanline type
			if ((width < MINELEN) || (MAXELEN < width))
			{
				Read_Old_Line(image.data(), width, s);
				RGBE_To_QRgbLine(image.data(), scanline, width);
				continue;
			}

			s >> val;

			if (s.atEnd()) 
			{
				return true;
			}

			if (val != 2)
			{
				s.device()->at( s.device()->at() - 1 );
				Read_Old_Line(image.data(), width, s);
				RGBE_To_QRgbLine(image.data(), scanline, width);
				continue;
			}

			s >> image[1];
			s >> image[2];
			s >> image[3];

			if (s.atEnd()) 
			{
				return true;
			}

			if ((image[1] != 2) || (image[2] & 128))
			{
				image[0] = 2;
				Read_Old_Line(image.data()+4, width-1, s);
				RGBE_To_QRgbLine(image.data(), scanline, width);
				continue;
			}

			if ((image[2] << 8 | image[3]) != width)
			{
				return false;
			}

			// read each component
			for (int i = 0; i < 4; i++)
			{
				for (int j = 0; j < width; )
				{
					s >> code;
					if (s.atEnd())
					{
						return false;
					}
					if (code > 128)
					{
						// run
						code &= 127;
						s >> val;
						while( code != 0 )
						{
							image[i + j * 4] = val;
							j++;
							code--;
						}
					}
					else
					{
						// non-run
						while( code != 0 )
						{
							s >> image[i +  j * 4];
							j++;
							code--;
						}
					}
				}
			}

			RGBE_To_QRgbLine(image.data(), scanline, width);
		}

		return true;
	}
		
} // namespace


KDE_EXPORT void kimgio_hdr_read( QImageIO * io )
{
	int len;
	char line[MAXLINE];
	//bool validHeader = false;
	bool validFormat = false;
	
	// Parse header	
	do {
		len = io->ioDevice()->readLine(line, MAXLINE);
	
		/*if (strcmp(line, "#?RADIANCE\n") == 0 || strcmp(line, "#?RGBE\n") == 0)
		{
			validHeader = true;
		}*/
		if (strcmp(line, "FORMAT=32-bit_rle_rgbe\n") == 0)
		{
			validFormat = true;
		}
		
	} while((len > 0) && (line[0] != '\n'));
	
	if( /*!validHeader ||*/ !validFormat )
	{
		kDebug(399) << "Unknown HDR format.";
		io->setImage( 0 );
		io->setStatus( -1 );
		return;
	}
	
	io->ioDevice()->readLine(line, MAXLINE);	
	
	char s1[3], s2[3];
	int width, height;
	if (sscanf(line, "%2[+-XY] %d %2[+-XY] %d\n", s1, &height, s2, &width) != 4)
	//if( sscanf(line, "-Y %d +X %d", &height, &width) < 2 )
	{
		kDebug(399) << "Invalid HDR file.";
		io->setImage( 0 );
		io->setStatus( -1 );
		return;
	}
	
	QDataStream s( io->ioDevice() );

	QImage img;
	if( !LoadHDR(s, width, height, img) ) 
	{
		kDebug(399) << "Error loading HDR file.";
		io->setImage( 0 );
		io->setStatus( -1 );
		return;
	}

	io->setImage( img );
	io->setStatus( 0 );
}


KDE_EXPORT void kimgio_hdr_write( QImageIO * )
{
	// intentionally not implemented (since writing low dynamic range data to a HDR file is nonsense.)
}
 
