/*
 *  This file is part of RawTherapee.
 *
 *
 *  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 "multilangmgr.h"
#include "thumbnail.h"
#include <sstream>
#include <iomanip>
#include "options.h"
#include "../rtengine/mytime.h"
#include <cstdio>
#include <cstdlib>
#include <glibmm.h>
#include "../rtengine/imagedata.h"
#include <glib/gstdio.h>
#include "guiutils.h"
#include "profilestore.h"
#include "batchqueue.h"
#include "../rtengine/safegtk.h"

using namespace rtengine::procparams;

Thumbnail::Thumbnail (CacheManager* cm, const Glib::ustring& fname, CacheImageData* cf)
    : fname(fname), cfs(*cf), cachemgr(cm), ref(1), enqueueNumber(0), tpp(NULL),
      pparamsValid(false), needsReProcessing(true),imageLoading(false), lastImg(NULL),
      lastW(0), lastH(0), lastScale(0), initial_(false)
{

    loadProcParams ();

    // should be safe to use the unprotected version of loadThumbnail, since we are in the constructor
    _loadThumbnail ();
    generateExifDateTimeStrings ();

    if (cfs.rankOld >= 0){
        // rank and inTrash were found in cache (old style), move them over to pparams

        // try to load the last saved parameters from the cache or from the paramfile file
        createProcParamsForUpdate(false, false); // this can execute customprofilebuilder to generate param file

        // TODO? should we call notifylisterners_procParamsChanged here?

        setRank(cfs.rankOld);
        setStage(cfs.inTrashOld);
    }

    delete tpp;
    tpp = 0;
}

Thumbnail::Thumbnail (CacheManager* cm, const Glib::ustring& fname, const std::string& md5)
    : fname(fname), cachemgr(cm), ref(1), enqueueNumber(0), tpp(NULL), pparamsValid(false),
      needsReProcessing(true),imageLoading(false), lastImg(NULL),
      initial_(true)
{


    cfs.md5 = md5;
    loadProcParams ();
    _generateThumbnailImage ();
    cfs.recentlySaved = false;

    initial_ = false;

    delete tpp;
    tpp = 0;
}

void Thumbnail::_generateThumbnailImage () {

	//  delete everything loaded into memory
	delete tpp;
	tpp = NULL;
	delete [] lastImg;
	lastImg = NULL;
	tw = -1;
	th = options.maxThumbnailHeight;
	imgRatio = -1.;

	// generate thumbnail image
	Glib::ustring ext = getExtension (fname);
	if (ext=="") 
		return;
	cfs.supported = false;
	cfs.exifValid = false;
	cfs.timeValid = false;

	if (ext.lowercase()=="jpg" || ext.lowercase()=="jpeg") {
		infoFromImage (fname);
		tpp = rtengine::Thumbnail::loadFromImage (fname, tw, th, 1, pparams.wb.equal);
		if (tpp)
			cfs.format = FT_Jpeg;
	}
	else if (ext.lowercase()=="png") {
		tpp = rtengine::Thumbnail::loadFromImage (fname, tw, th, 1, pparams.wb.equal);
		if (tpp)
			cfs.format = FT_Png;
	}
	else if (ext.lowercase()=="tif" || ext.lowercase()=="tiff") {
		infoFromImage (fname);
		tpp = rtengine::Thumbnail::loadFromImage (fname, tw, th, 1, pparams.wb.equal);
		if (tpp)
			cfs.format = FT_Tiff;
	}
	else {
		// RAW works like this:
		//  1. if we are here it's because we aren't in the cache so load the JPG
		//     image out of the RAW. Mark as "quick".
		//  2. if we don't find that then just grab the real image.
		bool quick = false;
		rtengine::RawMetaDataLocation ri;
		if ( initial_ && options.internalThumbIfUntouched)
		{
			quick = true;
			tpp = rtengine::Thumbnail::loadQuickFromRaw (fname, ri, tw, th, 1, TRUE);
		}
		if ( tpp == NULL )
		{
			quick = false;
			tpp = rtengine::Thumbnail::loadFromRaw (fname, ri, tw, th, 1, pparams.wb.equal, TRUE);
		}
		if (tpp) {
			cfs.format = FT_Raw;
			cfs.thumbImgType = quick ? CacheImageData::QUICK_THUMBNAIL : CacheImageData::FULL_THUMBNAIL;
			infoFromImage (fname, &ri);
		}
	}

    if (tpp)
    {
        tpp->getAutoWBMultipliers(cfs.redAWBMul, cfs.greenAWBMul, cfs.blueAWBMul);
        _saveThumbnail ();
        cfs.supported = true;
        needsReProcessing = true;

        cfs.save (getCacheFileName ("data")+".txt");

        generateExifDateTimeStrings ();
    }
}

bool Thumbnail::isSupported () {
    return cfs.supported;
}

const ProcParams& Thumbnail::getProcParams () {
    MyMutex::MyLock lock(mutex);
    return getProcParamsU();
}

// Unprotected version of getProcParams, when
const ProcParams& Thumbnail::getProcParamsU () {
    if (pparamsValid)
        return pparams;
    else {
        pparams = *(profileStore.getDefaultProcParams (getType()==FT_Raw));
        if (pparams.wb.method=="Camera") {
            double ct;
            getCamWB (ct, pparams.wb.green);
            pparams.wb.temperature = ct;
        }
        else if (pparams.wb.method=="Auto") {
            double ct;
            getAutoWB (ct, pparams.wb.green, pparams.wb.equal);
            pparams.wb.temperature = ct;
        }
    }
    return pparams; // there is no valid pp to return, but we have to return something
}

/** @brief  Create default params on demand and returns a new updatable object
 *
 *  The loaded profile may be partial, but it return a complete ProcParams (i.e. without ParamsEdited)
 *
 *  @param returnParams Ask to return a pointer to a ProcParams object if true
 *  @param forceCPB True if the Custom Profile Builder has to be invoked, False if the CPB has to be invoked if the profile doesn't
 *                  exist yet. It depends on other conditions too
 *  @param flaggingMode True if the ProcParams will be created because the file browser is being flagging an image
 *                      (rang, to trash, color labels). This parameter is passed to the CPB.
 *
 *  @return Return a pointer to a ProcPamas structure to be updated if returnParams is true and if everything went fine, NULL otherwise.
 */
