/*
-----------------------------------------------------------------------------
denef - Decode NEF image files
Copyright (C) 2000 Daniel Stephens (daniel@cheeseplant.org)

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

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
------------------------------------------------------------------------------
*/

#include <stdlib.h>
#include <math.h>
#include <iostream.h>
#include <fstream.h>

#include <map>
#include <vector>
#include <string>

#include <unistd.h>
#include <stdio.h>
#include <errno.h>

#include "options.h"

#include "nefdata.h"
#include "interp.h"

static const char *rcsid="$Id: denef.cc,v 1.3 2000/12/10 21:46:50 daniel Exp $";

// Gamma correction - taken from C. Poynton's FAQ on Gamma
// (http://www.inforamp.net/~poynton/)

static double
gammaCorrect(double d)
{
    if (d <= 0.018) {
	return d * 4.5;
    } else {
	return 1.099 * pow(d, 0.45) - 0.099;
    }
}

// Command line options

const int INT_NOT_SET = -997;

int    opt_width   = INT_NOT_SET;
int    opt_height  = INT_NOT_SET;
int    opt_x       = INT_NOT_SET;
int    opt_y       = INT_NOT_SET;

const char *opt_interp = "gradlumin";

double opt_scale   = 1.6;

bool   opt_cc      = true;
bool   opt_gc      = true;

bool        opt_hist     = false;
const char *opt_histfile = "histo.pgm";
const char *opt_outfile  = "nef.ppm";

bool   opt_show   = false;
bool   opt_help   = false;

void
Create_Options_List(vector<options::optionInterface *> &opts)
{
    opts.push_back(new options::intOption("startx", 0, opt_x));
    opts.push_back(new options::intOption("starty", 0, opt_y));
    opts.push_back(new options::intOption("width",  0, opt_width));
    opts.push_back(new options::intOption("height", 0, opt_height));

    opts.push_back(new options::stringOption("interp",   0, opt_interp));

    opts.push_back(new options::boolOption("cc",     0, opt_cc));
    opts.push_back(new options::doubleOption("scale",0, opt_scale));
    opts.push_back(new options::boolOption("gc",     0, opt_gc));

    opts.push_back(new options::boolOption("hist",   0, opt_hist));
    opts.push_back(new options::stringOption("histfile",   0, opt_histfile));
    opts.push_back(new options::stringOption("outfile",   0, opt_outfile));

    opts.push_back(new options::boolSetOption("show", 0, opt_show));
    opts.push_back(new options::boolSetOption("help", 0, opt_help));
}

void Show_Int_Option(ostream &os, int v, const char *notset="not set")
{
    if (v == INT_NOT_SET) {os << "[" << notset << "]" ; return;}
    os << "[" << v << "]";
}

void Show_Bool_Option(ostream &os, bool b)
{
    if (b) {os << "[Yes]"; return;}
    os << "[No]";
}

void
Show_Options(ostream &os)
{
    os << "Summary of options (Current settings in []'s)" << endl;

    os << endl << "Data source options:";

    os << endl 
       << "  --startx=<pixels>      Start X position of processed output ";
    Show_Int_Option(os, opt_x, "minimum");

    os << endl 
       << "  --starty=<pixels>      Start Y position of processed output ";
    Show_Int_Option(os, opt_y, "minimum");
    
    os << endl 
       << "  --width=<pixels>       Width of processed output ";
    Show_Int_Option(os, opt_width, "maxmimum");

    os << endl 
       << "  --height=<pixels>      Height of processed output ";
    Show_Int_Option(os, opt_height, "maxmimum");

    os << endl 
       << "  --interp=<name>        Interpolation algorithm [" 
       << opt_interp << "]";
    os << endl
       << "                         Available: null, basic, rgb, lumin, gradlumin";
    
    os << endl << endl << "Post-processing options:";

    os << endl 
       << "  --cc=<yes/no>          Perform sRGB color correction ";
    Show_Bool_Option(os, opt_cc);

    os << endl 
       << "  --scale=<value>        Set linear scale factor [" 
       << opt_scale << "]";

    os << endl 
       << "  --gc=<yes/no>          Perform gamma correction ";
    Show_Bool_Option(os, opt_gc);

    os << endl << endl << "File options:";

    os << endl 
       << "  --hist=<yes/no>        Create histogram file ";
    Show_Bool_Option(os, opt_hist);

    os << endl 
       << "  --histfile=<filename>  Histogram filename [" 
       << opt_histfile << "]";

    os << endl 
       << "  --outfile=<filename>   Image output filename [" 
       << opt_outfile << "]";

    
    os << endl << endl << "Misc. Options:";
    os << endl 
       << "  --show                 Show current settings before starting.";
    os << endl 
       << "  --help                 Show options and exit.";


    os << endl << endl;
}

