/***************************** 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 <QComboBox>
//#include <QDebug>
#include <QFile>
#include <QLabel>
#include <QListWidget>
#include <QPainter>
#include <QScrollBar>
#include <QTextEdit>
#include <QTextStream>
#include <QVBoxLayout>

#include "PlotMod.h"

#include "MvQLayerMetaDataWidget.h"

#include "Layer.h"
#include "MvKeyProfile.h"
#include "MvQKeyManager.h"
#include "MgQPlotScene.h"
#include "MgQSceneItem.h"
#include "MvQTheme.h"

#include "Cached.h"
#include "MvPath.hpp"

MvQLayerMetaDataWidget::MvQLayerMetaDataWidget(QWidget* parent) :
    QWidget(parent),
    sceneItem_(nullptr),
    layer_(nullptr),
    statsEnabled_(false),
    histoEnabled_(false),
    histoVisdefInitialIndex_(0)
{
    visdefs_ << "MCONT"
             << "PCONT"
             << "MSYMB"
             << "PSYMB"
             << "MWIND"
             << "PWIND";

    auto* layout = new QVBoxLayout;
    layout->setContentsMargins(0, 0, 0, 0);
    setLayout(layout);

    // TextEdit
    layerTe_ = new QTextEdit(this);
    layerTe_->setReadOnly(true);
    layerTe_->document()->setDefaultStyleSheet(MvQTheme::htmlTableCss());
    layerTe_->setWordWrapMode(QTextOption::WordWrap);
    // layerTe_->setLineWrapMode(QTextEdit::WidgetWidth);
    layout->addWidget(layerTe_, 1);

    // Stats + histo row

    auto* hb = new QHBoxLayout;
    layout->addLayout(hb);

    // Stats button
    statsTb_ = new QToolButton(this);
    statsTb_->setText(tr("Stats"));
    statsTb_->setToolTip(tr("Statistics"));
    statsTb_->setCheckable(true);
    statsTb_->setChecked(statsEnabled_);

    connect(statsTb_, SIGNAL(toggled(bool)),
            this, SLOT(slotStatsEnabled(bool)));

    // Histogram button
    histoTb_ = new QToolButton(this);
    histoTb_->setText(tr("Histogram"));
    histoTb_->setToolTip(tr("Histogram"));
    histoTb_->setCheckable(true);
    histoTb_->setChecked(histoEnabled_);

    connect(histoTb_, SIGNAL(toggled(bool)),
            this, SLOT(slotHistoEnabled(bool)));

    // QLabel *visdefLabel= new QLabel(tr("Histogram method:"));

    // ComboBox
    histoVisdefCb_ = new QComboBox(this);
    histoVisdefCb_->setSizeAdjustPolicy(QComboBox::AdjustToContents);

    connect(histoVisdefCb_, SIGNAL(activated(int)),
            this, SLOT(slotSelectHistoVisdef(int)));

    connect(histoTb_, SIGNAL(toggled(bool)),
            histoVisdefCb_, SLOT(setEnabled(bool)));


    hb->addWidget(statsTb_);
    hb->addWidget(histoTb_);
    // hb->addWidget(visdefLabel);
    hb->addWidget(histoVisdefCb_);
    hb->addStretch(1);
    // hb->addWidget(label,0,Qt::AlignRight);
    /*hb->addWidget(layerLabel);
    hb->addWidget(layerCb_);*/

    // Meta-data key
    keyManager_ = new MvQKeyManager(MvQKeyManager::LayerType);
    keyManager_->loadProfiles();
}

MvQLayerMetaDataWidget::~MvQLayerMetaDataWidget()
{
    delete keyManager_;
}

