/*

  t-adt.c

  Author: Antti Huima <huima@ssh.fi>

  Copyright (c) 1999 SSH Communications Security, Finland
  All rights reserved.

  Created Thu Sep  9 13:27:19 1999.

  */

#include "sshincludes.h"
#include "sshadt_i.h"           /* Needed for testing the internals */
#include "sshadt_assoc.h"
#include "sshadt_list.h"
#include "sshadt_map.h"
#include "sshadt_array.h"
#include "sshadt_priority_queue.h"
#include "sshdebug.h"
#include "sshregression.h"

#define SSH_DEBUG_MODULE "SshADTTest"

#define T SSH_REGRESSION_TEST_TIME
#define TI SSH_REGRESSION_TEST_WITH_INIT
#define TN(d,f) T(d,f,())

/* It must hold that NUM_ITEMS and RELATIVE_PRIME are relatively prime. */

#define NUM_ITEMS 20000
#define RELATIVE_PRIME 17

static int destroyed;

typedef struct {
  int i;
  SshADTHeaderStruct header;
} IntRecord;

/* Generic destructors. */

static void myalloc_destructor(void *ptr, void *ctx)
{
  ssh_xfree(ptr);
  destroyed++;
}

static void liballoc_destructor(void *ptr, void *ctx)
{
  SSH_DEBUG(8, ("Liballoc destructor"));
  destroyed++;
}

/* Int ptr hashing. */

static unsigned long int_hash(const void *ptr, void *ctx)
{
  return *((int *)ptr);
}

static int int_cmp(const void *ptr1, const void *ptr2, void *ctx)
{
  int a = *((int *)ptr1);
  int b = *((int *)ptr2);
  return a - b;
}

/* String hashing. */

static unsigned long str_hash(const void *ptr, void *ctx)
{
  const char *s = ptr;
  int i;
  int size = strlen(s);
  SshUInt32 h = 0;
  for (i = 0; i < size; i++)
    {
      h = ((h << 19) ^ (h >> 13)) + ((unsigned char *)s)[i];
    }
  return h;
}

static void *str_dup(void *ptr, void *ctx)
{
  return ssh_xstrdup(ptr);
}

static int str_cmp(const void *ptr1, const void *ptr2, void *ctx)
{
  return strcmp(ptr1, ptr2);
}

/* Tests for lists. */

/* List container creation. */

SshADTContainer create_list_voidptr(void)
{
  return ssh_adt_create_generic(SSH_ADT_LIST,
                                SSH_ADT_COMPARE, int_cmp,
                                SSH_ADT_DESTROY, myalloc_destructor,
                                SSH_ADT_ARGS_END);
}

SshADTContainer create_list_voidptr_with_header(void)
{
  return ssh_adt_create_generic(SSH_ADT_LIST,
                                SSH_ADT_COMPARE, int_cmp,
                                SSH_ADT_DESTROY, myalloc_destructor,
                                SSH_ADT_HEADER,
                                SSH_ADT_OFFSET_OF(IntRecord, header),
                                SSH_ADT_ARGS_END);
}

SshADTContainer create_list_liballoc(void)
{
  return ssh_adt_create_generic(SSH_ADT_LIST,
                                SSH_ADT_DESTROY, liballoc_destructor,
                                SSH_ADT_COMPARE, int_cmp,
                                SSH_ADT_SIZE, sizeof(int),
                                SSH_ADT_ARGS_END);
}

SshADTContainer create_list_liballoc_with_header(void)
{
  return ssh_adt_create_generic(SSH_ADT_LIST,
                                SSH_ADT_COMPARE, int_cmp,
                                SSH_ADT_DESTROY, liballoc_destructor,
                                SSH_ADT_SIZE, sizeof(IntRecord),
                                SSH_ADT_HEADER,
                                SSH_ADT_OFFSET_OF(IntRecord, header),
                                SSH_ADT_ARGS_END);
}

static void add_voidptr(SshADTContainer c, int i)
{
  int *ptr = ssh_xmalloc(sizeof(i));
  memcpy(ptr, &i, sizeof(i));
  ssh_adt_insert(c, ptr);
}

