/*
 *	Filters: Utility Functions Tests
 *
 *	(c) 2015 CZ.NIC z.s.p.o.
 *
 *	Can be freely distributed and used under the terms of the GNU GPL.
 */

#include "test/birdtest.h"
#include "test/bt-utils.h"

#include "filter/filter.h"
#include "filter/data.h"
#include "conf/conf.h"

#define MAX_TREE_HEIGHT 13

static struct f_tree *
new_tree(uint id)
{
  struct f_tree *tree = f_new_tree();
  tree->from.type  = tree->to.type  = T_INT;
  tree->from.val.i = tree->to.val.i = id;

  return tree;
}

/*
 * Show subtree in infix notation
 */
static void
show_subtree(struct f_tree *node)
{
  if (!node)
    return;

  show_subtree(node->left);

  if (node->from.val.i == node->to.val.i)
    bt_debug("%u ", node->from.val.i);
  else
    bt_debug("%u..%u ", node->from.val.i, node->to.val.i);

  show_subtree(node->right);
}

static void
show_tree2(struct f_tree *root_node, const char *tree_name)
{
  bt_debug("%s: \n", tree_name);
  bt_debug("[ ");
  show_subtree(root_node);
  bt_debug("]\n\n");
}

#define show_tree(tree) show_tree2(tree, #tree);

static uint
get_nodes_count_full_bin_tree(uint height)
{
  return (bt_naive_pow(2, height+1) - 1);
}

static struct f_tree *
get_balanced_full_subtree(uint height, uint idx)
{
  struct f_tree *node = new_tree(idx);
  if (height > 0)
  {
    uint nodes_in_subtree = get_nodes_count_full_bin_tree(--height);
    node->left  = get_balanced_full_subtree(height, idx - nodes_in_subtree/2 - 1);
    node->right = get_balanced_full_subtree(height, idx + nodes_in_subtree/2 + 1);
  }
  return node;
}

static struct f_tree *
get_balanced_full_tree(uint height)
{
  return get_balanced_full_subtree(height, get_nodes_count_full_bin_tree(height)/2);
}

static struct f_tree *
get_degenerated_left_tree(uint nodes_count)
{
  struct f_tree *old = NULL;
  struct f_tree *new = NULL;
  uint i;

  for (i = 0; i < nodes_count; i++)
  {
    old = new;
    new = new_tree(nodes_count-1-i);
    new->left = old;
  }

  return new;
}

static struct f_tree *
get_random_degenerated_left_tree(uint nodes_count)
{
  struct f_tree *tree = get_degenerated_left_tree(nodes_count);

  size_t avaible_indexes_size = nodes_count * sizeof(byte);
  byte *avaible_indexes = malloc(avaible_indexes_size);
  memset(avaible_indexes, 0, avaible_indexes_size);

  struct f_tree *n;
  for (n = tree; n; n = n->left)
  {
    uint selected_idx;
    do
    {
      selected_idx = bt_random() % nodes_count;
    } while(avaible_indexes[selected_idx] != 0);

    avaible_indexes[selected_idx] = 1;
    n->from.type  = n->to.type  = T_INT;
    n->from.val.i = n->to.val.i = selected_idx;
  }

  free(avaible_indexes);
  return tree;
}

static struct f_tree *
get_balanced_tree_with_ranged_values(uint nodes_count)
{
  struct f_tree *tree = get_degenerated_left_tree(nodes_count);

  uint idx = 0;
  struct f_tree *n;
  for (n = tree; n; n = n->left)
  {
    n->from.type = n->to.type = T_INT;
    n->from.val.i = idx;
    idx += (uint)bt_random() / nodes_count;	/* (... / nodes_count) preventing overflow an uint idx */
    n->to.val.i = idx++;
  }

  return build_tree(tree, true);
}