rtengine::procparams::ProcParams* Thumbnail::createProcParamsForUpdate(bool returnParams, bool forceCPB, bool flaggingMode) {

    static int index=0; // Will act as unique identifier during the session

    // try to load the last saved parameters from the cache or from the paramfile file
    ProcParams* ldprof = NULL;

    Glib::ustring defProf = getType()==FT_Raw ? options.defProfRaw : options.defProfImg;

    const CacheImageData* cfs=getCacheImageData();
    Glib::ustring defaultPparamsPath = options.findProfilePath(defProf);
    if (!options.CPBPath.empty() && !defaultPparamsPath.empty() && (!hasProcParams() || forceCPB) && cfs && cfs->exifValid) {
        // First generate the communication file, with general values and EXIF metadata
        rtengine::ImageMetaData* imageMetaData;
        if (getType()==FT_Raw) {
            rtengine::RawMetaDataLocation metaData = rtengine::Thumbnail::loadMetaDataFromRaw(fname);
            imageMetaData = rtengine::ImageMetaData::fromFile (fname, &metaData);
        }
        else
            imageMetaData = rtengine::ImageMetaData::fromFile (fname, NULL);

        Glib::ustring tmpFileName( Glib::build_filename(options.cacheBaseDir, Glib::ustring::compose("CPB_temp_%1.txt", index++)) );

        const rtexif::TagDirectory* exifDir=NULL;
        if (imageMetaData && (exifDir = imageMetaData->getExifData())) {
            Glib::ustring outFName;
            if (options.paramsLoadLocation==PLL_Input)
                outFName = fname+paramFileExtension;
            else
                outFName = getCacheFileName("profiles")+paramFileExtension;
            exifDir->CPBDump(tmpFileName, fname, outFName,
                             defaultPparamsPath == DEFPROFILE_INTERNAL ? DEFPROFILE_INTERNAL : Glib::build_filename(defaultPparamsPath, Glib::path_get_basename(defProf) + paramFileExtension),
                             cfs,
                             flaggingMode);
        }

        // For the filename etc. do NOT use streams, since they are not UTF8 safe
        Glib::ustring cmdLine = options.CPBPath + Glib::ustring(" \"") + tmpFileName + Glib::ustring("\"");

        if (options.rtSettings.verbose)
            printf("Custom profile builder's command line: %s\n", Glib::ustring(cmdLine).c_str());
        bool success = safe_spawn_command_line_sync (cmdLine);

        // Now they SHOULD be there (and potentially "partial"), so try to load them and store it as a full procparam
        if (success) loadProcParams();

        if (safe_file_test(tmpFileName, Glib::FILE_TEST_EXISTS )) safe_g_remove (tmpFileName);

        if (imageMetaData) delete imageMetaData;
    }

    if (returnParams && hasProcParams()) {
        ldprof = new ProcParams ();
        *ldprof = getProcParams ();
    }

    return ldprof;
}