static void add_voidptr_with_header(SshADTContainer c, int i)
{
  IntRecord *ptr = ssh_xmalloc(sizeof(IntRecord));
  ptr->i = i;
  ssh_adt_insert(c, ptr);
}

static void add_liballoc(SshADTContainer c, int i)
{
  ssh_adt_put(c, &i);
}

static void add_liballoc_with_header(SshADTContainer c, int i)
{
  IntRecord rec;
  rec.i = i;
  ssh_adt_put(c, &rec);
}

static Boolean list_check(SshADTContainer (* create)(void),
                          void (* adder)(SshADTContainer, int))
{
  SshADTContainer c;
  int i;
  int *ptr;
  SshADTHandle handle;

  c = (*(create))();

  for (i = 0; i < NUM_ITEMS; i++)
    {
      (*(adder))(c, i);
    }

  i = 0;

  for (handle = ssh_adt_enumerate_start(c);
       handle != SSH_ADT_INVALID;
       handle = ssh_adt_enumerate_next(c, handle))
    {
      ptr = ssh_adt_get(c, handle);
      if (*ptr != i) return FALSE;
      i++;
    }

  handle = ssh_adt_get_handle_to_location(c, SSH_ADT_END);

  i = NUM_ITEMS - 1;

  for (; handle != SSH_ADT_INVALID; handle = ssh_adt_previous(c, handle))
    {
      ptr = ssh_adt_get(c, handle);
      if (*ptr != i) return FALSE;
      i--;
    }

  destroyed = 0;
  ssh_adt_destroy(c);
  if (destroyed != NUM_ITEMS) return FALSE;

  /* Check sorting. */

  c = (*(create))();

  for (i = 0; i < NUM_ITEMS; i++)
    {
      (*(adder))(c, (i * RELATIVE_PRIME) % NUM_ITEMS);
    }

  ssh_adt_list_sort(c);

  i = 0;

  for (handle = ssh_adt_enumerate_start(c);
       handle != SSH_ADT_INVALID;
       handle = ssh_adt_enumerate_next(c, handle))
    {
      ptr = ssh_adt_get(c, handle);
      if (*ptr != i) return FALSE;
      i++;
    }

  destroyed = 0;
  ssh_adt_destroy(c);
  if (destroyed != NUM_ITEMS) return FALSE;

  return TRUE;
}

/* Tests for maps. */

static Boolean map_check(void)
{
  SshADTContainer c;
  int i;
  int k;
  SshADTHandle h;

  c = ssh_adt_create_generic(SSH_ADT_MAP,
                             SSH_ADT_HASH, int_hash,
                             SSH_ADT_COMPARE, int_cmp,
                             SSH_ADT_DESTROY, liballoc_destructor,
                             SSH_ADT_SIZE, sizeof(int),
                             SSH_ADT_ARGS_END);

  for (i = 0; i < NUM_ITEMS; i++)
    {
      ssh_adt_put(c, &i);
      k = i;
      h = ssh_adt_get_handle_to_equal(c, &k);
      if (h == SSH_ADT_INVALID) return FALSE;
      ssh_adt_map_set_map(c, h, (void *)i);
    }

  for (i = 0; i < NUM_ITEMS; i++)
    {
      k = random() % NUM_ITEMS;
      h = ssh_adt_get_handle_to_equal(c, &k);
      if (h == SSH_ADT_INVALID) return FALSE;
      if (ssh_adt_map_map(c, h) != ((void *)k)) return FALSE;
    }

  destroyed = 0;
  ssh_adt_destroy(c);
  if (destroyed != NUM_ITEMS) return FALSE;
  return TRUE;
}

