#include "stdlib.h"
#include "stdio.h"
#include "prb.h"

int fail_alloc_percent = 0;
int fail_alloc_rand = 0;

/**
 * Force MyMalloc and MyCalloc to return null pointers percentage of times.
 * e.g., after SetFailAlloc(50), MyMalloc and MyCalloc calls will return null
 * pointers 1 out of 2 times.
*/
void SetFailAlloc(unsigned int percentage)
{
   fail_alloc_percent = percentage;
   fail_alloc_rand = (int)(((unsigned long long)RAND_MAX)*percentage/100);
}

int FailAlloc()
{
   if (rand() < fail_alloc_rand) return 1;
   return 0;
}

void *MyMalloc(size_t size)
{
   if (FailAlloc()) {
      return NULL;
   } else {
      return malloc(size);
   }
}

void *MyCalloc(size_t nmemb, size_t size)
{
   if (FailAlloc()) {
      return NULL;
   } else {
      return calloc(nmemb, size);
   }
}

void MyFree(void *p)
{
   free(p);
}

int MyCompareFunc(const void* prb_a, const void* prb_b)
{
   int a = *((int *)(prb_a));
   int b = *((int *)(prb_b));
   return (a - b);
}

void *MyTableMalloc(struct libavl_allocator *allocator)
{
    return MyCalloc(1, sizeof(struct prb_table));
}

void MyTableFree(struct libavl_allocator *allocator, void *table)
{
   MyFree(table);
}

void *MyNodeMalloc(struct libavl_allocator *allocator)
{
    return MyCalloc(1, sizeof(struct prb_node));
}

void MyNodeFree(struct libavl_allocator *allocator, void *node)
{
   MyFree(node);
}

void* MyDataMalloc(void* data1, void* data2)
{
   int * p = MyMalloc(sizeof(int));
   if (p) {
      *p = *((int *)(data1));
   }
   return p;
}

void MyDataFree(void* data1, void*data2)
{
   MyFree(data1);
}

struct libavl_allocator MyAllocator = {.table_malloc = MyTableMalloc,
                                       .table_free   = MyTableFree,
                                       .node_malloc  = MyNodeMalloc,
                                       .node_free    = MyNodeFree};

struct prb_table* MyCreateTable()
{
   struct prb_table* tree = prb_create(&MyCompareFunc, &MyAllocator);
   if (!tree) {
      printf("couldn't create tree\n");
      return NULL;
   }
   int * p1 = malloc(sizeof(int));
   int * p2 = malloc(sizeof(int));
   if (!p1 || !p2) {
      printf("couldn't create nodes\n");
      prb_destroy(tree, MyDataFree);
      return NULL;
   }
   *p1 = 4;
   *p2 = 2;
   prb_probe(tree, p1);
   prb_probe(tree, p2);   
   return tree;
}
   
int main()
{
   struct prb_table* tree = MyCreateTable();
   if (!tree) {
      return -1;
   } else {
      printf("tree created\n");
   }

   /* after this function call, MyMalloc and MyCalloc calls will 
    *return NULL in 1 out 2 times
     */
   SetFailAlloc(50); 

   struct prb_table* new_tree = prb_copy(tree, MyDataMalloc, MyDataFree, NULL);
   if (!new_tree) {
      printf("failed to copy tree\n");
      prb_destroy(tree, MyDataFree);
   } else {
      printf("copied tree %p to %p\n", tree, new_tree);
      prb_destroy(tree, MyDataFree);
      prb_destroy(new_tree, MyDataFree);
   }
   return 0;
}