void MvQLayerMetaDataWidget::updateData()
{
    if (!layer_) {
        layerTe_->clear();
        return;
    }

    // Collect data
    MetaDataCollector infoMap;
    infoMap["_datatype"] = "";
    infoMap["_description"] = "";
    layerMetaData(infoMap);

    bool hasData = false;

    QString str;
    QString txt("<table><tbody>");
    std::string dataType, description;

    // Identifiy datatype
    auto it = infoMap.find("_datatype");
    if (it != infoMap.end()) {
        dataType = "System::" + it->second;
    }
    else {
        layerTe_->setHtml(tr("No meta data is available for this layer!"));
        return;
    }

    if ((it = infoMap.find("_description")) != infoMap.end()) {
        description = it->second;
    }

    // qDebug() << "layer info";
    // for(map<std::string,string>::iterator it=infoMap.begin(); it!= infoMap.end(); it++)
    // qDebug() << it->first.c_str() << it->second.c_str();

    // Get key profile
    MvKeyProfile* prof = keyManager_->findProfile(dataType);

    if (!prof) {
        layerTe_->setHtml(tr("No meta data is available for this layer!"));
        return;
    }


    infoMap.reset();

    // Find out the needed keys' list
    for (unsigned int i = 0; i < prof->size(); i++) {
        QString key = QString::fromStdString(prof->at(i)->name());
        QString label = QString::fromStdString(prof->at(i)->shortName());

        std::string group = prof->at(i)->metaData("group");
        std::string source = prof->at(i)->metaData("source");

        if (group == "stats" && !statsEnabled_)
            continue;
        if (group == "histo" && !histoEnabled_)
            continue;

        // Get keyType
        std::string keyType = prof->at(i)->metaData("type");

        // qDebug() << key << "type:" << keyType.c_str();

        if (keyType == "table") {
            foreach (QString s, QString::fromStdString(prof->at(i)->metaData("column")).split("/")) {
                infoMap[s.toStdString()] = "";
            }
        }
        else if (keyType == "list") {
            if (prof->at(i)->metaData("subType") != "label") {
                foreach (QString s, QString::fromStdString(prof->at(i)->metaData("item")).split("/")) {
                    infoMap[s.toStdString()] = "";
                    if (group == "stats") {
                        MetaDataAttribute attr;
                        attr.setGroup(MetaDataAttribute::StatsGroup);
                        infoMap.setAttribute(s.toStdString(), attr);
                    }
                }
            }
        }
        else if (keyType != "title") {
            infoMap[key.toStdString()] = "";
            MetaDataAttribute attr;
            if (source == "gribapi") {
                attr.setSource(MetaDataAttribute::GribApiSource);
            }
            if (group == "stats") {
                attr.setGroup(MetaDataAttribute::StatsGroup);
            }
            infoMap.setAttribute(key.toStdString(), attr);
        }
    }

    // infoMap.setMode(MetaDataCollector::Append);
    layerMetaData(infoMap);

    // Find out colspan for 2nd column
    int colSpan = getColSpan(prof, infoMap);

    // The first row is always the layer type
    if (!description.empty()) {
        addTextInfoRow("Layer type", description.c_str(), txt, colSpan);
        hasData = true;
    }

    if (prof->size() == 0) {
        for (auto it = infoMap.begin(); it != infoMap.end(); it++) {
            if (it->first != "_datatype" && it->first != "_description") {
                QString key(it->first.c_str());
                QString label(it->first.c_str());
                addTextInfoRow(label, key, infoMap, txt, colSpan);
                hasData = true;
            }
        }
    }
    else {
        // Loop through the profile items
        for (unsigned int i = 0; i < prof->size(); i++) {
            QString key = QString::fromStdString(prof->at(i)->name());
            QString label = QString::fromStdString(prof->at(i)->shortName());

            if (key == "_datatype" || key == "_layertype")
                continue;

            std::string group = prof->at(i)->metaData("group");

            if (group == "stats" && !statsEnabled_)
                continue;

            if (group == "histo" && !histoEnabled_)
                continue;

            std::string dep = prof->at(i)->metaData("dependency");
            if (!dep.empty() &&
                (infoMap.find(dep) == infoMap.end() || infoMap[dep].empty())) {
                continue;
            }


            // Get keyType
            std::string keyType = prof->at(i)->metaData("type");

            if (keyType == "table") {
                std::string subType = prof->at(i)->metaData("subType");
                QStringList keyLst = QString::fromStdString(prof->at(i)->metaData("column")).split("/");
                QStringList headerLst = QString::fromStdString(prof->at(i)->metaData("header")).split("/");
                addTableInfoRow(label, keyLst, headerLst, QString::fromStdString(subType), infoMap, txt, colSpan);
            }
            else if (keyType == "list") {
                std::string subType = prof->at(i)->metaData("subType");
                QStringList keyLst = QString::fromStdString(prof->at(i)->metaData("item")).split("/");
                addListInfoRow(label, keyLst, QString::fromStdString(subType), infoMap, txt);
            }
            else if (keyType == "image") {
                QString subType = QString::fromStdString(prof->at(i)->metaData("subType"));
                if (subType == "histogram" && histoEnabled_) {
                    QPixmap p = layerInfoImage("histo");
                    if (!p.isNull()) {
                        // txt+="<tr><td class=\"first\">" + label + "</td><td align=left colspan=\"" + QString::number(colSpan) + "\"><img src=\"histo.png\"></td></tr>";
                        txt += R"(<tr><td align="center" colspan=")" + QString::number(colSpan + 1) + R"("><img src="histo.png"></td></tr>)";
                        QTextDocument* doc = layerTe_->document();
                        doc->addResource(QTextDocument::ImageResource, QUrl("histo.png"), p.toImage());
                        // Histogram generation updates the infos so we need to get it again!
                        infoMap.reset();
                        layerMetaData(infoMap);

                        // qDebug() << "histo";
                        // for(map<std::string,string>::iterator it=infoMap.begin(); it!= infoMap.end(); it++)
                        //	qDebug() << it->first.c_str() << it->second.c_str();
                    }
                }
                else {
                    addImageInfoRow(label, key, infoMap, txt, colSpan);
                }
            }
            else if (keyType == "title") {
                // string dep=prof->at(i)->metaData("dependency");
                // if(dep.empty() ||
                //   (infoMap.find(dep) != infoMap.end() && !infoMap[dep].empty()))
                //{
                addTitleInfoRow(label, txt, colSpan);
                //}
            }
            else {
                addTextInfoRow(label, key, infoMap, txt, colSpan);
            }
        }

        hasData = true;
    }

    txt += "</tbody></table>";

    if (hasData == false) {
        txt = tr("No meta data is available for this layer!");
    }

    // qDebug() << "txt:" << txt;

    int oldScrollPos = layerTe_->verticalScrollBar()->value();
    layerTe_->setHtml(txt);
    layerTe_->verticalScrollBar()->setValue(oldScrollPos);
}

