#ifndef CATCH_UTILS_H_
#define CATCH_UTILS_H_

/*
 * Some helper functions to be used in catch based tests
 */

#include <limits>
#include <random>

#include "painlessmesh/protocol.hpp"

static std::random_device
    rd;  // Will be used to obtain a seed for the random number engine
static std::mt19937 gen(rd());

uint32_t runif(int from, int to) {
  std::uniform_int_distribution<int> distribution(from, to);
  return distribution(gen);
}

uint32_t rbinom(size_t n, double p) {
  std::binomial_distribution<uint32_t> distribution(n, p);
  return distribution(gen);
}

std::string randomString(uint32_t length) {
  std::string str;
  for (uint32_t i = 0; i < length; ++i) {
    char rnd = (char)runif(65, 90);
    str += rnd;
  }
  return str;
}

void randomCString(char* str, uint32_t length) {
  for (uint32_t i = 0; i < length; ++i) {
    char rnd = (char)runif(65, 90);
    str[i] = rnd;
  }
  str[length] = '\0';
}

painlessmesh::protocol::Single createSingle(int length = -1) {
  auto pkg = painlessmesh::protocol::Single();
  pkg.dest = runif(0, std::numeric_limits<int>::max());
  pkg.from = runif(0, std::numeric_limits<int>::max());

  if (length < 0) length = runif(0, 4096);
  pkg.msg = randomString(length);
  return pkg;
}

painlessmesh::protocol::Broadcast createBroadcast(int length = -1) {
  auto pkg = painlessmesh::protocol::Broadcast();
  pkg.dest = runif(0, std::numeric_limits<int>::max());
  pkg.from = runif(0, std::numeric_limits<int>::max());
  if (length < 0) length = runif(0, 4096);
  pkg.msg = randomString(length);
  return pkg;
}

/*
```
{
  "dest": ...,
  "from": ...,
  "type": ...,
  "subs": [
    {
      "nodeId": ...,
      "root" : true,
      "subs": [
        {
          "nodeId": ...,
          "subs": []
        }
      ]
    }
   ]
}
```
*/
painlessmesh::protocol::NodeTree createNodeTree(int nodes, int contains_root) {
  auto pkg = painlessmesh::protocol::NodeTree();
  pkg.nodeId = runif(0, std::numeric_limits<int>::max());
  if (contains_root == 0) {
    pkg.root = true;
  }
  --nodes;  // The current node
  --contains_root;
  auto noSubs = runif(1, 5);
  for (uint32_t i = 0; i < noSubs; ++i) {
    if (nodes > 0) {
      if (i == noSubs - 1) {
        pkg.subs.push_back(createNodeTree(nodes, contains_root));
      } else {
        auto newNodes = 1 + rbinom(nodes - 1, 1.0 / noSubs);
        nodes -= newNodes;
        if (newNodes > 0)
          pkg.subs.push_back(createNodeTree(newNodes, contains_root));
        contains_root -= newNodes;
      }
    }
  }
  return pkg;
}

painlessmesh::protocol::NodeSyncReply createNodeSyncReply(
    int nodes = -1, bool contains_root = true) {
  auto pkg = painlessmesh::protocol::NodeSyncReply();
  pkg.dest = runif(0, std::numeric_limits<int>::max());
  pkg.from = runif(0, std::numeric_limits<int>::max());
  if (nodes < 0) nodes = runif(1, 254);
  auto rt = -1;
  if (contains_root) rt = runif(0, nodes - 1);
  auto ns = createNodeTree(nodes, rt);
  pkg.subs = ns.subs;
  pkg.nodeId = ns.nodeId;
  pkg.root = ns.root;
  return pkg;
}

painlessmesh::protocol::NodeSyncRequest createNodeSyncRequest(
    int nodes = -1, bool contains_root = true) {
  auto pkg = painlessmesh::protocol::NodeSyncRequest();
  pkg.dest = runif(0, std::numeric_limits<int>::max());
  pkg.from = runif(0, std::numeric_limits<int>::max());
  if (nodes < 0) nodes = runif(1, 254);
  auto rt = -1;
  if (contains_root) rt = runif(0, nodes - 1);
  auto ns = createNodeTree(nodes, rt);
  pkg.subs = ns.subs;
  pkg.nodeId = ns.nodeId;
  pkg.root = ns.root;
  return pkg;
}

/*
```
{
    "dest": 887034362,
    "from": 37418,
    "type":4,
    "msg":{
         "type":0
    }
}

{
    "dest": 887034362,
    "from": 37418,
    "type":4,
    "msg":{
        "type":1,
        "t0":32990
    }
}

{
    "dest": 37418,
    "from": 887034362,
    "type":4,
    "msg":{
        "type":2,
        "t0":32990,
        "t1":448585896,
        "t2":448596056,
    }
}
```
*/
painlessmesh::protocol::TimeSync createTimeSync(int type = -1) {
  auto pkg = painlessmesh::protocol::TimeSync();
  pkg.dest = runif(0, std::numeric_limits<int>::max());
  pkg.from = runif(0, std::numeric_limits<int>::max());

  if (type < 0) type = runif(0, 2);
  pkg.msg.type = type;
  auto t = runif(0, std::numeric_limits<int>::max());
  if (type >= 1) pkg.msg.t0 = t;
  if (type >= 2) {
    t += runif(0, 10000);
    pkg.msg.t1 = t;
    t += runif(0, 10000);
    pkg.msg.t2 = t;
  }
  return pkg;
}

painlessmesh::protocol::TimeDelay createTimeDelay(int type = -1) {
  auto pkg = painlessmesh::protocol::TimeDelay();
  pkg.dest = runif(0, std::numeric_limits<int>::max());
  pkg.from = runif(0, std::numeric_limits<int>::max());

  if (type < 0) type = runif(0, 2);
  pkg.msg.type = type;
  auto t = runif(0, std::numeric_limits<int>::max());
  if (type == 1) pkg.msg.t0 = t;
  if (type == 2) {
    t += runif(0, 10000);
    pkg.msg.t1 = t;
    t += runif(0, 10000);
    pkg.msg.t2 = t;
  }
  return pkg;
}

#endif
