/***************************** 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 <QDebug>

#include "MvQGribDumpModel.h"

#include "MvQKeyMimeData.h"

#include "GribMetaData.h"
#include "MvKeyProfile.h"

//=====================================
//
//MvQGribMvDumpModel
//
//=====================================

MvQGribMvDumpModel::~MvQGribMvDumpModel()
{
    if (data_)
        delete data_;
}

void MvQGribMvDumpModel::clear()
{
    if (data_) {
        beginResetModel();
        delete data_;
        data_ = nullptr;
        endResetModel();
    }
}


void MvQGribMvDumpModel::setDumpData(GribMvDump* dump)
{
    beginResetModel();

    if (data_)
        delete data_;

    data_ = dump;

    //Reset the model (views will be notified)
    endResetModel();
}

void MvQGribMvDumpModel::setGribNameSpace(QString ns)
{
    beginResetModel();

    gribNameSpace_ = ns;

    //Reset the model (views will be notified)
    endResetModel();
}

void MvQGribMvDumpModel::setFilter(QString txt)
{
    beginResetModel();

    filter_ = txt;

    //Reset the model (views will be notified)
    endResetModel();
}

int MvQGribMvDumpModel::columnCount(const QModelIndex& /* parent */) const
{
    return 4;
}

int MvQGribMvDumpModel::rowCount(const QModelIndex& parent) const
{
    if (!data_)
        return 0;

    //Non-root
    if (!parent.isValid()) {
        return data_->itemNum();
    }
    return 0;
}

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

    if (role == Qt::UserRole) {
        if (index.column() != 0)
            return QVariant();

        GribItem* item = data_->item().at(index.row());
        QString name   = QString::fromStdString(item->name());
        if (gribNameSpace_ == "Default") {
            if (!filter_.isEmpty() && !name.contains(filter_, Qt::CaseInsensitive))
                return QString::number(0);

            if (name.contains(".") == false) {
                return QString::number(1);
            }
            else {
                return QString::number(0);
            }
        }
        else {
            if (name.startsWith(gribNameSpace_ + ".")) {
                return QString::number(1);
            }
            else {
                return QString::number(0);
            }
        }
    }
    else {
        GribItem* item = data_->item().at(index.row());
        return label(item, index.column());
    }
}


QVariant MvQGribMvDumpModel::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 KeyColum:
                return "Key";
            case TypeColumn:
                return "Type";
            case ValueColumn:
                return "Native value";
            case StrValueColumn:
                return "String value";
            default:
                return QVariant();
        }
    }
    else if (role == Qt::ToolTipRole) {
        switch (section) {
            case KeyColum:
                return "ecCodes key";
            case TypeColumn:
                return "Key type";
            case ValueColumn:
                return "Native value";
            case StrValueColumn:
                return "Value accessed as string";
            default:
                return QVariant();
        }
    }

    return QVariant();
}


QString MvQGribMvDumpModel::label(GribItem* item, const int column) const
{
    switch (column) {
        case KeyColum: {
            return QString::fromStdString(item->name());
        }
        case TypeColumn: {
            return QString::fromStdString(item->type());
        }
        case ValueColumn: {
            auto v = item->value();
            if (v.type() == MvVariant::LongType) {
                return QString::number(v.toLong());
            } else if (v.type() == MvVariant::DoubleType) {
                return QString::number(v.toDouble());
            }
            return QString::fromStdString(v.toString());
        }
        case StrValueColumn: {
            return QString::fromStdString(item->strValue());
        }
        default:
            return QString();
    }
}

QModelIndex MvQGribMvDumpModel::index(int row, int column, const QModelIndex& /*parent*/) const
{
    return createIndex(row, column, (void*)0);
}

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

Qt::ItemFlags MvQGribMvDumpModel::flags(const QModelIndex& /*index*/) const
{
    Qt::ItemFlags defaultFlags;

    defaultFlags = Qt::ItemIsEnabled |
                   Qt::ItemIsSelectable;

    return Qt::ItemIsDragEnabled | defaultFlags;
}

Qt::DropActions MvQGribMvDumpModel::supportedDropActions() const
{
    return Qt::CopyAction;
}

