/*
 * 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 "NodeQueryResult.hpp"

#include <QDebug>

#include "ServerHandler.hpp"
#include "VNode.hpp"

//========================================================
//
//  NodeQueryResultItem
//
//========================================================

NodeQueryResultItem::NodeQueryResultItem(VNode* node) : node_(node) {
    if (node_)
        server_ = node_->server();
}

NodeQueryResultItem::NodeQueryResultItem(NodeQueryResultTmp_ptr d) : node_(d->node_), attr_(d->attr_) {
    if (node_)
        server_ = node_->server();
}

void NodeQueryResultItem::invalidateNode() {
    assert(node_);
    path_ = node_->absNodePath();
    node_ = nullptr;
}

bool NodeQueryResultItem::updateNode() {
    if (node_)
        return (node_->server() != nullptr);

    else {
        node_ = server_->vRoot()->find(path_);
        path_.clear();
        return (node_ != nullptr);
    }

    return false;
}

QString NodeQueryResultItem::serverStr() const {
    return (server_) ? QString::fromStdString(server_->name()) : QString();
}

QString NodeQueryResultItem::pathStr() const {
    if (node_)
        return QString::fromStdString(node_->absNodePath());

    return QString::fromStdString(path_);
}

QString NodeQueryResultItem::typeStr() const {
    if (node_)
        return QString::fromStdString(node_->nodeType());

    return {"???"};
}

QString NodeQueryResultItem::stateStr() const {
    if (node_)
        return node_->stateName();
    return {"???"};
}

QColor NodeQueryResultItem::stateColour() const {
    if (node_)
        return node_->stateColour();
    return {Qt::transparent};
}

QString NodeQueryResultItem::stateChangeTimeAsString() const {
    QString s;
    if (node_)
        node_->statusChangeTime(s);

    return s;
}

unsigned int NodeQueryResultItem::stateChangeTime() const {
    if (node_)
        return node_->statusChangeTime();

    return 0;
}

QStringList NodeQueryResultItem::attr() const {
    return attr_;
}

void NodeQueryResultBlock::add(VNode* node, int pos) {
    if (pos_ == -1)
        pos_ = pos;

    cnt_++;

    if (nodes_[node].pos_ == -1) {
        nodes_[node].pos_ = pos;
    }
    nodes_[node].cnt_++;
}

void NodeQueryResultBlock::clear() {
    pos_ = -1;
    cnt_ = 0;
    nodes_.clear();
}

bool NodeQueryResultBlock::find(const VNode* nc, int& pos, int& cnt) {
    QHash<VNode*, Pos>::const_iterator it = nodes_.find(const_cast<VNode*>(nc));
    if (it != nodes_.end()) {
        pos = it.value().pos_;
        cnt = it.value().cnt_;
        return true;
    }

    return false;
}

//========================================================
//
//  NodeQueryResult
//
//========================================================

NodeQueryResult::NodeQueryResult(QObject* parent) : QObject(parent) {
}

NodeQueryResult::~NodeQueryResult() {
    clear();
}

NodeQueryResultItem* NodeQueryResult::itemAt(int i) {
    if (i >= 0 && i < static_cast<int>(data_.size()))
        return data_.at(i);

    return nullptr;
}

void NodeQueryResult::add(NodeQueryResultTmp_ptr item) {
    ServerHandler* s = item->node_->server();
    if (!s)
        return;

    attach(s);

    Q_EMIT beginAppendRow();

    data_.push_back(new NodeQueryResultItem(item));
    blocks_[s].add(item->node_, data_.size() - 1);

    Q_EMIT endAppendRow();
}

void NodeQueryResult::add(QList<NodeQueryResultTmp_ptr> items) {
    if (items.count() == 0)
        return;

    Q_EMIT beginAppendRows(items.count());

    for (int i = 0; i < items.count(); i++) {
        VNode* node      = items[i]->node_;
        ServerHandler* s = node->server();
        attach(s);
        data_.push_back(new NodeQueryResultItem(items[i]));
        blocks_[s].add(node, data_.size() - 1);
    }

    Q_EMIT endAppendRows(items.count());
}

void NodeQueryResult::add(std::vector<VInfo_ptr> items) {
    if (items.size() == 0)
        return;

    Q_EMIT beginAppendRows(items.size());

    for (auto& item : items) {
        if (item->isServer() || item->isNode()) {
            VNode* node      = item->node();
            ServerHandler* s = item->server();
            attach(s);
            data_.push_back(new NodeQueryResultItem(node));
            blocks_[s].add(node, data_.size() - 1);
        }
    }

    Q_EMIT endAppendRows(items.size());
}
void NodeQueryResult::clear() {
    Q_EMIT beginReset();

    for (auto it = blocks_.begin(); it != blocks_.end(); ++it) {
        it->first->removeServerObserver(this);
        it->first->removeNodeObserver(this);
    }
    blocks_.clear();

    for (auto it = data_.begin(); it != data_.end(); ++it) {
        delete *it;
    }

    data_.clear();

    Q_EMIT endReset();
}

void NodeQueryResult::clear(ServerHandler* server) {
    std::vector<NodeQueryResultItem*> prev = data_;
    data_.clear();

    // Adjust the servers
    detach(server);

    for (auto& block : blocks_) {
        block.second.clear();
    }

    // Adjust data
    for (auto it = prev.begin(); it != prev.end(); ++it) {
        ServerHandler* s = (*it)->node_->server();
        if (s == server) {
            delete *it;
        }
        else {
            data_.push_back(*it);
            blocks_[s].add((*it)->node_, data_.size() - 1);
        }
    }
}

void NodeQueryResult::serverClear(ServerHandler* server) {
    for (auto it = data_.begin(); it != data_.end(); ++it) {
        if (server == (*it)->server_) {
            (*it)->invalidateNode();
        }
    }
}

void NodeQueryResult::serverScan(ServerHandler* server) {
    std::vector<NodeQueryResultItem*> prev = data_;
    data_.clear();

    for (auto& block : blocks_) {
        block.second.clear();
    }

    for (auto it = prev.begin(); it != prev.end(); ++it) {
        if (server == (*it)->server_) {
            if ((*it)->updateNode()) {
                data_.push_back(*it);
                blocks_[server].add((*it)->node_, data_.size() - 1);
            }
            else
                delete *it;
        }
        else {
            data_.push_back(*it);
            blocks_[server].add((*it)->node_, data_.size() - 1);
        }
    }

    if (blocks_[server].cnt_ == 0)
        detach(server);
}

void NodeQueryResult::attach(ServerHandler* s) {
    if (blocks_.find(s) == blocks_.end()) {
        s->addServerObserver(this);
        s->addNodeObserver(this);
    }
}

void NodeQueryResult::detach(ServerHandler* s) {
    auto it = blocks_.find(s);
    if (it != blocks_.end()) {
        blocks_.erase(it);

        s->removeServerObserver(this);
        s->removeNodeObserver(this);
    }
}

/*void NodeQueryResult::detach(VNode *node)
{
        std::map<ServerHandler*,int>::iterator it=serverCnt_.find(node->server());
        if(it != serverCnt_.end())
        {
                it->second--;
                if(it->second == 0)
                {
                        detach(node->server());
                }
        }
}*/

