/*
 *  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 "cachemanager.h"
#include "options.h"
#include <glib/gstdio.h>
#include <giomm.h>
#include "guiutils.h"
#include "procparamchangers.h"
#include "../rtengine/safegtk.h"
#ifdef WIN32
#include <windows.h>
#endif

CacheManager*
CacheManager::getInstance(void)
{
	static CacheManager* instance_ = 0;
	if ( instance_ == 0 )
	{
		static MyMutex smutex_;
		MyMutex::MyLock lock(smutex_);
		if ( instance_ == 0 )
		{
			instance_ = new CacheManager();
		}
	}
	return instance_;
}

void CacheManager::init () {

    MyMutex::MyLock lock(mutex_);

    openEntries.clear ();
    baseDir = options.cacheBaseDir;

    if (!safe_file_test (baseDir, Glib::FILE_TEST_IS_DIR))
        safe_g_mkdir_with_parents (baseDir, 511);
    if (!safe_file_test (Glib::build_filename (baseDir, "profiles"), Glib::FILE_TEST_IS_DIR))
        safe_g_mkdir_with_parents (Glib::ustring(Glib::build_filename (baseDir, "profiles")), 511);
    if (!safe_file_test (Glib::build_filename (baseDir, "images"), Glib::FILE_TEST_IS_DIR))
        safe_g_mkdir_with_parents (Glib::ustring(Glib::build_filename (baseDir, "images")), 511);
    if (!safe_file_test (Glib::build_filename (baseDir, "aehistograms"), Glib::FILE_TEST_IS_DIR))
        safe_g_mkdir_with_parents (Glib::ustring(Glib::build_filename (baseDir, "aehistograms")), 511);
    if (!safe_file_test (Glib::build_filename (baseDir, "embprofiles"), Glib::FILE_TEST_IS_DIR))
        safe_g_mkdir_with_parents (Glib::ustring(Glib::build_filename (baseDir, "embprofiles")), 511);
    if (!safe_file_test (Glib::build_filename (baseDir, "data"), Glib::FILE_TEST_IS_DIR))
        safe_g_mkdir_with_parents (Glib::ustring(Glib::build_filename (baseDir, "data")), 511);
}

Thumbnail* CacheManager::getEntry (const Glib::ustring& fname) {

    Thumbnail* res = NULL;
    
    // take manager lock and search for entry, if found return it else release
    // lock and create it
	{
        MyMutex::MyLock lock(mutex_);

		string_thumb_map::iterator r = openEntries.find (fname);
		// if it is open, return it
		if (r!=openEntries.end()) {
			r->second->increaseRef ();
			return r->second;
		}
	}

    // compute the md5
    std::string md5 = getMD5 (fname);
    if (md5=="")
        return NULL;

    // build path name
    Glib::ustring cfname = getCacheFileName ("data", fname, md5) + ".txt";

    // let's see if we have it in the cache
    if (safe_file_test (cfname, Glib::FILE_TEST_EXISTS)) {
        CacheImageData* cfs = new CacheImageData ();
        int e = cfs->load (cfname);
        if (!e && cfs->supported==true)
            res = new Thumbnail (this, fname, cfs);
        if (res && !res->isSupported ()) {
            delete res;
            res = NULL;
        }
        delete cfs;
    }

	// if not, create a new one
    if (!res) {
        res = new Thumbnail (this, fname, md5);
        if (!res->isSupported ()) {
            delete res;
            res = NULL;
        }
    }

    // retake the lock and see if it was added while we we're unlocked, if it
    // was use it over our version. if not added we create the cache entry
    if (res)
	{
		MyMutex::MyLock lock(mutex_);

		string_thumb_map::iterator r = openEntries.find (fname);
		if (r!=openEntries.end()) {
			delete res;
			r->second->increaseRef ();
			return r->second;
		}

		// it wasn't, create a new entry
		openEntries[fname] = res;
	}

    return res;
}


void CacheManager::deleteEntry (const Glib::ustring& fname) {

    MyMutex::MyLock lock(mutex_);

    // check if it is opened
    string_thumb_map::iterator r = openEntries.find (fname);
    // if it is open, dont delete it
    if (r!=openEntries.end()) {
		std::string md5 = r->second->getMD5 ();

        // decrease reference count; this will call back into CacheManager so
        // we release the lock for it.
		{
            lock.release();
			r->second->decreaseRef ();
            lock.acquire();
		}

		// if in the editor, the thumbnail still exists. If not, delete it:
		r = openEntries.find (fname);
	    if (r==openEntries.end() && md5!="") {
			safe_g_remove (getCacheFileName ("data", fname, md5) + ".txt");
			safe_g_remove (getCacheFileName ("profiles", fname, md5) + paramFileExtension);
			safe_g_remove (getCacheFileName ("images", fname, md5) + ".rtti");
			safe_g_remove (getCacheFileName ("images", fname, md5) + ".cust16");
			safe_g_remove (getCacheFileName ("images", fname, md5) + ".cust");
			safe_g_remove (getCacheFileName ("images", fname, md5) + ".jpg");
			safe_g_remove (getCacheFileName ("aehistograms", fname, md5));
			safe_g_remove (getCacheFileName ("embprofiles", fname, md5) + ".icc");
		}
	}
	else {
	    std::string md5 = getMD5 (fname);
	    if (md5!="") {
	        safe_g_remove (getCacheFileName ("data", fname, md5) + ".txt");
	        safe_g_remove (getCacheFileName ("profiles", fname, md5) + paramFileExtension);
	        safe_g_remove (getCacheFileName ("images", fname, md5) + ".rtti");
	        safe_g_remove (getCacheFileName ("images", fname, md5) + ".cust16");
	        safe_g_remove (getCacheFileName ("images", fname, md5) + ".cust");
	        safe_g_remove (getCacheFileName ("images", fname, md5) + ".jpg");
	        safe_g_remove (getCacheFileName ("aehistograms", fname, md5));
	        safe_g_remove (getCacheFileName ("embprofiles", fname, md5) + ".icc");
	    }
	}
}

void CacheManager::clearFromCache (const Glib::ustring& fname, bool leavenotrace) {
	std::string md5 = getMD5 (fname);
	if (md5!="") {
		if (leavenotrace){
			safe_g_remove (getCacheFileName ("data", fname, md5) + ".txt");
			safe_g_remove (getCacheFileName ("profiles", fname, md5) + paramFileExtension);
		}
		safe_g_remove (getCacheFileName ("images", fname, md5) + ".rtti");
		safe_g_remove (getCacheFileName ("images", fname, md5) + ".cust16");
		safe_g_remove (getCacheFileName ("images", fname, md5) + ".cust");
		safe_g_remove (getCacheFileName ("images", fname, md5) + ".jpg");
		safe_g_remove (getCacheFileName ("aehistograms", fname, md5));
		safe_g_remove (getCacheFileName ("embprofiles", fname, md5) + ".icc");
	}
}

void CacheManager::renameEntry (const std::string& oldfilename, const std::string& oldmd5, const std::string& newfilename) {

    MyMutex::MyLock lock(mutex_);

    std::string newmd5 = getMD5 (newfilename);

    safe_g_rename (getCacheFileName ("profiles", oldfilename, oldmd5) + paramFileExtension, (getCacheFileName ("profiles", newfilename, newmd5) + paramFileExtension).c_str());
    safe_g_rename (getCacheFileName ("images", oldfilename, oldmd5) + ".rtti", getCacheFileName ("images", newfilename, newmd5) + ".rtti");
    safe_g_rename (getCacheFileName ("images", oldfilename, oldmd5) + ".cust16", getCacheFileName ("images", newfilename, newmd5) + ".cust16");
    safe_g_rename (getCacheFileName ("images", oldfilename, oldmd5) + ".cust", getCacheFileName ("images", newfilename, newmd5) + ".cust");
    safe_g_rename (getCacheFileName ("images", oldfilename, oldmd5) + ".jpg", getCacheFileName ("images", newfilename, newmd5) + ".jpg");
    safe_g_rename (getCacheFileName ("aehistograms", oldfilename, oldmd5), getCacheFileName ("aehistograms", newfilename, newmd5));
    safe_g_rename (getCacheFileName ("embprofiles", oldfilename, oldmd5) + ".icc", getCacheFileName ("embprofiles", newfilename, newmd5) + ".icc");
    safe_g_rename (getCacheFileName ("data", oldfilename, oldmd5) + ".txt", getCacheFileName ("data", newfilename, newmd5) + ".txt");

    // check if it is opened
    string_thumb_map::iterator r = openEntries.find (oldfilename);
    // if it is open, update md5
    if (r!=openEntries.end()) {
        Thumbnail* t = r->second;
        openEntries.erase (r);
        t->setFileName (newfilename);
        openEntries[newfilename] = t;
        t->updateCache ();
        t->saveThumbnail ();
    }
}

void CacheManager::closeThumbnail (Thumbnail* t) {

    MyMutex::MyLock lock(mutex_);

    t->updateCache ();
    string_thumb_map::iterator r = openEntries.find (t->getFileName());
    if (r!=openEntries.end()) 
        openEntries.erase (r);
    delete t;
}

void CacheManager::closeCache () {

    MyMutex::MyLock lock(mutex_);

    applyCacheSizeLimitation ();
}

void CacheManager::clearAll () {

    MyMutex::MyLock lock(mutex_);

    deleteDir ("images");
    deleteDir ("aehistograms");
    deleteDir ("embprofiles");
    deleteDir ("profiles");
    deleteDir ("data");
    
    // re-generate thumbnail images and clear profiles of open thumbnails
    //string_thumb_map::iterator i;
    //for (i=openEntries.begin(); i!=openEntries.end(); i++) {
    //    i->second->clearProcParams (CACHEMGR);
    //    i->second->generateThumbnailImage ();
    //    i->second->updateCache ();
    //}
}
void CacheManager::clearThumbImages () {

    MyMutex::MyLock lock(mutex_);

    deleteDir ("images");
    deleteDir ("aehistograms");
    deleteDir ("embprofiles");
}

void CacheManager::clearProfiles () {
    MyMutex::MyLock lock(mutex_);

    deleteDir ("profiles");
}

void CacheManager::deleteDir (const Glib::ustring& dirName) {

    try {
        Glib::Dir* dir = new Glib::Dir (Glib::build_filename (baseDir, dirName));
        for (Glib::DirIterator i = dir->begin(); i!=dir->end(); ++i) 
            safe_g_remove (Glib::build_filename (Glib::build_filename (baseDir, dirName), *i));
        delete dir;
    }
    catch (const Glib::FileError& fe) {
    }
}

std::string CacheManager::getMD5 (const Glib::ustring& fname) {

    Glib::RefPtr<Gio::File> file = Gio::File::create_for_path (fname);
    if (file && file->query_exists())	{

        #ifdef WIN32
        // Windows: file name + size + creation time
        // Safer because e.g. your camera image counter turns over. Do NOT use modified date, since tagging programs will change that
        wchar_t *wFname = (wchar_t*)g_utf8_to_utf16 (fname.c_str(), -1, NULL, NULL, NULL);
        WIN32_FILE_ATTRIBUTE_DATA fileAttr;
        bool success=GetFileAttributesExW(wFname, GetFileExInfoStandard, &fileAttr);
        g_free(wFname);

        if (success) {
            // Just need the low file size, since RAWs are never that large
            Glib::ustring fileID= Glib::ustring::compose ("%1-%2-%3-%4", fileAttr.nFileSizeLow, fileAttr.ftCreationTime.dwHighDateTime, fileAttr.ftCreationTime.dwLowDateTime, fname );
            return Glib::Checksum::compute_checksum (Glib::Checksum::CHECKSUM_MD5, fileID);
        }
        #else
        // Least common denominator: file name + size to identify a file
        Glib::RefPtr<Gio::FileInfo> info = safe_query_file_info (file);
        if (info)
            return Glib::Checksum::compute_checksum (Glib::Checksum::CHECKSUM_MD5, Glib::ustring::compose ("%1%2", fname, info->get_size()));
        #endif
    }
    return "";
}

Glib::ustring CacheManager::getCacheFileName (const Glib::ustring& subdir, const Glib::ustring& fname, const Glib::ustring& md5) {

    Glib::ustring cfn = Glib::build_filename (baseDir, subdir);
    Glib::ustring cname = Glib::path_get_basename (fname) + "." + md5;
    return Glib::build_filename (cfn, cname);
}

void CacheManager::applyCacheSizeLimitation () {

    // TODO: Improve this, it just blindly deletes image without looking at create time or something to keep the current ones
    std::vector<FileMTimeInfo> flist;
    Glib::ustring dataDir = Glib::build_filename (baseDir, "data");
    Glib::RefPtr<Gio::File> dir = Gio::File::create_for_path (dataDir);

    safe_build_file_list (dir, flist);

    if (flist.size() > options.maxCacheEntries) {
        std::sort (flist.begin(), flist.end());
        while (flist.size() > options.maxCacheEntries) {
            safe_g_remove (Glib::build_filename (Glib::build_filename (baseDir, "data"), flist.front().fname) + ".txt");
            safe_g_remove (Glib::build_filename (Glib::build_filename (baseDir, "images"), flist.front().fname) + ".rtti");
            safe_g_remove (Glib::build_filename (Glib::build_filename (baseDir, "images"), flist.front().fname) + ".cust16");
            safe_g_remove (Glib::build_filename (Glib::build_filename (baseDir, "images"), flist.front().fname) + ".cust");
            safe_g_remove (Glib::build_filename (Glib::build_filename (baseDir, "images"), flist.front().fname) + ".jpg");
            safe_g_remove (Glib::build_filename (Glib::build_filename (baseDir, "aehistograms"), flist.front().fname));
            safe_g_remove (Glib::build_filename (Glib::build_filename (baseDir, "embprofiles"), flist.front().fname) + ".icc");
            //                safe_g_remove (Glib::build_filename (Glib::build_filename (baseDir, "profiles"), flist.front().fname) + paramFileExtension);
            flist.erase (flist.begin());
        }
    }
}