QStringList MvQGribMvDumpModel::mimeTypes() const
{
    QStringList types = {"metview/mvkey"};
    return types;
}

QMimeData* MvQGribMvDumpModel::mimeData(const QModelIndexList& indexes) const
{
    auto* mimeData = new MvQKeyMimeData(this);

    QList<int> procRows;

    foreach (QModelIndex index, indexes) {
        if (index.isValid() &&
            procRows.indexOf(index.row()) == -1) {
            GribItem* item = data_->item().at(index.row());
            auto* key      = new MvKey(item->name(), item->name());
            mimeData->addKey(key, index.row());
            procRows << index.row();
        }
    }

    return mimeData;
}

bool MvQGribMvDumpModel::dropMimeData(const QMimeData* /*data*/,
                                      Qt::DropAction /*action*/, int /*row*/, int /*column*/, const QModelIndex& /*parent*/)
{
    return false;
}

//=====================================
//
// MvQGribWmoDumpModel
//
//=====================================

MvQGribWmoDumpModel::~MvQGribWmoDumpModel()
{
    if (data_)
        delete data_;
}

void MvQGribWmoDumpModel::clear()
{
    if (data_) {
        beginResetModel();
        delete data_;
        data_ = nullptr;
        endResetModel();
    }
}

void MvQGribWmoDumpModel::setDumpData(GribWmoDump* dump)
{
    beginResetModel();

    if (data_)
        delete data_;

    data_ = dump;

    //Reset the model (views will be notified)
    endResetModel();
}

int MvQGribWmoDumpModel::columnCount(const QModelIndex& /* parent */) const
{
    return 3;
}

int MvQGribWmoDumpModel::rowCount(const QModelIndex& parent) const
{
    if (!hasData())
        return 0;

    //Non-root
    if (parent.isValid()) {
        int id = parent.internalId();
        if (idToLevel(id) == 0) {
            return data_->section().at(id)->itemNum();
        }
        if (idToLevel(id) == 1) {
            int parentRow = idToParentRow(id);
            int row       = parent.row();
            int num       = data_->section().at(parentRow)->item().at(row)->arrayData().size();
            return num;
        }

        else {
            return 0;
        }
    }
    //Root
    else {
        return data_->sectionNum();
    }
}


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

    int id = index.internalId();

    int level = idToLevel(id);

    if (level == 0) {
        GribSection* section = data_->section().at(id);
        return label(section, index.column());
    }
    else if (level == 1) {
        int parentRow        = idToParentRow(id);
        GribSection* section = data_->section().at(parentRow);
        GribItem* item       = section->item().at(index.row());
        return label(item, index.column());
    }
    else if (level == 2) {
        //int parentId=index.parent().internalId();
        //int grandParentRow=idToParentRow(parentId);

        int grandParentRow   = idToGrandParentRow(id);
        GribSection* section = data_->section().at(grandParentRow);

        int parentRow  = idToParentRow(id);
        GribItem* item = section->item().at(parentRow);

        QString value(item->arrayData().at(index.row()).c_str());

        return label(value, index.column());
    }

    return QVariant();
}


QVariant MvQGribWmoDumpModel::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 PositionColum:
                return "Position";
            case KeyColumn:
                return "Key";
            case ValueColumn:
                return "Value";
            default:
                return QVariant();
        }
    }
    else if (role == Qt::ToolTipRole) {
        switch (section) {
            case PositionColum:
                return "Position within section";
            case KeyColumn:
                return "ecCodes key";
            case ValueColumn:
                return "Key value";
            default:
                return QVariant();
        }
    }

    return QVariant();
}


QString MvQGribWmoDumpModel::label(GribSection* section, const int column) const
{
    return column == PositionColum ? QString(section->name().c_str()) : QString();
}

QString MvQGribWmoDumpModel::label(GribItem* item, const int column) const
{
    switch (column) {
        case PositionColum: {
            return QString(item->pos().c_str());
        }
        case KeyColumn: {
            return QString(item->name().c_str());
        }
        case ValueColumn: {
            auto v = item->value();
            if (v.type() == MvVariant::LongType) {
                return QString::number(v.toLong());
            } else if (v.type() == MvVariant::DoubleType) {
                return QString::number(v.toDouble());
            }
            return QString::fromStdString(v.toString());
        }
        default:
            return QString();
    }
}