static Boolean strmap_check(void)
{
  SshADTContainer c;
  char buf[100];
  int i;
  int k;
  SshADTHandle h;

  c = ssh_adt_create_generic(SSH_ADT_MAP,
                             SSH_ADT_HASH, str_hash,
                             SSH_ADT_COMPARE, str_cmp,
                             SSH_ADT_DUPLICATE, str_dup,
                             SSH_ADT_DESTROY, myalloc_destructor,
                             SSH_ADT_ARGS_END);

  for (i = 0; i < NUM_ITEMS; i++)
    {
      snprintf(buf, sizeof(buf), "%d", i);
      ssh_adt_duplicate(c, buf);
      k = i;
      h = ssh_adt_get_handle_to_equal(c, buf);
      if (h == SSH_ADT_INVALID) return FALSE;
      ssh_adt_map_set_map(c, h, (void *)i);
    }

  for (i = 0; i < NUM_ITEMS; i++)
    {
      k = random() % NUM_ITEMS;
      snprintf(buf, sizeof(buf), "%d", k);
      h = ssh_adt_get_handle_to_equal(c, buf);
      if (h == SSH_ADT_INVALID) return FALSE;
      if (ssh_adt_map_map(c, h) != ((void *)k)) return FALSE;
    }

  destroyed = 0;
  ssh_adt_destroy(c);
  if (destroyed != NUM_ITEMS) return FALSE;
  return TRUE;
}

static int ref_count;

static void insert_hook(SshADTHandle h, void *ctx)
{
  ref_count++;
}

static void detach_hook(SshADTHandle h, void *ctx)
{
  ref_count++;
}

static Boolean hook_check(void)
{
  int i;
  SshADTContainer c = ssh_adt_create_generic(SSH_ADT_LIST,
                                             SSH_ADT_SIZE, sizeof(int),
                                             SSH_ADT_ARGS_END);

  ssh_adt_initialize_hooks(c);

  c->hooks->insert = insert_hook;
  c->hooks->detach = detach_hook;

  ref_count = 0;

  for (i = 0; i < NUM_ITEMS; i++)
    {
      ssh_adt_put(c, &i);
    }
  for (i = 0; i < NUM_ITEMS; i++)
    {
      ssh_adt_delete_from(c, SSH_ADT_END);
    }
  if (ref_count != 2 * NUM_ITEMS) return FALSE;
  ssh_adt_destroy(c);
  return TRUE;
}

static Boolean unimap_check(void)
{
  int i, k;
  int *p;
  SshADTHandle h1, h2;
  SshADTContainer c1 = ssh_adt_create_generic(SSH_ADT_MAP,
                                              SSH_ADT_HASH, int_hash,
                                              SSH_ADT_COMPARE, int_cmp,
                                              SSH_ADT_DESTROY,
                                              liballoc_destructor,
                                              SSH_ADT_SIZE, sizeof(int),
                                              SSH_ADT_ARGS_END);

  SshADTContainer c2 = ssh_adt_create_generic(SSH_ADT_LIST,
                                              SSH_ADT_DESTROY,
                                              liballoc_destructor,
                                              SSH_ADT_SIZE, sizeof(int),
                                              SSH_ADT_ARGS_END);

  ssh_adt_associate_unimap(c1, c2);

  destroyed = 0;

  for (i = 0; i < NUM_ITEMS; i++)
    {
      h1 = ssh_adt_put(c1, &i);
      for (k = i; k < i + 4; k++)
        {
          h2 = ssh_adt_put(c2, &k);
          SSH_DEBUG(9, ("h1=%p h2=%p\n", h1, h2));
          ssh_adt_map_set_map(c1, h1, h2);
        }      
    }

  if (destroyed != NUM_ITEMS * 3) return FALSE;

  for (i = 0; i < NUM_ITEMS; i++)
    {
      h1 = ssh_adt_get_handle_to_equal(c1, &i);
      if (h1 == SSH_ADT_INVALID) return FALSE;
      p = ssh_adt_get(c2, ssh_adt_map_map(c1, h1));
      if (p == NULL) return FALSE;
      if (*p != i + 3) return FALSE;
    }

  for (i = 0; i < NUM_ITEMS; i++)
    {
      ssh_adt_map_set_map(c1, ssh_adt_get_handle_to_equal(c1, &i),
                          SSH_ADT_INVALID);
    }

  if (destroyed != NUM_ITEMS * 4) return FALSE;
  ssh_adt_destroy(c1);
  ssh_adt_destroy(c2);
  if (destroyed != NUM_ITEMS * 5) return FALSE;
  return TRUE;
}


