/* -*- C++ -*-
 *  This file is part of RawTherapee.
 *
 *  Copyright (c) 2017 Alberto Griggio
 *
 *  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 <https://www.gnu.org/licenses/>.
 */

#include "dynamicprofile.h"

#include <stdlib.h>
#include <glibmm/regex.h>
#include <glibmm/fileutils.h>
#include <glibmm/miscutils.h>
#include <glibmm/keyfile.h>
#include <glibmm/fileutils.h>

#include "rtengine.h"
#include "../rtgui/options.h"

using namespace rtengine;
using namespace rtengine::procparams;

namespace
{

const int ISO_MAX = 819200;
const double FNUMBER_MAX = 100.0;
const double FOCALLEN_MAX = 10000.0;
const double SHUTTERSPEED_MAX = 1000.0;
const double EXPCOMP_MIN = -20.0;
const double EXPCOMP_MAX = 20.0;

} // namespace

DynamicProfileRules dynamicProfileRules;

bool DynamicProfileRule::Optional::operator() (const Glib::ustring &val) const
{
    if (!enabled) {
        return true;
    }

    if (value.find ("re:") == 0) {
        // this is a regexp
        return Glib::Regex::match_simple (value.substr (3), val, Glib::REGEX_CASELESS);
    } else {
        // normal string comparison
        return value.casefold() == val.casefold();
    }
}


DynamicProfileRule::DynamicProfileRule():
    serial_number (0),
    iso (0, ISO_MAX),
    fnumber (0, FNUMBER_MAX),
    focallen (0, FOCALLEN_MAX),
    shutterspeed (0, SHUTTERSPEED_MAX),
    expcomp (EXPCOMP_MIN, EXPCOMP_MAX)
{
}


bool DynamicProfileRule::operator< (const DynamicProfileRule &other) const
{
    return serial_number < other.serial_number;
}


bool DynamicProfileRule::matches (const rtengine::FramesMetaData *im,  const Glib::ustring& filename) const
{
    return (iso (im->getISOSpeed())
            && fnumber (im->getFNumber())
            && focallen (im->getFocalLen())
            && shutterspeed (im->getShutterSpeed())
            && expcomp (im->getExpComp())
            && camera (im->getCamera())
            && lens (im->getLens())
            && path (filename)
            && imagetype(im->getImageType()));
}

namespace
{

void get_int_range (DynamicProfileRule::Range<int> &dest,
                    const Glib::KeyFile &kf, const Glib::ustring &group,
                    const Glib::ustring &key)
{
    try {
        int min = kf.get_integer (group, key + "_min");
        int max = kf.get_integer (group, key + "_max");

        if (min <= max) {
            dest.min = min;
            dest.max = max;
        }
    } catch (Glib::KeyFileError &e) {
    }
}


void get_double_range (DynamicProfileRule::Range<double> &dest,
                       const Glib::KeyFile &kf, const Glib::ustring &group,
                       const Glib::ustring &key)
{
    try {
        double min = kf.get_double (group, key + "_min");
        double max = kf.get_double (group, key + "_max");

        if (min <= max) {
            dest.min = min;
            dest.max = max;
        }
    } catch (Glib::KeyFileError &e) {
    }
}


void get_optional (DynamicProfileRule::Optional &dest,
                   const Glib::KeyFile &kf, const Glib::ustring &group,
                   const Glib::ustring &key)
{
    try {
        bool e = kf.get_boolean (group, key + "_enabled");

        if (e) {
            Glib::ustring s = kf.get_string (group, key + "_value");
            dest.enabled = e;
            dest.value = s;
        }
    } catch (Glib::KeyFileError &) {
    }
}

void set_int_range (Glib::KeyFile &kf, const Glib::ustring &group,
                    const Glib::ustring &key,
                    const DynamicProfileRule::Range<int> &val)
{
    kf.set_integer (group, key + "_min", val.min);
    kf.set_integer (group, key + "_max", val.max);
}

void set_double_range (Glib::KeyFile &kf, const Glib::ustring &group,
                       const Glib::ustring &key,
                       const DynamicProfileRule::Range<double> &val)
{
    kf.set_double (group, key + "_min", val.min);
    kf.set_double (group, key + "_max", val.max);
}

void set_optional (Glib::KeyFile &kf, const Glib::ustring &group,
                   const Glib::ustring &key,
                   const DynamicProfileRule::Optional &val)
{
    kf.set_boolean (group, key + "_enabled", val.enabled);
    kf.set_string (group, key + "_value", val.value);
}

} // namespace

