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

#include "AbstractNodeView.hpp"
#include "ExpandStateNode.hpp"
#include "ServerHandler.hpp"
#include "TreeNodeModel.hpp"
#include "UIDebug.hpp"
#include "UiLog.hpp"
#include "VNode.hpp"
#include "VTree.hpp"
#include "ecflow/core/Str.hpp"

// #define _UI_EXPANDSTATE_DEBUG

ExpandState::ExpandState(AbstractNodeView* view, TreeNodeModel* model) : view_(view), model_(model), root_(nullptr) {
}

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

void ExpandState::init(const VNode* vnode) {
    clear();

    ServerHandler* server = vnode->server();
    Q_ASSERT(server);
    QModelIndex idx = model_->serverToIndex(server);
    bool expanded   = view_->isExpanded(idx);

    root_ = new ExpandStateNode(server->vRoot(), expanded);

    save(server->vRoot());
}

bool ExpandState::isEmpty() const {
    return !root_ || root_->children_.size() == 0;
}

void ExpandState::clear() {
    if (root_)
        delete root_;

    root_ = nullptr;
}

// Save the expand state for a whole subtree (it can be the whole VNode tree as well)
void ExpandState::save(const VNode* vnode) {
    UI_FUNCTION_LOG
    UI_ASSERT(vnode, "node is null");
    UiLog().dbg() << " " << vnode->name();

    if (!root_) {
        init(vnode);
        Q_ASSERT(root_);
        return;
    }

    if (ExpandStateNode* es = find(vnode->absNodePath())) {
        Q_ASSERT(root_);
        QModelIndex idx = model_->nodeToIndex(vnode);

        // It can happen that the VTree is empty but the server is already
        // loaded. It is the situation when we load the same server in multiple
        // tabs!!! We return here because there is nothing to save (and we would
        // have a crash when trying to get index of the vnodes via the model!!)
        if (VTreeNode* vtn = model_->indexToServerOrNode(idx)) {
            if (VTree* vt = vtn->root()) {
                if (vt->totalNum() == 0)
                    return;
            }
        }

        // save all the children recursively
        save(vnode, es, idx);
    }
}

#if 0
//Save the expand state for a whole subtree (it can be the whole VNode tree as well)
void ExpandState::save(const VNode *root)
{    
    UI_FUNCTION_LOG
    UI_ASSERT(root,"root is null");
    UiLog().dbg() << " " << root->name();

    QModelIndex rootIdx=model_->nodeToIndex(root);
    bool expanded=view_->isExpanded(rootIdx);
    if(root_ == 0 || root_->name_ != root->strName())
    {
        clear();
        root_=new ExpandStateNode(root,expanded);
    }
    else
    {
        root_->expanded_=expanded;
    }

    //It can happen that the VTree is empty but the server is already
    //loaded. It is the situation when we load the same server in multiple
    //tabs!!! We return here because there is nothing to save (and we would
    //have a crash when trying to get index of the vnodes via the model!!)
    if(VTreeNode* vtn=model_->indexToServerOrNode(rootIdx))
    {
        if(VTree* vt=vtn->root())
        {
            if(vt->totalNum() == 0)
                return;
        }
    }

    //save all the children recursively
    save(root,root_,rootIdx);
}
#endif

// Save the expand state for the given node.
//   node: the vnode to save
//   expandNode: the expand node representing the vnode
//   idx: the modelindex of the vnode in the TreeNodeModel. It can be invalid
//        since the model can use a filter and its tree (the VTree) might represent only
//        the subset of all the vnodes of a given server
void ExpandState::save(const VNode* node, ExpandStateNode* expandNode, const QModelIndex& idx) {
    std::size_t numExpandNode = expandNode->children_.size();
    std::size_t numNode       = node->numOfChildren();

    // the node and the expand node does not match. We clear the whole
    // contents of the expand node
    // CAN IT HAPPEN AT ALL??
    if (numExpandNode != numNode || expandNode->name_ != node->strName()) {
        UI_ASSERT(0,
                  "numExpandNode=" << numExpandNode << " numNode=" << numNode << " expandNode->name_="
                                   << expandNode->name_ << " node->strName()=" << node->strName());

        expandNode->reset(node, view_->isExpanded(idx));
        // At the this point the expand node children vector is
        // reserved, but contains null pointers
    }

    for (std::size_t i = 0; i < numNode; i++) {
        VNode* chNode                 = node->childAt(i);
        QModelIndex chIdx             = model_->nodeToIndex(chNode);
        ExpandStateNode* chExpandNode = expandNode->children_[i];

        // The expand node exists
        if (chExpandNode) {
            // We only set the expand state when the child node is in the current VTree (i.e. is filtered).
            // Otherwise we keep the original value
            if (chIdx.isValid())
                chExpandNode->setExpanded(view_->isExpanded(chIdx));
        }
        // ... create a new child expand node at the i-th place
        else {
            chExpandNode = expandNode->setChildAt(i, chNode, view_->isExpanded(chIdx));
        }

        // save all the children recursively
        save(chNode, chExpandNode, chIdx);
    }
}

// Collect the modelindexes of the expanded nodes in a VNode subtree.
// This is called after a server scan ended so the structure of the VNode tree and the
// expand tree might not match. In this case the expand tree has to be adjusted to the
// VNode tree.
void ExpandState::collectExpanded(const VNode* node, QSet<QPersistentModelIndex>& theSet) {
#ifdef _UI_EXPANDSTATE_DEBUG
    UI_FUNCTION_LOG
#endif
    Q_ASSERT(node);

    if (!root_)
        return;

    QModelIndex nodeIdx = model_->nodeToIndex(node);
#ifdef _UI_EXPANDSTATE_DEBUG
    UiLog().dbg() << " root=" << root_->name_;
#endif
    ExpandStateNode* expand = find(node->absNodePath());
    if (expand)
        collectExpanded(expand, node, nodeIdx, theSet);
    else {
#ifdef _UI_EXPANDSTATE_DEBUG
        UiLog().dbg() << " Node not found in expand tree";
#endif
    }
}

