#include <vector>
#include <variant>
#include <string>
#include <iostream>
#include "cpp-httplib/httplib.h"

const std::string cSite { "http://kit.local" };
const std::string cUrlComment { "/comment/?id=" };

using StatusT = httplib::StatusCode;
using ResultT = std::variant<std::string, StatusT>;
using ErrorsList = std::vector<StatusT>;

/* define << for ResultT and Node<T> (below) to print results */
static std::ostream& operator <<(std::ostream& os, const ResultT& val) {
  if (const std::string* pval = std::get_if<std::string>(&val))
    std::cout << "'" << *pval << "'";
  else if (const StatusT* code = std::get_if<StatusT>(&val))
    std::cout << "Error " << *code << ": " << httplib::status_message(*code);
  return os;
}

/* tree */
template<typename T>
struct Node {
  T value;
  std::vector<Node<T>> children;
  Node() = delete;
  Node(const T& val) : value(val) {}
  void insert(Node<T>& node)
  {
    if (node.value == value)
      return;
    children.push_back(node);
  }

  friend std::ostream& operator <<(std::ostream& os, const Node<T>& val)
  {
    os << "[" << val.value << "]";
    if (!val.children.empty())
      os << "\nChildren:\t";
    std::for_each(val.children.cbegin(), val.children.cend(),
      [&os] (const Node<T> node) { os << node; });
    return os;
  }
};
using CommentTree = Node<ResultT>;

template<typename T1, typename T2, typename T3>
Node<T2> traverse(const Node<T1>& src, T3 f) {
  Node<T2> result(f(src.value));
  std::for_each(src.children.cbegin(), src.children.cend(),
    [&] (const Node<T1>& node) {
      result.children.push_back(traverse<T1, T2, T3>(node, f));
    });
  return result;
}

template<typename T1, typename T2, typename T3>
void forAll(const Node<T1> &src, std::vector<T2> &result, T3 f) {
  f(src.value, result);
  std::for_each(src.children.cbegin(), src.children.cend(),
    [&] (const Node<T1>& node) {
      forAll<T1, T2, T3>(node, result, f);
    });
}

static ResultT tryGetContent(httplib::Client &c, const int x) {
  auto res = c.Get(cUrlComment + std::to_string(x));
  if (res->status == StatusT::OK_200)
    return res->body;
  return static_cast<StatusT>(res->status);
}

static std::variant<CommentTree, ErrorsList>
    tryGetContent(const Node<int> &src, ErrorsList &errors) {
  httplib::Client cli(cSite);

  auto actionGet = [&] (const int x) {
    return tryGetContent(cli, x);
  };
  CommentTree result = traverse<int, ResultT, decltype(actionGet)>(src, actionGet);

  auto collectErrors = [&] (const CommentTree& node, ErrorsList& list) {
    if (const StatusT* code = std::get_if<StatusT>(&node.value))
      list.push_back(*code);
  };
  forAll<ResultT, StatusT, decltype(collectErrors)>(result, errors, collectErrors);

  if (errors.size() > 0)
    return errors;
  return result;
}

int main() {
  using IntNode = Node<int>;

  IntNode tree { 10 };
  IntNode a { 100 }, b { 101 }, c { 102 };
  IntNode bb { 1001 };
  b.insert(bb);

  tree.insert(a);
  tree.insert(b);
  tree.insert(c);

  IntNode root { 1 };
  root.insert(tree);
  std::cout << "\n Source:\n";
  std::cout << root;

  ErrorsList errors;
  auto result = tryGetContent(root, errors);

  /* print result */
  if (const auto *list = std::get_if<ErrorsList>(&result)) {
    std::for_each(list->cbegin(), list->cend(), [&](const StatusT code) {
      std::cout << "\nError " << code << ": " << httplib::status_message(code);
    });
  } else if (const auto* node = std::get_if<CommentTree>(&result)) {
    std::cout << "\n Result:\n";
    std::cout << *node;
  }
  return 0;
}