void Thumbnail::notifylisterners_procParamsChanged(int whoChangedIt){
	for (size_t i=0; i<listeners.size(); i++)
		listeners[i]->procParamsChanged (this, whoChangedIt);
}

/*
 * Load the procparams from the cache or from the sidecar file (priority set in
 * the Preferences).
 *
 * The result is a complete ProcParams with default values merged with the values
 * from the default Raw or Image ProcParams, then with the values from the loaded
 * ProcParams (sidecar or cache file).
 */
void Thumbnail::loadProcParams () {
    MyMutex::MyLock lock(mutex);

    pparamsValid = false;
    pparams.setDefaults();
    const PartialProfile *defaultPP = profileStore.getDefaultPartialProfile(getType()==FT_Raw);
    defaultPP->applyTo(&pparams);

    if (options.paramsLoadLocation==PLL_Input) {
        // try to load it from params file next to the image file
        int ppres = pparams.load (fname + paramFileExtension);
        pparamsValid = !ppres && pparams.ppVersion>=220;
        // if no success, try to load the cached version of the procparams
        if (!pparamsValid) 
            pparamsValid = !pparams.load (getCacheFileName ("profiles")+paramFileExtension);
    }
    else {
        // try to load it from cache
        pparamsValid = !pparams.load (getCacheFileName ("profiles")+paramFileExtension);
        // if no success, try to load it from params file next to the image file
        if (!pparamsValid) {
            int ppres = pparams.load (fname + paramFileExtension);
            pparamsValid = !ppres && pparams.ppVersion>=220;
        }
    }
}

