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

 Copyright 2017 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 "BufrFilterEditor.h"

#include <QtGlobal>
#include <QApplication>
#include <QFile>
#include <QGridLayout>
#include <QLayoutItem>
#include <QPainter>
#include <QPaintEvent>
#include <QRegularExpression>
#include <QStyle>
#include <QTimer>

#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>

#include "BufrMetaData.h"
#include "MvEccBufr.h"
#include "MvMiscellaneous.h"
#include "PropertyLineEditor.h"
#include "MvQMethods.h"
#include "MvQProperty.h"
#include "MvQTheme.h"

BufrFilterEditor::BufrFilterEditor(QWidget* parent) :
    QWidget(parent)
{
    loadInit();

    linePen_ = QPen(MvQTheme::colour("iconeditor", "line_item_pen"));
    framePen_ = QPen(MvQTheme::colour("iconeditor", "frame_pen"));

    auto* vb = new QVBoxLayout(this);

    grid_ = new QGridLayout();
    grid_->setHorizontalSpacing(4);
    grid_->setVerticalSpacing(0);

    vb->addLayout(grid_);
    vb->setContentsMargins(1, 1, 0, 2);
    vb->addStretch(1);

    EditLineFactory elf(grid_, this);

    Q_FOREACH (MvQProperty* gr, groups_) {
        // Create header for the group
        auto hdr = new EditLineHeader(gr->param("title"), grid_, this);
        hdr->setGroupProp(gr);
        headers_[gr->name()] = hdr;

        // Add line editors to the header
        Q_FOREACH (MvQProperty* prop, gr->children()) {
            if (prop->param("hidden") != "1") {
                lines_[prop->name()] = elf.make(prop, hdr);
            }
        }
        hdr->init();
    }

    //-----------------------------------
    // Set up option dependecies
    //-----------------------------------

    // Output
    EditLine* line = lines_["EXTRACT"];
    Q_ASSERT(line);

    // this also calls slotParamCountLeChanged()
    slotOutputLeChanged(line, line->value());

    connect(line, SIGNAL(valueChanged(EditLine*, QString)),
            this, SLOT(slotOutputLeChanged(EditLine*, QString)));

    line = lines_["PARAMETER_COUNT"];
    Q_ASSERT(line);

    connect(line, SIGNAL(valueChanged(EditLine*, QString)),
            this, SLOT(slotParamCountLeChanged(EditLine*, QString)));

    // Coordinate condition
    line = lines_["COORDINATE_COUNT"];
    Q_ASSERT(line);

    connect(line, SIGNAL(valueChanged(EditLine*, QString)),
            this, SLOT(slotCoordKeyCountLeChanged(EditLine*, QString)));

    // Date
    line = lines_["DATEMODE"];
    Q_ASSERT(line);
    slotDateModeChanged(line, line->value());

    connect(line, SIGNAL(valueChanged(EditLine*, QString)),
            this, SLOT(slotDateModeChanged(EditLine*, QString)));

    // Custom key
    line = lines_["CUSTOM_COUNT"];
    Q_ASSERT(line);
    slotCustomKeyCountLeChanged(line, line->value());

    connect(line, SIGNAL(valueChanged(EditLine*, QString)),
            this, SLOT(slotCustomKeyCountLeChanged(EditLine*, QString)));
}

void BufrFilterEditor::updateHelpers(BufrMetaData* decoder, int msgIdx)
{
    if (!decoder)
        return;

    if (msgIdx < 0 || static_cast<int>(decoder->messages().size()) <= msgIdx)
        return;

    MvEccBufrMessage* msg = decoder->message(msgIdx);
    QPair<int, int> msgType(msg->dataCategory(), msg->dataSubCategory());
    if (!helperTypes_.contains(msgType)) {
        bool initNeed = helperTypes_.isEmpty();
        helperTypes_ << msgType;
        std::set<std::string> msgKeys;
        decoder->readMessageKeys(msgIdx, msgKeys);

        Q_FOREACH (EditLine* le, lines_) {
            Q_ASSERT(le);
            if (MvQProperty* prop = le->property()) {
                if (prop->type() == MvQProperty::DateType) {
                    le->setHelper(QString::fromStdString(decoder->firstTypicalDate()));
                }
                else if (prop->name().startsWith("PARAMETER_") ||
                         prop->name().contains(QRegularExpression("COORDINATE_\\d+")) ||
                         prop->name().startsWith("CUSTOM_KEY_")) {
                    if (initNeed)
                        le->setCompleter(msgKeys);
                    else
                        le->updateCompleter(msgKeys);
                }
            }
        }
    }
}