void
sRGB_Color_Correct(fullImageData *result)
{
    rgbTriple *mt = result->Data();
    double r,g,b;
    for (int i=0; i < (result->Width() * result->Height()); ++i, ++mt) {
	r = mt->r;
	g = mt->g;
	b = mt->b;

	// Pretty workable sRGB
	mt->r =  1.0428413 * r - 0.3416602 * g + 0.0509819 * b;
	mt->g = -0.1237831 * r + 1.2423621 * g - 0.3368242 * b;
	mt->b = -0.0078414 * r - 0.3630551 * g + 1.1382747 * b;

//      Oversaturated
//	mt->r =  1.1446599 * r - 0.4594811 * g + 0.0899564 * b;
//	mt->g = -0.0890504 * r + 1.1254676 * g - 0.3461829 * b;
//	mt->b =  0.0014389 * r - 0.2964618 * g + 1.1186995 * b;
    }
}

void
Gamma_Correct(fullImageData *result, double scale=1.0)
{
    rgbTriple *mt = result->Data();
    for (int i=0; i < (result->Width() * result->Height()); ++i, ++mt) {
	mt->r = gammaCorrect(mt->r * scale);
	mt->g = gammaCorrect(mt->g * scale);
	mt->b = gammaCorrect(mt->b * scale);
    }
}

void
Scale_Data(fullImageData *result, double scale=1.0)
{
    rgbTriple *mt = result->Data();
    for (int i=0; i < (result->Width() * result->Height()); ++i, ++mt) {
	mt->r *= scale;
	mt->g *= scale;
	mt->b *= scale;
    }
}