void Thumbnail::clearProcParams (int whoClearedIt) {

/*  Clarification on current "clear profile" functionality:
    a. if rank/colorlabel/inTrash are NOT set, 
    the "clear profile" will delete the pp3 file (as before).

    b. if any of the rank/colorlabel/inTrash ARE set, 
    the "clear profile" will lead to execution of ProcParams::setDefaults 
    (the CPB is NOT called) to set the params values and will preserve 
    rank/colorlabel/inTrash in the param file. */

	{
    MyMutex::MyLock lock(mutex);

    // preserve rank, colorlabel and inTrash across clear
    int rank = getRank();
    int colorlabel = getColorLabel();
    int inTrash = getStage();


    cfs.recentlySaved = false;
    pparamsValid = false;
    needsReProcessing = true;

    //TODO: run though customprofilebuilder?
    // probably not as this is the only option to set param values to default

    // reset the params to defaults
    pparams.setDefaults();

    // and restore rank and inTrash
    setRank(rank);
    setColorLabel(colorlabel);
    setStage(inTrash);

    // params could get validated by rank/inTrash values restored above
    if (pparamsValid)
    {
        updateCache();
    }
    else
    {
        // remove param file from cache
        Glib::ustring fname_ = getCacheFileName ("profiles")+paramFileExtension;
        if (safe_file_test (fname_, Glib::FILE_TEST_EXISTS))
            safe_g_remove (fname_);
        // remove param file located next to the file
//        fname_ = removeExtension(fname) + paramFileExtension;
        fname_ = fname + paramFileExtension;
        if (safe_file_test(fname_, Glib::FILE_TEST_EXISTS))
            safe_g_remove (fname_);
        fname_ = removeExtension(fname) + paramFileExtension;
        if (safe_file_test (fname_, Glib::FILE_TEST_EXISTS))
            safe_g_remove (fname_);
    }

	} // end of mutex lock

    for (size_t i=0; i<listeners.size(); i++)
        listeners[i]->procParamsChanged (this, whoClearedIt);
}

bool Thumbnail::hasProcParams () {
    
    return pparamsValid;
}

void Thumbnail::setProcParams (const ProcParams& pp, ParamsEdited* pe, int whoChangedIt, bool updateCacheNow) {

	{
    MyMutex::MyLock lock(mutex);

    if (pparams.sharpening.threshold.isDouble() != pp.sharpening.threshold.isDouble())
        printf("WARNING: Sharpening different!\n");
    if (pparams.vibrance.psthreshold.isDouble() != pp.vibrance.psthreshold.isDouble())
        printf("WARNING: Vibrance different!\n");

    if (pparams!=pp) 
        cfs.recentlySaved = false;

    // do not update rank, colorlabel and inTrash
    int rank = getRank();
    int colorlabel = getColorLabel();
    int inTrash = getStage();

    if (pe) {
        pe->combine(pparams, pp, true);
    }
    else pparams = pp;
    pparamsValid = true;
    needsReProcessing = true;

    setRank(rank);
    setColorLabel(colorlabel);
    setStage(inTrash);

    if (updateCacheNow)
        updateCache ();

	} // end of mutex lock

    for (size_t i=0; i<listeners.size(); i++)
        listeners[i]->procParamsChanged (this, whoChangedIt);
}

bool Thumbnail::isRecentlySaved () {
    
    return cfs.recentlySaved;
}

void Thumbnail::imageDeveloped () {
        
    cfs.recentlySaved = true;
    cfs.save (getCacheFileName ("data")+".txt");
    pparams.save (getCacheFileName ("profiles")+paramFileExtension);
}

void Thumbnail::imageEnqueued () {

    enqueueNumber++;
}

void Thumbnail::imageRemovedFromQueue () {

    enqueueNumber--;
}

bool Thumbnail::isEnqueued () {
    
    return enqueueNumber > 0;
}

void Thumbnail::increaseRef ()
{
    MyMutex::MyLock lock(mutex);
    ++ref;
}

void Thumbnail::decreaseRef () 
{
	{
		MyMutex::MyLock lock(mutex);
		if ( ref == 0 )
		{
			return;
		}
		if ( --ref != 0 )
		{
			return;
		}
	}
	cachemgr->closeThumbnail (this); 
}

void Thumbnail::getThumbnailSize (int &w, int &h, const rtengine::procparams::ProcParams *pparams) {
	int tw_ = tw;
	int th_ = th;
	float imgRatio_ = imgRatio;

	if (pparams) {
		int ppCoarse = pparams->coarse.rotate;
		if (ppCoarse >= 180) ppCoarse -= 180;

		int thisCoarse = this->pparams.coarse.rotate;
		if (thisCoarse >= 180) thisCoarse -= 180;

		if (thisCoarse != ppCoarse) {
			// different orientation -> swapping width & height
			int tmp = th_;
			th_ = tw_;
			tw_ = tmp;
			if (imgRatio_ >= 0.0001f)
				imgRatio_ = 1.f/imgRatio_;
		}
	}

	if (imgRatio_ > 0.)
		w = (int)(imgRatio_ * (float)h);
	else
		w = tw_ * h / th_;
}