// We init the helpers when a new file is selected
void BufrFilterEditor::initHelpers(BufrMetaData* decoder, int msgIdx)
{
    helperTypes_.clear();
    updateHelpers(decoder, 0);
    if (msgIdx != 0)
        updateHelpers(decoder, msgIdx);
}

void BufrFilterEditor::slotOutputLeChanged(EditLine*, QString /*mode*/)
{
    adjustExtractOptions();
    adjustCoordOptions();
}

void BufrFilterEditor::slotParamCountLeChanged(EditLine*, QString)
{
    adjustExtractOptions();
}

void BufrFilterEditor::slotCoordModeLeChanged(EditLine*, QString)
{
    adjustCoordOptions();
}

void BufrFilterEditor::slotCoordKeyCountLeChanged(EditLine*, QString /*mode*/)
{
    adjustCoordOptions();
}

void BufrFilterEditor::slotCustomModeLeChanged(EditLine*, QString)
{
    adjustCustomOptions();
}

void BufrFilterEditor::slotCustomKeyCountLeChanged(EditLine*, QString /*mode*/)
{
    adjustCustomOptions();
}

void BufrFilterEditor::adjustExtractOptions()
{
    EditLine* outLe = lines_.value("EXTRACT", 0);
    Q_ASSERT(outLe);

    EditLine* cntLe = lines_.value("PARAMETER_COUNT", 0);
    Q_ASSERT(cntLe);
    int cnt = cntLe->value().toInt();  // the number of visible param lines
    int firstHiddenPar = -1;

    EditLine* collectLe = lines_.value("EXTRACT_MODE", 0);
    Q_ASSERT(collectLe);

    EditLine* collectCoordLe = lines_.value("EXTRACT_COORDINATE", 0);
    Q_ASSERT(collectCoordLe);

    EditLine* collectDateLe = lines_.value("EXTRACT_DATE", 0);
    Q_ASSERT(collectDateLe);

    EditLine* missingLe = lines_.value("MISSING_DATA", 0);
    Q_ASSERT(missingLe);

    if (outLe->value() != "OFF") {
        cntLe->setHiddenByRule(false);
        collectLe->setHiddenByRule(false);
        collectCoordLe->setHiddenByRule(false);
        collectDateLe->setHiddenByRule(false);
        missingLe->setHiddenByRule(false);
        firstHiddenPar = cnt;
    }
    else {
        cntLe->setHiddenByRule(true);
        collectLe->setHiddenByRule(true);
        collectCoordLe->setHiddenByRule(true);
        collectDateLe->setHiddenByRule(true);
        missingLe->setHiddenByRule(true);
        firstHiddenPar = 0;
    }

    setParGroupVisibility("PARAMETER_", firstHiddenPar);
    setParGroupVisibility("PARAMETER_OPERATOR_", firstHiddenPar);
    setParGroupVisibility("PARAMETER_VALUE_", firstHiddenPar);

#if 0
    EditLine* parLe=lines_.value("PARAMETER_1",0);
    int i=0;
    while(parLe)
    {
       parLe->setHiddenByRule(i >= firstHiddenPar);
       i++;
       parLe=lines_.value("PARAMETER_" + QString::number(i+1),0);
    }

    EditLine* valLe=lines_.value("PARAMETER_OPERATOR_1",0);
    i=0;
    while(valLe)
    {
       valLe->setHiddenByRule(i >= firstHiddenPar);
       i++;
       valLe=lines_.value("PARAMETER_OPERATOR_" + QString::number(i+1),0);
    }

    EditLine* valLe=lines_.value("PARAMETER_VALUE_1",0);
    i=0;
    while(valLe)
    {
       valLe->setHiddenByRule(i >= firstHiddenPar);
       i++;
       valLe=lines_.value("PARAMETER_VALUE_" + QString::number(i+1),0);
    }
#endif

    delayedForceRepaint();
}