void NodeQueryResult::notifyServerDelete(ServerHandler* server) {
    Q_EMIT beginReset();
    clear(server);
    Q_EMIT endReset();
}

void NodeQueryResult::notifyBeginServerClear(ServerHandler* server) {
    serverClear(server);
}

void NodeQueryResult::notifyEndServerClear(ServerHandler*) {
}

void NodeQueryResult::notifyBeginServerScan(ServerHandler*, const VServerChange&) {
    Q_EMIT beginReset();
}

void NodeQueryResult::notifyEndServerScan(ServerHandler* server) {
    serverScan(server);
    Q_EMIT endReset();
}

void NodeQueryResult::notifyServerRenamed(ServerHandler*, const std::string& /*oldName*/) {
    Q_EMIT beginReset();
    Q_EMIT endReset();
}

void NodeQueryResult::notifyBeginNodeChange(const VNode* node,
                                            const std::vector<ecf::Aspect::Type>& aspect,
                                            const VNodeChange&) {
    bool changed = false;
    for (auto it : aspect) {
        // Changes in the nodes
        if (it == ecf::Aspect::STATE || it == ecf::Aspect::SUSPENDED) {
            changed = true;
            break;
        }
    }

    if (changed) {
        int pos = -1;
        int cnt = 0;
        if (range(node, pos, cnt))
            Q_EMIT stateChanged(node, pos, cnt);
    }
}

void NodeQueryResult::notifyEndNodeChange(const VNode*, const std::vector<ecf::Aspect::Type>&, const VNodeChange&) {
}

//-----------------------------------------------
// Mapping
//-----------------------------------------------

bool NodeQueryResult::range(const VNode* node, int& pos, int& cnt) {
    ServerHandler* server = node->server();
    auto it               = blocks_.find(server);
    if (it != blocks_.end()) {
        if (it->second.find(node, pos, cnt))
            return true;
    }
    return false;
}