void Thumbnail::getFinalSize (const rtengine::procparams::ProcParams& pparams, int& w, int& h) {
    MyMutex::MyLock lock(mutex);

    // WARNING: When downscaled, the ratio have loosed a lot of precision, so we can't get back the exact initial dimensions
    double fw = lastW*lastScale;
    double fh = lastH*lastScale;

    if (pparams.coarse.rotate==90 || pparams.coarse.rotate==270) {
        fh = lastW*lastScale;
        fw = lastH*lastScale;
    }
    if (!pparams.resize.enabled) {
        w = fw;
        h = fh;
    }
    else {
        w = (int)(fw+0.5);
        h = (int)(fh+0.5);
    }
}


rtengine::IImage8* Thumbnail::processThumbImage (const rtengine::procparams::ProcParams& pparams, int h, double& scale) {

    MyMutex::MyLock lock(mutex);

    if ( tpp == 0 ) {
        _loadThumbnail();
        if ( tpp == 0 )
            return 0;
    }

    rtengine::IImage8* image = 0;

    if ( cfs.thumbImgType == CacheImageData::QUICK_THUMBNAIL ) {
        // RAW internal thumbnail, no profile yet: just do some rotation etc.
        image = tpp->quickProcessImage (pparams, h, rtengine::TI_Nearest, scale);
    }
    else {
        // Full thumbnail: apply profile
        image = tpp->processImage (pparams, h, rtengine::TI_Bilinear, cfs.getCamera(), cfs.focalLen, cfs.focalLen35mm, cfs.focusDist, cfs.shutter, cfs.fnumber, cfs.iso, cfs.expcomp, scale );
    }

    tpp->getDimensions(lastW,lastH,lastScale);

    delete tpp;
    tpp = 0;
    return image;
}

rtengine::IImage8* Thumbnail::upgradeThumbImage (const rtengine::procparams::ProcParams& pparams, int h, double& scale) {

	MyMutex::MyLock lock(mutex);

	if ( cfs.thumbImgType != CacheImageData::QUICK_THUMBNAIL )
	{
		return 0;
	}

	_generateThumbnailImage();
 	if ( tpp == 0 )
 	{
 		return 0;
 	}
 
 	rtengine::IImage8* image = tpp->processImage (pparams, h, rtengine::TI_Bilinear, cfs.getCamera(), cfs.focalLen, cfs.focalLen35mm, cfs.focusDist,cfs.shutter, cfs.fnumber, cfs.iso, cfs.expcomp,  scale );
    tpp->getDimensions(lastW,lastH,lastScale);
 
 	delete tpp;
 	tpp = 0;
 	return image;
}

void Thumbnail::generateExifDateTimeStrings () {

    exifString = "";
    dateTimeString = "";

    if (!cfs.exifValid)
        return;

    exifString = Glib::ustring::compose ("f/%1 %2s %3%4 %5mm", Glib::ustring(rtengine::ImageData::apertureToString(cfs.fnumber)), Glib::ustring(rtengine::ImageData::shutterToString(cfs.shutter)), M("QINFO_ISO"), cfs.iso, Glib::ustring::format(std::setw(3), std::fixed, std::setprecision(2), cfs.focalLen));

    if (options.fbShowExpComp && cfs.expcomp!="0.00" && cfs.expcomp!="") // don't show exposure compensation if it is 0.00EV;old cache iles do not have ExpComp, so value will not be displayed. 
    	exifString = Glib::ustring::compose ("%1 %2EV", exifString, cfs.expcomp); // append exposure compensation to exifString
    std::string dateFormat = options.dateFormat;
    std::ostringstream ostr;
    bool spec = false;
    for (size_t i=0; i<dateFormat.size(); i++)
        if (spec && dateFormat[i]=='y') {
            ostr << cfs.year;
            spec = false;
        }
        else if (spec && dateFormat[i]=='m') {
            ostr << (int)cfs.month;
            spec = false;
        }
        else if (spec && dateFormat[i]=='d') {
            ostr << (int)cfs.day;
            spec = false;
        }
        else if (dateFormat[i]=='%') 
            spec = true;
        else {
            ostr << (char)dateFormat[i];
            spec = false;
        }

    ostr << " " << (int)cfs.hour;
    ostr << ":" << std::setw(2) << std::setfill('0') << (int)cfs.min;
    ostr << ":" << std::setw(2) << std::setfill('0') << (int)cfs.sec;

    dateTimeString = ostr.str ();
}

