/*
 * Copyright 2009- ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation
 * nor does it submit to any jurisdiction.
 */

#include "NodeQueryResultModel.hpp"

#include <QDebug>

#include "ModelColumn.hpp"
#include "NodeQueryResult.hpp"
#include "ServerHandler.hpp"
#include "VAttribute.hpp"
#include "VAttributeType.hpp"
#include "VNode.hpp"

NodeQueryResultModel::NodeQueryResultModel(QObject* parent) : QAbstractItemModel(parent) {
    columns_ = ModelColumn::def("query_columns");
    Q_ASSERT(columns_);

    // Check the mapping between the enum and column ids
    Q_ASSERT(columns_->id(ServerColumn) == "server");
    Q_ASSERT(columns_->id(PathColumn) == "path");
    Q_ASSERT(columns_->id(StatusColumn) == "status");
    Q_ASSERT(columns_->id(TypeColumn) == "type");
    Q_ASSERT(columns_->id(StatusChangeColumn) == "statusChange");
    Q_ASSERT(columns_->id(AttributeColumn) == "attribute");

    data_ = new NodeQueryResult(this);

    connect(data_, SIGNAL(beginAppendRow()), this, SLOT(slotBeginAppendRow()));

    connect(data_, SIGNAL(endAppendRow()), this, SLOT(slotEndAppendRow()));

    connect(data_, SIGNAL(beginAppendRows(int)), this, SLOT(slotBeginAppendRows(int)));

    connect(data_, SIGNAL(endAppendRows(int)), this, SLOT(slotEndAppendRows(int)));

    connect(data_, SIGNAL(beginRemoveRow(int)), this, SLOT(slotBeginRemoveRow(int)));

    connect(data_, SIGNAL(endRemoveRow(int)), this, SLOT(slotEndRemoveRow(int)));

    connect(data_, SIGNAL(beginRemoveRows(int, int)), this, SLOT(slotBeginRemoveRows(int, int)));

    connect(data_, SIGNAL(endRemoveRows(int, int)), this, SLOT(slotEndRemoveRows(int, int)));

    connect(data_, SIGNAL(beginReset()), this, SLOT(slotBeginReset()));

    connect(data_, SIGNAL(endReset()), this, SLOT(slotEndReset()));

    connect(data_, SIGNAL(stateChanged(const VNode*, int, int)), this, SLOT(slotStateChanged(const VNode*, int, int)));
}

NodeQueryResultModel::~NodeQueryResultModel() = default;

void NodeQueryResultModel::clearData() {
    data_->clear();
}

bool NodeQueryResultModel::hasData() const {
    return data_->size() > 0;
}

int NodeQueryResultModel::columnCount(const QModelIndex& /*parent */) const {
    return columns_->count();
}

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

    // Parent is the root:
    if (!parent.isValid()) {
        return data_->size();
    }

    return 0;
}

Qt::ItemFlags NodeQueryResultModel::flags(const QModelIndex& /*index*/) const {
    return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

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

    int row = index.row();
    if (row < 0 || row >= data_->size())
        return {};

    auto id                = static_cast<ColumnType>(index.column());
    NodeQueryResultItem* d = data_->itemAt(row);

    if (role == Qt::DisplayRole) {
        if (id == PathColumn)
            return d->pathStr();
        else if (id == ServerColumn)
            return d->serverStr();
        else if (id == TypeColumn)
            return d->typeStr();
        else if (id == StatusColumn)
            return d->stateStr();
        else if (id == StatusChangeColumn)
            return d->stateChangeTimeAsString();
        else if (id == AttributeColumn)
            return d->attr();

        return {};
    }
    else if (role == Qt::BackgroundRole) {
        if (id == StatusColumn)
            return d->stateColour();

        return {};
    }
    else if (role == Qt::TextAlignmentRole) {
        if (id == StatusColumn || id == TypeColumn || id == StatusChangeColumn)
            return Qt::AlignCenter;

        return {};
    }

    else if (role == SortRole) {
        if (id == PathColumn)
            return d->pathStr();
        else if (id == ServerColumn)
            return d->serverStr();
        else if (id == TypeColumn)
            return d->typeStr();
        else if (id == StatusColumn)
            return d->stateStr();
        else if (id == StatusChangeColumn)
            return d->stateChangeTime();
        else if (id == AttributeColumn)
            return d->attr();

        return {};
    }

    return {};
}

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

    auto id = static_cast<ColumnType>(section);

    if (role == Qt::DisplayRole)
        return columns_->label(section);
    else if (role == Qt::UserRole)
        return columns_->id(section);
    else if (role == Qt::ToolTipRole)
        return columns_->tooltip(section);
    else if (role == Qt::TextAlignmentRole) {
        if (id == StatusColumn || id == TypeColumn || id == StatusChangeColumn)
            return Qt::AlignCenter;
    }

    return {};
}

