/*
 *  This file is part of RawTherapee.
 *
 *  Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
 *
 *  RawTherapee 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 3 of the License, or
 *  (at your option) any later version.
 * 
 *  RawTherapee 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 RawTherapee.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <rtengine.h>
#include <colortemp.h>
#include <imagesource.h>
#include <improcfun.h>
#include <curves.h>
#include <iccstore.h>
#include <processingjob.h>
#include <glibmm.h>
#include <options.h>
#include <iostream>
#include <rawimagesource.h>
#undef THREAD_PRIORITY_NORMAL

#define CLIP(a) ((a)>0?((a)<65535?(a):65535):0)


namespace rtengine {
IImage16* processImage (ProcessingJob* pjob, int& errorCode, ProgressListener* pl, bool tunnelMetaData) {

    errorCode = 0;

    ProcessingJobImpl* job = (ProcessingJobImpl*) pjob;

    if (pl) {
        pl->setProgressStr ("PROGRESSBAR_PROCESSING");
        pl->setProgress (0.0);
    }

    InitialImage* ii = job->initialImage;
    if (!ii) {
        ii = InitialImage::load (job->fname, job->isRaw, &errorCode);
        if (errorCode) {
            delete job;
            return NULL;
        }
    }
    procparams::ProcParams& params = job->pparams;

    // aquire image from imagesource
    ImageSource* imgsrc = ii->getImageSource ();

    int tr = TR_NONE;
    if (params.coarse.rotate==90)  tr |= TR_R90;
    if (params.coarse.rotate==180) tr |= TR_R180;
    if (params.coarse.rotate==270) tr |= TR_R270;
    if (params.coarse.hflip)       tr |= TR_HFLIP;
    if (params.coarse.vflip)       tr |= TR_VFLIP;

    int fw, fh;
    imgsrc->getFullSize (fw, fh, tr);

    // check the crop params
	if (params.crop.x > fw || params.crop.y > fh) {
		// the crop is completely out of the image, so we disable the crop
		params.crop.enabled = false;
		// and we set the values to the defaults
		params.crop.x = 0;
		params.crop.y = 0;
		params.crop.w = fw;
		params.crop.h = fh;
	}
	else {
		if ((params.crop.x + params.crop.w) > fw) {
			// crop overflow in the width dimension ; we trim it
			params.crop.w = fw-params.crop.x;
		}
		if ((params.crop.y + params.crop.h) > fh) {
			// crop overflow in the height dimension ; we trim it
			params.crop.h = fh-params.crop.y;
		}
	}

    ImProcFunctions ipf (&params, true);

	// set the color temperature
    ColorTemp currWB = ColorTemp (params.wb.temperature, params.wb.green);
    if (params.wb.method=="Camera")
        currWB = imgsrc->getWB ();
    else if (params.wb.method=="Auto")
        currWB = imgsrc->getAutoWB ();

    PreviewProps pp (0, 0, fw, fh, 1);
    imgsrc->preprocess( params.raw );
	if (pl) pl->setProgress (0.20);
    imgsrc->demosaic( params.raw );
	if (pl) pl->setProgress (0.40);
    Imagefloat* baseImg = new Imagefloat (fw, fh);
    imgsrc->getImage (currWB, tr, baseImg, pp, params.hlrecovery, params.icm, params.raw);
    if (pl) pl->setProgress (0.45);


    // perform first analysis
    LUTu hist16 (65536);
    ipf.firstAnalysis (baseImg, &params, hist16, imgsrc->getGamma());

    // perform transform (excepted resizing)
    if (ipf.needsTransform()) {
        Imagefloat* trImg = new Imagefloat (fw, fh);
        ipf.transform (baseImg, trImg, 0, 0, 0, 0, fw, fh);
        delete baseImg;
        baseImg = trImg;
    }

    // update blurmap


    SHMap* shmap = NULL;
    if (params.sh.enabled) {
        shmap = new SHMap (fw, fh, true);
        double radius = sqrt (double(fw*fw+fh*fh)) / 2.0;
		double shradius = params.sh.radius;
		if (!params.sh.hq) shradius *= radius / 1800.0;
		shmap->update (baseImg, shradius, ipf.lumimul, params.sh.hq, 1);
    }
    // RGB processing
//!!!// auto exposure!!!
    double br = params.toneCurve.expcomp;
    int    bl = params.toneCurve.black;

    if (params.toneCurve.autoexp) {
        LUTu aehist; int aehistcompr;
        imgsrc->getAutoExpHistogram (aehist, aehistcompr);
        ipf.getAutoExp (aehist, aehistcompr, imgsrc->getDefGain(), params.toneCurve.clip, br, bl);
    }

    LUTf curve1 (65536,0);
    LUTf curve2 (65536,0);
	LUTf curve (65536,0);
	LUTf satcurve (65536,0);
	LUTu dummy;

    CurveFactory::complexCurve (br, bl/65535.0, params.toneCurve.hlcompr, params.toneCurve.hlcomprthresh, params.toneCurve.shcompr, params.toneCurve.brightness, params.toneCurve.contrast, imgsrc->getGamma(), true, params.toneCurve.curve, 
        hist16, dummy, curve1, curve2, curve, dummy);

	LabImage* labView = new LabImage (fw,fh);

    ipf.rgbProc (baseImg, labView, curve1, curve2, curve, shmap, params.toneCurve.saturation);

    if (shmap)
    delete shmap;

    if (pl) 
        pl->setProgress (0.5);


    // luminance histogram update
    hist16.clear();
    for (int i=0; i<fh; i++)
        for (int j=0; j<fw; j++)
            hist16[CLIP((int)((labView->L[i][j])))]++;

    // luminance processing
	CurveFactory::complexLCurve (params.labCurve.brightness, params.labCurve.contrast, params.labCurve.lcurve, hist16, hist16, curve, dummy, 1);
	ipf.luminanceCurve (labView, labView, curve);
	CurveFactory::complexsgnCurve (params.labCurve.saturation, params.labCurve.enable_saturationlimiter, params.labCurve.saturationlimit, \
								   params.labCurve.acurve, params.labCurve.bcurve, curve1, curve2, satcurve, 1);
	ipf.chrominanceCurve (labView, labView, curve1, curve2, satcurve);

  	ipf.impulsedenoise (labView);
	ipf.defringe (labView);
	//ipf.lumadenoise (labView, buffer);
 	ipf.dirpyrdenoise (labView);

    if (params.sharpening.enabled) {
        float** buffer = new float*[fh];
        for (int i=0; i<fh; i++)
            buffer[i] = new float[fw];

        ipf.sharpening (labView, (float**)buffer);

        for (int i=0; i<fh; i++)
            delete [] buffer[i];
        delete [] buffer; buffer=NULL;
    }

    // wavelet equalizer
    //ipf.waveletEqualizer (labView, true, true);
	
	// directional pyramid equalizer
    ipf.dirpyrequalizer (labView);//TODO: this is the luminance tonecurve, not the RGB one


    if (pl) pl->setProgress (0.60);

    // crop and convert to rgb16
    int cx = 0, cy = 0, cw = labView->W, ch = labView->H;
    if (params.crop.enabled) {
        cx = params.crop.x;
        cy = params.crop.y;
        cw = params.crop.w;
        ch = params.crop.h;
    }
	
    if(params.icm.gamma != "default")
    {	// if select gamma output between BT709, sRGB, linear, low, high, 2.2 , 1.8
	Image16* readyImg = ipf.lab2rgb16b (labView, cx, cy, cw, ch, params.icm.output, params.icm.working, params.icm.gamma);
    delete labView;
    if (pl) pl->setProgress (0.70);

    // get the resize parameters
	int refw, refh;
	double tmpScale;

	if (params.resize.enabled) {

		if (params.crop.enabled && params.resize.appliesTo == "Cropped area") {
			// the resize values applies to the crop dimensions
			refw = cw;
			refh = ch;
		}
		else {
			// the resize values applies to the image dimensions
			// if a crop exists, it will be resized to the calculated scale
			refw = fw;
			refh = fh;
		}

		switch(params.resize.dataspec) {
		case (1):
			// Width
			tmpScale = (double)params.resize.width/(double)refw;
			break;
		case (2):
			// Height
			tmpScale = (double)params.resize.height/(double)refh;
			break;
		case (3):
			// FitBox
			if ((double)refw/(double)refh > (double)params.resize.width/(double)params.resize.height) {
				tmpScale = (double)params.resize.width/(double)refw;
			}
			else {
				tmpScale = (double)params.resize.height/(double)refh;
			}
			break;
		default:
			// Scale
			tmpScale = params.resize.scale;
			break;
		}

	    // resize image
	    if (fabs(tmpScale-1.0)>1e-5)
	    {
	    	int imw, imh;
			if (params.crop.enabled && params.resize.appliesTo == "Full image") {
				imw = cw;
				imh = ch;
			}
			else {
				imw = refw;
				imh = refh;
			}
			imw = (int)( (double)imw * tmpScale + 0.5 );
			imh = (int)( (double)imh * tmpScale + 0.5 );
	        Image16* tempImage = new Image16 (imw, imh);
	        ipf.resize (readyImg, tempImage, tmpScale);
	        delete readyImg;
	        readyImg = tempImage;
	    }
	}


    if (tunnelMetaData)
        readyImg->setMetadata (ii->getMetaData()->getExifData ());
    else
        readyImg->setMetadata (ii->getMetaData()->getExifData (), params.exif, params.iptc);
	

    ProfileContent pc;
	Glib::ustring chpro;
	int present_space[7]={0,0,0,0,0,0,0};
	 std::vector<std::string> opnames = rtengine::getOutputProfiles ();
	 //test if files are in system
    for (int j=0; j<7;j++) {
	//one can modify "option" [Color Management] to adapt name of profile ih there are different for windows, MacOS, Linux ?? 
	if(j==0) chpro=options.rtSettings.prophoto;
	else if(j==1) chpro=options.rtSettings.adobe;
	else if(j==2) chpro=options.rtSettings.widegamut;	
	else if(j==3) chpro=options.rtSettings.beta;	
	else if(j==4) chpro=options.rtSettings.best;	
	else if(j==5) chpro=options.rtSettings.bruce;	
	else if(j==6) chpro=options.rtSettings.srgb;	
	for (int i=0; i<opnames.size(); i++)
       if(chpro.compare(opnames[i]) ==0) present_space[j]=1; 
	      if (present_space[j]==0) { if (pl) pl->setProgressStr ("Missing file..");pl->setProgress (0.0);}// display file not present: not very good display information...!!
        }
		//choose output profile
		if(params.icm.working=="ProPhoto" && present_space[0]==1)  params.icm.output=options.rtSettings.prophoto;
		else if(params.icm.working=="Adobe RGB" && present_space[1]==1)  params.icm.output=options.rtSettings.adobe;
		else if(params.icm.working=="WideGamut" && present_space[2]==1)  params.icm.output=options.rtSettings.widegamut;
		else if(params.icm.working=="Beta RGB" && present_space[3]==1)  params.icm.output=options.rtSettings.beta;
		else if(params.icm.working=="BestRGB" && present_space[4]==1)  params.icm.output=options.rtSettings.best;
		else if(params.icm.working=="BruceRGB" && present_space[5]==1)  params.icm.output=options.rtSettings.bruce;
		else params.icm.output=options.rtSettings.srgb; //if not found or choice=srgb

	
    if (params.icm.output.compare (0, 6, "No ICM") && params.icm.output!="")  
        pc = iccStore->getContent (params.icm.output);
	
    readyImg->setOutputProfile (pc.data, pc.length);
	
    delete baseImg;
    
    if (!job->initialImage)
        ii->decreaseRef ();

    delete job;
    if (pl)
        pl->setProgress (0.75);
	
    return readyImg;
	}
	else
	{//if default mode : profil = selection by choice in list (Prophoto.icm, sRGB.icm, etc., etc.) , gamma = gamma of profile
	Image16* readyImg = ipf.lab2rgb16 (labView, cx, cy, cw, ch, params.icm.output);
    delete labView;
    if (pl) pl->setProgress (0.70);

    // get the resize parameters
	int refw, refh;
	double tmpScale;

	if (params.resize.enabled) {

		if (params.crop.enabled && params.resize.appliesTo == "Cropped area") {
			// the resize values applies to the crop dimensions
			refw = cw;
			refh = ch;
		}
		else {
			// the resize values applies to the image dimensions
			// if a crop exists, it will be resized to the calculated scale
			refw = fw;
			refh = fh;
		}

		switch(params.resize.dataspec) {
		case (1):
			// Width
			tmpScale = (double)params.resize.width/(double)refw;
			break;
		case (2):
			// Height
			tmpScale = (double)params.resize.height/(double)refh;
			break;
		case (3):
			// FitBox
			if ((double)refw/(double)refh > (double)params.resize.width/(double)params.resize.height) {
				tmpScale = (double)params.resize.width/(double)refw;
			}
			else {
				tmpScale = (double)params.resize.height/(double)refh;
			}
			break;
		default:
			// Scale
			tmpScale = params.resize.scale;
			break;
		}

	    // resize image
	    if (fabs(tmpScale-1.0)>1e-5)
	    {
	    	int imw, imh;
			if (params.crop.enabled && params.resize.appliesTo == "Full image") {
				imw = cw;
				imh = ch;
			}
			else {
				imw = refw;
				imh = refh;
			}
			imw = (int)( (double)imw * tmpScale + 0.5 );
			imh = (int)( (double)imh * tmpScale + 0.5 );
	        Image16* tempImage = new Image16 (imw, imh);
	        ipf.resize (readyImg, tempImage, tmpScale);
	        delete readyImg;
	        readyImg = tempImage;
	    }
	}


    if (tunnelMetaData)
        readyImg->setMetadata (ii->getMetaData()->getExifData ());
    else
        readyImg->setMetadata (ii->getMetaData()->getExifData (), params.exif, params.iptc);
	

    ProfileContent pc;
    if (params.icm.output.compare (0, 6, "No ICM") && params.icm.output!="")  
        pc = iccStore->getContent (params.icm.output);
	
    readyImg->setOutputProfile (pc.data, pc.length);
	
    delete baseImg;
    
    if (!job->initialImage)
        ii->decreaseRef ();

    delete job;
    if (pl)
        pl->setProgress (0.75);
	
    return readyImg;
	
	}
}

void batchProcessingThread (ProcessingJob* job, BatchProcessingListener* bpl, bool tunnelMetaData) {

    ProcessingJob* currentJob = job;
    
    while (currentJob) {
        int errorCode;
        IImage16* img = processImage (currentJob, errorCode, bpl, tunnelMetaData);
        if (errorCode) 
            bpl->error ("Can not load input image.");
        currentJob = bpl->imageReady (img);
    }
}

void startBatchProcessing (ProcessingJob* job, BatchProcessingListener* bpl, bool tunnelMetaData) {

    if (bpl)
        Glib::Thread::create(sigc::bind(sigc::ptr_fun(batchProcessingThread), job, bpl, tunnelMetaData), 0, true, true, Glib::THREAD_PRIORITY_LOW);
    
}

}
