/***************************** 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 "MvQOdbModel.h"

#include "MvOdb.h"

MvQOdbVarModel::MvQOdbVarModel() = default;

void MvQOdbVarModel::setBaseData(QList<MvQOdbVar*>& data)
{
    data_ = data;

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


int MvQOdbVarModel::columnCount(const QModelIndex& /* parent */) const
{
    return 2;
}

int MvQOdbVarModel::rowCount(const QModelIndex& parent) const
{
    // Non-root
    if (parent.isValid()) {
        return 0;
    }
    // Root
    else {
        return data_.count();
    }
}


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

    MvQOdbVar* var = data_[index.row()];
    return label(var, index.column());
}


QVariant MvQOdbVarModel::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);

    return section == 0 ? "Name" : section == 1 ? "Value"
                                                : QVariant();
}


QString MvQOdbVarModel::label(MvQOdbVar* var, const int column) const
{
    switch (column) {
        case 0: {
            return var->name();
        }
        case 1: {
            return var->value();
        }

        default:
            return {};
    }
}

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

    return createIndex(row, column, static_cast<void*>(nullptr));
}


QModelIndex MvQOdbVarModel::parent(const QModelIndex&) const
{
    return {};
}

//=========================================
//
// MvQOdbColumnModel
//
//=========================================

MvQOdbColumnModel::MvQOdbColumnModel(bool showTableColumn) :
    showTableColumn_(showTableColumn)
{
    typeMap_[MvQOdbColumn::None] = "none";
    typeMap_[MvQOdbColumn::Int] = "int";
    typeMap_[MvQOdbColumn::Float] = "float";
    typeMap_[MvQOdbColumn::Double] = "double";
    typeMap_[MvQOdbColumn::String] = "string";
    typeMap_[MvQOdbColumn::Bitfield] = "bitfield";
}

void MvQOdbColumnModel::setBaseData(const QList<MvQOdbColumn*>& data, MvQOdbMetaData::OdbVersion version)
{
    beginResetModel();
    data_ = data;
    odbVersion_ = version;
    endResetModel();
}


int MvQOdbColumnModel::columnCount(const QModelIndex& /* parent */) const
{
    if (odbVersion_ == MvQOdbMetaData::Version1) {
        if (showTableColumn_)
            return 3;
        else
            return 2;
    }
    else if (odbVersion_ == MvQOdbMetaData::Version2) {
        if (showTableColumn_)
            return 6;
        else
            return 5;
    }
    else
        return 0;
}

int MvQOdbColumnModel::rowCount(const QModelIndex& parent) const
{
    // Non-root
    if (parent.isValid()) {
        int id = parent.internalId();
        if (idToLevel(id) == 0) {
            return data_.at(id)->bitfieldNum();
        }
        else {
            return 0;
        }
    }
    // Root
    else {
        return data_.count();
    }
}


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

    int id = index.internalId();
    if (idToLevel(id) == 0) {
        MvQOdbColumn* col = data_.at(id);
        return label(col, index.column());
    }
    else if (idToLevel(id) == 1) {
        int parentRow = idToParentRow(id);
        MvQOdbColumn* col = data_.at(parentRow);
        MvQOdbBitfieldMember* bit = col->bitfield().at(index.row());
        return label(bit, index.column());
    }

    return {};
}


QVariant MvQOdbColumnModel::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 (odbVersion_ == MvQOdbMetaData::Version1) {
        switch (section) {
            case 0:
                return tr("Name");
            case 1:
                return tr("Type");
            case 2:
                return (showTableColumn_) ? tr("Table") : QVariant();
            default:
                return {};
        }
    }
    else if (odbVersion_ == MvQOdbMetaData::Version2) {
        switch (section) {
            case 0:
                return tr("Name");
            case 1:
                return tr("Type");
            case 2:
                return tr("Constant");
            case 3:
                return tr("Min");
            case 4:
                return tr("Max");
            // case 5:
            //	return tr("Missing value");
            case 5:
                return (showTableColumn_) ? tr("Table") : QVariant();
            default:
                return {};
        }
    }

    return {};
}


