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

 Copyright 2016 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 "BufrKeyWidget.h"

#include "MvQBufrDataItem.h"
#include "MvEccBufr.h"
#include "MvQKeyProfileModel.h"
#include "MvKeyProfile.h"
#include "MvQMethods.h"
#include "MvQTheme.h"

#include <QTreeWidgetItem>

#include <cmath>


BufrKeyWidget::BufrKeyWidget(QWidget* parent) :
    QWidget(parent)
{
    setupUi(this);

    te_->setReadOnly(true);

    model_ = new MvQKeyProfileModel(this);
    model_->setShadeFirstColumn(true);

    sortModel_ = new MvQKeyProfileSortFilterModel(this);
    sortModel_->setSourceModel(model_);
    sortModel_->setDynamicSortFilter(true);
    sortModel_->setFilterRole(Qt::UserRole);
    sortModel_->setFilterFixedString("1");
    sortModel_->setFilterKeyColumn(0);

    tree_->setModels(model_, sortModel_);
    tree_->setUniformRowHeights(true);
    tree_->setAlternatingRowColors(false);
    tree_->hide();

    te_->document()->setDefaultStyleSheet(MvQTheme::htmlTableCss());

    connect(tree_, SIGNAL(selectionChanged(QModelIndex)),
            this, SLOT(slotValueItemSelected(QModelIndex)));
}

void BufrKeyWidget::clear()
{
    broadcast_ = false;
    te_->clear();
    model_->clear();
    tree_->hide();
}

// Subset is indexed from 0 here!!!!
void BufrKeyWidget::setData(MvQBufrDataItem* item, int subset, MvEccBufrMessage* ecc)
{
    te_->clear();
    model_->clear();
    tree_->hide();
    broadcast_ = false;

    if (!item)
        return;

    QString tp;
    QString key = item->key();
    QString value = item->rawValue(subset);
    QString units = item->data("units");
    QString element = item->data("code");
    QString index = item->data("index");
    int bitWidth = item->data("width").toInt();

    if (key.isEmpty())
        return;

    tp = "<table width=\'100%\'>";

    // Key block
    tp += formatRow("Key", key);
    if (!index.isEmpty())
        tp += formatRow("Index", index);

    // Value block
    tp += R"(<tr><td class='title' colspan='2' align='center'>Value</td></tr>)";
    tp += formatRow("Value", value);
    if (!units.isEmpty()) {
        tp += formatRow("Units", units);
        if (value != "missing") {
            if (units == "CODE TABLE") {
                if (MvBufrCodeTable* table = MvBufrCodeTable::find(element.toInt(), ecc)) {
                    const std::string& v = table->value(value.toInt());
                    tp += formatRow("Code text", QString::fromStdString(v));
                }
            }
            else if (units == "FLAG TABLE") {
                if (MvBufrFlagTable* table = MvBufrFlagTable::find(element.toInt(), ecc)) {
                    MvBufrFlagInfo flags;
                    table->values(value.toInt(), bitWidth, flags);
                    if (flags.bits().empty() == false) {
                        tp += formatRow("Bits", QString::fromStdString(flags.bits()));
                        for (int i = 0; i < flags.num(); i++) {
                            tp += formatRow("Flag[" + QString::fromStdString(flags.bitPosition(i)) + "]",
                                            QString::fromStdString(flags.description(i)));
                        }
                    }
                }
            }
        }
    }


    // Element block
    if (!element.isEmpty()) {
        tp += R"(<tr><td class='title' colspan='2' align='center'>Element</td></tr>)";
        tp += formatElementBlock(element);

        // Scale etc
        tp += R"(<tr><td class='title' colspan='2' align='center'>Representation</td></tr>)";
        tp += formatRow("Scale", item->data("scale"));
        tp += formatRow("Reference", item->data("reference"));
        tp += formatRow("Width", item->data("width"));

        QString eccType = "double";
        QString unitsUpper = units.toUpper();
        if (unitsUpper.startsWith("CCITT") ||
            unitsUpper.startsWith("CHARACTER"))
            eccType = "string";
        else if (unitsUpper.contains("CODE TABLE"))
            eccType = "CODE TABLE";
        else if (unitsUpper.startsWith("FLAG TABLE"))
            eccType = "FLAG TABLE";
        else if (item->data("scale").toInt() <= 0)
            eccType = "long";

        if (eccType == "long" || eccType == "double") {
            float scale = item->data("scale").toFloat();
            float ref = item->data("reference").toFloat();
            float width = item->data("width").toFloat();
            float minv = ref / std::pow(10., scale);
            float maxv = (std::pow(2., width) - 2 + ref) / pow(10, scale);
            float precision = pow(10., -scale);
            tp += formatRow("Min", QString::number(minv));
            tp += formatRow("Max", QString::number(maxv));
            tp += formatRow("Precision", QString::number(precision));
        }

        tp += formatRow("type", eccType);
    }

    tp += "</table>";
    te_->insertHtml(tp);

    // Display array values in tree
    if (item->valueIsArray() ||
        (key == "unexpandedDescriptors" && item->valueArray().count() > 1)) {
        int n = item->valueArray().count();
        if (n > 0) {
            auto* prof = new MvKeyProfile("tmp");
            std::string firstName = (key == "unexpandedDescriptors") ? "pos" : "subset";
            std::string secondName = (key == "unexpandedDescriptors") ? "descriptor" : "value";
            auto* firstKey = new MvKey(firstName, firstName);
            firstKey->setValueType(MvKey::IntType);
            firstKey->setIntRange(1, n);
            prof->addKey(firstKey);
            auto* secondKey = new MvKey(secondName, secondName);
            secondKey->setValueType(MvKey::StringType);
            prof->addKey(secondKey);

            for (int i = 0; i < n; ++i) {
                secondKey->addValue(item->valueArray()[i].toStdString());
            }

            model_->setKeyProfile(prof);

            tree_->show();
            tree_->resizeColumnToContents(0);

            // subset is indexed from 0 here
            if (key != "unexpandedDescriptors" && subset >= 0 && subset < n) {
                tree_->setCurrentIndex(sortModel_->mapFromSource(model_->index(subset, 0)));
            }
        }
    }
    if (key != "unexpandedDescriptors") {
        broadcast_ = true;
    }
    else {
        broadcast_ = false;
    }
}

