/*
 * This file is part of libsh4lt.
 *
 * libsh4lt is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "./information-tree.hpp"

#include <algorithm>
#include <iostream>

#include "../utils/scope-exit.hpp"

namespace sh4lt {

auto InfoTree::make() -> InfoTree::ptr {
  // can't use make_shared because ctor is private
  std::shared_ptr<InfoTree> tree;
  tree.reset(new InfoTree());
  tree->me_ = tree;
  return tree;
}

auto InfoTree::merge(InfoTree::ptrc first, InfoTree::ptrc second) -> InfoTree::ptr {
  auto res = InfoTree::copy(first);
  std::list<std::string> path;

  if (second)
    preorder_tree_walk(
        second,
        [&](const std::string& name, InfoTree::ptrc tree, bool is_array_element) {
          std::string key;
          for (const auto& it : path) {
            key.append(".").append(it);
          }
          if (is_array_element) res->tag_as_array(key, true);
          path.push_back(name);
          key.append(".").append(name);
          auto value = tree->read_data();
          if (!value.empty()) res->graft(key, make(value));
          return true;
        },
        [&](const std::string&, InfoTree::ptrc, bool) {
          path.pop_back();
          return true;
        });
  return res;
}

auto InfoTree::copy(InfoTree::ptrc tree) -> InfoTree::ptr {
  auto res = InfoTree::make();
  std::list<std::string> path;

  if (tree) {
    const auto root_value = tree->get_value();
    if (!root_value.empty()) {
      res->set_value(root_value);
    }
    preorder_tree_walk(
        tree,
        [&](const std::string& name, InfoTree::ptrc tree, bool is_array_element) {
          std::string key{"."};
          for (const auto& it : path) {
            key.append(it).append(".");
          }
          if (is_array_element) {
            res->tag_as_array(key, true);
          }
          path.push_back(name);
          key.append(".").append(name);
          auto value = tree->read_data();
          if (!value.empty())
            res->graft(key, make(value));
          else
            res->graft(key, make());
          return true;
        },
        [&](const std::string&, InfoTree::ptrc, bool) {
          path.pop_back();
          return true;
        });
  }
  return res;
}

auto InfoTree::collect_values(InfoTree::ptrc tree,
                              collect_predicate_t predicate,
                              bool continue_search_in_siblings) -> std::list<Any> {
  std::list<Any> res;
  preorder_tree_walk(
      tree,
      [&](const std::string& name, InfoTree::ptrc node, bool) {
        if (predicate(name, node)) {
          res.emplace_back(Any(node->read_data()));
          return continue_search_in_siblings;
        }
        return true;
      },
      [&](const std::string&, InfoTree::ptrc, bool) { return true; });
  return res;
}

auto InfoTree::make(const char* data) -> InfoTree::ptr { return make(std::string(data)); }

auto InfoTree::make_null() -> InfoTree::ptr {
  std::shared_ptr<InfoTree> tree;
  return tree;
}

void InfoTree::preorder_tree_walk(InfoTree::ptrc tree,
                                  const InfoTree::OnNodeFunction& on_visiting_node,
                                  const InfoTree::OnNodeFunction& on_node_visited) {
  std::unique_lock<std::recursive_mutex> lock(tree->mutex_);
  for (const auto& it : tree->children_) {
    if (!on_visiting_node(it.key, it.tree.get(), tree->is_array_) && it.tree.get()) break;
    preorder_tree_walk(it.tree.get(), on_visiting_node, on_node_visited);
    on_node_visited(it.key, it.tree.get(), tree->is_array_);
  }
}

InfoTree::InfoTree(const Any& data) : data_(data) {}

InfoTree::InfoTree(Any&& data) : data_(data) {}

auto InfoTree::empty() const -> bool {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  return children_.empty() && data_.empty();
}

auto InfoTree::is_leaf() const -> bool {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  return children_.empty();
}

auto InfoTree::is_array() const -> bool {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  return is_array_;
}

auto InfoTree::has_data() const -> bool {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  return !data_.empty();
}

auto InfoTree::get_value() const -> Any {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  return data_;
}

auto InfoTree::read_data() const -> const Any& {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  return data_;
}

void InfoTree::set_value(const Any& data) {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  data_ = data;
}

void InfoTree::set_value(const char* data) {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  data_ = std::string(data);
}

void InfoTree::set_value(std::nullptr_t ptr) {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  data_ = ptr;
}

auto InfoTree::branch_is_leaf(const std::string& path) const -> bool {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  if (path_is_root(path)) return children_.empty();
  auto found = get_node(path);
  if (nullptr != found.parent) return (*found.parent)[found.index].tree->children_.empty();
  return false;
}

auto InfoTree::branch_is_array(const std::string& path) const -> bool {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  if (path_is_root(path)) return children_.empty();
  auto found = get_node(path);
  if (nullptr != found.parent) return (*found.parent)[found.index].tree->is_array_;
  return false;
}

auto InfoTree::branch_has_data(const std::string& path) const -> bool {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  if (path_is_root(path)) return !data_.empty();
  auto found = get_node(path);
  if (nullptr != found.parent) return !(*found.parent)[found.index].tree->data_.empty();
  return false;
}

auto InfoTree::branch_get_value(const std::string& path) const -> Any {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  if (path_is_root(path)) return data_;
  auto found = get_node(path);
  if (nullptr != found.parent) return (*found.parent)[found.index].tree->data_;
  Any res;
  return res;
}

auto InfoTree::get_copy() const -> InfoTree::ptr { return copy(this); }

auto InfoTree::branch_get_copy(const std::string& path) const -> InfoTree::ptr {
  if (path_is_root(path)) return copy(this);
  auto found = get_node(path);
  if (nullptr == found.parent) return nullptr;
  return copy((*found.parent)[found.index].tree.get());
}

auto InfoTree::branch_set_value(const std::string& path, const Any& data) -> bool {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  if (path_is_root(path)) return (data_ = data);
  auto found = get_node(path);
  if (nullptr != found.parent) {
    (*found.parent)[found.index].tree->data_ = data;
    return true;
  }
  return false;
}

auto InfoTree::branch_set_value(const std::string& path, const char* data) -> bool {
  return branch_set_value(path, std::string(data));
}

auto InfoTree::branch_set_value(const std::string& path, std::nullptr_t ptr) -> bool {
  return branch_set_value(path, Any(ptr));
}

auto InfoTree::get_child_index(const std::string& key) const -> children_index_t {
  auto found = std::find_if(children_.begin(), children_.end(), [key](const InfoTree::child_t& s) {
    return (0 == s.key.compare(key));
  });
  return children_.end() != found ? found - children_.begin() : kInvalidIndex;
}

auto InfoTree::prune(const std::string& path) -> ptr {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  auto found = get_node(path);
  if (nullptr != found.parent) {
    On_scope_exit { found.parent->erase(found.parent->begin() + static_cast<long>(found.index)); };
    return (*found.parent)[found.index].tree;
  }
  return InfoTree::make_null();
}

auto InfoTree::get_tree(const std::string& path) -> ptr {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  if (path_is_root(path)) return me_.lock();
  auto found = get_node(path);
  if (nullptr != found.parent) return (*found.parent)[found.index].tree;
  // not found
  return make();
}

auto InfoTree::get_node(const std::string& path) const -> get_node_return_t {
  std::istringstream iss(path);
  return get_next(iss, nullptr, 0);
}

auto InfoTree::get_next(std::istringstream& path,
                        InfoTree::children_t* parent_vector,
                        children_index_t index) const -> get_node_return_t {
  std::string child_key;
  if (!std::getline(path, child_key, '.')) return {parent_vector, index};
  if (child_key.empty()) return get_next(path, parent_vector, index);

  auto child_index = get_child_index(child_key);
  if (child_index == kInvalidIndex) return {nullptr, kInvalidIndex};
  return children_[child_index].tree->get_next(path, &children_, child_index);
}

auto InfoTree::graft(const std::string& where, const InfoTree::ptr& tree) -> bool {
  if (!tree) return false;
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  std::istringstream iss(where);
  return !graft_next(iss, this, tree);
}

auto InfoTree::graft_next(std::istringstream& path, InfoTree* tree, const InfoTree::ptr& leaf)
    -> bool {
  std::string child;
  if (!std::getline(path, child, '.')) return true;
  if (child.empty())  // in case of two or more consecutive dots
    return graft_next(path, tree, leaf);
  auto index = tree->get_child_index(child);
  if (index != kInvalidIndex) {
    if (graft_next(path, tree->children_[index].tree.get(), leaf))
      // graft on already existing child
      // replacing the previously empy tree with the one to graft
      tree->children_[index].tree = leaf;
  } else {
    InfoTree::ptr child_node = make();
    tree->children_.emplace_back(child, child_node);
    if (graft_next(path, child_node.get(), leaf))  // graft on already existing child
    {
      // replacing empty tree for replacement by leaf
      tree->children_.pop_back();
      tree->children_.emplace_back(child, leaf);
    }
  }
  return false;
}

auto InfoTree::tag_as_array(const std::string& path, bool is_array) -> bool {
  InfoTree::ptr tree = InfoTree::get_tree(path);
  if (!static_cast<bool>(tree)) return false;
  tree->is_array_ = is_array;
  return true;
}

void InfoTree::tag_as_array(bool is_array) { is_array_ = is_array; }

auto InfoTree::make_array(bool is_array) -> bool {
  is_array_ = is_array;
  return true;
}

auto InfoTree::get_child_keys() const -> std::vector<std::string> {
  std::vector<std::string> res;
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  res.resize(children_.size());
  std::transform(children_.cbegin(), children_.cend(), res.begin(), [](const child_t& child) {
    return child.key;
  });
  return res;
}

auto InfoTree::get_child_keys(const std::string& path) const -> std::vector<std::string> {
  std::vector<std::string> res;
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  // if root is asked
  if (path_is_root(path)) {
    res.resize(children_.size());
    std::transform(children_.cbegin(), children_.cend(), res.begin(), [](const child_t& child) {
      return child.key;
    });
    return res;
  }
  // else looking into childrens
  auto found = get_node(path);
  if (nullptr != found.parent) {
    res.resize((*found.parent)[found.index].tree->children_.size());
    std::transform((*found.parent)[found.index].tree->children_.cbegin(),
                   (*found.parent)[found.index].tree->children_.cend(),
                   res.begin(),
                   [](const child_t& child) { return child.key; });
  }
  return res;
}

auto InfoTree::copy_leaf_values(const std::string& path) const -> std::list<std::string> {
  std::list<std::string> res;
  InfoTree::ptr tree;
  {  // finding the node
    std::lock_guard<std::recursive_mutex> lock(mutex_);
    if (path_is_root(path))
      tree = me_.lock();
    else {
      auto found = get_node(path);
      if (nullptr == found.parent) return res;
      tree = (*found.parent)[found.index].tree;
    }
  }
  preorder_tree_walk(
      tree.get(),
      [&res](const std::string& /*key*/, InfoTree::ptrc node, bool /*is_array_element*/) {
        if (node->is_leaf()) res.push_back(Any::to_string(node->read_data()));
        return true;
      },
      [](const std::string&, InfoTree::ptrc, bool) { return true; });
  return res;
}