void ExpandState::collectExpanded(ExpandStateNode* expand,
                                  const VNode* node,
                                  const QModelIndex& nodeIdx,
                                  QSet<QPersistentModelIndex>& theSet) {
#ifdef _UI_EXPANDSTATE_DEBUG
    UI_FUNCTION_LOG
#endif
    // The contents of expand node and the vnode might differ. We try to
    // adjust the expand node to the vnode with this call.
    bool adjusted = expand->adjustContents(node);

    std::size_t numExpand = expand->children_.size();
    std::size_t numNode   = node->numOfChildren();

    UI_ASSERT(numExpand == numNode, "node=" << node->strName() << " numExpand=" << numExpand << " numNode=" << numNode);

    // Lookup the node in the model
    // QModelIndex nodeIdx=model_->nodeToIndex(node);
    if (expand->expanded_ && nodeIdx.isValid()) {
        theSet.insert(nodeIdx);

#ifdef _UI_EXPANDSTATE_DEBUG
        UiLog().dbg() << "  " << expand->name_;
#endif
    }

    // We need to see what to do with newly added nodes. We either expand
    // all its children or leave it unexpanded. It depends on the expandedAll_
    // flag set in any of the parents.
    bool parentExpandedAll = false;
    if (adjusted) {
        parentExpandedAll = needToExpandNewChild(expand, node->absNodePath());
    }

    for (std::size_t i = 0; i < numExpand; i++) {
        VNode* chNode = node->childAt(i);
        UI_ASSERT(chNode, "chNode is null");
        QModelIndex chIdx         = model_->nodeToIndex(chNode);
        ExpandStateNode* chExpand = expand->children_[i];

        // An existing expand node
        if (chExpand) {
            UI_ASSERT(chExpand->name_ == chNode->strName(),
                      " chExpand->name_=" << chExpand->name_ << " chNode->strName()=" << chNode->strName());
        }

        // A not-yet-allocated expand node. The corresponding vnode has just been
        // added to the tree. We have to decide what to do with its expand state!!!
        else {
            // create an expand node. Collapsed by default
            chExpand = expand->setChildAt(i, chNode, false);

            // save all the children recursively
            save(chNode, chExpand, chIdx);

            // expand recursively the new expand node if needed
            if (parentExpandedAll)
                chExpand->setExpandedRecursively(1);
            else {
                UiLog().dbg() << " newly added node not expanded!!";
            }
        }

        collectExpanded(chExpand, chNode, chIdx, theSet);
    }
}

void ExpandState::saveExpandAll(const VNode* node) {
    UI_ASSERT(node, "node is null");
    if (ExpandStateNode* expand = find(node->absNodePath())) {
        expand->setExpandedAll();
    }
}

void ExpandState::saveCollapseAll(const VNode* node) {
    UI_ASSERT(node, "node is null");
    if (ExpandStateNode* expand = find(node->absNodePath())) {
        expand->setCollapsedAll();
    }
}

// Find an expand node using its ful path
ExpandStateNode* ExpandState::find(const std::string& fullPath) {
    if (!root_)
        return nullptr;

    if (fullPath.empty())
        return nullptr;

    if (fullPath == "/")
        return root_;

    std::vector<std::string> pathVec;
    ecf::algorithm::split(pathVec, fullPath, "/");

    if (pathVec.size() > 0 && pathVec[0].empty()) {
        pathVec.erase(pathVec.begin());
    }

    return root_->find(pathVec);
}

bool ExpandState::needToExpandNewChild(ExpandStateNode* expandNode, const std::string& expandNodePath) const {

    if (expandNode->expandedAll_ == 1)
        return true;

    if (expandNode->collapsedAll_ == 1)
        return false;

    // Checks if any of the parents have expandedAll set. In this case we
    // expand recursively all the new expand node
    std::vector<ExpandStateNode*> parents;
    collectParents(expandNodePath, parents);
    for (auto it = parents.rbegin(); it != parents.rend(); ++it) {
        if ((*it)->expandedAll_ == 1) {
            return true;
        }
        else if ((*it)->collapsedAll_ == 1) {
            return false;
        }
    }
    return false;
}

// We need to do it this way because we do not store the parents in the nodes for memory efficiency.
void ExpandState::collectParents(const std::string& fullPath, std::vector<ExpandStateNode*>& parents) const {
    if (!root_)
        return;

    // how split works:
    //   str="/"  -> "",""
    //   str="/a" -> "","a"
    //   str=" "  -> " "
    //   str=""   -> ""

    std::vector<std::string> pathVec;
    boost::split(pathVec, fullPath, boost::is_any_of("/"));
    if (pathVec.size() > 0 && pathVec[0].empty()) {
        pathVec.erase(pathVec.begin());
    }

    ExpandStateNode* p = root_;
    std::size_t num    = pathVec.size();
    for (std::size_t i = 0; i < num && p != nullptr; i++) {
        parents.push_back(p);
        p = p->findChild(pathVec[i]);
    }
}

void ExpandState::updateServerName(const std::string& serverName) {
    if (root_) {
        root_->setName(serverName);
    }
}

void ExpandState::print() const {
    if (!root_)
        return;

    std::string indent = "";
    root_->print(indent, true);
}