void MvQLayerMetaDataWidget::addTitleInfoRow(QString label, QString& info, int colSpan)
{
    info += R"(<tr><td class="title" align="center" colspan=")" + QString::number(colSpan + 1) + +"\">" + label + "</td></tr>";
}

void MvQLayerMetaDataWidget::addTextInfoRow(QString label, QString key, const std::map<std::string, std::string>& infoMap, QString& info, int colSpan)
{
    auto it = infoMap.find(key.toStdString());
    if (it == infoMap.end())
        return;
    QString str = QString::fromStdString(it->second);
    if (str.isEmpty())
        return;

    info += "<tr><td class=\"first\">" + label + "</td><td colspan=\"" + QString::number(colSpan) + "\">" + str + "</td></tr>";
}


void MvQLayerMetaDataWidget::addTextInfoRow(QString label, QString value, QString& info, int colSpan)
{
    if (value.isEmpty())
        return;
    info += "<tr><td class=\"first\">" + label + "</td><td colspan=\"" + QString::number(colSpan) + "\">" + value + "</td></tr>";
}


void MvQLayerMetaDataWidget::addImageInfoRow(QString label, QString key, const std::map<std::string, std::string>& infoMap, QString& info, int colSpan)
{
    auto it = infoMap.find(key.toStdString());
    if (it == infoMap.end())
        return;
    QString str = QString::fromStdString(it->second);
    if (str.isEmpty())
        return;

    info += "<tr><td class=\"first\">" + label + "</td><td align=left colspan=\"" + QString::number(colSpan) + "\"><img src=" + str + "></td></tr>";
}