QString BufrKeyWidget::formatKey(QString key)
{
    //    auto subCol = MvQTheme::subText();
    //    return MvQ::formatText(key, subCol);
    return key;
}

QString BufrKeyWidget::formatRow(QString key, QString val)
{
    QString s = "<tr><td>" + formatKey(key) + "</td><td>" + val + "</td></tr>";
    return s;
}

QString BufrKeyWidget::formatElementBlock(QString val)
{
    QString s;
    QString key("Element");
    if (val.length() == 6) {
        QString type = val.mid(0, 1);
        QString cat = val.mid(1, 2);
        QString code = val.mid(3, 3);

        QString defType = "???";
        switch (MvEccBufr::elementDefType(val.toInt())) {
            case MvEccBufr::WmoElement:
                defType = "WMO";
                break;
            case MvEccBufr::LocalElement:
                defType = "local";
                break;
            default:
                break;
        }

        QString typeStr = MvQ::formatText(type, MvQTheme::colour("bufrelement", "type"));    //"<b><font color=\'" + typeCol.name() + "\'>" + type + "</font></b>";
        QString catStr = MvQ::formatText(cat, MvQTheme::colour("bufrelement", "category"));  //"<b><font color=\'" + catCol.name() + "\'>" + cat + "</font></b>";
        QString codeStr = MvQ::formatText(code, MvQTheme::colour("bufrelement", "code"));    //"<b><font color=\'" + codeCol.name() + "\'>" + code + "</font></b>";

        s += formatRow(key, typeStr + catStr + codeStr);
        s += formatRow("Category", catStr);
        s += formatRow("Code", codeStr);
        s += formatRow("Definition", defType);
    }
    else {
        s = formatRow(key, val);
    }
    return s;
}

void BufrKeyWidget::slotValueItemSelected(const QModelIndex& idx)
{
    if (broadcast_) {
        auto srcIdx = sortModel_->mapToSource(idx);
        if (srcIdx.isValid()) {
            // subset indexing starts at 1
            emit subsetSelected(srcIdx.row() + 1);
        }
    }
}