QString MvQGribWmoDumpModel::label(QString value, const int column) const
{
    return column == ValueColumn ? value : QString();
}

QModelIndex MvQGribWmoDumpModel::index(int row, int column, const QModelIndex& parent) const
{
    if (!data_ || row < 0 || column < 0 || parent.column() > 3) {
        return QModelIndex();
    }

    //Parent is non-root -> level-1 items: id is the parent row (number+1)*1000
    if (parent.isValid()) {
        int id;
        int parentId    = parent.internalId();
        int parentLevel = idToLevel(parentId);

        if (parentLevel == 0) {
            id = (parent.row() + 1) * 1000;
        }
        else if (parentLevel == 1) {
            id = parentId + (parent.row() + 1);
        }
        else {
            return QModelIndex();
        }

        return createIndex(row, column, id);
    }
    //Parent is root -> level-0 items: id is the row number
    else {
        return createIndex(row, column, row);
    }
}

QModelIndex MvQGribWmoDumpModel::parent(const QModelIndex& index) const
{
    if (!index.isValid()) {
        return QModelIndex();
    }

    int id = index.internalId();
    if (idToLevel(id) == 0) {
        return QModelIndex();
    }
    else if (idToLevel(id) == 1) {
        int parentRow = idToParentRow(id);
        return createIndex(parentRow, 0, parentRow);
    }
    else if (idToLevel(id) == 2) {
        int parentRow      = idToParentRow(id);
        int grandParentRow = idToGrandParentRow(id);
        return createIndex(parentRow, 0, (grandParentRow + 1) * 1000);
    }

    return QModelIndex();
}

int MvQGribWmoDumpModel::idToLevel(int id) const
{
    if (id >= 0 && id < 1000)
        return 0;
    else if (id % 1000 == 0)
        return 1;
    else
        return 2;
}

int MvQGribWmoDumpModel::idToParentRow(int id) const
{
    int level = idToLevel(id);

    if (level == 0)
        return -1;
    else if (level == 1)
        return id / 1000 - 1;
    else if (level == 2)
        return id - (id / 1000) * 1000 - 1;

    return -1;
}

int MvQGribWmoDumpModel::idToGrandParentRow(int id) const
{
    int level = idToLevel(id);

    if (level == 0)
        return -1;
    else if (level == 1)
        return -1;
    else if (level == 2)
        return id / 1000 - 1;

    return -1;
}

Qt::ItemFlags MvQGribWmoDumpModel::flags(const QModelIndex& /*index*/) const
{
    Qt::ItemFlags defaultFlags;

    defaultFlags = Qt::ItemIsEnabled |
                   Qt::ItemIsSelectable;

    return Qt::ItemIsDragEnabled | defaultFlags;
}

Qt::DropActions MvQGribWmoDumpModel::supportedDropActions() const
{
    return Qt::CopyAction;
}

QStringList MvQGribWmoDumpModel::mimeTypes() const
{
    QStringList types = {"metview/mvkey"};
    return types;
}

QMimeData* MvQGribWmoDumpModel::mimeData(const QModelIndexList& indexes) const
{
    auto* mimeData = new MvQKeyMimeData(this);

    QList<int> procRows;

    foreach (QModelIndex index, indexes) {
        int id = index.internalId();
        if (index.isValid() && idToLevel(id) == 1 &&
            procRows.indexOf(index.row()) == -1) {
            //Index to the second column!!!
            QModelIndex col = createIndex(index.row(), 1, id);

            std::string keyName = data(col).toString().toStdString();
            auto* key           = new MvKey(keyName, keyName);
            mimeData->addKey(key, index.row());
            procRows << index.row();
        }
    }

    return mimeData;
}

bool MvQGribWmoDumpModel::dropMimeData(const QMimeData* /*data*/,
                                       Qt::DropAction /*action*/, int /*row*/, int /*column*/, const QModelIndex& /*parent*/)
{
    return false;
}

