/***************************** 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 <QStyle>
#include <QTimer>

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

#include "BufrMetaData.h"
#include "MvEccBufr.h"
#include "MvMiscelaneous.h"
#include "PropertyLineEditor.h"
#include "MvQProperty.h"

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

    QVBoxLayout* 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);

    EditLineHeader* hdr = 0;
    EditLineFactory elf(grid_, this);

    Q_FOREACH (MvQProperty* gr, groups_) {
        //Create header for the group
        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(QRegExp("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
{
    Q_FOREACH (MvQProperty* p, props_) {
        if (p->name() == id) {
            return p;
        }
    }
    return 0;
}

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 QString();
}

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

    //paint bg
    QBrush bgBrush(QColor(242, 242, 242));
    if (parentWidget()) {
        QPalette pal = parentWidget()->palette();
        bgBrush      = QBrush(pal.color(QPalette::Window));
    }
    painter.fillRect(0, 0, this->width(), this->height(), bgBrush);

    Q_ASSERT(grid_);

    //static QBrush bgBrushLeft_=QBrush(QColor(242,242,242));
    //static QBrush bgBrushRight_=QBrush(QColor(228,228,229));

    static QBrush bgBrushLeft_  = QBrush(QColor(250, 250, 250));
    static QBrush bgBrushRight_ = QBrush(QColor(226, 226, 226));
    //static QBrush bgBrushRight_=QBrush(QColor(252,252,252));

    static QPen linePenHorizontal_     = QPen(QColor(189, 189, 189));
    static QPen linePenVertical_       = QPen(QColor(120, 120, 120));
    static QPen linePenSeparatorLeft_  = QPen(QColor(80, 80, 80));
    static QPen linePenSeparatorRight_ = QPen(QColor(140, 140, 140));

    painter.setPen(linePenHorizontal_);

    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;
            }
        }
    }

    //Paint the background

    //Left side
    if (vLinePos > 0)
        painter.fillRect(0, 0, vLinePos, this->height(), bgBrushLeft_);

    //right side
    painter.fillRect(vLinePos, 0, this->width() - vLinePos + 1, this->height(), bgBrushRight_);

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

            if (w && w->isVisible() && !currentIsHeader) {
#if 0
                bool newBlock=false;
                if(!prevIsHeader)
                {
                    if(QLayoutItem* labelItem = grid_->itemAtPosition(i,1))
                        if(QWidget* label=labelItem->widget())
                            newBlock=(label && label->property("newBlock").toString() == "1");
                }
#endif

                QRect r = item->geometry();

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

#if 0
                //block counter
                if(newBlock)
                {
                    currentBlock++;
                    if(currentBlock == 2)
                    currentBlock=0;
                }

                QRect rr(0,y,this->width(),h);
                //QRect rr(0,y,vLinePos,h);

                //QColor c=bgBrushLeft_.color();
                //int dc=currentBlock*15;
                //c=QColor(c.red()-dc,c.green()-dc,c.blue()-dc);

                int dc=currentBlock*6;
                int hue=188, sat=16, val=255;
                QColor c=QColor::fromHsv(hue,sat,val);
                c.setHsv(hue,sat,val-dc);

                if(currentBlock==0)
                    //c=QColor(252,252,252);
                    //c=QColor(252,255,222);
                    //c=QColor(255,242,222);
                    //c=QColor(252,246,237);
//c=QColor(241,244,252);
                    c=QColor(243,246,253);

                    //c=QColor(238,241,249);
                else
                    //c=QColor(236,236,236);
                    //c=QColor(252,255,195);
                //c=QColor(255,230,191);
                    //c=QColor(255,242,222);


                c=QColor(227,230,243);
                    //c=QColor(241,244,252);

                //painter.fillRect(rr,c);
#endif
                //horizontal line at the top of the row
                painter.drawLine(0, y, this->width(), y);

#if 0
                //block separator
                if(!prevIsHeader && newBlock)
                {
                    painter.setPen(linePenSeparatorLeft_);
                    painter.drawLine(0, y,vLinePos,y);
                    painter.setPen(linePenSeparatorRight_);
                    painter.drawLine(vLinePos, y,this->width(),y);
                    painter.setPen(linePenHorizontal_);
                }
#endif
                lastY = y + h;
            }
        }

#if 0
        if(currentIsHeader)
            currentBlock=0;
#endif
        prevIsHeader = currentIsHeader;
    }

    //The last Horizontal line
    if (!prevIsHeader)
        painter.drawLine(0, lastY - 1, this->width(), lastY - 1);

    //Vertical lines
    if (grid_->rowCount() > 1) {
        if (vLinePos > 0) {
            painter.setPen(linePenVertical_);
            painter.drawLine(vLinePos, 0, vLinePos, lastY);
        }
    }

    //The rest of the bg
    painter.fillRect(QRect(0, lastY, this->width(), this->height() - lastY), bgBrush);

    //Frame
    painter.setPen(QColor(120, 120, 120));
    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();
        MvQProperty* 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();
                    MvQProperty* 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());
                }
            }
        }
    }
}