QModelIndex NodeQueryResultModel::index(int row, int column, const QModelIndex& parent) const {
    if (!hasData() || row < 0 || column < 0) {
        return {};
    }

    // When parent is the root this index refers to a node or server
    if (!parent.isValid()) {
        return createIndex(row, column);
    }

    return {};
}

QModelIndex NodeQueryResultModel::parent(const QModelIndex& /*child*/) const {
    return {};
}

VInfo_ptr NodeQueryResultModel::nodeInfo(const QModelIndex& index) {
    // For invalid index no info is created.
    if (!index.isValid()) {
        VInfo_ptr res;
        return res;
    }

    if (index.row() >= 0 && index.row() <= data_->size()) {
        NodeQueryResultItem* d = data_->itemAt(index.row());

        if (ServerHandler* s = d->server_) {
            if (d->node_) {
                // server
                if (d->node_->isServer()) {
                    return VInfoServer::create(s);
                }
                // node
                else if (!d->hasAttribute()) {
                    return VInfoNode::create(d->node_);
                }
                // attribute
                else {
                    if (VAttribute* a = d->node_->findAttribute(d->attr_))
                        return VInfoAttribute::create(a);
                    else
                        return VInfoNode::create(d->node_);
                }
            }
        }
    }

    VInfo_ptr res;
    return res;
}

QModelIndex NodeQueryResultModel::infoToIndex(VInfo_ptr /*info*/) {
    /*if(info && info.get())
    {
            if(info->isServer())
            {
                    if(ServerHandler *s=info->server())
                    {
                            return serverToIndex(s);
                    }
            }
            else if(VNode* n=info->node())
            {
                            return nodeToIndex(n);
            }
    }*/

    return {};
}

void NodeQueryResultModel::slotBeginAppendRow() {
    int num = data_->size();
    Q_EMIT beginInsertRows(QModelIndex(), num, num);

    Q_EMIT endInsertRows();
}

void NodeQueryResultModel::slotEndAppendRow() {
    Q_EMIT endInsertRows();
}

void NodeQueryResultModel::slotBeginAppendRows(int n) {
    if (n <= 0)
        return;

    int num = data_->size();
    Q_EMIT beginInsertRows(QModelIndex(), num, num + n - 1);
}

void NodeQueryResultModel::slotEndAppendRows(int n) {
    if (n <= 0)
        return;
    Q_EMIT endInsertRows();
}

void NodeQueryResultModel::slotBeginRemoveRow(int row) {
    beginRemoveRows(QModelIndex(), row, row);
}

void NodeQueryResultModel::slotEndRemoveRow(int /*row*/) {
    endRemoveRows();
}

void NodeQueryResultModel::slotBeginRemoveRows(int rowStart, int rowEnd) {
    beginRemoveRows(QModelIndex(), rowStart, rowEnd);
}

void NodeQueryResultModel::slotEndRemoveRows(int, int) {
    endRemoveRows();
}

void NodeQueryResultModel::slotBeginReset() {
    beginResetModel();
}

void NodeQueryResultModel::slotEndReset() {
    endResetModel();
}

void NodeQueryResultModel::slotStateChanged(const VNode*, int pos, int cnt) {
    int col = columns_->indexOf("status");

    if (col != -1) {
        QModelIndex fromIdx = index(pos, col);
        QModelIndex toIdx   = index(pos + cnt - 1, col);

        Q_EMIT dataChanged(fromIdx, toIdx);
    }
}