void BufrFilterEditor::adjustCoordOptions()
{
    EditLine* outLe = lines_.value("EXTRACT", 0);
    Q_ASSERT(outLe);

    EditLine* coordCntLe = lines_.value("COORDINATE_COUNT", 0);
    Q_ASSERT(coordCntLe);
    int cnt = coordCntLe->value().toInt();
    int firstHiddenKey = -1;

    if (outLe->value() != "OFF") {
        coordCntLe->setHiddenByRule(false);
        firstHiddenKey = cnt;
    }
    else {
        coordCntLe->setHiddenByRule(true);
        firstHiddenKey = 0;
    }

    setParGroupVisibility("COORDINATE_", firstHiddenKey);
    setParGroupVisibility("COORDINATE_OPERATOR_", firstHiddenKey);
    setParGroupVisibility("COORDINATE_VALUE_", firstHiddenKey);

    delayedForceRepaint();
}

void BufrFilterEditor::slotDateModeChanged(EditLine*, QString mode)
{
    QStringList mode1Lst, mode2Lst;

    mode1Lst << "DATE"
             << "TIME"
             << "WINDOW_IN_MINUTES";
    mode2Lst << "DATE_1"
             << "TIME_1"
             << "DATE_2"
             << "TIME_2";

    bool show = (mode == "WINDOW") ? true : false;

    Q_FOREACH (QString s, mode1Lst) {
        EditLine* le = lines_.value(s, 0);
        Q_ASSERT(le);
        le->setHiddenByRule(!show);
    }
    Q_FOREACH (QString s, mode2Lst) {
        EditLine* le = lines_.value(s, 0);
        Q_ASSERT(le);
        le->setHiddenByRule(show);
    }

    delayedForceRepaint();
}

void BufrFilterEditor::adjustCustomOptions()
{
    EditLine* customCntLe = lines_.value("CUSTOM_COUNT", 0);
    Q_ASSERT(customCntLe);
    int cnt = customCntLe->value().toInt();
    int firstHiddenKey = -1;

    customCntLe->setHiddenByRule(false);
    if (customCntLe->value() == "0") {
        firstHiddenKey = 0;  // we hide them all
    }
    else {
        firstHiddenKey = cnt;
    }

    setParGroupVisibility("CUSTOM_KEY_", firstHiddenKey);
    setParGroupVisibility("CUSTOM_OPERATOR_", firstHiddenKey);
    setParGroupVisibility("CUSTOM_VALUE_", firstHiddenKey);

#if 0
    EditLine* customKeyLe=lines_.value("CUSTOM_KEY_1",0);
    int i=0;
    while(customKeyLe)
    {
       customKeyLe->setHiddenByRule(i >= firstHiddenKey);
       i++;
       customKeyLe=lines_.value("CUSTOM_KEY_" + QString::number(i+1),0);
    }

    EditLine* customValLe=lines_.value("CUSTOM_VALUE_1",0);
    i=0;
    while(customValLe)
    {
       customValLe->setHiddenByRule(i >= firstHiddenKey);
       i++;
       customValLe=lines_.value("CUSTOM_VALUE_" + QString::number(i+1),0);
    }

    EditLine* customValLe=lines_.value("CUSTOM_VALUE_1",0);
    i=0;
    while(customValLe)
    {
       customValLe->setHiddenByRule(i >= firstHiddenKey);
       i++;
       customValLe=lines_.value("CUSTOM_VALUE_" + QString::number(i+1),0);
    }
#endif

    delayedForceRepaint();
}

void BufrFilterEditor::setParGroupVisibility(QString parPattern, int firstHiddenKey)
{
    EditLine* line = lines_.value(parPattern + "1", 0);
    Q_ASSERT(line);
    int i = 0;
    while (line) {
        line->setHiddenByRule(i >= firstHiddenKey);
        i++;
        line = lines_.value(parPattern + QString::number(i + 1), 0);
    }
}


void BufrFilterEditor::delayedForceRepaint()
{
    QTimer::singleShot(1, this, SLOT(forceRepaint()));
}

void BufrFilterEditor::forceRepaint()
{
    repaint();
    qApp->processEvents();
}

void BufrFilterEditor::expandAllGroups()
{
    Q_FOREACH (EditLineHeader* h, headers_) {
        Q_ASSERT(h);
        h->expand();
    }
    delayedForceRepaint();
}

void BufrFilterEditor::expandEditedGroups()
{
    Q_FOREACH (EditLineHeader* h, headers_) {
        Q_ASSERT(h);
        if (h->hasEdited())
            h->expand();
        else
            h->collapse();
    }
    delayedForceRepaint();
}

void BufrFilterEditor::collapseAllGroups()
{
    Q_FOREACH (EditLineHeader* h, headers_) {
        Q_ASSERT(h);
        h->collapse();
    }
    delayedForceRepaint();
}