static int
t_balancing(void)
{
  uint height;
  for (height = 1; height < MAX_TREE_HEIGHT; height++)
  {
    uint nodes_count = get_nodes_count_full_bin_tree(height);

    struct f_tree *simple_degenerated_tree = get_degenerated_left_tree(nodes_count);
    show_tree(simple_degenerated_tree);

    struct f_tree *expected_balanced_tree = get_balanced_full_tree(height);
    show_tree(expected_balanced_tree);

    struct f_tree *balanced_tree_from_simple = build_tree(simple_degenerated_tree, true);
    show_tree(balanced_tree_from_simple);

    bt_assert(same_tree(balanced_tree_from_simple, expected_balanced_tree));

    tmp_flush();
  }

  return 1;
}


static int
t_balancing_random(void)
{
  uint height;
  for (height = 1; height < MAX_TREE_HEIGHT; height++)
  {
    uint nodes_count = get_nodes_count_full_bin_tree(height);

    struct f_tree *expected_balanced_tree = get_balanced_full_tree(height);

    uint i;
    for(i = 0; i < 10; i++)
    {
      struct lp_state *lps = lp_save(tmp_linpool);

      struct f_tree *random_degenerated_tree = get_random_degenerated_left_tree(nodes_count);
      show_tree(random_degenerated_tree);

      struct f_tree *balanced_tree_from_random = build_tree(random_degenerated_tree, true);

      show_tree(expected_balanced_tree);
      show_tree(balanced_tree_from_random);

      bt_assert(same_tree(balanced_tree_from_random, expected_balanced_tree));

      lp_restore(tmp_linpool, lps);
    }

    tmp_flush();
  }

  return 1;
}

static int
t_find(void)
{
  uint height;
  for (height = 1; height < MAX_TREE_HEIGHT; height++)
  {
    uint nodes_count = get_nodes_count_full_bin_tree(height);

    struct f_tree *tree = get_balanced_full_tree(height);
    show_tree(tree);

    struct f_val looking_up_value = {
	.type = T_INT
    };
    for(looking_up_value.val.i = 0; looking_up_value.val.i < nodes_count; looking_up_value.val.i++)
    {
      const struct f_tree *found_tree = find_tree(tree, &looking_up_value);
      bt_assert((val_compare(&looking_up_value, &(found_tree->from)) == 0) && (val_compare(&looking_up_value, &(found_tree->to)) == 0));
    }

    tmp_flush();
  }

  return 1;
}

static uint
get_max_value_in_unbalanced_tree(struct f_tree *node, uint max)
{
  if (!node)
    return max;

  if (node->to.val.i > max)
    max = node->to.val.i;

  uint max_left  = get_max_value_in_unbalanced_tree(node->left, max);
  if (max_left > max)
    max = max_left;

  uint max_right = get_max_value_in_unbalanced_tree(node->right, max);
  if (max_right > max)
    max = max_right;

  return max;
}

static int
t_find_ranges(void)
{
  uint height;
  for (height = 1; height < MAX_TREE_HEIGHT; height++)
  {
    uint nodes_count = get_nodes_count_full_bin_tree(height);

    struct f_tree *tree = get_balanced_tree_with_ranged_values(nodes_count);
    uint max_value = get_max_value_in_unbalanced_tree(tree, 0);

    show_tree(tree);

    bt_debug("max_value: %u \n", max_value);

    struct f_val needle = {
	.type = T_INT
    };
    uint *i = &needle.val.i;

    for(*i = 0; *i <= max_value; *i += (uint)bt_random()/nodes_count)
    {
      const struct f_tree *found_tree = find_tree(tree, &needle);
      bt_debug("searching: %u \n", *i);
      bt_assert(
	  (val_compare(&needle, &(found_tree->from)) == 0) || (val_compare(&needle, &(found_tree->to)) == 0) ||
	 ((val_compare(&needle, &(found_tree->from)) == 1) && (val_compare(&needle, &(found_tree->to)) == -1))
      );
    }

    tmp_flush();
  }

  return 1;
}

int
main(int argc, char *argv[])
{
  bt_init(argc, argv);
  bt_bird_init();
  cfg_mem = tmp_linpool;

  bt_test_suite(t_balancing, "Balancing strong unbalanced trees");
  bt_test_suite(t_balancing_random, "Balancing random unbalanced trees");
  bt_test_suite(t_find, "Finding values in trees");
  bt_test_suite(t_find_ranges, "Finding values in trees with random ranged values");

  return bt_exit_value();
}
