/***************************** 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 {};
    }

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

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

    return {};
}


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

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

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

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

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

    return {};
}


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 {item->pos().c_str()};
        }
        case KeyColumn: {
            return {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 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 {};
    }

    // Parent is non-root -> level-1 items: id is the parent row (number+1)*1000
    if (parent.isValid()) {
        int id = 0;
        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 {};
        }

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

    int id = index.internalId();
    if (idToLevel(id) == 0) {
        return {};
    }
    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 {};
}

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

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

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


QString MvQGribStdDumpModel::label(GribItem* item, const int column) const
{
    switch (column) {
        case KeyColum: {
            return {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 {item->description().c_str()};
        }

        default:
            return {};
    }
}

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

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

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

    return {};
}

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

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

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


    return {};
}


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

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


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