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

#include "UIDebug.hpp"
#include "UiLog.hpp"
#include "VNode.hpp"

ExpandStateNode::ExpandStateNode(const VNode* node, bool expanded)
    : name_(node->strName()),
      expanded_(expanded),
      expandedAll_(0),
      collapsedAll_(0) {
    reserveChildren(node->numOfChildren());
}

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

// Clear the contents and create a new children vector
// with NULLs!
void ExpandStateNode::reset(const VNode* node, bool expanded) {
    clear();
    name_         = node->strName();
    expanded_     = expanded;
    expandedAll_  = 0;
    collapsedAll_ = 0;
    reserveChildren(node->numOfChildren());
}

void ExpandStateNode::clear() {
    name_.clear();
    expanded_     = 0;
    expandedAll_  = 0;
    collapsedAll_ = 0;
    for (auto& i : children_) {
        delete i;
    }
    children_ = std::vector<ExpandStateNode*>();
}

// Create new children vector filled with NULLs
void ExpandStateNode::reserveChildren(std::size_t num) {
    UI_ASSERT(children_.size() == 0, "children_.size()=" << children_.size());

    ExpandStateNode* exn = nullptr;
    children_            = std::vector<ExpandStateNode*>();
    children_.resize(num, exn);
}

// Allocate a new child and place it into then children vector at the specified position
ExpandStateNode* ExpandStateNode::setChildAt(std::size_t index, VNode* node, bool expanded) {
    auto* exn        = new ExpandStateNode(node, expanded);
    children_[index] = exn;
    return exn;
}

//"Expand all children" was called on the node
void ExpandStateNode::setExpandedAll() {
    expandedAll_  = 1;
    collapsedAll_ = 0;
    // Set the expand state on all the descendants
    std::size_t num = children_.size();
    for (std::size_t i = 0; i < num; i++)
        children_[i]->setExpandedRecursively(1);
}

//"Collapse all children" was called on the node
void ExpandStateNode::setCollapsedAll() {
    expandedAll_  = 0;
    collapsedAll_ = 1;
    // Set the expand state on all the descendants
    std::size_t num = children_.size();
    for (std::size_t i = 0; i < num; i++)
        children_[i]->setExpandedRecursively(0);
}

void ExpandStateNode::setExpandedRecursively(unsigned int expanded) {
    expanded_       = expanded;
    expandedAll_    = 0;
    collapsedAll_   = 0;
    std::size_t num = children_.size();
    for (std::size_t i = 0; i < num; i++) {
        children_[i]->setExpandedRecursively(expanded);
    }
}

// Find a descendant
ExpandStateNode* ExpandStateNode::find(const std::vector<std::string>& pathVec) {
    if (pathVec.size() == 0)
        return this;

    if (pathVec.size() == 1) {
        return findChild(pathVec.at(0));
    }

    std::vector<std::string> rest(pathVec.begin() + 1, pathVec.end());
    ExpandStateNode* n = findChild(pathVec.at(0));

    return n ? n->find(rest) : nullptr;
}

// Find a child with the given name
ExpandStateNode* ExpandStateNode::findChild(const std::string& theName) const {
    std::size_t num = children_.size();
    for (std::size_t i = 0; i < num; i++) {
        // A child can be NULL temporarily
        if (children_[i] && children_[i]->name_ == theName)
            return children_[i];
    }
    return nullptr;
}

// Find a child with the given name. Returns its position as well.
ExpandStateNode* ExpandStateNode::findChild(const std::string& theName, std::size_t& pos) const {
    pos             = -1;
    std::size_t num = children_.size();
    for (std::size_t i = 0; i < num; i++) {
        // A child can be NULL temporarily
        if (children_[i] && children_[i]->name_ == theName) {
            pos = i;
            return children_[i];
        }
    }
    return nullptr;
}

void ExpandStateNode::print(std::string& indent, bool recursive) const {
    UiLog().dbg() << indent << name_ << " " << expanded_;
    if (recursive) {
        indent += "  ";
        std::size_t num = children_.size();
        for (std::size_t i = 0; i < num; i++)
            children_[i]->print(indent, true);

        indent.resize(indent.size() - 2);
    }
}

// Adjust the contents of the given expand node to the contents of the vnode it
// represents. Return true if an adjustment was actually needed.
bool ExpandStateNode::adjustContents(const VNode* node) {
    std::size_t numExpand = children_.size();
    std::size_t numNode   = node->numOfChildren();

    // Check if the children of the expand node and the vnode are the same.
    // They migh differ when:
    //   -the order of the nodes changed
    //   -nodes were added/removed
    bool same = false;
    if (numExpand == numNode) {
        same = true;
        for (std::size_t i = 0; i < numNode; i++) {
            // items in children can be null pointers
            if (!children_[i] || children_[i]->name_ != node->childAt(i)->strName()) {
                same = false;
                break;
            }
        }
    }

    // the children of the expand node and the vnode are not the same!!!
    if (!same) {
        // create a new children vector for the expand node and
        // copy all the expand children into it whose name appear in the
        // vnode children
        std::vector<ExpandStateNode*> chVec;
        for (std::size_t i = 0; i < numNode; i++) {
            std::size_t chPos = -1;
            if (ExpandStateNode* chExpand = findChild(node->childAt(i)->strName(), chPos)) {
                chVec.push_back(chExpand);
                children_[chPos] = nullptr;
            }
            else
                chVec.push_back(nullptr);
        }

        // Delete the children vector of the expand node. It eaither contains NULLs or
        // children objects which do not exist anymore!
        for (std::size_t i = 0; i < numExpand; i++) {
            if (children_[i] != nullptr)
                delete children_[i];
        }

        children_.clear();

        // reassign the children vector to the expand node
        children_ = chVec;
    }

    return !same;
}
