/*
 *  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 "batchqueuepanel.h"
#include "options.h"
// #include "preferences.h"
#include "multilangmgr.h"
#include "rtimage.h"
#include "rtwindow.h"
#include "soundman.h"

static Glib::ustring makeFolderLabel(Glib::ustring path)
{
    if (!Glib::file_test(path, Glib::FILE_TEST_IS_DIR)) {
        return "(" + M("GENERAL_NONE") + ")";
    }

    if (path.size() > 40) {
        size_t last_ds = path.find_last_of(G_DIR_SEPARATOR);

        if (last_ds != Glib::ustring::npos && last_ds > 10) {
            path = "..." + path.substr(last_ds);
        }
    }

    return path;
}

BatchQueuePanel::BatchQueuePanel(FileCatalog *aFileCatalog): parent(nullptr)
{

    batchQueue = Gtk::manage(new BatchQueue(aFileCatalog));

    Gtk::VBox *batchQueueButtonBox = Gtk::manage(new Gtk::VBox);
    batchQueueButtonBox->set_name("BatchQueueButtons");

    qStartStop = Gtk::manage(new Gtk::Switch());
    qStartStop->set_tooltip_markup(M("QUEUE_STARTSTOP_TOOLTIP"));
    qStartStopConn = qStartStop->property_active().signal_changed().connect(
        sigc::mem_fun(*this, &BatchQueuePanel::startOrStopBatchProc));

    qAutoStart = Gtk::manage(new Gtk::CheckButton(M("QUEUE_AUTOSTART")));
    qAutoStart->set_tooltip_text(M("QUEUE_AUTOSTART_TOOLTIP"));
    qAutoStart->set_active(options.procQueueEnabled);

    queueShouldRun = false;

    const auto set_style = [](Gtk::Frame *f) -> void {
#if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION < 20
        f->set_name("ExpanderBox2");
#endif
    };

    batchQueueButtonBox->pack_start(*qStartStop, Gtk::PACK_SHRINK, 4);
    batchQueueButtonBox->pack_start(*qAutoStart, Gtk::PACK_SHRINK, 4);
    Gtk::Frame *bbox = Gtk::manage(new Gtk::Frame(M("MAIN_FRAME_QUEUE")));
    bbox->add(*batchQueueButtonBox);
    set_style(bbox);

    // Output directory selection
    fdir = Gtk::manage(new Gtk::Frame(M("QUEUE_LOCATION_TITLE")));
    fdir->set_label_align(0.01, 0.5);
    Gtk::VBox *odvb = Gtk::manage(new Gtk::VBox());
    Gtk::HBox *hb2 = Gtk::manage(new Gtk::HBox());
    useTemplate =
        Gtk::manage(new Gtk::RadioButton(M("QUEUE_LOCATION_TEMPLATE") + ":"));
    hb2->pack_start(*useTemplate, Gtk::PACK_SHRINK, 4);
    outdirTemplate = Gtk::manage(new Gtk::Entry());
    hb2->pack_start(*outdirTemplate);
    odvb->pack_start(*hb2, Gtk::PACK_SHRINK, 4);
    outdirTemplate->set_tooltip_markup(M("QUEUE_LOCATION_TEMPLATE_TOOLTIP"));
    useTemplate->set_tooltip_markup(M("QUEUE_LOCATION_TEMPLATE_TOOLTIP"));
    Gtk::HBox *hb3 = Gtk::manage(new Gtk::HBox());
    useFolder =
        Gtk::manage(new Gtk::RadioButton(M("QUEUE_LOCATION_FOLDER") + ":"));
    hb3->pack_start(*useFolder, Gtk::PACK_SHRINK, 4);

#if 0 // defined(__APPLE__) || defined(__linux__)
    // At the time of writing (2013-11-11) the gtkmm FileChooserButton with ACTION_SELECT_FOLDER
    // is so buggy on these platforms (OS X and Linux) that we rather employ this ugly button hack.
    // When/if GTKMM gets fixed we can go back to use the FileChooserButton, like we do on Windows.
    outdirFolderButton = Gtk::manage (new Gtk::Button("(" + M("GENERAL_NONE") + ")"));
    outdirFolderButton->set_alignment(0.0, 0.0);
    hb3->pack_start (*outdirFolderButton);
    outdirFolderButton->signal_pressed().connect( sigc::mem_fun(*this, &BatchQueuePanel::pathFolderButtonPressed) );
    outdirFolderButton->set_label(makeFolderLabel(options.savePathFolder));
    Gtk::Image* folderImg = Gtk::manage (new RTImage ("folder-closed.svg"));
    folderImg->show ();
    outdirFolderButton->set_image (*folderImg);
    outdirFolder = nullptr;
#else
    outdirFolder = Gtk::manage(new MyFileChooserButton(
        M("QUEUE_LOCATION_FOLDER"), Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER));
    hb3->pack_start(*outdirFolder);
    outdirFolder->signal_selection_changed().connect(
        sigc::mem_fun(*this, &BatchQueuePanel::pathFolderChanged));

    if (Glib::file_test(options.savePathFolder, Glib::FILE_TEST_IS_DIR)) {
        outdirFolder->set_current_folder(options.savePathFolder);
    } else {
        outdirFolder->set_current_folder(Glib::get_home_dir());
    }

    outdirFolderButton = 0;
#endif

    odvb->pack_start(*hb3, Gtk::PACK_SHRINK, 4);
    Gtk::RadioButton::Group g = useTemplate->get_group();
    useFolder->set_group(g);
    fdir->add(*odvb);
    set_style(fdir);

    // Output file format selection
    fformat = Gtk::manage(new Gtk::Frame(M("QUEUE_FORMAT_TITLE")));
    saveFormatPanel = Gtk::manage(new SaveFormatPanel());
    setExpandAlignProperties(saveFormatPanel, true, false, Gtk::ALIGN_FILL,
                             Gtk::ALIGN_CENTER);
    fformat->add(*saveFormatPanel);
    set_style(fformat);

    outdirTemplate->set_text(options.savePathTemplate);
    useTemplate->set_active(options.saveUsePathTemplate);
    useFolder->set_active(!options.saveUsePathTemplate);

    // setup signal handlers
    outdirTemplate->signal_changed().connect(
        sigc::mem_fun(*this, &BatchQueuePanel::saveOptions));
    useTemplate->signal_toggled().connect(
        sigc::mem_fun(*this, &BatchQueuePanel::saveOptions));
    useFolder->signal_toggled().connect(
        sigc::mem_fun(*this, &BatchQueuePanel::saveOptions));
    saveFormatPanel->setListener(this);

    // setup button bar
    topBox = Gtk::manage(new Gtk::HBox());
    pack_start(*topBox, Gtk::PACK_SHRINK, 4);
    topBox->set_name("BatchQueueButtonsMainContainer");

    topBox->pack_start(*bbox, Gtk::PACK_SHRINK, 4);
    Gtk::VBox *vb = Gtk::manage(new Gtk::VBox());
    vb->pack_start(*fdir);

    // setup export profile
    {
        apply_batch_profile_ = Gtk::manage(
            new Gtk::CheckButton(M("QUEUE_APPLY_BATCH_PROFILE") + ": "));
        apply_batch_profile_->set_active(false);
        profiles_cb_ = Gtk::manage(new ProfileStoreComboBox());
        Gtk::HBox *hb = Gtk::manage(new Gtk::HBox());
        hb->pack_start(*apply_batch_profile_, Gtk::PACK_SHRINK, 4);
        hb->pack_start(*profiles_cb_, Gtk::PACK_EXPAND_WIDGET, 4);
        vb->pack_start(*hb, Gtk::PACK_SHRINK, 8);
        profiles_cb_->updateProfileList();
        auto &info =
            options.export_profile_map[options.saveFormatBatch.getKey()];
        apply_batch_profile_->set_active(info.enabled);
        if (!profiles_cb_->setActiveRowFromFullPath(info.profile)) {
            profiles_cb_->unset_active();
        }
        apply_batch_profile_conn_ =
            apply_batch_profile_->signal_toggled().connect(sigc::mem_fun(
                *this, &BatchQueuePanel::applyBatchProfileToggled));
        profiles_cb_conn_ = profiles_cb_->signal_changed().connect(
            sigc::mem_fun(*this, &BatchQueuePanel::applyBatchProfileToggled));
    }

    topBox->pack_start(*vb, Gtk::PACK_EXPAND_WIDGET, 4);
    topBox->pack_start(*fformat, Gtk::PACK_EXPAND_WIDGET, 4);

    // add middle browser area
    pack_start(*batchQueue);

    // lower box with thumbnail zoom
    bottomBox = Gtk::manage(new Gtk::HBox());
    pack_start(*bottomBox, Gtk::PACK_SHRINK);

    // thumbnail zoom
    Gtk::HBox *zoomBox = Gtk::manage(new Gtk::HBox());
    zoomBox->pack_start(*Gtk::manage(new Gtk::VSeparator), Gtk::PACK_SHRINK, 4);
    Gtk::Label *zoomLabel = Gtk::manage(new Gtk::Label(
        Glib::ustring("<b>") + M("FILEBROWSER_THUMBSIZE") + ":</b>"));
    zoomLabel->set_use_markup(true);
    zoomBox->pack_start(*zoomLabel, Gtk::PACK_SHRINK, 4);
    zoomInButton = Gtk::manage(new Gtk::Button());
    zoomInButton->set_image(*Gtk::manage(new RTImage("magnifier-plus.svg")));
    zoomInButton->signal_pressed().connect(
        sigc::mem_fun(*batchQueue, &BatchQueue::zoomIn));
    zoomInButton->set_relief(Gtk::RELIEF_NONE);
    zoomInButton->set_tooltip_markup(M("FILEBROWSER_ZOOMINHINT"));
    zoomBox->pack_end(*zoomInButton, Gtk::PACK_SHRINK);
    zoomOutButton = Gtk::manage(new Gtk::Button());
    zoomOutButton->set_image(*Gtk::manage(new RTImage("magnifier-minus.svg")));
    zoomOutButton->signal_pressed().connect(
        sigc::mem_fun(*batchQueue, &BatchQueue::zoomOut));
    zoomOutButton->set_relief(Gtk::RELIEF_NONE);
    zoomOutButton->set_tooltip_markup(M("FILEBROWSER_ZOOMOUTHINT"));
    zoomBox->pack_end(*zoomOutButton, Gtk::PACK_SHRINK);
    bottomBox->pack_end(*zoomBox, Gtk::PACK_SHRINK);

    batchQueue->setBatchQueueListener(this);

    show_all();

    if (batchQueue->loadBatchQueue()) {
        idle_register.add(
            [this]() -> bool {
                batchQueue->resizeLoadedQueue();
                return false;
            },
            G_PRIORITY_LOW);
    }
}

BatchQueuePanel::~BatchQueuePanel() { idle_register.destroy(); }

void BatchQueuePanel::init(RTWindow *parent)
{
    this->parent = parent;

    saveFormatPanel->init(options.saveFormatBatch);
}

// it is expected to have a non null forceOrientation value on Preferences
// update only. In this case, qsize is ignored and computed automatically
void BatchQueuePanel::updateTab(int qsize, int forceOrientation)
{
    Gtk::Notebook *nb = (Gtk::Notebook *)(this->get_parent());

    if (forceOrientation > 0) {
        qsize = batchQueue->getEntries().size();
    }

    Gtk::Grid *grid = Gtk::manage(new Gtk::Grid());
    if ((forceOrientation == 0 && !options.tabbedUI) ||
        (forceOrientation == 2)) {
        Gtk::Label *l;

        if (!qsize) {
            grid->attach_next_to(*Gtk::manage(new RTImage("gears.svg")),
                                 Gtk::POS_TOP, 1, 1);
            l = Gtk::manage(
                new Gtk::Label(Glib::ustring(" ") + M("MAIN_FRAME_QUEUE")));
        } else if (qStartStop->get_active()) {
            grid->attach_next_to(*Gtk::manage(new RTImage("gears-play.svg")),
                                 Gtk::POS_TOP, 1, 1);
            l = Gtk::manage(new Gtk::Label(Glib::ustring(" ") +
                                           M("MAIN_FRAME_QUEUE") + " [" +
                                           Glib::ustring::format(qsize) + "]"));
        } else {
            grid->attach_next_to(*Gtk::manage(new RTImage("gears-pause.svg")),
                                 Gtk::POS_TOP, 1, 1);
            l = Gtk::manage(new Gtk::Label(Glib::ustring(" ") +
                                           M("MAIN_FRAME_QUEUE") + " [" +
                                           Glib::ustring::format(qsize) + "]"));
        }

        l->set_angle(90);
        grid->attach_next_to(*l, Gtk::POS_TOP, 1, 1);
        grid->set_tooltip_markup(M("MAIN_FRAME_QUEUE_TOOLTIP"));
        grid->show_all();

        if (nb) {
            nb->set_tab_label(*this, *grid);
        }
    } else {
        if (!qsize) {
            grid->attach_next_to(*Gtk::manage(new RTImage("gears.svg")),
                                 Gtk::POS_RIGHT, 1, 1);
            grid->attach_next_to(
                *Gtk::manage(new Gtk::Label(M("MAIN_FRAME_QUEUE"))),
                Gtk::POS_RIGHT, 1, 1);
        } else if (qStartStop->get_active()) {
            grid->attach_next_to(*Gtk::manage(new RTImage("gears-play.svg")),
                                 Gtk::POS_RIGHT, 1, 1);
            grid->attach_next_to(*Gtk::manage(new Gtk::Label(
                                     M("MAIN_FRAME_QUEUE") + " [" +
                                     Glib::ustring::format(qsize) + "]")),
                                 Gtk::POS_RIGHT, 1, 1);
        } else {
            grid->attach_next_to(*Gtk::manage(new RTImage("gears-pause.svg")),
                                 Gtk::POS_RIGHT, 1, 1);
            grid->attach_next_to(*Gtk::manage(new Gtk::Label(
                                     M("MAIN_FRAME_QUEUE") + " [" +
                                     Glib::ustring::format(qsize) + "]")),
                                 Gtk::POS_RIGHT, 1, 1);
        }

        grid->set_tooltip_markup(M("MAIN_FRAME_QUEUE_TOOLTIP"));
        grid->show_all();

        if (nb) {
            nb->set_tab_label(*this, *grid);
        }
    }
}

void BatchQueuePanel::queueSizeChanged(int qsize, bool queueRunning,
                                       bool queueError,
                                       const Glib::ustring &queueErrorMessage)
{
    setGuiFromBatchState(queueRunning, qsize);

    if (!queueRunning && qsize == 0 && queueShouldRun) {
        // There was work, but it is all done now.
        queueShouldRun = false;

        SoundManager::playSoundAsync(options.sndBatchQueueDone);
    }

    if (queueError) {
        parent->error(queueErrorMessage);
        // Gtk::MessageDialog msgd (queueErrorMessage, true, Gtk::MESSAGE_ERROR,
        // Gtk::BUTTONS_OK, true); msgd.run ();
    }
}

void BatchQueuePanel::startOrStopBatchProc()
{
    if (qStartStop->get_state()) {
        startBatchProc();
    } else {
        stopBatchProc();
    }
}

void BatchQueuePanel::startBatchProc()
{
    if (batchQueue->hasJobs()) {
        // Update the *desired* state of the queue, then launch it.  The switch
        // state is not updated here; it is changed by the queueSizeChanged()
        // callback in response to the *reported* state.
        queueShouldRun = true;

        const rtengine::procparams::PartialProfile *bp = nullptr;
        if (apply_batch_profile_->get_active()) {
            auto entry = profiles_cb_->getSelectedEntry();
            if (entry) {
                bp = ProfileStore::getInstance()->getProfile(entry);
            }
        }
        bqprof_.clear();
        if (bp) {
            bqprof_.add(bp);
        }
        bp = rtengine::ImageIOManager::getInstance()->getSaveProfile(
            options.saveFormatBatch.format);
        if (bp) {
            bqprof_.add(bp);
        }
        if (bqprof_) {
            bp = &bqprof_;
        } else {
            bp = nullptr;
        }
        batchQueue->setBatchProfile(bp);

        saveOptions();
        batchQueue->startProcessing();
    }
}

void BatchQueuePanel::stopBatchProc()
{
    // There is nothing much to do here except set the desired state, which the
    // background queue thread must check.  It will notify queueSizeChanged()
    // when it stops.
    queueShouldRun = false;
}

void BatchQueuePanel::setGuiFromBatchState(bool queueRunning, int qsize)
{
    // Change the GUI state in response to the reported queue state
    if (qsize == 0 || (qsize == 1 && queueRunning)) {
        qStartStop->set_sensitive(false);
    } else {
        qStartStop->set_sensitive(true);
    }

    qStartStopConn.block(true);
    qStartStop->set_active(queueRunning);
    qStartStopConn.block(false);

    fdir->set_sensitive(!queueRunning);
    fformat->set_sensitive(!queueRunning);
    apply_batch_profile_->set_sensitive(!queueRunning);
    profiles_cb_->set_sensitive(!queueRunning);

    updateTab(qsize);
}

void BatchQueuePanel::addBatchQueueJobs(
    const std::vector<BatchQueueEntry *> &entries, bool head)
{
    batchQueue->addEntries(entries, head);

    if (!qStartStop->get_active() && qAutoStart->get_active()) {
        // Auto-start as if the user had pressed the qStartStop switch
        startBatchProc();
    }
}

void BatchQueuePanel::saveOptions()
{
    options.savePathTemplate = outdirTemplate->get_text();
    options.saveUsePathTemplate = useTemplate->get_active();
    options.procQueueEnabled = qAutoStart->get_active();
    // options.batch_queue_use_profile = apply_batch_profile_->get_active();
    // options.batch_queue_profile_path =
    // profiles_cb_->getFullPathFromActiveRow();
}

bool BatchQueuePanel::handleShortcutKey(GdkEventKey *event)
{
    bool ctrl = event->state & GDK_CONTROL_MASK;

    if (ctrl) {
        switch (getKeyval(event)) {
        case GDK_KEY_s:
            if (qStartStop->get_active()) {
                stopBatchProc();
            } else {
                startBatchProc();
            }

            return true;
        }
    }

    return batchQueue->keyPressed(event);
}

bool BatchQueuePanel::canStartNext()
{
    // This function is called from the background BatchQueue thread.  It
    // cannot call UI functions; we keep the desired state in an atomic.
    return queueShouldRun;
}

void BatchQueuePanel::pathFolderButtonPressed()
{

    Gtk::FileChooserDialog fc(getToplevelWindow(this),
                              M("QUEUE_LOCATION_FOLDER"),
                              Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER);
    fc.add_button("_Cancel", Gtk::RESPONSE_CANCEL); // STOCKICON WAS THERE
    fc.add_button("_OK", Gtk::RESPONSE_OK);         // STOCKICON WAS THERE
    fc.set_filename(options.savePathFolder);
    fc.set_transient_for(*parent);
    int result = fc.run();

    if (result == Gtk::RESPONSE_OK) {
        if (Glib::file_test(fc.get_current_folder(), Glib::FILE_TEST_IS_DIR)) {
            options.savePathFolder = fc.get_filename();
            outdirFolderButton->set_label(
                makeFolderLabel(options.savePathFolder));
        }
    }
}

// We only want to save the following when it changes,
// since these settings are shared with editorpanel :
void BatchQueuePanel::pathFolderChanged()
{
    options.savePathFolder = outdirFolder->get_filename();
}

void BatchQueuePanel::formatChanged(const Glib::ustring &format)
{
    options.saveFormatBatch = saveFormatPanel->getFormat();
    auto &info = options.export_profile_map[options.saveFormatBatch.getKey()];
    ConnectionBlocker b1(apply_batch_profile_conn_);
    ConnectionBlocker b2(profiles_cb_conn_);
    apply_batch_profile_->set_active(info.enabled);
    if (!profiles_cb_->setActiveRowFromFullPath(info.profile)) {
        profiles_cb_->unset_active();
    }
}

void BatchQueuePanel::applyBatchProfileToggled()
{
    auto &info =
        options.export_profile_map[saveFormatPanel->getFormat().getKey()];
    info.enabled = apply_batch_profile_->get_active();
    info.profile = profiles_cb_->getFullPathFromActiveRow();
}

void BatchQueuePanel::refreshProfiles()
{
    auto pth = profiles_cb_->getFullPathFromActiveRow();
    profiles_cb_->updateProfileList();
    if (!profiles_cb_->setActiveRowFromFullPath(pth)) {
        profiles_cb_->unset_active();
    }
}