extern int
main(int argc, char *argv[])
{
    vector<options::optionInterface *> opts;

    Create_Options_List(opts);

    bool optok = options::Process_Options(argc, argv, opts, true, true);
    
    if (!optok) {
	cerr << endl
	     << "See '" << argv[0] << " --help' for usage information." 
	     << endl;
	exit(2);
    }

    if (opt_help) {
	cout << "Usage: " << argv[0] << " [options] filename" << endl << endl;
	Show_Options(cout);
	exit(0);
	
    }

    if (opt_show) Show_Options(cout);

    if (argc < 2) {
	cerr << "No filename given. Exiting" << endl;
	cerr << endl
	     << "See '" << argv[0] << " --help' for usage information." 
	     << endl;
	exit(0);
    }

    ifstream is(argv[1]);
    if (!is) {
	int err = errno;
	cerr << "Failed to open NEF input file '" << argv[1] << "':n" 
	     << strerror(err) << endl;
	exit(2);
    }

    cerr << "Reading NEF Data" << endl;
    nefImageData *nef = Load_NEF_Data(is);
//    nefImageData *nef = Test_NEF_Data();
    is.close();

    if (!nef) {exit(2);}

    ifInterpolator *interpolator = 0;

    if (strcmp(opt_interp, "null") == 0) {
	interpolator = new nullInterpolator();
    } else if (strcmp(opt_interp, "basic") == 0) {
	interpolator = new basicInterpolator();
    } else if (strcmp(opt_interp, "rgb") == 0) {
	interpolator = new rgbInterpolator();
    } else if (strcmp(opt_interp, "lumin") == 0) {
	interpolator = new luminanceInterpolator();
    } else if (strcmp(opt_interp, "gradlumin") == 0) {
	interpolator = new gradientLuminanceInterpolator();
    }

    if (!interpolator) {
	cerr << "Invalid interpolator '" << opt_interp << "'" << endl;
	exit(2);
    }

    int retwidth  = nef->Width() - interpolator->Fringe() * 2;    
    int retheight = nef->Height() - interpolator->Fringe() * 2;

    // Work out the border stuff - there's essentially a 6 pixel border
    // around the 'real image' section.

    int xclip = interpolator->Fringe() - 6;
    int yclip = interpolator->Fringe() - 6;

    if (opt_x == INT_NOT_SET) {
	opt_x = xclip;
    } else if (opt_x < xclip) {
	cerr << "WARNING: Clipping startx to " << xclip << endl;
	opt_x = xclip;
    }

    if (opt_y == INT_NOT_SET) {
	opt_y = yclip;
    } else if (opt_y < yclip) {
	cerr << "WARNING: Clipping starty to " << yclip << endl;
	opt_y = yclip;
    }

    int xofs = opt_x - xclip;
    int yofs = opt_y - yclip;

    if (xofs >= retwidth) {
	cerr << "Cannot have startx greater than available width" << endl;
	exit(2);
    }

    if (yofs >= retheight) {
	cerr << "Cannot have starty greater than available height" << endl;
	exit(2);
    }

    retwidth -= xofs;
    retheight -= yofs;

    if (opt_width == INT_NOT_SET) {
	opt_width = retwidth;
    } else if (opt_width < 1) {
	cerr << "Cannot have width < 1" << endl;
	exit(2);
    } else if (opt_width > retwidth) {
	cerr << "WARNING: Clipping width to " << retwidth << endl;
    }

    retwidth = opt_width;

    if (opt_height == INT_NOT_SET) {
	opt_height = retheight;
    } else if (opt_height < 1) {
	cerr << "Cannot have height < 1" << endl;
	exit(2);
    } else if (opt_height > retheight) {
	cerr << "WARNING: Clipping height to " << retheight << endl;
    }

    retheight = opt_height;

    fullImageData *result = new fullImageData(retwidth, retheight);

    cerr << "Interpolating [" << opt_x << "," << opt_y << "] - [" 
	 << opt_width << "x" << opt_height << "] (" << opt_interp << "): ";

    for (int xo=0; xo < retwidth; xo += interpolator->Max_Width()) {
	int w = retwidth - xo;
	if (w > interpolator->Max_Width()) {w = interpolator->Max_Width();}
	for (int yo=0; yo < retheight; yo += interpolator->Max_Height()) {
	    int h = retheight - yo;
	    if (h > interpolator->Max_Height()) {
		h = interpolator->Max_Height();
	    }

	    cerr << ".";
	    interpolator->Interpolate(*nef,
				      xo + xofs,
				      yo + yofs,
				      w, h,
				      *result, xo, yo);
	}
    }
    cerr << endl;
    delete interpolator;

    if (opt_cc) {
	cerr << "Color correcting to sRGB..." << endl;
	sRGB_Color_Correct(result);
    }

    if (opt_gc) {
	cerr << "Applying gamma/scaling..." << endl;
	Gamma_Correct(result, opt_scale);
    } else if (opt_scale != 1.0) {
	cerr << "Scaling..." << endl;
	Scale_Data(result, opt_scale);
    }

    const rgbTriple *t = result->Data();

    double emin = t->Luminance();
    double emax = emin;

    cerr << "Determining data range: ";
    
    int i;
    for (i=0; i < (retwidth * retheight); ++i, ++t) {
	double e = t->Luminance();
	if (e>emax) {emax = e;}
	if (e<emin) {emin = e;}
    }

    cout << emin << " - " << emax << endl;

    const int histogramSize = 256;
    int histogram[histogramSize];
    double hlimit[histogramSize+1];
    double cmax = retwidth * retheight;

    cerr << "Building histogram: ";
    for (i=0; i<histogramSize; ++i) histogram[i] = 0;

    t = result->Data();
    for (i=0; i < (retwidth * retheight); ++i, ++t) {
	double e = t->Luminance();
//	e = (e - emin) / (emax - emin);
	
	int cell = int(e * (double)histogramSize);
	if (cell < 0) cell = 0;
	if (cell >= histogramSize) {cell = histogramSize-1;}
	histogram[cell] ++;
    }
    

    double cumul = 0;

    double rmin = 0.0;
    double rmax = 1.0;

    hlimit[0] = 0;
    for (i=0; i<histogramSize; ++i) {
	cumul += histogram[i];
	hlimit[i+1] = cumul/cmax;

	if (hlimit[i+1] <= 0.01) {rmin = (double)i/(double)histogramSize;}
	if (hlimit[i+1] >  0.99) {
	    if (rmax > 0.9999999) {
		rmax = (double)i/(double)histogramSize;
	    }
	} 
    }

    cerr << rmin << " - " << rmax << endl;

    if (opt_hist) {
	cerr << "Writing histogram (" << opt_histfile << ")" << endl;
	{
	    int histogramHeight = 200;
	    double cmult = (double)(histogramHeight * 500) / (double)cmax; 
	    ofstream os(opt_histfile);
	    if (!os) {
		int err = errno;
		cerr << "Unable to open histogram '" << opt_histfile
		     << "': " << strerror(err) << endl;
		exit(2);
	    }

	    os << "P5" << endl
	       << histogramSize << " " << histogramHeight << endl
	       << 99 << endl;

	    for (int r=(histogramHeight-1); r>=0; --r) {
		for (i=0; i<histogramSize; ++i) {
		    unsigned char u=0;
		    int v = (int)((double)histogram[i] * cmult) - (r * 100);
		    if (v < 0) {v = 0;}
		    if (v > 99) {v = 99;}
		    u=v;
		    os << u;
		}
	    }
	    os.close();
	}
    }

#if 0
    cerr << "Applying histogram" << endl;

    rgbTriple *mt = result->Data();
    for (i=0; i < (retwidth * retheight); ++i, ++mt) {
	double oe = mt->Luminance();
	if (oe < 0) {oe = 0;}
	double e = (oe - emin) / (emax - emin);
	
/*
	double incell = e * (double)histogramSize;
	int cell = int(e * (double)histogramSize);
	if (cell < 0) cell = 0;
	if (cell >= histogramSize) {cell = histogramSize-1;}
	
	incell -= cell;
	double s = hlimit[cell] + (hlimit[cell+1] - hlimit[cell]) * incell;

*/
	double s = (e -rmin) / (rmax-rmin)  * 0.90 + 0.05;
//	double s = (e -rmin) / (rmax-rmin)  * 0.95 ;
//	double s = (e -rmin) / (rmax-rmin)  * 0.30 ;
//	double s = (e - rmin) / (rmax-rmin);

	if (oe <= 0) {
	    s=0;
	} else {
	    s = s/oe;
	}

	mt->r *= s;
	mt->g *= s;
	mt->b *= s;
    }
#endif

    cerr << "Writing PPM data (" << opt_outfile << ")" << endl;

    ofstream os(opt_outfile);
    if (!os) {
	int err = errno;
	cerr << "Unable to open output file '" << opt_outfile
	     << "': " << strerror(err) << endl;
	exit(2);
    }

    os << "P6" << endl
       << retwidth << " " << retheight << endl
       << "255" << endl;

    unsigned char obuf[3000];
    int oofs=0;

    int *err[6];

    for (i=0; i<6; ++i) {
	err[i] = new int[retwidth+1];
	for (int x=0; x<=retwidth; ++x) {
	    (err[i])[x] = 0;
	}
    }

    int curofs = 0;
    int newofs = 3;

    t = result->Data();
    
    double rv,gv,bv;
    int iv,ov,eov,errv,ebit;
    i=0;

    double mul = 1.0;
    double valofs = 0.0;

//    if ((emax < 1.0) && (emax > 0.0)) {mul = 1.0 / emax;}

//    if ((rmax < 1.0) && (rmax > 0.0)) {mul = 1.0 / rmax;}

    for (int y=0; y<retheight; ++y) {
	curofs = 3-curofs;
	newofs = 3-curofs;
	for (int x=0; x<=retwidth; ++x) {
	    (err[newofs])[x] = 0;
	    (err[newofs+1])[x] = 0;
	    (err[newofs+2])[x] = 0;
	}
	for (int x=0; x<retwidth; ++x, ++t) {
	    rv = (t->r + valofs) * mul;
	    gv = (t->g + valofs) * mul;
	    bv = (t->b + valofs) * mul;

	    if (rv < 0.0) rv=0.0;
	    if (rv > 1.0) rv=1.0;

	    if (gv < 0.0) gv=0.0;
	    if (gv > 1.0) gv=1.0;

	    if (bv < 0.0) bv=0.0;
	    if (bv > 1.0) bv=1.0;


	    rv *= 256000.0;
	    gv *= 256000.0;
	    bv *= 256000.0;

	    // ----------------------------------------

	    iv = (int)(round(rv));
	    if (iv > 255999) {iv = 255999;}

	    iv += (err[curofs])[x];
	    ov = iv / 1000;
	    if (ov < 0) {ov = 0;}
	    if (ov > 255) {ov=255;}
	    eov = ov * 1000 + (ov * 1000 / 255);
	    if (eov > 255999) {eov = 255999;}

	    errv =  iv - eov ;
	    ebit = errv * 7 / 16;
	    (err[curofs])[x+1] += ebit;
	    errv -= ebit;
	    ebit = errv / 3;
	    if (x>0) {err[newofs][x-1] += ebit;}
	    errv -= ebit;
	    ebit = errv * 5/ 6;
	    err[newofs][x] += ebit;
	    errv -= ebit;
	    err[newofs][x+1] += errv; 

	    obuf[oofs++] = ov;

	    // ----------------------------------------

	    iv = (int)(round(gv));
	    if (iv > 255999) {iv = 255999;}

	    iv += (err[curofs+1])[x];
	    ov = iv / 1000;
	    if (ov < 0) {ov = 0;}
	    if (ov > 255) {ov=255;}
	    eov = ov * 1000 + (ov * 1000 / 255);
	    if (eov > 255999) {eov = 255999;}

	    errv =  iv - eov ;
	    ebit = errv * 7 / 16;
	    (err[curofs+1])[x+1] += ebit;
	    errv -= ebit;
	    ebit = errv / 3;
	    if (x>0) {err[newofs+1][x-1] += ebit;}
	    errv -= ebit;
	    ebit = errv * 5/ 6;
	    err[newofs+1][x] += ebit;
	    errv -= ebit;
	    err[newofs+1][x+1] += errv; 

	    obuf[oofs++] = ov;


	    // ----------------------------------------

	    iv = (int)(round(bv));
	    if (iv > 255999) {iv = 255999;}

	    iv += (err[curofs+2])[x];
	    ov = iv / 1000;
	    if (ov < 0) {ov = 0;}
	    if (ov > 255) {ov=255;}
	    eov = ov * 1000 + (ov * 1000 / 255);
	    if (eov > 255999) {eov = 255999;}

	    errv =  iv - eov ;
	    ebit = errv * 7 / 16;
	    (err[curofs+2])[x+1] += ebit;
	    errv -= ebit;
	    ebit = errv / 3;
	    if (x>0) {err[newofs+2][x-1] += ebit;}
	    errv -= ebit;
	    ebit = errv * 5/ 6;
	    err[newofs+2][x] += ebit;
	    errv -= ebit;
	    err[newofs+2][x+1] += errv; 

	    obuf[oofs++] = ov;

	    if (oofs > 2997) {
		os.write(obuf, oofs);
		oofs =0;
	    }
	}
    }

    if (oofs > 0) {
	os.write(obuf, oofs);
	oofs =0;
    }
    os.close();
    

    exit(0);
}

