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

 Copyright 2013 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 "MvQStationsWidget.h"

#include <cstring>

#include <QDebug>
#include <QTreeView>
#include <QVBoxLayout>

#include "MvQComboLine.h"

#include <math.h>
#include <gdbm.h>

extern "C" {
GDBM_FILE db = nullptr;
}

QList<MvStationData*> MvQStationsModel::data_;

//========================================================
//
// MvQStationsModel
//
//========================================================

MvQStationsModel::MvQStationsModel(QObject* parent) :
    QAbstractItemModel(parent)

{
    loadDb();
}

void MvQStationsModel::loadDb()
{
    if (data_.count() > 0)
        return;

    char mode = 'i';

    char database[1024];
    int blocksize = 0; /* less than 512 forces the system default */
    sprintf(database, "%s-%c.db", getenv("METVIEW_STATIONS"), mode);
    db = gdbm_open(database, blocksize, GDBM_READER, 0777, 0);

    if (db == nullptr) {
        qDebug() << "Cannot open WMO station database";
        return;
    }

    datum key = gdbm_firstkey(db);
    while (key.dptr != nullptr) {
        datum content = gdbm_fetch(db, key);
        auto* sdata   = new MvStationData;
        std::memcpy(sdata, content.dptr, qMin(content.dsize, static_cast<int>(sizeof(MvStationData))));
        data_ << sdata;
        key = gdbm_nextkey(db, key);
    }

    gdbm_close(db);
    db = nullptr;
}


int MvQStationsModel::columnCount(const QModelIndex& /* parent */) const
{
    return 5;
}

int MvQStationsModel::rowCount(const QModelIndex& parent) const
{
    if (!parent.isValid())
        return data_.count();
    else
        return 0;
}


QVariant MvQStationsModel::data(const QModelIndex& index, int role) const
{
    if (!index.isValid() ||
        (role != Qt::DisplayRole)) {
        return QVariant();
    }

    MvStationData* v = data_.at(index.row());

    if (role == Qt::DisplayRole) {
        switch (index.column()) {
            case 0:
                return v->ident;
            case 1:
                return v->pos.lat / 100.;
            case 2:
                return v->pos.lon / 100.;
            case 3:
                return QString(v->pos.flags);
            case 4:
                return QString(v->name);
            default:
                return QString();
        }
    }

    return QVariant();
}


QVariant MvQStationsModel::headerData(const int section, const Qt::Orientation orient, const int role) const
{
    if (orient != Qt::Horizontal)
        return QAbstractItemModel::headerData(section, orient, role);

    if (role == Qt::DisplayRole) {
        switch (section) {
            case 0:
                return QObject::tr("Id");
            case 1:
                return QObject::tr("Lat");
            case 2:
                return QObject::tr("Lon");
            case 3:
                return QObject::tr("S");
            case 4:
                return QObject::tr("Name");
            default:
                return QVariant();
        }
    }
    else if (role == Qt::ToolTipRole) {
        switch (section) {
            case 0:
                return QObject::tr("WMO id");
            case 1:
                return QObject::tr("Latitude");
            case 2:
                return QObject::tr("Longitude");
            case 3:
                return QObject::tr("Sounding type");
            case 4:
                return QObject::tr("Name");
            default:
                return QVariant();
        }
    }

    return QVariant();
}

QModelIndex MvQStationsModel::index(int row, int column, const QModelIndex& /*parent*/) const
{
    if (data_.count() == 0 || column < 0) {
        return QModelIndex();
    }

    return createIndex(row, column, (void*)0);
}

QModelIndex MvQStationsModel::parent(const QModelIndex& /*index */) const
{
    return QModelIndex();
}

//========================================================
//
// MvQStationsFilterModel
//
//========================================================

MvQStationsFilterModel::MvQStationsFilterModel(QObject* parent) :
    QSortFilterProxyModel(parent)
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
    filters_ << FilterDef("Name", NameMode, "", QRegularExpression(".*")) << FilterDef("Id", IdMode, "", QRegularExpression("[0-9]{1,5}")) << FilterDef("Wmo block", WmoMode, "", QRegularExpression("[0-9]{1,2}")) << FilterDef("Position", PositionMode, "Format: lat/lon/tolerance (in degrees)", QRegularExpression("\\-?[0-9]+\\.?[0-9]*/\\-?[0-9]+\\.?[0-9]*/[0-9]+\\.?[0-9]*")) << FilterDef("Area", AreaMode, "Format: N/W/S/E (in degrees)", QRegularExpression("\\-?[0-9]+\\.?[0-9]*/\\-?[0-9]+\\.?[0-9]*/\\-?[0-9]+\\.?[0-9]*/\\-?[0-9]+\\.?[0-9]*"));
#else
    filters_ << FilterDef("Name", NameMode, "", QRegExp(".*")) << FilterDef("Id", IdMode, "", QRegExp("[0-9]{1,5}")) << FilterDef("Wmo block", WmoMode, "", QRegExp("[0-9]{1,2}")) << FilterDef("Position", PositionMode, "Format: lat/lon/tolerance (in degrees)", QRegExp("\\-?[0-9]+\\.?[0-9]*/\\-?[0-9]+\\.?[0-9]*/[0-9]+\\.?[0-9]*")) << FilterDef("Area", AreaMode, "Format: N/W/S/E (in degrees)", QRegExp("\\-?[0-9]+\\.?[0-9]*/\\-?[0-9]+\\.?[0-9]*/\\-?[0-9]+\\.?[0-9]*/\\-?[0-9]+\\.?[0-9]*"));
#endif
    filterMode_ = NameMode;
}