void MvQLayerMetaDataWidget::addListInfoRow(QString label, QStringList keys, QString listType,
                                            const std::map<std::string, std::string>& infoMap, QString& info)
{
    if (keys.count() <= 0)
        return;

    QStringList keyVal;
    if (listType == "label") {
        keyVal = keys;
    }
    else {
        std::map<std::string, std::string>::const_iterator it;
        foreach (QString key, keys) {
            it = infoMap.find(key.toStdString());
            if (it != infoMap.end()) {
                keyVal << QString::fromStdString(it->second);
            }
            else {
                keyVal << QString();
            }
        }
    }

    info += "<tr>";
    info += "<td class=\"first\">" + label + "</td>";

    foreach (QString key, keyVal) {
        if (listType == "label") {
            info += "<td class=\"first\">" + key + "</td>";
        }
        else {
            info += "<td>" + key + "</td>";
        }
    }

    info += "</tr>";
}

void MvQLayerMetaDataWidget::addTableInfoRow(QString label, QStringList keys, QStringList headers, QString tableType,
                                             const std::map<std::string, std::string>& infoMap, QString& info, int colSpan)
{
    if (keys.count() <= 0)
        return;

    std::map<std::string, std::string>::const_iterator it;

    // Loop for the columns
    QList<QStringList> keyVal;
    int rowNum = 0;
    foreach (QString key, keys) {
        // qDebug() << "key" << key;
        it = infoMap.find(key.toStdString());
        if (it == infoMap.end())
            return;
        // qDebug() << "key" << key << QString::fromStdString(it->second);
        keyVal << QString::fromStdString(it->second).split("/");

        // Each column must have the same number of rows
        if (rowNum == 0)
            rowNum = keyVal.back().count();
        else if (rowNum != keyVal.back().count())
            return;
    }

    if (rowNum <= 0)
        return;
    if (headers.count() > 0)
        rowNum++;

    if (tableType == "histogram") {
        info += "<tr>";
        if (headers.count() > 0) {
            foreach (QString s, headers) {
                info += "<td class=\"first\">" + s + "</td>";
            }
            info += "</tr>";
        }


        QImage img(70 + 16, 15, QImage::Format_ARGB32_Premultiplied);
        QPainter painter(&img);
        painter.setPen(Qt::black);
        QTextDocument* doc = layerTe_->document();

        float maxVal = 0.;
        for (int i = 0; i < keyVal[3].count(); i++) {
            const float fVal = keyVal[3].at(i).toFloat();
            if (fVal > maxVal)
                maxVal = fVal;
        }

        for (int i = 0; i < keyVal[0].count(); i++) {
            if (headers.count() > 0 || i > 0) {
                info += "<tr>";
            }

            QColor col(Qt::red);
            QStringList clst = keyVal[0].at(i).split(":");
            if (clst.count() == 3) {
                col = QColor::fromRgbF(clst[0].toFloat(), clst[1].toFloat(), clst[2].toFloat());
            }

            const float fVal = keyVal[3].at(i).toFloat();
            int r = static_cast<int>(fVal / maxVal * 70.);
            painter.fillRect(0, 0, 70 + 16, 15, QColor("#F9F9F9"));
            // painter.fillRect(0,0,r,15,col);

            painter.setBrush(col);
            painter.drawRect(0, 0, 10, 14);

            if (r > 2) {
                painter.drawRect(16, 0, r - 1, 14);
            }

            // Crate a deep copy of the image to add it to the document resources
            QImage imgRes = img.copy();

            QString imgName = "imgBar" + QString::number(i);
            doc->addResource(QTextDocument::ImageResource, QUrl(imgName), imgRes);

            info += "<td><img src=\"" + imgName + "\"></td>";

            for (int j = 1; j < keyVal.count(); j++) {
                info += "<td>" + keyVal[j].at(i) + "</td>";
            }

            info += "</tr>";
        }
    }
    else if (tableType == "noShortName" || tableType == "noShortNameHighlightFirst") {
        if (headers.count() > 0 && (headers.count() > 1 || !headers[0].isEmpty())) {
            info += "<tr>";
            if (headers.count() - 1 >= colSpan) {
                foreach (QString s, headers) {
                    info += "<td class=\"first\">" + s + "</td>";
                }
            }
            // We need to stretch the last column
            else {
                for (int i = 0; i < headers.count() - 1; i++) {
                    info += "<td class=\"first\">" + headers[i] + "</td>";
                }

                info += R"(<td class="first" colspan=")" + QString::number(colSpan - headers.count() + 2) +
                        "\" >" + headers[headers.count() - 1] + "</td>";
            }

            info += "</tr>";
        }

        for (int i = 0; i < keyVal[0].count(); i++) {
            info += "<tr>";
            if (keyVal.count() - 1 >= colSpan) {
                for (int j = 0; j < keyVal.count(); j++) {
                    if (j == 0 && tableType == "noShortNameHighlightFirst")
                        info += "<td class=\"first\" >" + keyVal[j].at(i) + "</td>";
                    else
                        info += "<td>" + keyVal[j].at(i) + "</td>";
                }
            }
            else {
                for (int j = 0; j < keyVal.count() - 1; j++) {
                    if (j == 0 && tableType == "noShortNameHighlightFirst")
                        info += "<td class=\"first\" >" + keyVal[j].at(i) + "</td>";
                    else
                        info += "<td>" + keyVal[j].at(i) + "</td>";
                }

                info += "<td colspan=\"" + QString::number(colSpan - keyVal.count() + 2) +
                        "\" >" + keyVal[keyVal.count() - 1].at(i) + "</td>";
            }
            info += "</tr>";
        }
    }
    else {
        info += "<tr>";
        info += R"(<td class="first" rowspan=")" + QString::number(rowNum) + "\" >" + label + "</td>";

        if (headers.count() > 0) {
            foreach (QString s, headers) {
                info += "<td class=\"first\">" + s + "</td>";
            }
            info += "</tr>";
        }

        for (int i = 0; i < keyVal[0].count(); i++) {
            if (headers.count() > 0 || i > 0) {
                info += "<tr>";
            }

            foreach (QStringList lst, keyVal) {
                info += "<td>" + lst[i] + "</td>";
            }
            info += "</tr>";
        }
    }
}

