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

#include "ServerHandler.hpp"
#include "UiLog.hpp"
#include "VAttribute.hpp"
#include "VAttributeType.hpp"
#include "VItemPathParser.hpp"
#include "VNode.hpp"

// #define _UI_VINFO_DEBUG

//========================================
//
// VInfo
//
//========================================

VInfo::VInfo(ServerHandler* server, VNode* node, VAttribute* attr) : server_(server), node_(node), attr_(attr) {
    if (server_) {
        server_->addServerObserver(this);
    }
}

VInfo::~VInfo() {
#ifdef _UI_VINFO_DEBUG
    UiLog().dbg() << "VInfo::~VInfo() --> " << this;
#endif
    if (server_) {
        server_->removeServerObserver(this);
    }

    for (auto it = observers_.begin(); it != observers_.end(); ++it) {
        (*it)->notifyDelete(this);
    }

#ifdef _UI_VINFO_DEBUG
    UiLog().dbg() << "<-- ~VInfo()";
#endif
}

void VInfo::notifyServerDelete(ServerHandler* /*server*/) {
    server_ = nullptr;
    node_   = nullptr;
    attr_   = nullptr;

    // This function is called from the server destructor. We do not remove this object from the ServerObservers
    dataLost();
}

void VInfo::dataLost() {
    std::vector<VInfoObserver*> obsTmp = observers_;
    observers_.clear();

    for (auto o : obsTmp) {
        o->notifyDataLost(this);
    }

    attr_ = nullptr;
}

void VInfo::notifyBeginServerClear(ServerHandler* server) {
    assert(server_ == server);
    node_ = nullptr;
    attr_ = nullptr;
}

void VInfo::notifyEndServerClear(ServerHandler* server) {
    assert(server_ == server);
    node_ = nullptr;
    attr_ = nullptr;
}

void VInfo::notifyEndServerScan(ServerHandler* server) {
    assert(server_ == server);
    regainData();
}

void VInfo::notifyServerRenamed(ServerHandler* server, const std::string& /*oldName*/) {
    if (server_ == server) {
        //        UiLog().dbg() << UI_FN_INFO <<  "storedPath_=" << storedPath_;
        storedPath_ = VItemPathParser::updateServerName(storedPath_, server->name());
        //        UiLog().dbg() << " -> " << storedPath_;
    }
}

void VInfo::regainData() {
    if (!server_) {
        dataLost();
        return;
    }

    if (!node_) {
        if (isServer()) {
            node_ = server_->vRoot();
            return;
        }
        else if (isNode()) {
            VItemPathParser p(storedPath_);
            if (p.itemType() == VItemPathParser::NodeType) {
                node_ = server_->vRoot()->find(p.node());
                if (node_) {
                    return;
                }
            }
            if (!node_) {
                dataLost();
                return;
            }
        }
    }

    if (isAttribute()) {
        VItemPathParser p(storedPath_);
        if (p.itemType() == VItemPathParser::AttributeType) {
            if (!node_) {
                node_ = server_->vRoot()->find(p.node());
            }
            if (node_) {
                attr_ = node_->findAttribute(p.type(), p.attribute());
            }
            if (!node_ || !attr_) {
                dataLost();
            }
        }
    }
}

std::string VInfo::storedNodePath() const {
    VItemPathParser p(storedPath_);
    if (p.itemType() == VItemPathParser::ServerType) {
        return "/";
    }
    else {
        return p.node();
    }
}

void VInfo::addObserver(VInfoObserver* o) {
    auto it = std::find(observers_.begin(), observers_.end(), o);
    if (it == observers_.end()) {
        observers_.push_back(o);
    }
}

void VInfo::removeObserver(VInfoObserver* o) {
    auto it = std::find(observers_.begin(), observers_.end(), o);
    if (it != observers_.end()) {
        observers_.erase(it);
    }
}

bool VInfo::operator==(const VInfo& other) {
    if (server_ == other.server_ && node_ == other.node_ && storedPath_ == other.storedPath_) {
        if ((!attr_ && other.attr_) || (attr_ && !other.attr_)) {
            return false;
        }

        else if (attr_ && other.attr_) {
            return (attr_->type() == other.attr_->type() && attr_->data() == other.attr_->data());
        }
        else {
            return true;
        }
    }
    return false;
}

VInfo_ptr VInfo::createParent(VInfo_ptr info) {
    if (!info) {
        return {};
    }

    if (info->isServer()) {
        return info;
    }
    else if (info->isNode()) {
        return VInfoServer::create(info->server());
    }
    else if (info->isAttribute()) {
        return VInfoNode::create(info->node());
    }

    return {};
}