QStringList MvQStationsFilterModel::filterLabels() const
{
    QStringList lst;
    foreach (FilterDef def, filters_)
        lst << def.label;

    return lst;
}

#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
QList<QRegularExpression> MvQStationsFilterModel::filterRegExps() const
{
    QList<QRegularExpression> lst;
#else
QList<QRegExp> MvQStationsFilterModel::filterRegExps() const
{
    QList<QRegExp> lst;
#endif
    foreach (FilterDef def, filters_)
        lst << def.regExp;

    return lst;
}

QStringList MvQStationsFilterModel::filterToolTips() const
{
    QStringList lst;
    foreach (FilterDef def, filters_)
        lst << def.desc;

    return lst;
}

int MvQStationsFilterModel::filterModeIndex() const
{
    for (int i = 0; i < filters_.count(); i++)
        if (filters_.at(i).mode == filterMode_)
            return i;

    return -1;
}

void MvQStationsFilterModel::slotFilterModeChanged(int index)
{
    if (index >= 0 && index < filters_.count()) {
        filterMode_ = filters_.at(index).mode;
        filterText_.clear();
        invalidateFilter();
    }
}

void MvQStationsFilterModel::slotFilterTextChanged(QString txt)
{
    filterText_ = txt;
    invalidateFilter();
}

bool MvQStationsFilterModel::filterAcceptsRow(int sourceRow,
                                              const QModelIndex& sourceParent) const
{
    if (filterText_.isEmpty())
        return true;

    QModelIndex index;
    QStringList lst;

    switch (filterMode_) {
        case NameMode:
            index = sourceModel()->index(sourceRow, 4, sourceParent);
            return sourceModel()->data(index).toString().contains(filterText_, Qt::CaseInsensitive);
            break;
        case IdMode:
            index = sourceModel()->index(sourceRow, 0, sourceParent);
            return sourceModel()->data(index).toString().startsWith(filterText_);
            break;
        case WmoMode:
            index = sourceModel()->index(sourceRow, 0, sourceParent);
            return sourceModel()->data(index).toInt() / 1000 == filterText_.toInt();
            break;
        case PositionMode:
            lst = filterText_.split("/");
            if (lst.count() >= 2 && lst[0] != "-" && lst[1] != "-") {
                float lat = sourceModel()->data(sourceModel()->index(sourceRow, 1, sourceParent)).toFloat();
                float lon = sourceModel()->data(sourceModel()->index(sourceRow, 2, sourceParent)).toFloat();

                float delta = (lst.count() == 3 && lst[2].toFloat() >= 0.) ? lst[2].toFloat() : 1.;
                return fabs(lat - lst[0].toFloat()) < delta && fabs(lon - lst[1].toFloat()) <= delta;
            }
            else
                return true;
            break;
        case AreaMode:
            lst = filterText_.split("/");
            if (lst.count() == 4) {
                float lat = sourceModel()->data(sourceModel()->index(sourceRow, 1, sourceParent)).toFloat();
                float lon = sourceModel()->data(sourceModel()->index(sourceRow, 2, sourceParent)).toFloat();

                return lat <= lst[0].toFloat() && lat >= lst[2].toFloat() &&
                       lon <= lst[3].toFloat() && lon >= lst[1].toFloat();
            }
            else
                return true;
            break;

        default:
            return true;
            break;
    }

    return true;
}


//====================================
//
// MvQStationsWidget
//
//====================================

MvQStationsWidget::MvQStationsWidget(SelectionMode sm, QWidget* parent) :
    QWidget(parent),
    selectionMode_(sm)
{
    auto* vb = new QVBoxLayout(this);
    vb->setSpacing(0);
    vb->setContentsMargins(1, 1, 1, 1);

    tree_ = new QTreeView(this);
    vb->addWidget(tree_);

    model_     = new MvQStationsModel(this);
    sortModel_ = new MvQStationsFilterModel(this);
    sortModel_->setSourceModel(model_);
    sortModel_->setDynamicSortFilter(true);

    tree_->setModel(sortModel_);
    tree_->setSortingEnabled(true);
    tree_->setUniformRowHeights(true);
    tree_->setRootIsDecorated(false);
    tree_->setAlternatingRowColors(true);

    for (int i = 0; i < model_->columnCount() - 1; i++)
        tree_->resizeColumnToContents(i);

    filterCb_ = new MvQComboLine(sortModel_->filterLabels(), this);
    vb->addWidget(filterCb_);

    filterCb_->setRegExps(sortModel_->filterRegExps());
    filterCb_->setToolTips(sortModel_->filterToolTips());
    filterCb_->setCurrentIndex(sortModel_->filterModeIndex());

    connect(tree_, SIGNAL(activated(const QModelIndex&)),
            this, SLOT(slotSelected(const QModelIndex&)));

    connect(filterCb_, SIGNAL(currentIndexChanged(int)),
            sortModel_, SLOT(slotFilterModeChanged(int)));

    connect(filterCb_, SIGNAL(textChanged(QString)),
            sortModel_, SLOT(slotFilterTextChanged(QString)));
}

void MvQStationsWidget::slotSelected(const QModelIndex& index)
{
    QModelIndex index1;

    switch (selectionMode_) {
        case NameMode:
            index1 = sortModel_->index(index.row(), 4);
            emit selected(sortModel_->data(index1).toString());
            break;
        case IdMode:
            index1 = sortModel_->index(index.row(), 0);
            emit selected(sortModel_->data(index1).toString());
            break;
        case WmoMode:
            index1 = sortModel_->index(index.row(), 0);
            emit selected(QString::number(sortModel_->data(index1).toInt() / 100));
            break;
        default:
            break;
    }
}
