/*
 * 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.
 */

/**
 * @file  information-tree.hpp
 *
 * @brief Tree data structure for storing, formating and serializing (JSON)
 * informations
 *
 * The information tree is largely inspired from the boost' property tree.
 * It provides a data structure that stores an arbitrarily deeply nested
 * tree of values, indexed at each level by some key. Each node of the
 * tree stores its own value, plus an ordered list of its subnodes and their
 * keys.
 * The tree allows easy access to any of its nodes by means of a path,
 * which is a concatenation of multiple keys.
 *
 */

#ifndef SH4LT_INFORMATION_TREE_H_
#define SH4LT_INFORMATION_TREE_H_

#include "../utils/any.hpp"
#include <algorithm>
#include <functional>
#include <iostream>
#include <limits>
#include <list>
#include <memory>
#include <mutex>
#include <string>
#include <type_traits>
#include <vector>

namespace sh4lt {

class InfoTree {
 public:
  using ptr = std::shared_ptr<InfoTree>;  // shared
  using ptrc = const InfoTree*;           // const
  using rptr = InfoTree*;                 // raw

  using OnNodeFunction =
      std::function<bool(const std::string& name, InfoTree::ptrc tree, bool is_array_element)>;

  // factory
  static auto make() -> InfoTree::ptr;

  template <typename ValueType>
  static auto make(ValueType data) -> InfoTree::ptr {
    // can't use make_shared because ctor is private
    std::shared_ptr<InfoTree> tree;
    tree.reset(new InfoTree(std::forward<ValueType>(data)));
    tree->me_ = tree;
    return tree;
  }
  // InfoTree will store a std::string
  static auto make(const char* data) -> InfoTree::ptr;
  static auto make_null() -> InfoTree::ptr;

  static auto copy(InfoTree::ptrc) -> InfoTree::ptr;
  // merge copy values from "second" tree into "first" tree
  static auto merge(InfoTree::ptrc first, InfoTree::ptrc second) -> InfoTree::ptr;

  // graft will create the path and graft the tree,
  // or remove old one and replace will the new tree
  auto graft(const std::string& path, const InfoTree::ptr& tree) -> bool;
  // graft by value
  template <typename T>
  auto vgraft(const std::string& path, const T& val) -> bool {
    return graft(path, InfoTree::make(val));
  }
  // return empty tree if nothing can be pruned
  auto prune(const std::string& path) -> InfoTree::ptr;


  // const methods
  auto empty() const -> bool;
  auto is_leaf() const -> bool;
  auto is_array() const -> bool;
  auto has_data() const -> bool;
  auto branch_is_array(const std::string& path) const -> bool;
  auto branch_is_leaf(const std::string& path) const -> bool;
  auto branch_has_data(const std::string& path) const -> bool;

  auto read_data() const -> const Any&;
  template <typename T>
  auto branch_read_data(const std::string& path) const -> T {
    std::unique_lock<std::recursive_mutex> lock(mutex_);
    auto found = get_node(path);
    if (nullptr != found.parent) return (*found.parent)[found.index].tree->data_.copy_as<T>();
    return T();
  }

  // get/set:
  auto get_value() const -> Any;
  void set_value(const Any& data);
  void set_value(const char* data);
  void set_value(std::nullptr_t ptr);
  auto branch_get_value(const std::string& path) const -> Any;
  template <typename T>
  auto branch_set_value(const std::string& path, T data) -> bool {
    return branch_set_value(path, Any(data));
  }
  auto branch_set_value(const std::string& path, const Any& data) -> bool;
  auto branch_set_value(const std::string& path, const char* data) -> bool;
  auto branch_set_value(const std::string& path, std::nullptr_t ptr) -> bool;
  auto for_each_in_array(const std::string& path, const std::function<void(InfoTree*)>& fun)
      -> bool;
  auto cfor_each_in_array(const std::string& path,
                          const std::function<void(const InfoTree*)>& fun) const -> bool;
  // return false if the path does not exist
  // when a path is tagged as an array, keys might be discarded
  // by some serializers, such as JSON
  auto tag_as_array(const std::string& path, bool is_array) -> bool;
  void tag_as_array(bool is_array);
  auto make_array(bool is_array) -> bool;

   // copy
  auto branch_get_copy(const std::string& path) const -> InfoTree::ptr;
  auto get_copy() const -> InfoTree::ptr;

  // get but not remove
  auto get_tree(const std::string& path) -> InfoTree::ptr;

  // get child keys - returning a newly allocated list
  auto get_child_keys(const std::string& path) const -> std::vector<std::string>;
  auto get_child_keys() const -> std::vector<std::string>;
  // collecting values (serialized)
  using collect_predicate_t = std::function<bool(const std::string& key, InfoTree::ptrc node)>;
  // the predicate can return false in order to stop searching in subtrees
  static auto collect_values(InfoTree::ptrc tree,
                             collect_predicate_t predicate,
                             bool continue_search_in_siblings) -> std::list<Any>;

  // walk
  static void preorder_tree_walk(InfoTree::ptrc tree,
                                 const InfoTree::OnNodeFunction& on_visiting_node,
                                 const InfoTree::OnNodeFunction& on_node_visited);
  static auto get_subtree(InfoTree::ptrc tree, const std::string& path) -> InfoTree::ptrc;

  // get child key in place, use with std::insert_iterator
  template <typename Iter>
  auto copy_and_insert_child_keys(const std::string& path, Iter pos) const -> bool {
    std::unique_lock<std::recursive_mutex> lock(mutex_);
    auto found = get_node(path);
    if (nullptr != found.parent) {
      std::transform((*found.parent)[found.index].tree->children_.begin(),
                     (*found.parent)[found.index].tree->children_.end(),
                     pos,
                     [](const child_t& child) { return child.key; });
      return true;
    }
    return false;
  }

  // get leaf values in a newly allocated container
  auto copy_leaf_values(const std::string& path) const -> std::list<std::string>;

 private:
  struct child_t {
    child_t(std::string k, InfoTree::ptr t) : key(std::move(k)), tree(std::move(t)) {}
    std::string key{};
    InfoTree::ptr tree {};
  };
  using children_t = std::vector<child_t>;
  using children_index_t = children_t::size_type;
  static constexpr children_index_t kInvalidIndex = std::numeric_limits<children_index_t>::max();
  struct get_node_return_t {
    get_node_return_t(InfoTree::children_t*p, children_index_t i): parent (p), index(i) {}
    InfoTree::children_t* parent{nullptr};
    children_index_t index{kInvalidIndex};
  };

  Any data_{};
  bool is_array_{false};
  mutable children_t children_{};
  mutable std::recursive_mutex mutex_{};
  std::weak_ptr<InfoTree> me_{};

  InfoTree() = default;
  template <typename U>
  InfoTree(const U& data) : data_(Any(data)) {}
  template <typename U>
  InfoTree(U&& data) : data_(Any(std::forward<U>(data))) {}
  explicit InfoTree(const Any& data);
  explicit InfoTree(Any&& data);
  auto get_child_index(const std::string& key) const -> children_t::size_type;
  static auto graft_next(std::istringstream& path, InfoTree* tree, const InfoTree::ptr& leaf)
      -> bool;
  auto get_node(const std::string& path) const -> get_node_return_t;
  auto get_next(std::istringstream& path,
                             children_t* parent_vector_result,
                             children_index_t result_index) const -> get_node_return_t;
  static auto path_is_root(const std::string& path) -> bool;
};

}  // namespace sh4lt
#endif