void BufrFilterEditor::clear()
{
    Q_FOREACH (EditLine* line, lines_) {
        Q_ASSERT(line);
        line->clear();
    }
    delayedForceRepaint();
}

void BufrFilterEditor::applyChange()
{
    Q_FOREACH (EditLine* line, lines_) {
        Q_ASSERT(line);
        line->applyChange();
    }
}

const BufrFilterDef& BufrFilterEditor::filterDef()
{
    applyChange();

    def_.clear();

    // Output
    addToDef("EXTRACT");
    addToDef("MISSING_DATA");

    addToDef("PARAMETER_COUNT");
    MvQProperty* p = findProperty("PARAMETER_COUNT");
    Q_ASSERT(p);
    int cnt = p->value().toInt();
    for (int i = 0; i < cnt; i++) {
        addToDef("PARAMETER_" + QString::number(i + 1));
        addToDef("PARAMETER_RANK_" + QString::number(i + 1));
        addToDef("PARAMETER_OPERATOR_" + QString::number(i + 1));
        addToDef("PARAMETER_VALUE_" + QString::number(i + 1));
    }

    // Coord
    addToDef("COORDINATE_COUNT");
    p = findProperty("COORDINATE_COUNT");
    Q_ASSERT(p);
    cnt = p->value().toInt();
    for (int i = 0; i < cnt; i++) {
        addToDef("COORDINATE_" + QString::number(i + 1));
        addToDef("COORDINATE_RANK_" + QString::number(i + 1));
        addToDef("COORDINATE_OPERATOR_" + QString::number(i + 1));
        addToDef("COORDINATE_VALUE_" + QString::number(i + 1));
    }

    // collect
    addToDef("EXTRACT_MODE");
    addToDef("EXTRACT_COORDINATE");
    addToDef("EXTRACT_DATE");

    // Index
    addToDef("MESSAGE_INDEX");

    // Edition
    addToDef("EDITION");
    addToDef("CENTRE");
    addToDef("SUBCENTRE");
    addToDef("MASTERTABLE");
    addToDef("LOCALTABLE");

    // Type
    addToDef("DATA_TYPE");
    addToDef("DATA_SUBTYPE");

    // Rdb type
    addToDef("RDB_TYPE");

    // Identification
    addToDef("IDENT_KEY");
    addToDef("IDENT_VALUE");

    // Date, time
    addToDef("DATEMODE");
    addToDef("DATE");
    addToDef("TIME");
    addToDef("WINDOW_IN_MINUTES");
    addToDef("DATE_1");
    addToDef("TIME_1");
    addToDef("DATE_2");
    addToDef("TIME_2");

    // Area
    addToDef("AREA");

    // Custom
    addToDef("CUSTOM_COUNT");
    p = findProperty("CUSTOM_COUNT");
    Q_ASSERT(p);
    cnt = p->value().toInt();
    for (int i = 0; i < cnt; i++) {
        addToDef("CUSTOM_KEY_" + QString::number(i + 1));
        addToDef("CUSTOM_RANK_" + QString::number(i + 1));
        addToDef("CUSTOM_OPERATOR_" + QString::number(i + 1));
        addToDef("CUSTOM_VALUE_" + QString::number(i + 1));
    }

    return def_;
}

MvQProperty* BufrFilterEditor::findProperty(QString id) const
{
    for (auto p : props_) {
        if (p->name() == id) {
            return p;
        }
    }
    return nullptr;
}

void BufrFilterEditor::addToDef(QString id)
{
    MvQProperty* p = findProperty(id);
    Q_ASSERT(p);
    if (p) {
        std::string v = p->valueAsString();
        // if(!v.empty())
        def_.add(id.toStdString(), v);
    }
}

QString BufrFilterEditor::lineValue(QString id)
{
    Q_ASSERT(lines_.find(id) != lines_.end());

    if (EditLine* e = lines_.value(id, 0)) {
        return e->value();
    }
    return {};
}

