/*  This file is part of ksquirrel-libs (http://ksquirrel.sf.net)

    Copyright (c) 2005 Dmitry Baryshev <ksquirrel@tut.by>

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

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "ksquirrel-libs/fmt_types.h"
#include "ksquirrel-libs/fileio.h"

#include "fmt_codec_hdr_defs.h"
#include "fmt_codec_hdr.h"

#include "ksquirrel-libs/error.h"
#include "ksquirrel-libs/fmt_utils.h"

#include "../xpm/codec_hdr.xpm"

/*
 *
 * Radiance HDR image
 *
 */

fmt_codec::fmt_codec() : fmt_codec_base()
{}

fmt_codec::~fmt_codec()
{}

void fmt_codec::options(codec_options *o)
{
    o->version = "0.1.0";
    o->name = "Radiance HDR image";
    o->filter = "*.hdr ";
    o->config = "";
    o->mime  = "#.RADIANCE";
    o->mimetype = "image/x-hdr";
    o->pixmap = codec_hdr;
    o->readable = true;
    o->canbemultiple = false;
    o->writestatic = false;
    o->writeanimated = false;
    o->needtempfile = false;
}

s32 fmt_codec::read_init(const std::string &file)
{
    scanline = 0;

    frs.open(file.c_str(), ios::binary | ios::in);

    if(!frs.good())
        return SQE_R_NOFILE;

    currentImage = -1;
    read_error = false;

    finfo.animated = false;

    if(!getHdrHead()) return SQE_R_BADFILE;

    if(strcmp(hdr.sig, "#?RADIANCE")) return SQE_R_BADFILE;

    return SQE_OK;
}

s32 fmt_codec::read_next()
{
    currentImage++;

    if(currentImage)
        return SQE_NOTOK;

	fmt_image image;

    image.w = hdr.width;
    image.h = hdr.height;
    image.bpp = 32;

    scanline = new u8 [hdr.width * sizeof(RGBA)];
    
    if(!scanline) return SQE_R_NOMEMORY;

    image.compression = "RGBE";
    image.colorspace = fmt_utils::colorSpaceByBpp(32);

    finfo.image.push_back(image);

    return SQE_OK;
}

s32 fmt_codec::read_next_pass()
{
    return SQE_OK;
}

s32 fmt_codec::read_scanline(RGBA *scan)
{
    u32 r, g, b, e, i, j;
    fmt_image *im = image(currentImage);
    fmt_utils::fillAlpha(scan, im->w);

    if(!read_scan(scanline, im->w))
	return SQE_R_BADFILE;

    for(j = 0, i = 0; j < (u32)im->w * 4; j += 4, i++)
    {
        float t;

        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;

        // t = *(float *)&e;
        /* avoid "dereferencing type-punned pointer will break strict-aliasing rules" warning */
        memcpy(&t, &e, sizeof(t));
        (scan + i)->r = u8((r / 255.0f) * t);
        (scan + i)->g = u8((g / 255.0f) * t);
        (scan + i)->b = u8((b / 255.0f) * t);
    }

    return SQE_OK;
}

void fmt_codec::read_close()
{
    frs.close();

    finfo.meta.clear();
    finfo.image.clear();

    delete [] scanline;
    scanline = 0;
}

//
// These two methods were taken from DevIL
// http://imagelib.org
//

bool fmt_codec::read_scan(u8 *scanline, const s32 w)
{
	u8 *runner, c;
	u32 r, g, b, e, read, shift;

	if(!frs.readK(&c, sizeof(u8))) return false; r = c;
	if(!frs.readK(&c, sizeof(u8))) return false; g = c;
	if(!frs.readK(&c, sizeof(u8))) return false; b = c;
	if(!frs.readK(&c, sizeof(u8))) return false; e = c;

	//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)
	{
		u32 length = (b << 8) | e;
		u32 j, t, k;

		if (length > (u32)w)
			length = w; //fix broken files

		for(k = 0; k < 4; ++k)
		{
			runner = scanline + k;
			j = 0;

			while (j < length)
			{
				if(!frs.readK(&c, sizeof(u8))) return false;

				t = c;

				if(t > 128)
				{ //Run?
					if(!frs.readK(&c, sizeof(u8))) return false;

					t &= 127;

					//copy current byte
					while (t > 0 && j < length)
					{
						*runner = c;
						runner += 4;
						--t;
						++j;
					}
				}
				else
				{ //No Run.
					//read new bytes
					while (t > 0 && j < length)
					{
						if(!frs.readK(&c, sizeof(u8))) return false;

						*runner = c;
						runner += 4;
						--t;
						++j;
					}
				}
			}
		}

		return true; //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 < (u32)w)
	{
		if (read != 0)
		{
		    if(!frs.readK(&c, sizeof(u8))) return false; r = c;
		    if(!frs.readK(&c, sizeof(u8))) return false; g = c;
		    if(!frs.readK(&c, sizeof(u8))) return false; b = c;
		    if(!frs.readK(&c, sizeof(u8))) return false; e = c;
		}

		//if all three mantissas are 1, then this is a rle
		//count dword
		if (r == 1 && g == 1 && b == 1)
		{
			u32 length = e;
			u32 j;

			for (j = length << shift; j > 0; --j)
			{
				memcpy(runner, runner - 4, 4);
				runner += 4;
			}
			//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;
			read += length;
		}
		else
		{
			runner[0] = r;
			runner[1] = g;
			runner[2] = b;
			runner[3] = e;

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

    return true;
}

bool fmt_codec::getHdrHead()
{
	bool done = false;
	s8 a, b;
	s8 x[2], y[2];
	const u32 buffSize = 80;
	s8 buff[buffSize];
	u32 count = 0;

	if(!frs.readK(hdr.sig, sizeof(hdr.sig)-1)) return false;

	hdr.sig[10] = '\0';

	//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(!frs.readK(&a, sizeof(s8)))
		return false;

	while(!done)
	{
		if(!frs.readK(&b, sizeof(s8))) return false;

		if (b == '\n' && a == '\n')
			done = true;
		else
			a = b;
	}

	//read dimensions (note that this assumes a somewhat valid image)
	if(!frs.readK(&a, sizeof(s8))) return false;

	while (a != '\n')
	{
		buff[count] = a;

		if(!frs.readK(&a, sizeof(s8))) return false;

		++count;
		if (count > buffSize-1) {
			return false;
		}
	}

	buff[count] = '\0';

	sscanf(buff, "%s %d %s %d", x, &hdr.width, y, &hdr.height);

	return true;
}

#include "fmt_codec_cd_func.h"