static Boolean bimap_check(void)
{
  int i, k;
  int *p;
  SshADTHandle h1, h2;
  SshADTContainer c1 = ssh_adt_create_generic(SSH_ADT_MAP,
                                              SSH_ADT_HASH, int_hash,
                                              SSH_ADT_COMPARE, int_cmp,
                                              SSH_ADT_DESTROY,
                                              liballoc_destructor,
                                              SSH_ADT_SIZE, sizeof(int),
                                              SSH_ADT_ARGS_END);

  SshADTContainer c2 = ssh_adt_create_generic(SSH_ADT_MAP,
                                              SSH_ADT_HASH, int_hash,
                                              SSH_ADT_COMPARE, int_cmp,
                                              SSH_ADT_DESTROY,
                                              liballoc_destructor,
                                              SSH_ADT_SIZE, sizeof(int),
                                              SSH_ADT_ARGS_END);

  ssh_adt_associate_bimap(c1, c2);

  destroyed = 0;

  for (i = 0; i < NUM_ITEMS; i++)
    {
      h1 = ssh_adt_put(c1, &i);
      k = i + 10;
      h2 = ssh_adt_put(c2, &k);
      ssh_adt_map_set_map(c1, h1, h2);
    }

  for (i = 0; i < NUM_ITEMS; i++)
    {

      k = i + 10;

      h2 = ssh_adt_get_handle_to_equal(c2, &k);
      if (h2 == SSH_ADT_INVALID) return FALSE;
      p = ssh_adt_get(c2, h2);
      if (p == NULL) return FALSE;
      if (*p != k) return FALSE;
      h1 = ssh_adt_map_map(c2, h2);
      if (h1 == SSH_ADT_INVALID) return FALSE;
      p = ssh_adt_get(c1, h1);
      if (p == NULL) return FALSE;
      if (*p != i) return FALSE;

      h1 = h2 = SSH_ADT_INVALID; p = NULL;

      h1 = ssh_adt_get_handle_to_equal(c1, &i);
      if (h1 == SSH_ADT_INVALID) return FALSE;
      p = ssh_adt_get(c1, h1);
      if (p == NULL) return FALSE;
      if (*p != i) return FALSE;
      h2 = ssh_adt_map_map(c1, h1);
      if (h2 == SSH_ADT_INVALID) return FALSE;
      p = ssh_adt_get(c2, h2);
      if (p == NULL) return FALSE;
      if (*p != k) return FALSE;
    }

  ssh_adt_destroy(c1);
  ssh_adt_destroy(c2);
  if (destroyed != NUM_ITEMS * 2) return FALSE;
  return TRUE;
}

static SshADTContainer create_array_voidptr(void)
{
  return ssh_adt_create_generic(SSH_ADT_ARRAY,
                                SSH_ADT_DESTROY, myalloc_destructor,
                                SSH_ADT_ARGS_END);
}

static SshADTContainer create_array_liballoc(void)
{
  return ssh_adt_create_generic(SSH_ADT_ARRAY,
                                SSH_ADT_SIZE, sizeof(int),
                                SSH_ADT_DESTROY, liballoc_destructor,
                                SSH_ADT_ARGS_END);
}

static void array_add_voidptr(SshADTContainer c, int idx, int data)
{
  int *obj = ssh_xmalloc(sizeof(*obj));
  *obj = data;
  ssh_adt_insert_to(c, SSH_ADT_INDEX(idx), obj);
}

static void array_add_liballoc(SshADTContainer c, int idx, int data)
{
  ssh_adt_put_to(c, SSH_ADT_INDEX(idx), &data);
}