int MvQLayerMetaDataWidget::getColSpan(MvKeyProfile* prof, const std::map<std::string, std::string>& infoMap)
{
    // Find out colspan for 2nd column
    int colSpan = 1;
    for (unsigned int i = 0; i < prof->size(); i++) {
        std::string group = prof->at(i)->metaData("group");
        if (group == "histo" && !histoEnabled_)
            continue;

        if (group == "stats" && !statsEnabled_)
            continue;

        std::string dep = prof->at(i)->metaData("dependency");
        if (!dep.empty()) {
            auto it = infoMap.find(dep);
            if (it == infoMap.end() || it->second.empty()) {
                continue;
            }
        }

        std::string keyType = prof->at(i)->metaData("type");
        if (keyType == "table") {
            std::string subType = prof->at(i)->metaData("subType");

            int colNum = QString::fromStdString(prof->at(i)->metaData("column")).split("/").count();
            if (subType == "histogram") {
                colNum -= 1;
            }
            else if (subType == "noShortName") {
                colNum -= 1;
            }

            if (colNum == 0)
                colNum = QString::fromStdString(prof->at(i)->metaData("header")).split("/").count();
            if (colSpan < colNum)
                colSpan = colNum;
        }
        else if (keyType == "list") {
            int itemNum = QString::fromStdString(prof->at(i)->metaData("item")).split("/").count();
            if (colSpan < itemNum)
                colSpan = itemNum;
        }
    }

    return colSpan;
}

void MvQLayerMetaDataWidget::layerMetaData(MetaDataCollector& md)
{
    if (layer_ && sceneItem_) {
        // it's not the end of the world if Magics has a problem at this point
        bool prevMagErrorState = PlotMod::Instance().setMagicsErrorIgnored(true);
        try {
            sceneItem_->layerMetaDataForCurrentStep(layer_, md);
        }
        catch (MagicsException& e) {
        }

        PlotMod::Instance().setMagicsErrorIgnored(prevMagErrorState);
    }
}

QString MvQLayerMetaDataWidget::layerMetaData(QString key)
{
    if (!layer_ || !sceneItem_)
        return {};

    MetaDataCollector md;
    std::string keyStr = key.toStdString();
    md[keyStr] = "";

    sceneItem_->layerMetaDataForCurrentStep(layer_, md);

    return QString::fromStdString(md[keyStr]);
}