//=====================================
//
// MvQGribStdDumpModel
//
//=====================================


MvQGribStdDumpModel::~MvQGribStdDumpModel()
{
    if (data_)
        delete data_;
}

void MvQGribStdDumpModel::clear()
{
    if (data_) {
        beginResetModel();
        delete data_;
        data_ = nullptr;
        endResetModel();
    }
}

void MvQGribStdDumpModel::setDumpData(GribStdDump* dump)
{
    beginResetModel();

    if (data_)
        delete data_;

    data_ = dump;

    endResetModel();
}

int MvQGribStdDumpModel::columnCount(const QModelIndex& /* parent */) const
{
    return 3;
}

int MvQGribStdDumpModel::rowCount(const QModelIndex& parent) const
{
    if (!hasData())
        return 0;

    //Non-root
    if (parent.isValid()) {
        int id = parent.internalId();
        if (idToLevel(id) == 0) {
            return data_->item().at(id)->arrayData().size();
        }
        else {
            return 0;
        }
    }
    //Root
    else {
        return data_->itemNum();
    }
}


QVariant MvQGribStdDumpModel::data(const QModelIndex& index, int role) const
{
    if (!hasData())
        return QVariant();

    if (!index.isValid() || role != Qt::DisplayRole) {
        return QVariant();
    }

    int id = index.internalId();
    if (idToLevel(id) == 0) {
        GribItem* item = data_->item().at(id);
        return label(item, index.column());
    }
    else if (idToLevel(id) == 1) {
        int parentRow     = idToParentRow(id);
        GribItem* item    = data_->item().at(parentRow);
        std::string value = item->arrayData().at(index.row());
        return label(value, index.column());
    }

    return QVariant();
}


QVariant MvQGribStdDumpModel::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 KeyColum:
                return "Key";
            case ValueColumn:
                return "Value";
            case DescColumn:
                return "Description";
            default:
                return QVariant();
        }
    }
    else if (role == Qt::ToolTipRole) {
        switch (section) {
            case KeyColum:
                return "ecCodes Key";
            case ValueColumn:
                return "Key value";
            case DescColumn:
                return "Key description";
            default:
                return QVariant();
        }
    }
    return QVariant();
}


QString MvQGribStdDumpModel::label(GribItem* item, const int column) const
{
    switch (column) {
        case KeyColum: {
            return QString(item->name().c_str());
        }
        case ValueColumn: {
            auto v = item->value();
            if (v.type() == MvVariant::LongType) {
                return QString::number(v.toLong());
            } else if (v.type() == MvVariant::DoubleType) {
                return QString::number(v.toDouble());
            }
            return QString::fromStdString(v.toString());
        }
        case DescColumn: {
            return QString(item->description().c_str());
        }

        default:
            return QString();
    }
}

QString MvQGribStdDumpModel::label(std::string value, const int column) const
{
    return column == KeyColum ? QString(value.c_str()) : QString();
}

QModelIndex MvQGribStdDumpModel::index(int row, int column, const QModelIndex& parent) const
{
    if (!data_ || row < 0 || column < 0 || parent.column() > 3) {
        return QModelIndex();
    }

    //Parent is non-root -> level-1 items: id is the (parent row number +1)*1000
    if (parent.isValid()) {
        int id = (parent.row() + 1) * 1000;
        return createIndex(row, column, id);
    }
    //Parent is root -> level-0 items: id is the row number
    else {
        return createIndex(row, column, row);
    }
}

QModelIndex MvQGribStdDumpModel::parent(const QModelIndex& index) const
{
    if (!index.isValid()) {
        return QModelIndex();
    }

    int id = index.internalId();
    if (idToLevel(id) == 0) {
        return QModelIndex();
    }
    else {
        int parentRow = idToParentRow(id);
        return createIndex(parentRow, 0, parentRow);
    }

    return QModelIndex();
}

int MvQGribStdDumpModel::idToLevel(int id) const
{
    if (id >= 0 && id < 1000)
        return 0;
    else
        return 1;
}

int MvQGribStdDumpModel::idToParentRow(int id) const
{
    if (idToLevel(id) == 0)
        return -1;
    else
        return id / 1000 - 1;
}