void BufrFilterEditor::paintEvent(QPaintEvent* /*event*/)
{
    QPainter painter(this);

    // paint bg
    QPalette pal = palette();
    painter.fillRect(0, 0, this->width(), this->height(), pal.color(QPalette::Window));

    Q_ASSERT(grid_);
    painter.setPen(linePen_);

    int spacingV = grid_->verticalSpacing();
    int spacingH = grid_->horizontalSpacing();
    int lastY = 0;
    bool currentIsHeader = false;
    bool prevIsHeader = true;

#if 0
    int currentBlock=0;
#endif
    // Detect the vertical line pos
    int vLinePos = 0;
    for (int i = 0; i < grid_->rowCount(); i++) {
        QLayoutItem* item = grid_->itemAtPosition(i, 3);
        if (item) {
            QWidget* w = item->widget();
            if (w->property("lineEditHeader").toString() != "1" && w->isVisible()) {
                QRect r = item->geometry();
                vLinePos = r.x() - spacingH / 2;
                break;
            }
        }
    }

    for (int i = 0; i < grid_->rowCount(); i++) {
        QLayoutItem* item = grid_->itemAtPosition(i, 3);
        if (item) {
            if (QWidget* w = item->widget()) {
                currentIsHeader = (w->property("lineEditHeader").toString() == "1");

                if (w->isVisible() && !currentIsHeader) {
                    QRect r = item->geometry();

                    vLinePos = r.x() - spacingH / 2;
                    int h = r.height() + spacingV;
                    int y = r.y() - spacingV / 2;

                    // horizontal line at the top of the row
                    MvQ::safeDrawLine(QPoint(0, y), QPoint(this->width(), y), &painter);
                    lastY = y + h;
                }
            }
        }
        prevIsHeader = currentIsHeader;
    }

    // The last Horizontal line
    if (!prevIsHeader) {
        MvQ::safeDrawLine(QPoint(0, lastY - 1), QPoint(this->width(), lastY - 1), &painter);
    }

    // Vertical lines
    if (grid_->rowCount() > 1) {
        if (vLinePos > 0) {
            MvQ::safeDrawLine(QPoint(vLinePos, 0), QPoint(vLinePos, lastY), &painter);
        }
    }

    // Frame
    painter.setPen(framePen_);
    painter.drawRect(0, 0, this->width(), this->height());
}

void BufrFilterEditor::loadInit()
{
    std::string fName = metview::appDefDirFile("BufrFilterDef.json");

    QFile fIn(fName.c_str());
    if (!fIn.open(QIODevice::ReadOnly | QIODevice::Text)) {
#ifdef METVIEW
        marslog(LOG_WARN, "BufrFilterEditor::loadInit --> Could not open json config file!");
#endif
        return;
    }

    QByteArray json = fIn.readAll();
    QJsonDocument doc = QJsonDocument::fromJson(json);

    // This document is an array of groups
    Q_ASSERT(doc.isArray());
    for (int i = 0; i < doc.array().count(); i++) {
        // We process one group
        QJsonObject grObj = doc.array()[i].toObject();
        Q_ASSERT(!grObj.isEmpty());
        QString groupName = grObj.value("name").toString();
        auto* grProp = new MvQProperty(groupName.toStdString());
        groups_ << grProp;

        // Iterat through th ojects in a group
        for (QJsonObject::const_iterator it = grObj.constBegin(); it != grObj.constEnd(); ++it) {
            QString name = it.key();

            // The "options" object contains the array of the options
            if (name == "options") {
                Q_ASSERT(it.value().isArray());
                QJsonArray chArr = it.value().toArray();
                for (int j = 0; j < chArr.count(); j++) {
                    QJsonObject chObj = chArr[j].toObject();
                    Q_ASSERT(!chObj.isEmpty());
                    QString chName = chObj.value("name").toString();
                    auto* chProp = new MvQProperty(chName.toStdString());
                    grProp->addChild(chProp);
                    loadProperty(chArr[j], chProp);
                    chProp->adjustAfterLoad();
                    props_ << chProp;
                }
            }
            // Other properties - they must be strings
            else if (name != "name" && it.value().isString()) {
                grProp->setParam(name, it.value().toString());
            }
        }
    }
}

void BufrFilterEditor::loadProperty(QJsonValue pt, MvQProperty* prop)
{
    if (pt.isObject()) {
        QJsonObject ob = pt.toObject();
        for (QJsonObject::const_iterator it = ob.constBegin();
             it != ob.constEnd(); ++it) {
            QString name = it.key();
            if (it.value().isString()) {
                // Default value
                if (name == "default") {
                    prop->setDefaultValue(it.value().toString().toStdString());
                }
                else {
                    prop->setParam(name, it.value().toString());
                }
            }
        }
    }
}