// AKA histogram
QPixmap MvQLayerMetaDataWidget::layerInfoImage(QString /*key*/)
{
    if (!layer_ || !sceneItem_)
        return {};

    QStringList lst = histoVisdefCb_->itemData(histoVisdefCb_->currentIndex()).toStringList();
    QHash<QString, QString> vdef;
    if (lst.count() == 2) {
        vdef["visdefName"] = lst[0];
        vdef["visdefClass"] = lst[1];
    }

    QPixmap p;
    // it's not the end of the world if Magics has a problem at this point
    bool prevMagErrorState = PlotMod::Instance().setMagicsErrorIgnored(true);
    try {
        p = sceneItem_->layerInfoImageForCurrentStep(layer_, vdef);
    }
    catch (MagicsException& e) {
    }

    PlotMod::Instance().setMagicsErrorIgnored(prevMagErrorState);

    return p;
}

//=============================================
//
// Signals and slots
//
//=============================================


void MvQLayerMetaDataWidget::slotHistoEnabled(bool b)
{
    histoEnabled_ = b;
    updateData();
}

void MvQLayerMetaDataWidget::slotStatsEnabled(bool b)
{
    statsEnabled_ = b;
    updateData();
}

void MvQLayerMetaDataWidget::slotSelectHistoVisdef(int /*index*/)
{
    updateData();
}

void MvQLayerMetaDataWidget::slotFrameChanged()
{
    updateData();
}

//===============================================
//
// Reset
//
//===============================================

void MvQLayerMetaDataWidget::setLayer(MgQLayerItem* layer)
{
    layer_ = layer;

    if (!layer_) {
        histoVisdefCb_->clear();
        updateData();
        return;
    }

    // Update histogram visdef combo
    MgQIconList icons;

    if (sceneItem_)
        sceneItem_->layerIconsForCurrentStep(layer_, icons);

    int lastIndex = 0;

    if (histoVisdefCb_->count() == 0) {
        lastIndex = histoVisdefInitialIndex_;
    }
    else {
        lastIndex = histoVisdefCb_->currentIndex();
    }

    histoVisdefCb_->clear();
    histoVisdefCb_->addItem(tr("Data values"));

    for (const auto& icon : icons) {
        QString vdefClass = icon.class_;

        if (visdefs_.contains(vdefClass)) {
            QString str;
            QStringList lst = icon.name_.split("/");
            if (!lst.isEmpty()) {
                str = lst.last();
            }

            histoVisdefCb_->addItem(str);

            lst.clear();
            lst << icon.name_ << vdefClass;
            histoVisdefCb_->setItemData(histoVisdefCb_->count() - 1, lst);


            if (visdefPixmaps_.find(vdefClass) == visdefPixmaps_.end()) {
                Cached iconFileName = MakeIconPath(vdefClass.toStdString().c_str());
                visdefPixmaps_[vdefClass] = QPixmap::fromImage(QImage((const char*)iconFileName, "xpm")).scaled(QSize(16, 16), Qt::KeepAspectRatio, Qt::SmoothTransformation);
            }
            histoVisdefCb_->setItemIcon(histoVisdefCb_->count() - 1, visdefPixmaps_[vdefClass]);
        }
    }

    if (lastIndex == -1) {
        histoVisdefCb_->setCurrentIndex(0);
    }
    else if (lastIndex < histoVisdefCb_->count()) {
        histoVisdefCb_->setCurrentIndex(lastIndex);
    }
    else {
        histoVisdefCb_->setCurrentIndex(histoVisdefCb_->count() - 1);
    }

    // Update data widget
    updateData();
}

void MvQLayerMetaDataWidget::setHistoVisdefInitialIndex(int index)
{
    histoVisdefInitialIndex_ = index;
}

void MvQLayerMetaDataWidget::reset(MgQSceneItem* sceneItem, MgQLayerItem* layer)
{
    sceneItem_ = sceneItem;
    layer_ = layer;
    setLayer(layer_);
}
