/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include <algorithm>
#include <sstream>

#include <QtGlobal>
#include <MvQFileInfo.h>
#include <QLabel>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QTabWidget>
#include <QSettings>
#include <QDialogButtonBox>
#include <QGroupBox>
#include <QMessageBox>

#include <QHBoxLayout>

#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
#include <QRegularExpressionValidator>
#else
#include <QRegExpValidator>
#endif

#include "ExportDialog.h"
#include "GenAppService.hpp"
#include "MvPath.hpp"
#include "MvRequestUtil.hpp"
#include "ObjectList.h"
#include "MvQFileDialog.h"
#include "MvQMethods.h"
#include "uPlot.h"

#include <QDebug>

ExportDialog::ExportDialog(int currentFrame, int totalFrames,
                           const std::string& startDir, bool export_qt,
                           QWidget* parent) :
    QFileDialog(parent, "Export"),
    current_(currentFrame),
    total_(totalFrames),
    export_qt_(export_qt)
{
    // Set widget mode behaviour to "Save As"
    this->setAcceptMode(QFileDialog::AcceptSave);

    // Setting for Mac OS X
    setOption(QFileDialog::DontUseNativeDialog, true);

    // url sidebar
    MvQFileDialog::updateSidebar(this, QString::fromStdString(startDir));

    // Determine default target file/dir
    MvQFileDialog::initTargetFile(this, QString::fromStdString(startDir));

    // Restore previous gui settings
    QSettings settings(MVQ_QSETTINGS_DEFAULT_ARGS, "uPlotExportDialog");
    settings.beginGroup("main");

    int fIndex = settings.value("formatIndex").toInt();

    std::vector<bool> frameSelRB;  // frame selection radio buttons
    frameSelRB.push_back(settings.value("frameSelection1").toBool());
    frameSelRB.push_back(settings.value("frameSelection2").toBool());
    frameSelRB.push_back(settings.value("frameSelection3").toBool());
    if (!frameSelRB[0] && !frameSelRB[1] && !frameSelRB[2])  // very first time
        frameSelRB[0] = true;

    QString frameSelLE = settings.value("lineEditor").toString();
    settings.endGroup();

    // Set filter options to select which files to be shown
#if 0
    QStringList filters;
    filters << "All Files (*.*)"
            << "Images (*.png *.ps *.eps *.jpg *.kml *.gif *.svg *.pdf)";
    this->setNameFilters(filters);
#endif

    // Get the main layout
    auto* mainLayout = dynamic_cast<QGridLayout*>(layout());
    int row = mainLayout->rowCount();
    int col = mainLayout->columnCount();

    // Build label for the Output format
    auto* label = new QLabel(this);
    label->setText(tr("Output format:"));
    mainLayout->addWidget(label, row, 0);

    // Retrieve and save the list of output formats from the resources file
    comboBox_ = new QComboBox(this);
    if (export_qt_)
        ObjectList::FindAll("output_format_qt", reqFormats_);
    else
        ObjectList::FindAll("output_format", reqFormats_);
    int index = 0;
    while (reqFormats_) {
        // Add output format to the user interface
        comboBox_->insertItem(index++, (const char*)reqFormats_("class"));
        reqFormats_.advance();
    }

    if (fIndex < comboBox_->count())
        comboBox_->setCurrentIndex(fIndex);
    else
        comboBox_->setCurrentIndex(0);

    // Build the Edit button
    bEdit_ = new QToolButton(this);
    QIcon icon;
    icon.addPixmap(QPixmap(QString::fromUtf8(":/uPlot/configure.svg")), QIcon::Normal, QIcon::Off);
    bEdit_->setIcon(icon);
    bEdit_->setToolTip(tr("Edit selected format"));
    bEdit_->setPopupMode(QToolButton::InstantPopup);

    // Buil a horizontal layout to hold the combobox and the toolbutton
    auto* hb = new QHBoxLayout;
    hb->addWidget(comboBox_);
    hb->addWidget(bEdit_);
    mainLayout->addLayout(hb, row, 1, 1, 1);

    connect(bEdit_, SIGNAL(clicked()), this, SLOT(slotEditFormat()));

    // Build Frame Selection widget
    bool enabled = !export_qt_;
    buildFrameSelection(row - 2, col - 1, mainLayout, frameSelRB, frameSelLE,
                        enabled);

    // Move Save and Cancel buttons to the bottom.
    // Save and Cancel buttons are defined together in a QDialogButtonBox.
    // Therefore, only plays with one of the buttons.
    row++;
    QLayoutItem* liCancel = mainLayout->itemAtPosition(row - 3, col - 1);
    mainLayout->removeItem(liCancel);
    auto* bb = dynamic_cast<QDialogButtonBox*>(liCancel->widget());
    bb->setOrientation(Qt::Horizontal);
    mainLayout->addItem(liCancel, row, col - 1);
}