bool DynamicProfileRules::loadRules()
{
    dynamicRules.clear();
    Glib::KeyFile kf;
    const Glib::ustring fileName = Glib::build_filename (Options::rtdir, "dynamicprofile.cfg");

    try {
        if (!(Glib::file_test(fileName, Glib::FILE_TEST_EXISTS) && kf.load_from_file (fileName))) {
            return false;
        }
    } catch (Glib::Error &e) {
        return false;
    }

    if (settings->verbose) {
        printf ("loading dynamic profiles...\n");
    }

    auto groups = kf.get_groups();

    for (auto group : groups) {
        // groups are of the form "rule N", where N is a positive integer
        if (group.find ("rule ") != 0) {
            return false;
        }

        std::istringstream buf (group.c_str() + 5);
        int serial = 0;

        if (! (buf >> serial) || !buf.eof()) {
            return false;
        }

        if (settings->verbose) {
            printf (" loading rule %d\n", serial);
        }

        dynamicRules.emplace_back (DynamicProfileRule());
        DynamicProfileRule &rule = dynamicRules.back();
        rule.serial_number = serial;
        get_int_range (rule.iso, kf, group, "iso");
        get_double_range (rule.fnumber, kf, group, "fnumber");
        get_double_range (rule.focallen, kf, group, "focallen");
        get_double_range (rule.shutterspeed, kf, group, "shutterspeed");
        get_double_range (rule.expcomp, kf, group, "expcomp");
        get_optional (rule.camera, kf, group, "camera");
        get_optional (rule.lens, kf, group, "lens");
        get_optional (rule.path, kf, group, "path");
        get_optional (rule.imagetype, kf, group, "imagetype");

        try {
            rule.profilepath = kf.get_string (group, "profilepath");
			#if defined (_WIN32)
			// if this is Windows, replace any "/" in the path with "\\"
			size_t pos = rule.profilepath.find("/");
			while (pos != Glib::ustring::npos) {
				rule.profilepath.replace(pos, 1, "\\");
				pos = rule.profilepath.find("/", pos);
			}
			#endif
			#if !defined (_WIN32)
			// if this is not Windows, replace any "\\" in the path with "/"
			size_t pos = rule.profilepath.find("\\");
			while (pos != Glib::ustring::npos) {
				rule.profilepath.replace(pos, 1, "/");
				pos = rule.profilepath.find("\\", pos);
			}
			#endif
        } catch (Glib::KeyFileError &) {
            dynamicRules.pop_back();
        }
    }

    std::sort (dynamicRules.begin(), dynamicRules.end());
    rulesLoaded = true;
    return true;
}

bool DynamicProfileRules::storeRules()
{
    if (settings->verbose) {
        printf ("saving dynamic profiles...\n");
    }

    Glib::KeyFile kf;

    for (auto &rule : dynamicRules) {
        std::ostringstream buf;
        buf << "rule " << rule.serial_number;
        Glib::ustring group = buf.str();
        set_int_range (kf, group, "iso", rule.iso);
        set_double_range (kf, group, "fnumber", rule.fnumber);
        set_double_range (kf, group, "focallen", rule.focallen);
        set_double_range (kf, group, "shutterspeed", rule.shutterspeed);
        set_double_range (kf, group, "expcomp", rule.expcomp);
        set_optional (kf, group, "camera", rule.camera);
        set_optional (kf, group, "lens", rule.lens);
        set_optional (kf, group, "path", rule.path);
        set_optional (kf, group, "imagetype", rule.imagetype);
        kf.set_string (group, "profilepath", rule.profilepath);
    }

	std::string fn = Glib::build_filename (Options::rtdir, "dynamicprofile.cfg");
	if (Glib::file_test(fn, Glib::FILE_TEST_IS_SYMLINK)) {
		// file is symlink; use target instead
		// symlinks apparently are not recognízed on Windows
		return kf.save_to_file (g_file_read_link (fn.c_str(), NULL));
	} else {
		return kf.save_to_file (fn);
	}
}

const std::vector<DynamicProfileRule> &DynamicProfileRules::getRules()
{
    if (!rulesLoaded) {
        loadRules();
    }

    return dynamicRules;
}

void DynamicProfileRules::setRules (const std::vector<DynamicProfileRule> &r)
{
    dynamicRules = r;
}