QVariant MvQOdbColumnModel::label(MvQOdbColumn* col, const int column) const
{
    if (odbVersion_ == MvQOdbMetaData::Version1) {
        switch (column) {
            case 0: {
                return col->name();
            }
            case 1: {
                return col->type();
            }
            case 2: {
                return (showTableColumn_) ? col->table() : QString();
            }
            default:
                return QString();
        }
    }

    else if (odbVersion_ == MvQOdbMetaData::Version2) {
        switch (column) {
            case 0: {
                return col->name();
            }
            case 1: {
                return col->type();
            }
            case 2: {
                return (col->isConstant()) ? QString("y") : QString("n");
            }
            case 3: {
                if (col->type() == "float")
                    return QString::number(col->min());
                else if (col->type() == "int")
                    return QString::number(static_cast<int>(col->min()));
                else
                    return QString("N/A");
            }
            case 4: {
                if (col->type() == "float")
                    return QString::number(col->max());
                else if (col->type() == "int")
                    return QString::number(static_cast<int>(col->max()));
                else
                    return QString("N/A");
            }
            // case 5:
            //{
            //	return ((col->type() =="float" || col->type() == "int") && col->hasMissingValue())?QString::number(col->missingValue()):QString("N/A");
            // }
            case 5: {
                return (showTableColumn_) ? col->table() : QString();
            }

            default:
                return QString();
        }
    }

    return QString();
}

QString MvQOdbColumnModel::label(MvQOdbBitfieldMember* bitfield, const int column) const
{
    switch (column) {
        case 0: {
            return bitfield->name();
        }
        case 1: {
            QString s1 = QString("%1").arg(bitfield->pos(), 2, 10, QChar('0'));
            QString str = "Pos: ";
            str += s1;
            str += "  Width: ";
            str += QString::number(bitfield->size());
            str += " bit";
            return str;
        }
        default:
            return {};
    }
}

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

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


QModelIndex MvQOdbColumnModel::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 MvQOdbColumnModel::idToLevel(int id) const
{
    if (id >= 0 && id < 10000)
        return 0;
    else
        return 1;
}

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


//=========================================
//
// MvQOdbDataModel
//
//=========================================

MvQOdbDataModel::MvQOdbDataModel() :
    hasTmpFilter_(false)
{
    data_ = nullptr;
}

void MvQOdbDataModel::dataIsAboutToChange()
{
    beginResetModel();
}

void MvQOdbDataModel::setBaseData(MvAbstractOdb* data)
{
    data_ = data;
    data_->loadAllColumns();

    // sorting
    columnOrder_.clear();
    QMap<QString, int> cOrd;
    for (unsigned int i = 0; i < data_->columns().size(); i++) {
        QString str = QString::fromStdString(data_->columns().at(i)->name());
        cOrd[str] = i;
    }

    QMapIterator<QString, int> it(cOrd);
    while (it.hasNext()) {
        it.next();
        columnOrder_ << it.value();
    }

    hasTmpFilter_ = false;

    // qDebug() << "model size:" << data_.count();
    // Reset the model (views will be notified)
    endResetModel();
}

int MvQOdbDataModel::columnCount(const QModelIndex& /* parent */) const
{
    if (data_ != nullptr)
        return data_->columnNum() + 1;
    else
        return 0;
}