VInfo_ptr VInfo::createFromPath(ServerHandler* s, const std::string& path) {
    if (!s || path.empty()) {
        return {};
    }

    VItemPathParser p(path);

    if (p.itemType() == VItemPathParser::ServerType) {
        return VInfoServer::create(s);
    }
    else if (p.itemType() == VItemPathParser::NodeType) {
        if (VNode* n = s->vRoot()->find(p.node())) {
            return VInfoNode::create(n);
        }
    }
    else if (p.itemType() == VItemPathParser::AttributeType) {
        if (VNode* n = s->vRoot()->find(p.node())) {
            if (VAttribute* a = n->findAttribute(p.type(), p.attribute())) {
                return VInfoAttribute::create(a);
            }
        }
    }

    return {};
}

VInfo_ptr VInfo::createFromPath(const std::string& path) {
    if (path.empty()) {
        return {};
    }

    VItemPathParser p(path);
    if (!p.server().empty()) {
        if (ServerHandler* s = ServerHandler::find(p.server())) {
            return createFromPath(s, path);
        }
    }
    return {};
}

VInfo_ptr VInfo::createFromItem(VItem* item) {
    if (!item) {
        return {};
    }

    if (VServer* s = item->isServer()) {
        return VInfoServer::create(s->server());
    }
    else if (VNode* n = item->isNode()) {
        return VInfoNode::create(n);
    }
    else if (VAttribute* a = item->isAttribute()) {
        return VInfoAttribute::create(a);
    }

    return {};
}

//=========================================
//
// VInfoServer
//
//=========================================

VInfoServer::VInfoServer(ServerHandler* server) : VInfo(server, nullptr) {
    if (server_) {
        node_       = server_->vRoot();
        storedPath_ = VItemPathParser::encodeWithServer(server_->name(), "/", "server");
    }
}

VInfo_ptr VInfoServer::create(ServerHandler* server) {
    return VInfo_ptr(new VInfoServer(server));
}

bool VInfoServer::hasData() const {
    return server_ != nullptr;
}

void VInfoServer::accept(VInfoVisitor* v) {
    v->visit(this);
}

std::string VInfoServer::name() {
    return (server_) ? (server_->name()) : (std::string());
}

std::string VInfoServer::path() {
    return name() + "://";
}

VItem* VInfoServer::item() const {
    return node_;
}

//=========================================
//
// VInfoNode
//
//=========================================

VInfoNode::VInfoNode(ServerHandler* server, VNode* node) : VInfo(server, node) {
    if (node_) {
        assert(server_);
        storedPath_ = VItemPathParser::encodeWithServer(server_->name(), node_->absNodePath(), "node");
    }
}

VInfo_ptr VInfoNode::create(VNode* node) {
    ServerHandler* server = nullptr;
    if (node) {
        server = node->server();
    }
    return VInfo_ptr(new VInfoNode(server, node));
}

bool VInfoNode::hasData() const {
    return server_ != nullptr && node_ != nullptr;
}

void VInfoNode::accept(VInfoVisitor* v) {
    v->visit(this);
}

std::string VInfoNode::name() {
    if (node_ && node_->node()) {
        return node_->strName();
    }

    return {};
}

std::string VInfoNode::path() {
    std::string p;
    if (server_) {
        p = server_->name();
    }

    if (node_ && node_->node()) {
        p += ":/" + node_->absNodePath();
    }

    return p;
}

std::string VInfoNode::serverAlias() {
    std::string p;
    if (server_) {
        p = server_->name();
    }
    return p;
}

std::string VInfoNode::nodePath() {
    std::string p;
    if (node_ && node_->node()) {
        p = node_->absNodePath();
    }
    return p;
}

std::string VInfoNode::relativePath() {
    std::string p;
    if (node_ && node_->node()) {
        p = node_->absNodePath();
    }
    return p;
}

VItem* VInfoNode::item() const {
    return node_;
}

//=========================================
//
// VInfoAttribute
//
//=========================================

VInfoAttribute::VInfoAttribute(ServerHandler* server, VNode* node, VAttribute* attr) : VInfo(server, node, attr) {
    if (attr_) {
        assert(server_);
        storedPath_ = VItemPathParser::encodeWithServer(server_->name(), attr_->fullPath(), attr_->typeName());
    }
}

VInfoAttribute::~VInfoAttribute() = default;

bool VInfoAttribute::hasData() const {
    return server_ != nullptr && node_ != nullptr && attr_ != nullptr;
}

void VInfoAttribute::accept(VInfoVisitor* v) {
    v->visit(this);
}

VInfo_ptr VInfoAttribute::create(VAttribute* att) {
    ServerHandler* server = nullptr;
    VNode* node           = att->parent();
    if (node) {
        server = node->server();
    }

    return VInfo_ptr(new VInfoAttribute(server, node, att));
}

std::string VInfoAttribute::path() {
    std::string p;
    if (server_) {
        p = server_->name();
    }
    if (attr_) {
        p += ":/" + attr_->fullPath();
    }

    return p;
}

std::string VInfoAttribute::nodePath() {
    std::string p;
    if (node_ && node_->node()) {
        p = node_->absNodePath();
    }
    return p;
}

std::string VInfoAttribute::name() {
    return (attr_) ? attr_->strName() : std::string();
}

VItem* VInfoAttribute::item() const {
    return attr_;
}