const Glib::ustring& Thumbnail::getExifString () {

    return exifString;
}

const Glib::ustring& Thumbnail::getDateTimeString () {

    return dateTimeString;
}

void Thumbnail::getAutoWB (double& temp, double& green, double equal) {
	if (cfs.redAWBMul != -1.0) {
		rtengine::ColorTemp ct(cfs.redAWBMul, cfs.greenAWBMul, cfs.blueAWBMul, equal);
		temp = ct.getTemp();
		green = ct.getGreen();
	}
	else
		temp = green = -1.0;
}


ThFileType Thumbnail::getType () {

    return (ThFileType) cfs.format;
}

int Thumbnail::infoFromImage (const Glib::ustring& fname, rtengine::RawMetaDataLocation* rml) {

    rtengine::ImageMetaData* idata = rtengine::ImageMetaData::fromFile (fname, rml);
    if (!idata)
        return 0;

    int deg = 0;
    cfs.timeValid = false;
    cfs.exifValid = false;
    if (idata->hasExif()) {
        cfs.shutter  = idata->getShutterSpeed ();
        cfs.fnumber  = idata->getFNumber ();		
        cfs.focalLen = idata->getFocalLen ();
        cfs.focalLen35mm = idata->getFocalLen35mm ();
        cfs.focusDist = idata->getFocusDist ();
        cfs.iso      = idata->getISOSpeed ();
        cfs.expcomp  = idata->expcompToString (idata->getExpComp(), false); // do not mask Zero expcomp
        cfs.year     = 1900 + idata->getDateTime().tm_year;
        cfs.month    = idata->getDateTime().tm_mon + 1;
        cfs.day      = idata->getDateTime().tm_mday;
        cfs.hour     = idata->getDateTime().tm_hour;
        cfs.min      = idata->getDateTime().tm_min;
        cfs.sec      = idata->getDateTime().tm_sec;
        cfs.timeValid = true;
        cfs.exifValid = true;
        cfs.lens      = idata->getLens();
        cfs.camMake   = idata->getMake();
        cfs.camModel  = idata->getModel();

        if (idata->getOrientation()=="Rotate 90 CW") {
            deg = 90;
        }
        else if (idata->getOrientation()=="Rotate 180") {
            deg = 180;
        }
        else if (idata->getOrientation()=="Rotate 270 CW") {
            deg = 270;
        }
    }
    else {
        cfs.lens     = "Unknown";
        cfs.camMake  = "Unknown";
        cfs.camModel = "Unknown";
    }
    // get image filetype
    std::string::size_type idx;
    idx = fname.rfind('.');
    if(idx != std::string::npos){cfs.filetype = fname.substr(idx+1);}
    else {cfs.filetype="";}

    delete idata;
    return deg;
}

/*
 * Read all thumbnail's data from the cache; build and save them if doesn't exist - NON PROTECTED
 * This includes:
 *  - image's bitmap (*.rtti)
 *  - auto exposure's histogram (full thumbnail only)
 *  - embedded profile (full thumbnail only)
 *  - LiveThumbData section of the data file
 */