static Boolean array_check(SshADTContainer (* create)(void),
                           void (* add)(SshADTContainer, int idx, int data))
{
  SshADTContainer c;
  int i; int q;
  int check[5000];

  for (i = 0; i < 5000; i++)
    {
      check[i] = -1;
    }

  c = (* create)();

  destroyed = 0;

  for (i = 0; i < NUM_ITEMS; i++)
    {
      q = random() % (500 + i);
      if (q >= 5000) q = 4999;
      (* add)(c, q, i);
      check[q] = i;

      if (i % 200 == 199)
        {
          SshADTHandle h;
          h = ssh_adt_enumerate_start(c);
          q = 0;
          while (h != SSH_ADT_INVALID && q < 5000)
            {
              int *obj;
              obj = ssh_adt_get(c, h);

              if ((obj != NULL && (*obj < 0 || *obj >= NUM_ITEMS))
                  || (obj == NULL && check[q] != -1)
                  || (obj != NULL && check[q] != *obj))
                {
                  fprintf(stderr, "%u %d %p %d %d\n", (unsigned int)h, q, obj,
                          check[q], obj==NULL ? -1 : *obj);
                  return FALSE;
                }

              h = ssh_adt_enumerate_next(c, h);
              q++;
            }
        }
    }

  ssh_adt_destroy(c);
  if (destroyed != NUM_ITEMS) return FALSE;
  return TRUE;
}

static Boolean pq_check(void)
{
  SshADTContainer pq, check_list;
  SshADTHandle ph, lh;
  int *a, *b;
  int i, item, count, j;

  check_list = create_list_liballoc();
  pq = ssh_adt_create_generic(SSH_ADT_PRIORITY_QUEUE,
                              SSH_ADT_SIZE, sizeof(int),
                              SSH_ADT_COMPARE, int_cmp,
                              SSH_ADT_DESTROY, liballoc_destructor,
                              SSH_ADT_ARGS_END);

  count = 0;

  destroyed = 0;

  for (i = 0; i < NUM_ITEMS; i++)
    {
      item = random() % 200;

      ssh_adt_put(check_list, &item);
      ssh_adt_put(pq, &item);

      count++;

      if (count > NUM_ITEMS/10)
        {
          if (!(count % 20))
            {
              ssh_adt_list_sort(check_list);
              for (j = 0; j < 20; j++)
                {
                  ph = ssh_adt_get_handle_to_location(pq, SSH_ADT_DEFAULT);
                  lh = ssh_adt_get_handle_to_location(check_list,
                                                      SSH_ADT_BEGINNING);
                  a = ssh_adt_get(pq, ph);
                  b = ssh_adt_get(check_list, lh);

                  if (*a != *b)
                    {
                      fprintf(stderr, "%d <=> %d\n", *a, *b);
                      return FALSE;
                    }
                  
                  ssh_adt_delete(pq, ph);
                  ssh_adt_delete(check_list, lh);
                }
            }
        }
    }

  ssh_adt_destroy(check_list);
  ssh_adt_destroy(pq);

  if (destroyed != 2 * NUM_ITEMS) return FALSE;

  return TRUE;
}

static void run_tests(void)
{
  ssh_regression_section("Lists");
  T("List, void ptrs w/ header",
    list_check, (create_list_voidptr_with_header,
                 add_voidptr_with_header));
  T("List, void ptrs", list_check, (create_list_voidptr,
                                            add_voidptr));
  T("List, lib allocated",
    list_check, (create_list_liballoc,
                 add_liballoc));
  T("List, lib allocated w/ header",
    list_check, (create_list_liballoc_with_header,
                 add_liballoc_with_header));

  ssh_regression_section("Maps");

  TN("Basic mapping", map_check);
  TN("String mapping", strmap_check);

  ssh_regression_section("Hooks");

  TN("Hooks", hook_check);

  ssh_regression_section("Hooked containers");

  TN("Unimap", unimap_check);
  TN("Bimap", bimap_check);

  ssh_regression_section("Dynamic arrays");

  T("Array, void ptrs",
    array_check, (create_array_voidptr,
                  array_add_voidptr));
  T("Array, lib allocated",
    array_check, (create_array_liballoc, array_add_liballoc));

  ssh_regression_section("Priority Queues");

  T("Priority Queues, lib allocated",
    pq_check, ());
}

int main(int argc, char **argv)
{
  ssh_regression_init(&argc, &argv, "ADT Library",
                      "huima@ssh.fi");
  run_tests();          
  ssh_regression_finish();
  /* Not reached. */
  exit(1);
}