int MvQOdbDataModel::rowCount(const QModelIndex& parent) const
{
    // Non-root
    if (parent.isValid()) {
        return 0;
    }
    // Root

    if (data_ != nullptr) {
        if (data_->chunkSize() == 0) {
            if (hasTmpFilter_) {
                return tmpFilter_.size();
            }
            else if (hasFilter()) {
                return filter_.size();
            }
            else {
                return data_->rowNum();
            }
        }
        else {
            return data_->nbRowsInChunk();
        }
    }
    else
        return 0;
}

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

    if (!index.isValid() || index.column() < 0 || index.column() >= data_->columnNum() + 1) {
        return {};
    }

    if (index.column() == 0) {
        if (data_->chunkSize() == 0)
            return index.row() + 1;
        else
            return data_->chunkSize() * data_->currentChunk() + index.row() + 1;
    }

    int dataColumn = index.column() - 1;
    if (dataColumn >= 0 && dataColumn < columnOrder_.count()) {
        dataColumn = columnOrder_[dataColumn];
    }


    const MvOdbColumn* col = data_->loadColumn(dataColumn);
    if (!col || col->isLoaded() == false)
        return {};


    int dataRow = index.row();
    if (hasTmpFilter_) {
        Q_ASSERT(dataRow <= static_cast<int>(tmpFilter_.size()));
        dataRow = tmpFilter_[dataRow];
    }
    else if (hasFilter()) {
        dataRow = filter_[dataRow];
    }

    if (col->type() == MvOdbColumn::Int) {
        return col->intData().at(dataRow);
    }
    else if (col->type() == MvOdbColumn::Bitfield) {
        return static_cast<unsigned int>(col->floatData().at(dataRow));
    }
    else if (col->type() == MvOdbColumn::Float) {
        return col->floatData().at(dataRow);
    }
    else if (col->type() == MvOdbColumn::Double) {
        return col->doubleData().at(dataRow);
    }
    else if (col->type() == MvOdbColumn::String) {
        return QString::fromStdString(col->stringData().at(dataRow));
    }
    else if (col->type() != MvOdbColumn::None) {
        return QString::number(col->floatData().at(dataRow));
    }
    else {
        return {};
    }
}

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

    if (section < 0 || !data_ || (data_ && section >= static_cast<int>(data_->columns().size()) + 1))
        return {};

    if (section == 0)
        return QString(tr("Row"));


    int dataColumn = section - 1;
    if (dataColumn >= 0 && dataColumn < columnOrder_.count()) {
        dataColumn = columnOrder_[dataColumn];
    }

    QString str = QString::fromStdString(data_->columns().at(dataColumn)->name());

    if (role == Qt::DisplayRole)
        return str.split("@").first();
    else if (role == Qt::ToolTipRole)
        return str;

    return QString();
}

QModelIndex MvQOdbDataModel::index(int row, int column, const QModelIndex& /*parent*/) const
{
    if (data_ == nullptr || data_->rowNum() <= 0 || column < 0) {
        return {};
    }

    return createIndex(row, column, static_cast<void*>(nullptr));
}


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

void MvQOdbDataModel::setFilter(const std::vector<int>& filter)
{
    if (filter.size() == 0 && filter_.size() == 0)
        return;

    if (!data_) {
        filter_ = filter;
        hasTmpFilter_ = false;
        tmpFilter_.clear();
    }

    else if (data_->chunkSize() == 0 && data_->rowNum() >= static_cast<int>(filter.size())) {
        beginResetModel();
        filter_ = filter;
        hasTmpFilter_ = false;
        tmpFilter_.clear();
        endResetModel();
    }
}

void MvQOdbDataModel::setTmpFilter(const std::vector<int>& tmpFilter)
{
    if (filter_.size() == 0)
        return;

    if (!data_) {
        hasTmpFilter_ = true;
        tmpFilter_ = tmpFilter;
    }
    else if (data_->chunkSize() == 0 && data_->rowNum() >= static_cast<int>(tmpFilter_.size())) {
        beginResetModel();
        hasTmpFilter_ = true;
        tmpFilter_ = tmpFilter;
        endResetModel();
    }
}

void MvQOdbDataModel::clearTmpFilter()
{
    if (hasTmpFilter_) {
        if (!data_) {
            hasTmpFilter_ = false;
            tmpFilter_.clear();
        }

        else if (data_->chunkSize() == 0 && data_->rowNum() >= static_cast<int>(filter_.size())) {
            beginResetModel();
            hasTmpFilter_ = false;
            tmpFilter_.clear();
            endResetModel();
        }
    }
}

bool MvQOdbDataModel::filtered() const
{
    return (data_ && data_->chunkSize() == 0 && (filter_.size() > 0 || tmpFilter_.size() > 0));
}

bool MvQOdbDataModel::hasFilter() const
{
    return (data_ && data_->chunkSize() == 0 && filter_.size() > 0);
}