void Thumbnail::_loadThumbnail(bool firstTrial) {

    needsReProcessing = true;
    tw = -1;
    th = options.maxThumbnailHeight;
    delete tpp;
    tpp = new rtengine::Thumbnail ();
    tpp->isRaw = (cfs.format == (int) FT_Raw);

    // load supplementary data
    bool succ = tpp->readData (getCacheFileName ("data")+".txt");

    if (succ)
        tpp->getAutoWBMultipliers(cfs.redAWBMul, cfs.greenAWBMul, cfs.blueAWBMul);

    // thumbnail image
    succ = succ && tpp->readImage (getCacheFileName ("images"));

    if (!succ && firstTrial) {
        _generateThumbnailImage ();
        if (cfs.supported && firstTrial)
            _loadThumbnail (false);

        if (tpp==NULL) return;
    }
    else if (!succ) {
        delete tpp;
        tpp = NULL;
        return;
    }
 
    if ( cfs.thumbImgType == CacheImageData::FULL_THUMBNAIL ) {
        // load aehistogram
        tpp->readAEHistogram (getCacheFileName ("aehistograms"));

        // load embedded profile
        tpp->readEmbProfile (getCacheFileName ("embprofiles")+".icc");

        tpp->init ();
    }
 
    if (!initial_ && tpp) tw = tpp->getImageWidth (getProcParamsU(), th, imgRatio);  // this might return 0 if image was just building
}

/*
 * Read all thumbnail's data from the cache; build and save them if doesn't exist - MUTEX PROTECTED
 * This includes:
 *  - image's bitmap (*.rtti)
 *  - auto exposure's histogram (full thumbnail only)
 *  - embedded profile (full thumbnail only)
 *  - LiveThumbData section of the data file
 */
void Thumbnail::loadThumbnail (bool firstTrial) {
    MyMutex::MyLock lock(mutex);
    _loadThumbnail(firstTrial);
}

/*
 * Save thumbnail's data to the cache - NON PROTECTED
 * This includes:
 *  - image's bitmap (*.rtti)
 *  - auto exposure's histogram (full thumbnail only)
 *  - embedded profile (full thumbnail only)
 *  - LiveThumbData section of the data file
 */
void Thumbnail::_saveThumbnail () {

    if (!tpp)
        return;

    if (safe_g_remove (getCacheFileName ("images")+".rtti") == -1) {
        // No file deleted, so we try to deleted obsolete files, if any
        safe_g_remove (getCacheFileName ("images")+".cust");
        safe_g_remove (getCacheFileName ("images")+".cust16");
        safe_g_remove (getCacheFileName ("images")+".jpg");
    }
    
    // save thumbnail image
    tpp->writeImage (getCacheFileName ("images"), 1);

    // save aehistogram
    tpp->writeAEHistogram (getCacheFileName ("aehistograms"));

    // save embedded profile
    tpp->writeEmbProfile (getCacheFileName ("embprofiles")+".icc");

    // save supplementary data
    tpp->writeData (getCacheFileName ("data")+".txt");
}

/*
 * Save thumbnail's data to the cache - MUTEX PROTECTED
 * This includes:
 *  - image's bitmap (*.rtti)
 *  - auto exposure's histogram (full thumbnail only)
 *  - embedded profile (full thumbnail only)
 *  - LiveThumbData section of the data file
 */
void Thumbnail::saveThumbnail () 
{
   	MyMutex::MyLock lock(mutex);
    _saveThumbnail();
}

/*
 * Update the cached files
 *  - updatePParams==true (default)        : write the procparams file (sidecar or cache, depending on the options)
 *  - updateCacheImageData==true (default) : write the CacheImageData values in the cache folder,
 *                                           i.e. some General, DateTime, ExifInfo, File info and ExtraRawInfo,
 */
void Thumbnail::updateCache (bool updatePParams, bool updateCacheImageData) {

    if (updatePParams && pparamsValid) {
        pparams.save (
            options.saveParamsFile  ? fname + paramFileExtension : "",
            options.saveParamsCache ? getCacheFileName ("profiles")+paramFileExtension : "",
            true
        );
    }
    if (updateCacheImageData)
    cfs.save (getCacheFileName ("data")+".txt");
}