auto InfoTree::get_subtree(InfoTree::ptrc tree, const std::string& path) -> InfoTree::ptrc {
  std::lock_guard<std::recursive_mutex> lock(tree->mutex_);
  auto found = tree->get_node(path);
  if (nullptr == found.parent) return nullptr;
  return (*found.parent)[found.index].tree.get();
}

auto InfoTree::path_is_root(const std::string& path) -> bool {
  return (path == ".") || (path == "..");
}

auto InfoTree::for_each_in_array(const std::string& path, const std::function<void(InfoTree*)>& fun)
    -> bool {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  // finding the parent node of the array
  auto children = children_;
  if (path_is_root(path)) {
    if (!is_array_) return false;
  } else {
    auto found = get_node(path);
    if (!found.parent) return false;
    auto& tree = (*found.parent)[found.index].tree;
    if (!tree->is_array_) return false;
    children = tree->children_;
  }

  // apply function to array elements
  for (auto& child : children) {
    fun(child.tree.get());
  }
  return true;
}

auto InfoTree::cfor_each_in_array(const std::string& path,
                                  const std::function<void(const InfoTree*)>& fun) const -> bool {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  // finding the parent node of the array
  auto children = children_;
  if (path_is_root(path)) {
    if (!is_array_) return false;
  } else {
    auto found = get_node(path);
    if (!found.parent) return false;
    auto& tree = (*found.parent)[found.index].tree;
    if (!tree->is_array_) return false;
    children = tree->children_;
  }

  // apply function to array elements
  for (auto& child : children) {
    fun(child.tree.get());
  }
  return true;
}

}  // namespace sh4lt
