/* -*- 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/miscutils.h>
#include <glibmm/keyfile.h>

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

using namespace rtengine;
using namespace rtengine::procparams;

namespace
{

const int ISO_MAX = 512000;
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
{
    return (iso (im->getISOSpeed())
            && fnumber (im->getFNumber())
            && focallen (im->getFocalLen())
            && shutterspeed (im->getShutterSpeed())
            && expcomp (im->getExpComp())
            && camera (im->getCamera())
            && lens (im->getLens())
            && imagetype(im->getImageType(0)));
}

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;

    try {
        if (!kf.load_from_file (Glib::build_filename (Options::rtdir, "dynamicprofile.cfg"))) {
            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.imagetype, kf, group, "imagetype");

        try {
            rule.profilepath = kf.get_string (group, "profilepath");
        } 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, "imagetype", rule.imagetype);
        kf.set_string (group, "profilepath", rule.profilepath);
    }

    return kf.save_to_file (Glib::build_filename (Options::rtdir, "dynamicprofile.cfg"));
}

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

    return dynamicRules;
}

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