#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <alloca.h>
#include <iostream.h>

#include "tiffio.h"
#include "exdisplay.h"

class MyTIFFDriver : public ExDisplay {
public:
    MyTIFFDriver () : tif(NULL), filename(NULL) { };
    virtual ~MyTIFFDriver ();

    virtual bool needsScanline ();
    virtual bool canRedraw ();
    virtual bool open (const char *name,
		       int numChannels, const char **channelNames,
		       int xRes, int yRes,
		       int rxorigin, int ryorigin, 
		       int rxpixels, int rypixels, 
		       int nparams, const Parameter *params);
    virtual bool start ();
    virtual bool data (const PixelPacket &pixels);
    virtual bool finish ();
private:
    TIFF *tif;
    char *filename;
    int xres, yres, fullxres, fullyres, xorigin, yorigin;
    float zero, one, minval, maxval, ditheramp;
    float gain, gamma;
    int samples, bitspersample, sampleformat;
    bool debug;
    int alphachannel;
};


bool MyTIFFDriver::needsScanline() { return true; }

bool MyTIFFDriver::canRedraw() { return false; }


bool
MyTIFFDriver::open (const char *name,
		    int numChannels, const char **channelNames,
		    int xRes, int yRes,
		    int rxorigin, int ryorigin, 
		    int rxpixels, int rypixels, 
		    int nparams, const Parameter *params)
{
    filename = strdup(name);
    fullxres = xRes;
    fullyres = yRes;
    xorigin = rxorigin;
    yorigin = ryorigin;
    xres = rxpixels;
    yres = rypixels;
    samples = numChannels;

    // Set reasonable defaults
    zero = 0.0f;  one = 255.0f;
    minval = 0.0f;  maxval = 255.0f;
    ditheramp = 0.5f;
    gain = gamma = 1.0f;
    debug = false;

    // Try to identify the alpha channel
    alphachannel = -1;
    for (int c = 0;  c < numChannels;  ++c) {
	if (! strcmp(channelNames[c], "a"))
	    alphachannel = c;
    }

    int index;
    float *fval;
    int *ival;
    if (fval = (float *)findValue("quantize", TYPE_FLOAT, 4, index, nparams, params)) {
	zero = fval[0];
	one = fval[1];
	minval = fval[2];
	maxval = fval[3];
    }
    if (fval = (float *)findValue("dither", TYPE_FLOAT, 1, index, nparams, params))
	ditheramp = fval[0];
    if (fval = (float *)findValue("gain", TYPE_FLOAT, 1, index, nparams, params))
	gain = fval[0];
    if (fval = (float *)findValue("gamma", TYPE_FLOAT, 1, index, nparams, params))
	gamma = fval[0];
    if (ival = (int *)findValue("debug", TYPE_INT, 1, index, nparams, params))
	debug = (ival[0] != 0);
    if (maxval == 0.0f) {
	sampleformat = SAMPLEFORMAT_IEEEFP;
	bitspersample = 32;
    } else {
	sampleformat = SAMPLEFORMAT_UINT;
	if (maxval > 65535.0)
	    bitspersample = 32;
	else if (maxval > 255.0)
	    bitspersample = 16;
	else bitspersample = 8;
    }
    if (debug)
	cerr << "Opening " << name << endl;
    return true;
}



bool
MyTIFFDriver::start ()
{    if (debug)
	cerr << "start\n";
    tif = TIFFOpen (filename, "w");
    if (!tif) {
	error ("Could not open TIFF file");
	return false;
    }
    TIFFSetField (tif, TIFFTAG_IMAGEWIDTH, xres);
    TIFFSetField (tif, TIFFTAG_IMAGELENGTH, yres);
    TIFFSetField (tif, TIFFTAG_XPOSITION, (double)xorigin);
    TIFFSetField (tif, TIFFTAG_YPOSITION, (double)yorigin);
    TIFFSetField (tif, TIFFTAG_PIXAR_IMAGEFULLWIDTH, fullxres);
    TIFFSetField (tif, TIFFTAG_PIXAR_IMAGEFULLLENGTH, fullyres);
    TIFFSetField (tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
    TIFFSetField (tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
    TIFFSetField (tif, TIFFTAG_SAMPLESPERPIXEL, samples);
    TIFFSetField (tif, TIFFTAG_BITSPERSAMPLE, bitspersample);
    TIFFSetField (tif, TIFFTAG_SAMPLEFORMAT, sampleformat);
    TIFFSetField (tif, TIFFTAG_PHOTOMETRIC, 
		  (samples > 1) ? PHOTOMETRIC_RGB : PHOTOMETRIC_MINISBLACK);
    if (samples == 4) {
	unsigned short sampleinfo = EXTRASAMPLE_ASSOCALPHA;
	TIFFSetField (tif, TIFFTAG_EXTRASAMPLES, 1, &sampleinfo);
    }
    return true;
}



bool
MyTIFFDriver::data (const PixelPacket &pixels)
{
    if (debug)
	cerr << "data of range (" << pixels.x0 << ',' << pixels.y0 << ") - ("
	     << pixels.x1 << ',' << pixels.y1 << ")\n";
    unsigned char *buf;
    int xsamps = xres*samples;
    buf = (unsigned char *)alloca (xsamps*bitspersample/8);
    float *fbuf = (float *)alloca (xsamps*sizeof(float));
    for (int y = pixels.y0;  y <= pixels.y1;  ++y) {
	int x, c, i;
	// Gamma correct, dither, and quantize
	int offset = (y-pixels.y0)*pixels.rowStride;
	for (x = pixels.x0, i = 0;  x <= pixels.x1;  ++x) {
	    for (c = 0;  c < samples;  ++c)
		fbuf[i++] = pixels.data[offset+c];
	    offset += pixels.pixelStride;
	}
	if (gain != 1.0f || gamma != 1.0f) {
	    float invgamma = 1.0f/gamma;
	    for (x = 0;  x < xsamps;  x += samples)
		for (c = 0;  c < samples;  ++c)
		    if (c != alphachannel)  // Don't correct alpha!
			fbuf[x+c] = exposure (fbuf[x+c], gain, invgamma);
	}
	if (bitspersample == 8) {
	    for (x = 0;  x < xsamps;  ++x)
		buf[x] = (unsigned char) quantize (fbuf[x], zero, one,
						   minval, maxval, ditheramp);
	} else if (bitspersample == 16) {
	    for (x = 0;  x < xsamps;  ++x)
		((unsigned short *)buf)[x] = 
		    (unsigned short) quantize (fbuf[x], zero, one,
					       minval, maxval, ditheramp);
	} else {
	    for (x = 0;  x < xsamps;  ++x)
		((float *)buf)[x] = fbuf[x];
	}
	TIFFWriteScanline (tif, buf, y-yorigin);
    }
    return true;
}



bool
MyTIFFDriver::finish()
{
    if (debug)
	cerr << "finish\n";
    if (tif)
	TIFFClose (tif);
    return true;
}



MyTIFFDriver::~MyTIFFDriver()
{
    if (debug)
	cerr << "destroying MyTIFFDriver\n";
    if (filename)
	free (filename);
}



extern "C" {

EXPORT ExDisplay *
CreateDisplay (int, ExDisplay::Parameter *)
{
    return new MyTIFFDriver();
}


EXPORT int
DisplayVersion (void)
{
    return EX_DISPLAY_VERSION;
}

}