Thumbnail::~Thumbnail () {
    mutex.lock();

    delete [] lastImg;
    delete tpp;
    mutex.unlock();
}

Glib::ustring Thumbnail::getCacheFileName (Glib::ustring subdir) {

    return cachemgr->getCacheFileName (subdir, fname, cfs.md5);
}

void Thumbnail::setFileName (const Glib::ustring fn) { 
    
    fname = fn; 
    cfs.md5 = cachemgr->getMD5 (fname); 
}

void Thumbnail::addThumbnailListener (ThumbnailListener* tnl) {

    increaseRef();
    listeners.push_back (tnl);
}

void Thumbnail::removeThumbnailListener (ThumbnailListener* tnl) {

    std::vector<ThumbnailListener*>::iterator f = std::find (listeners.begin(), listeners.end(), tnl);
    if (f!=listeners.end()) {
        listeners.erase (f);
        decreaseRef();
	}
}

// Calculates the standard filename for the automatically named batch result 
// and opens it in OS default viewer
// destination: 1=Batch conf. file; 2=batch out dir; 3=RAW dir
// Return: Success?
bool Thumbnail::openDefaultViewer(int destination) {

#ifdef WIN32 
    Glib::ustring openFName;

    if (destination==1) {
            openFName = Glib::ustring::compose ("%1.%2", BatchQueue::calcAutoFileNameBase(fname), options.saveFormatBatch.format);
            if (safe_file_test (openFName, Glib::FILE_TEST_EXISTS)) {
              wchar_t *wfilename = (wchar_t*)g_utf8_to_utf16 (openFName.c_str(), -1, NULL, NULL, NULL);
              ShellExecuteW(NULL, L"open", wfilename, NULL, NULL, SW_SHOWMAXIMIZED );
              g_free(wfilename);
            } else {
                printf("%s not found\n",openFName.data());
                return false;
            }
    } else {
        openFName = destination == 3 ? fname
            : Glib::ustring::compose ("%1.%2", BatchQueue::calcAutoFileNameBase(fname), options.saveFormatBatch.format);

        printf("Opening %s\n", openFName.c_str());

        if (safe_file_test (openFName, Glib::FILE_TEST_EXISTS)) {
            // Output file exists, so open explorer and select output file
            wchar_t* org=(wchar_t*)g_utf8_to_utf16 (Glib::ustring::compose("/select,\"%1\"", openFName).c_str(), -1, NULL, NULL, NULL);
            wchar_t* par=new wchar_t[wcslen(org)+1];
            wcscpy(par, org);

            // In this case the / disturbs
            wchar_t* p = par+1;  // skip the first backslash
            while (*p!=0) {
                if (*p==L'/') *p=L'\\';
                p++;
            }

            ShellExecuteW(NULL, L"open", L"explorer.exe", par, NULL, SW_SHOWNORMAL );

            delete[] par;
            g_free(org);
        } else if (safe_file_test (Glib::path_get_dirname(openFName), Glib::FILE_TEST_EXISTS)) {
            // Out file does not exist, but directory
            wchar_t *wfilename = (wchar_t*)g_utf8_to_utf16 (Glib::path_get_dirname(openFName).c_str(), -1, NULL, NULL, NULL);
            ShellExecuteW(NULL, L"explore", wfilename, NULL, NULL, SW_SHOWNORMAL );
            g_free(wfilename);
        } else {
            printf("File and dir not found\n");
            return false;
        }
    }

    return true;

#else
        // TODO: Add more OSes here
        printf("Automatic opening not supported on this OS\n");
        return false;
#endif

}

bool Thumbnail::imageLoad(bool loading)
{
    MyMutex::MyLock lock(mutex);
    bool previous = imageLoading;
    if( loading && !previous ){
        imageLoading = true;
        return true;
    }else if( !loading )
        imageLoading = false;
    return false;
}