// Destructor
ExportDialog::~ExportDialog()
{
    writeSettings();
}

void ExportDialog::writeSettings()
{
    MvQFileDialog::writeLastGlobalSavedFile(this);

    // Save current gui settings
    QSettings settings(MVQ_QSETTINGS_DEFAULT_ARGS, "uPlotExportDialog");
    settings.beginGroup("main");
    //    settings.setValue("fileName", selectedFiles());

    int fIndex = comboBox_->currentIndex();
    settings.setValue("formatIndex", fIndex);

    settings.setValue("frameSelection1", cbFs1_->isChecked());
    settings.setValue("frameSelection2", cbFs2_->isChecked());
    settings.setValue("frameSelection3", cbFs3_->isChecked());

    settings.setValue("lineEditor", leFs3_->text());
    settings.endGroup();
}

// Callback from the SAVE button
void ExportDialog::accept()
{
    // This function is also called if the user selected a filename
    // from the treeView. If this is the case just continue.
    if (sender()->objectName() == "treeView")
        return;

    // Get filename from the user interface
    QStringList s = selectedFiles();
    std::string filename = s.at(0).toStdString();

    // Get output format name from the user interface
    std::string format = comboBox_->currentText().toStdString();

    // Get the output format request from the Metview/Defaults directory.
    // If it does not exist, create a default request.
    // Get the output format verb name
    const char* verb = this->getFormatVerb(format);
    if (!verb) {
        std::ostringstream info;
        info << "Format name not found: " << format << std::ends;
        errorMessage((const char*)info.str().c_str());
        QDialog::reject();
        return;
    }

    // Read the selected output format request from the Metview/Defaults directory and expand it.
    reqOut_ = ObjectList::UserDefaultRequest(verb);
    reqOut_ = ObjectList::ExpandRequest(reqOut_, EXPAND_DEFAULTS);

    // Add output filename
    reqOut_("OUTPUT_NAME") = filename.c_str();

    // Get frames selection
    if (!getFrameSelection(reqOut_)) {
        QDialog::reject();
        return;
    }

    QDialog::accept();
}


void ExportDialog::slotEditFormat()
{
    // Get short format name
    std::string format = comboBox_->currentText().toStdString();

    // Get output format request
    MvRequest reqFormat;
    if (export_qt_)
        reqFormat = ObjectList::Find("output_format_qt", format.c_str());
    else
        reqFormat = ObjectList::Find("output_format", format.c_str());

    // Read the selected output format request from the Metview/Defaults directory.
    // If not found, create a default request
    MvRequest reqOut = ObjectList::UserDefaultRequest((const char*)reqFormat("output"));

    // Parameter _NAME contains the output format filename.
    // If the file already exists, this parameter contains the absolute path.
    // Otherwise, it contains the following filename: absolute_path/<output_format_name>,
    // which needs to be replaced by relative_path/output_format_name.
    // There is a need to update this filename because the Desktop's Icon
    // Editor requires a relative path in order to be able to save the file.
    // If a relative path is not given, the Editor creates a temporary file, which
    // is not appropriate here.
    if (!(const char*)reqOut("_NAME")) {
        errorMessage("Parameter _NAME not found!", nullptr);
        return;
    }

    // If it is a new default request, then remove characters '<' and '>'
    std::string name = mbasename((const char*)reqOut("_NAME"));
    if (name[0] == '<')  // it is a default filename, remove the brackets
        name = name.substr(1, name.size() - 2);

    // Get the prefix path (METVIEW_USER_DIRECTORY) and check it againt the filename
    std::string userDir = GetUserDirectory();
    std::string path = mdirname((const char*)reqOut("_NAME"));
    std::size_t found = path.find(userDir);
    if (found != std::string::npos)
        path = path.substr(userDir.size());  // remove the prefix path

    // Compose the filename
    std::string filename = path + '/' + name;
    reqOut("_NAME") = filename.c_str();

    // Call Metview user interface's editor
    CallGenAppService(*this, &ExportDialog::edited, reqOut);
}

void ExportDialog::edited(MvRequest& req)
{
    // Nothing to be edited. Button CANCEL selected by user.
    if (!req)
        return;

    // Nothing to be done. The edited file will be read when it is needed.
}

void ExportDialog::slotRangeFrameSelection(bool checked)
{
    // Enable/Disable line edit
    leFs3_->setEnabled(checked);
}