Qt::ItemFlags MvQGribStdDumpModel::flags(const QModelIndex& /*index*/) const
{
    Qt::ItemFlags defaultFlags;

    defaultFlags = Qt::ItemIsEnabled |
                   Qt::ItemIsSelectable;

    return Qt::ItemIsDragEnabled | defaultFlags;
}

Qt::DropActions MvQGribStdDumpModel::supportedDropActions() const
{
    return Qt::CopyAction;
}

QStringList MvQGribStdDumpModel::mimeTypes() const
{
    QStringList types;
    types << "metview/mvkey";
    return types;
}

QMimeData* MvQGribStdDumpModel::mimeData(const QModelIndexList& indexes) const
{
    auto* mimeData = new MvQKeyMimeData(this);

    QList<int> procRows;

    foreach (QModelIndex index, indexes) {
        int id = index.internalId();
        if (index.isValid() && idToLevel(id) == 0 &&
            procRows.indexOf(index.row()) == -1) {
            std::string keyName = data(index).toString().toStdString();
            auto* key           = new MvKey(keyName, keyName);
            mimeData->addKey(key, index.row());
            procRows << index.row();
        }
    }

    return mimeData;
}

bool MvQGribStdDumpModel::dropMimeData(const QMimeData* /*data*/,
                                       Qt::DropAction /*action*/, int /*row*/, int /*column*/, const QModelIndex& /*parent*/)
{
    return false;
}


//=====================================
//
//MvQGribValueDumpModel
//
//=====================================

MvQGribValueDumpModel::~MvQGribValueDumpModel()
{
    if (data_)
        delete data_;
}

void MvQGribValueDumpModel::clear()
{
    if (data_) {
        beginResetModel();
        delete data_;
        data_ = nullptr;
        endResetModel();
    }
}

void MvQGribValueDumpModel::setDumpData(GribValueDump* dump)
{
    beginResetModel();

    if (data_)
        delete data_;

    data_ = dump;

    if (data_) {
        if (data_->decimalPlaces() > 0) {
            minValAsDecimal_ = 0.0001;
        }
        else {
            minValAsDecimal_ = pow(10., data_->decimalPlaces() - 4);
        }
    }

    //Reset the model (views will be notified)
    endResetModel();
}

int MvQGribValueDumpModel::columnCount(const QModelIndex& /* parent */) const
{
    return 4;
}

int MvQGribValueDumpModel::rowCount(const QModelIndex& parent) const
{
    if (!data_)
        return 0;

    //Non-root
    if (!parent.isValid()) {
        return data_->num();
    }
    else {
        return 0;
    }
}


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

    if (role == Qt::DisplayRole) {
        switch (index.column()) {
            case 0:
                return index.row() + 1;
            case 1:
                return QString::number(data_->latitude()[index.row()], 'f', 3);
            case 2:
                return QString::number(data_->longitude()[index.row()], 'f', 3);
            case 3:
                if (fabs(data_->value()[index.row()]) > minValAsDecimal_) {
                    return QString::number(data_->value()[index.row()], 'f', data_->decimalPlaces() + 4);
                }
                else {
                    return QString::number(data_->value()[index.row()], 'e', 4);
                }
                break;
            default:
                return QVariant();
        }
    }

    //Will be used for sorting
    else if (role == Qt::UserRole) {
        switch (index.column()) {
            case 0:
                return index.row() + 1;
            case 1:
                return data_->latitude()[index.row()];
            case 2:
                return data_->longitude()[index.row()];
            case 3:
                return data_->value()[index.row()];
            default:
                return QVariant();
        }
    }


    return QVariant();
}


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

    if (role == Qt::DisplayRole) {
        switch (section) {
            case 0:
                return tr("Index");
            case 1:
                return tr("Latitude");
            case 2:
                return tr("Longitude");
            case 3:
                return tr("Value");
            default:
                return QVariant();
        }
    }
    return QVariant();
}

QModelIndex MvQGribValueDumpModel::index(int row, int column, const QModelIndex& /*parent*/) const
{
    return createIndex(row, column, (void*)0);
}


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