/***************************** 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 "NcExaminer.h"

#include "LogHandler.h"
#include "MvMiscellaneous.h"
#include "MvNetCDF.h"
#include "MvQAbout.h"
#include "MvQFileInfoLabel.h"
#include "MvQLogPanel.h"
#include "PlainTextWidget.h"
#include "StatusMsgHandler.h"
#include "MvQMethods.h"

#include <QAction>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QSettings>
#include <QSplitter>
#include <QStatusBar>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QVBoxLayout>

NcExaminer::NcExaminer(QWidget* parent) :
    MvQMainWindow(parent),
    data_(0),
    actionFileInfo_(0),
    treeParam_(0),
    initDump_(false)
{
    setAttribute(Qt::WA_DeleteOnClose);
    setWindowTitle(tr("Metview - Netcdf Examiner"));

    // Initial size
    setInitialSize(1100, 800);

    // Main splitter
    mainSplitter_ = new QSplitter;
    mainSplitter_->setOrientation(Qt::Vertical);
    mainSplitter_->setOpaqueResize(false);
    setCentralWidget(mainSplitter_);

    // The main layout (the upper part of mainSplitter)
    auto* mainLayout = new QVBoxLayout;
    mainLayout->setObjectName(QString::fromUtf8("vboxLayout"));
    mainLayout->setContentsMargins(0, 0, 0, 0);
    mainLayout->setSpacing(1);
    auto* w = new QWidget;
    w->setLayout(mainLayout);
    mainSplitter_->addWidget(w);

    // File info area
    fileInfoLabel_ = new MvQFileInfoLabel;
    mainLayout->addWidget(fileInfoLabel_);

    // Central tab
    metaTab_ = new QTabWidget;
    mainLayout->addWidget(metaTab_);

    // Set up tabs
    setupParamBox();
    setupDumpBox();

    // Log
    logPanel_ = new MvQLogPanel(this);
    mainSplitter_->addWidget(logPanel_);

    //--------------------------
    // Status bar
    //--------------------------

    statusMessageLabel_ = new QLabel("");
    statusMessageLabel_->setFrameShape(QFrame::NoFrame);
    statusBar()->addPermanentWidget(statusMessageLabel_, 1);  // '1' means 'please stretch me when resized'

    StatusMsgHandler::instance()->init(statusMessageLabel_);

    //----------------------------
    // Setup menus and toolbars
    //----------------------------

    setupViewActions();
    setupHelpActions();

    setupMenus(menuItems_);

    connect(metaTab_, SIGNAL(currentChanged(int)),
            this, SLOT(slotTabIndexChanged(int)));

    readSettings();
}

NcExaminer::~NcExaminer()
{
    // Save settings
    writeSettings();

    // Clean memory
    delete data_;
}

void NcExaminer::setupViewActions()
{
    actionFileInfo_ = new QAction(this);
    actionFileInfo_->setObjectName(QString::fromUtf8("actionFileInfo"));
    actionFileInfo_->setText(tr("File info"));
    actionFileInfo_->setCheckable(true);
    actionFileInfo_->setChecked(true);
    actionFileInfo_->setToolTip(tr("View file info"));
    QIcon icon;
    icon.addPixmap(QPixmap(QString::fromUtf8(":/examiner/info_light.svg")), QIcon::Normal, QIcon::Off);
    actionFileInfo_->setIcon(icon);

    actionLog_ = new QAction(this);
    actionLog_->setObjectName(QString::fromUtf8("actionLog"));
    actionLog_->setText(tr("&Log"));
    actionLog_->setCheckable(true);
    actionLog_->setChecked(false);
    actionLog_->setToolTip(tr("View log"));
    QIcon icon1;
    icon1.addPixmap(QPixmap(QString::fromUtf8(":/examiner/log.svg")), QIcon::Normal, QIcon::Off);
    actionLog_->setIcon(icon1);

    logPanel_->hide();  // hide log area

    // Signals and slots
    connect(actionFileInfo_, SIGNAL(toggled(bool)),
            fileInfoLabel_, SLOT(setVisible(bool)));

    connect(actionLog_, SIGNAL(toggled(bool)),
            logPanel_, SLOT(setVisible(bool)));

    MvQMainWindow::MenuType menuType = MvQMainWindow::ViewMenu;
    menuItems_[menuType].push_back(new MvQMenuItem(actionFileInfo_));
    menuItems_[menuType].push_back(new MvQMenuItem(actionLog_));
}

void NcExaminer::setupHelpActions()
{
    // About
    auto* actionAbout = new QAction(this);
    actionAbout->setObjectName(QString::fromUtf8("actionAbout"));
    actionAbout->setText(tr("&About NetCDF Examiner"));

    connect(actionAbout, SIGNAL(triggered()), this, SLOT(slotShowAboutBox()));

    MvQMainWindow::MenuType menuType = MvQMainWindow::HelpMenu;
    menuItems_[menuType].push_back(new MvQMenuItem(actionAbout, MvQMenuItem::MenuTarget));
}

void NcExaminer::setupParamBox()
{
    // Build a box layout
    auto* textRepLayout = new QVBoxLayout;
    textRepLayout->setContentsMargins(0, 0, 0, 0);
    auto* textRepPanel = new QWidget(this);
    textRepPanel->setLayout(textRepLayout);
    metaTab_->addTab(textRepPanel, tr("Meta data"));

    // Build a tree to show the data representation
    treeParam_ = new QTreeWidget(this);
    treeParam_->setAlternatingRowColors(true);
    treeParam_->setAllColumnsShowFocus(true);
    textRepLayout->addWidget(treeParam_);

    // Build the text headers
    QStringList headerTexts;
    headerTexts << tr("Parameters") << tr("Values");
    treeParam_->setHeaderLabels(headerTexts);

    // Build the three main leaves
    auto* top1 = new QTreeWidgetItem(treeParam_);
    top1->setText(0, tr("Variables"));
    top1->setExpanded(true);

    auto* top2 = new QTreeWidgetItem(treeParam_);
    top2->setText(0, tr("Dimensions"));
    top2->setExpanded(true);

    auto* top3 = new QTreeWidgetItem(treeParam_);
    top3->setText(0, tr("Global Attributes"));
    top3->setExpanded(true);
}

void NcExaminer::setupDumpBox()
{
    // Build a box layout
    auto* dumpLayout = new QVBoxLayout;
    dumpLayout->setContentsMargins(0, 0, 0, 0);
    auto* dumpPanel = new QWidget(this);
    dumpPanel->setLayout(dumpLayout);
    metaTab_->addTab(dumpPanel, tr("Ncdump"));

    dumpText_ = new PlainTextWidget(this);
    dumpText_->editor()->setReadOnly(true);
    dumpLayout->addWidget(dumpText_);
}

bool NcExaminer::initMetaData(const std::string& filename)
{
    // Clean metadata if already exists
    if (data_) {
        delete data_;
        data_ = 0;
    }

    // Write initial message in the log area
    GuiLog().task() << "Loading  NetCDF file";

    // Initialize netcdf metadata object
    data_ = new MvNetCDF(filename, 'r');

    // Update File info label
    updateFileInfoLabel();

    // Check if the netcdf file is correct
    if (!data_->isValid()) {
        delete data_;
        data_ = 0;

        GuiLog().error() << "File does not seem to be a valid NetCDF file";
        StatusMsgHandler::instance()->failed();
        return false;
    }

    StatusMsgHandler::instance()->done();

    // Update Parameters box
    // It assumes the following order: Variables, Dimensions, Global Attributes
    if (treeParam_->topLevelItemCount() != 3)
        return false;

    // Variables
    this->showVariables();

    // Dimensions
    int i;
    MvNcDim* dim;
    QTreeWidgetItem* item = treeParam_->topLevelItem(1);
    for (i = 0; i < data_->getNumberOfDimensions(); i++) {
        dim = data_->getDimension(i);
        auto* sub = new QTreeWidgetItem(item);
        sub->setText(0, QString(dim->name()));
        sub->setText(1, QString::number(dim->size()));
    }

    // Global attributes
    item = treeParam_->topLevelItem(2);
    if (data_->getNumberOfAttributes() == 0)
        item->setDisabled(true);
    else {
        std::string values;
        MvNcAtt* att;
        for (i = 0; i < data_->getNumberOfAttributes(); i++) {
            att = data_->getAttribute(i);
            att->getValues(values);
            auto* sub = new QTreeWidgetItem(item);
            sub->setText(0, QString(att->name()));
            sub->setText(1, QString::fromStdString(values));
        }
    }
    // Fits the two columns nicely
    treeParam_->resizeColumnToContents(0);

    return true;
}

void NcExaminer::showDumpBox()
{
    std::stringstream in, err;

    std::string buf = "ncdump '" + data_->path() + "'";

    // Send initial info to the log area
    GuiLog().task() << "Calling netCDF ncdump command" << GuiLog().commandKey() << buf;

    // Run dump command
    metview::shellCommand(buf, in, err);

    // Any error?
    if (!err.str().empty()) {
        GuiLog().error() << err.str();
        StatusMsgHandler::instance()->failed();
        return;
    }

    // Get stdout of the ncdump command
    dumpText_->editor()->setPlainText(QString::fromStdString(in.str()));

    // Update log area
    StatusMsgHandler::instance()->done();
}

void NcExaminer::slotTabIndexChanged(int index)
{
    // Deal with Dump tab only and initialized it only once
    // Build the netcdf dump text output into the DumpBox tab
    if (index == 1 && !initDump_) {
        this->showDumpBox();
        initDump_ = true;
    }
}

void NcExaminer::showVariables()
{
    int i, j;
    MvNcVar* var;
    MvNcDim* dim;
    QTreeWidgetItem* item = treeParam_->topLevelItem(0);

    // Main loop over variables
    for (i = 0; i < data_->getNumberOfVariables(); i++) {
        var = data_->getVariable(i);  // variable information

        // Get variable's type
        std::string str;
        var->getStringType(str);

        // Add variable name to the interface
        auto* sub = new QTreeWidgetItem(item);
        sub->setText(0, QString::fromStdString(var->name()));
        sub->setData(0, Qt::UserRole, i);
        sub->setExpanded(false);

        // Add variable type and dimensions
        int numDim = var->getNumberOfDimensions();
        QString qstr = " ( ";
        for (j = 0; j < numDim; j++) {
            dim = var->getDimension(j);
            if (j == (numDim - 1))
                qstr += dim->name() + (QString) " )";
            else
                qstr += dim->name() + (QString) ", ";
        }
        auto* sub1 = new QTreeWidgetItem(sub);
        sub1->setText(0, QString("Type"));
        sub1->setText(1, QString::fromStdString(str));
        auto* sub2 = new QTreeWidgetItem(sub);
        sub2->setText(0, QString("Dimensions"));
        sub2->setText(1, qstr);

        // Add variable attributes
        if (var->getNumberOfAttributes()) {
            auto* subat = new QTreeWidgetItem(sub);
            subat->setText(0, QString("Attributes"));
            for (j = 0; j < var->getNumberOfAttributes(); j++) {
                MvNcAtt* att = var->getAttribute(j);
                att->getValues(str);
                auto* sub1 = new QTreeWidgetItem(subat);
                sub1->setText(0, QString(att->name()));
                sub1->setText(1, QString::fromStdString(str));
            }
        }

        // Add first n values
        long nvals = getNumberValues(var);  // total number of values
        long maxvals = 20;                  // number of values to be printed
        QStringList strvals;
        getDataValues(var, strvals, maxvals);  // get values
        auto* sub3 = new QTreeWidgetItem(sub);
        sub3->setText(0, "Data values");
        for (j = 0; j < maxvals; j++) {
            auto* sub1 = new QTreeWidgetItem(sub3);
            sub1->setText(1, strvals[j]);
        }
        if (maxvals < nvals) {
            auto* sub1 = new QTreeWidgetItem(sub3);
            qstr = "  ... " + QString::number(nvals - maxvals) + " more values";
            sub1->setText(1, qstr);
        }
    }
}

void NcExaminer::getDataValues(MvNcVar* var, QStringList& sdata, long& nvals)
{
    // Get data
    long* edges = var->edges();
    if (var->type() == ncChar) {
        std::vector<Cached> datavec;
        var->get(datavec, edges, nvals);
        print_data(datavec, sdata, nvals);
    }
    else if (var->type() == ncFloat) {
        std::vector<float> datavec;
        var->get(datavec, edges, nvals);
        print_data(datavec, sdata, nvals);
    }
    else if (var->type() == ncDouble) {
        std::vector<double> datavec;
        var->get(datavec, edges, nvals);
        print_data(datavec, sdata, nvals);
    }
    else if (var->type() == ncByte) {
        std::vector<ncbyte> datavec;
        var->get(datavec, edges, nvals);
        print_data(datavec, sdata, nvals);
    }
    else if (var->type() == ncShort) {
        std::vector<short> datavec;
        var->get(datavec, edges, nvals);
        print_data(datavec, sdata, nvals);
    }
    else if (var->type() == ncLong) {
        std::vector<nclong> datavec;
        var->get(datavec, edges, nvals);
        print_data(datavec, sdata, nvals);
    }

    nvals = sdata.size();
    return;
}

// Template function to write values to Re widget
template <class T>
void NcExaminer::print_data(std::vector<T>& datavec, QStringList& sdata, long nvals)
{
    long num = (datavec.size() < (unsigned)nvals) ? datavec.size() : nvals;
    for (long j = 0; j < num; j++) {
        std::stringstream oss;
        oss << datavec[j];
        sdata.append(QString::fromStdString(oss.str()));
    }
}

long NcExaminer::getNumberValues(MvNcVar* var)
{
    // Special code for type "char" because NetCDF does not
    // nullterminate strings; so, it provides an extra info:
    // "maximum string size", which is written as the last dimension.
    int ndim = var->getNumberOfDimensions();
    if (var->type() == ncChar)
        --ndim;

    // Get total number of values
    long nval = 1L;
    long* edges = var->edges();
    for (int i = 0; i < ndim; i++)
        nval *= edges[i];

    return nval;
}

void NcExaminer::updateFileInfoLabel()
{
    fileInfoLabel_->setTextLabel(QString::fromStdString(data_->path()));
}

void NcExaminer::slotShowAboutBox()
{
    MvQAbout about("NetCDF Examiner", "", MvQAbout::NetcdfVersion | MvQAbout::MetviewVersion);
    about.exec();
}

void NcExaminer::writeSettings()
{
    QSettings settings(MVQ_QSETTINGS_DEFAULT_ARGS, "MV4-NcExaminer");

    settings.beginGroup("mainWindow");
    settings.setValue("geometry", saveGeometry());
    settings.setValue("mainSplitter", mainSplitter_->saveState());
    settings.setValue("actionFileInfoStatus", actionFileInfo_->isChecked());
    settings.endGroup();
}

void NcExaminer::readSettings()
{
    QSettings settings(MVQ_QSETTINGS_DEFAULT_ARGS, "MV4-NcExaminer");

    QVariant value;
    settings.beginGroup("mainWindow");
    restoreGeometry(settings.value("geometry").toByteArray());
    mainSplitter_->restoreState(settings.value("mainSplitter").toByteArray());

    if (settings.value("actionFileInfoStatus").isNull())
        actionFileInfo_->setChecked(true);
    else
        actionFileInfo_->setChecked(settings.value("actionFileInfoStatus").toBool());

    settings.endGroup();
}