void ExportDialog::buildFrameSelection(int row, int col,
                                       QGridLayout* mainLayout,
                                       const std::vector<bool>& frameSelRB,
                                       const QString& frameSelLE,
                                       bool enabled)
{
    // Group Box with radio buttons and one line edit
    auto* gbFs = new QGroupBox(tr("Frame Selection"), this);
    cbFs1_ = new QRadioButton(tr("&Current"), this);
    cbFs2_ = new QRadioButton(tr("&All"), this);
    cbFs3_ = new QRadioButton(tr("&Range"), this);
    cbFs1_->setChecked(frameSelRB[0]);
    cbFs2_->setChecked(frameSelRB[1]);
    cbFs3_->setChecked(frameSelRB[2]);

    // Line edit with a validator
    leFs3_ = new QLineEdit(this);
    leFs3_->setToolTip(tr("Specify one or more frame ranges, e.g. 1-3,6,7"));
    QString pattern = R"(^\d{1,}(-\d{1,})?(,\d{1,}|,\d{1,}-\d{1,})*)";

#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
    QRegularExpression rx(pattern);
    QValidator* validator = new QRegularExpressionValidator(rx, this);
#else
    QRegExp rx(pattern);
    QValidator* validator = new QRegExpValidator(rx, this);
#endif
    leFs3_->setValidator(validator);
    leFs3_->setText(frameSelLE);
    cbFs3_->isChecked() ? leFs3_->setEnabled(true) : leFs3_->setEnabled(false);

    // Set layout
    auto* glFs = new QGridLayout(gbFs);
    glFs->addWidget(cbFs1_, 0, 0);
    glFs->addWidget(cbFs2_, 1, 0);
    glFs->addWidget(cbFs3_, 2, 0);
    glFs->addWidget(leFs3_, 2, 1);

    // Conexions
    connect(cbFs3_, SIGNAL(toggled(bool)), this, SLOT(slotRangeFrameSelection(bool)));

    // Set widget enabled/not enabled
    gbFs->setEnabled(enabled);

    // Attach widget to the parent
    mainLayout->addWidget(gbFs, row, col, 3, 1);
}

bool ExportDialog::getFrameSelection(MvRequest& req)
{
    // Three options: CURRENT, ALL, RANGE
    // ALL: nothing need to be done
    if (cbFs2_->isChecked())
        return true;

    // CURRENT: get the current frame numbeer
    int i = 0;
    QList<int> list;
    if (cbFs1_->isChecked()) {
        list.append(current_ + 1);  // start from 1, not from 0
    }
    else  // RANGE
    {
        // Get the list of range entries (split by comma)
        QStringList list1 = leFs3_->text().split(",",
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
                                                 Qt::SkipEmptyParts);
#else
                                                 QString::SkipEmptyParts);
#endif

        // Analyse all entries
        for (i = 0; i < list1.size(); i++) {
            // Check if it is a single value or a range of values
            QStringList list2 = list1.at(i).split("-",
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
                                                  Qt::SkipEmptyParts);
#else
                                                  QString::SkipEmptyParts);
#endif

            if (list2.size() == 1)  // single value
                list.append(list2.at(0).toInt());
            else {
                int i1 = list2.at(0).toInt();
                int i2 = list2.at(1).toInt();
                if (i1 > i2)  // invert sequence
                {
                    int iaux = i1;
                    i1 = i2;
                    i2 = iaux;
                }
                for (int j = i1; j <= i2; j++)
                    list.append(j);
            }
        }

        // Check if the range of values is valid
        std::sort(list.begin(), list.end());
        if (list.at(list.size() - 1) > total_) {
            std::ostringstream info;
            info << "Enter values from 1 to " << total_ << std::ends;
            errorMessage("Invalid range!", (const char*)info.str().c_str());
            return false;
        }
    }

    // Save list of frames selection for all drivers
    while (req) {
        for (i = 0; i < list.size(); i++)
            req.addValue("OUTPUT_FRAME_LIST", list.at(i));

        req.advance();
    }

    req.rewind();

    return true;
}

void ExportDialog::errorMessage(const char* msg, const char* info)
{
    QMessageBox msgBox;
    msgBox.setIcon(QMessageBox::Critical);
    msgBox.setWindowTitle(tr("Export Error"));
    msgBox.setText(QString::fromUtf8(msg));
    if (info)
        msgBox.setInformativeText(QString::fromUtf8(info));

    msgBox.exec();
}

const char* ExportDialog::getFormatVerb(std::string shortName)
{
    reqFormats_.rewind();
    while (reqFormats_) {
        if ((const char*)reqFormats_("class") == shortName)
            return (const char*)reqFormats_("output");

        reqFormats_.advance();
    }

    return nullptr;  // not found
}
