#include <glib.h>
#include <stdlib.h>

#define SIZE       50
#define NUMBER_MIN 0000
#define NUMBER_MAX 9999


static guint32 array[SIZE];


static gint
sort (gconstpointer p1, gconstpointer p2)
{
  gint32 a, b;

  a = GPOINTER_TO_INT (p1);
  b = GPOINTER_TO_INT (p2);

  return (a > b ? +1 : a == b ? 0 : -1);
}

/*
 * glist sort tests
 */
static void
test_list_sort (void)
{
  GList *list = NULL;
  gint   i;

  for (i = 0; i < SIZE; i++)
    list = g_list_append (list, GINT_TO_POINTER (array[i]));

  list = g_list_sort (list, sort);
  for (i = 0; i < SIZE - 1; i++)
    {
      gpointer p1, p2;

      p1 = g_list_nth_data (list, i);
      p2 = g_list_nth_data (list, i+1);

      g_assert (GPOINTER_TO_INT (p1) <= GPOINTER_TO_INT (p2));
    }

  g_list_free (list);
}

static void
test_list_sort_with_data (void)
{
  GList *list = NULL;
  gint   i;

  for (i = 0; i < SIZE; i++)
    list = g_list_append (list, GINT_TO_POINTER (array[i]));

  list = g_list_sort_with_data (list, (GCompareDataFunc)sort, NULL);
  for (i = 0; i < SIZE - 1; i++)
    {
      gpointer p1, p2;

      p1 = g_list_nth_data (list, i);
      p2 = g_list_nth_data (list, i+1);

      g_assert (GPOINTER_TO_INT (p1) <= GPOINTER_TO_INT (p2));
    }

  g_list_free (list);
}

/* Test that the sort is stable. */
static void
test_list_sort_stable (void)
{
  GList *list = NULL;  /* (element-type utf8) */
  GList *copy = NULL;  /* (element-type utf8) */
  gsize i;

  /* Build a test list, already ordered. */
  for (i = 0; i < SIZE; i++)
    list = g_list_append (list, g_strdup_printf ("%" G_GSIZE_FORMAT, i / 5));

  /* Take a copy and sort it. */
  copy = g_list_copy (list);
  copy = g_list_sort (copy, (GCompareFunc) g_strcmp0);

  /* Compare the two lists, checking pointers are equal to ensure the elements
   * have been kept stable. */
  for (i = 0; i < SIZE; i++)
    {
      gpointer p1, p2;

      p1 = g_list_nth_data (list, i);
      p2 = g_list_nth_data (list, i);

      g_assert (p1 == p2);
    }

  g_list_free (copy);
  g_list_free_full (list, g_free);
}

static void
test_list_insert_sorted (void)
{
  GList *list = NULL;
  gint   i;

  for (i = 0; i < SIZE; i++)
    list = g_list_insert_sorted (list, GINT_TO_POINTER (array[i]), sort);

  for (i = 0; i < SIZE - 1; i++)
    {
      gpointer p1, p2;

      p1 = g_list_nth_data (list, i);
      p2 = g_list_nth_data (list, i+1);

      g_assert (GPOINTER_TO_INT (p1) <= GPOINTER_TO_INT (p2));
    }

  g_list_free (list);
}

static void
test_list_insert_sorted_with_data (void)
{
  GList *list = NULL;
  gint   i;

  for (i = 0; i < SIZE; i++)
    list = g_list_insert_sorted_with_data (list,
                                           GINT_TO_POINTER (array[i]),
                                           (GCompareDataFunc)sort,
                                           NULL);

  for (i = 0; i < SIZE - 1; i++)
    {
      gpointer p1, p2;

      p1 = g_list_nth_data (list, i);
      p2 = g_list_nth_data (list, i+1);

      g_assert (GPOINTER_TO_INT (p1) <= GPOINTER_TO_INT (p2));
    }

  g_list_free (list);
}

static void
test_list_reverse (void)
{
  GList *list = NULL;
  GList *st;
  gint   nums[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  gint   i;

  for (i = 0; i < 10; i++)
    list = g_list_append (list, &nums[i]);

  list = g_list_reverse (list);

  for (i = 0; i < 10; i++)
    {
      st = g_list_nth (list, i);
      g_assert (*((gint*) st->data) == (9 - i));
    }

  g_list_free (list);
}

static void
test_list_nth (void)
{
  GList *list = NULL;
  GList *st;
  gint   nums[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  gint   i;

  for (i = 0; i < 10; i++)
    list = g_list_append (list, &nums[i]);

  for (i = 0; i < 10; i++)
    {
      st = g_list_nth (list, i);
      g_assert (*((gint*) st->data) == i);
    }

  g_list_free (list);
}

static void
test_list_concat (void)
{
  GList *list1 = NULL;
  GList *list2 = NULL;
  GList *st;
  gint   nums[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  gint i;

  for (i = 0; i < 5; i++)
    {
      list1 = g_list_append (list1, &nums[i]);
      list2 = g_list_append (list2, &nums[i+5]);
    }

  g_assert_cmpint (g_list_length (list1), ==, 5);
  g_assert_cmpint (g_list_length (list2), ==, 5);

  list1 = g_list_concat (list1, list2);

  g_assert_cmpint (g_list_length (list1), ==, 10);

  for (i = 0; i < 10; i++)
    {
      st = g_list_nth (list1, i);
      g_assert (*((gint*) st->data) == i);
    }

  list2 = g_list_concat (NULL, list1);
  g_assert_cmpint (g_list_length (list2), ==, 10);

  list2 = g_list_concat (list1, NULL);
  g_assert_cmpint (g_list_length (list2), ==, 10);

  list2 = g_list_concat (NULL, NULL);
  g_assert (list2 == NULL);

  g_list_free (list1);
}

static void
test_list_remove (void)
{
  GList *list = NULL;
  GList *st;
  gint   nums[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  gint   i;

  for (i = 0; i < 10; i++)
    {
      list = g_list_append (list, &nums[i]);
      list = g_list_append (list, &nums[i]);
    }

  g_assert_cmpint (g_list_length (list), ==, 20);

  for (i = 0; i < 10; i++)
    {
      list = g_list_remove (list, &nums[i]);
    }

  g_assert_cmpint (g_list_length (list), ==, 10);

  for (i = 0; i < 10; i++)
    {
      st = g_list_nth (list, i);
      g_assert (*((gint*) st->data) == i);
    }

  g_list_free (list);
}

static void
test_list_remove_all (void)
{
  GList *list = NULL;
  gint   nums[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  gint   i;

  for (i = 0; i < 10; i++)
    {
      list = g_list_append (list, &nums[i]);
      list = g_list_append (list, &nums[i]);
    }

  g_assert_cmpint (g_list_length (list), ==, 20);

  for (i = 0; i < 5; i++)
    {
      list = g_list_remove_all (list, &nums[2 * i + 1]);
      list = g_list_remove_all (list, &nums[8 - 2 * i]);
    }

  g_assert_cmpint (g_list_length (list), ==, 0);
  g_assert (list == NULL);
}

static void
test_list_first_last (void)
{
  GList *list = NULL;
  GList *st;
  gint   nums[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  gint   i;

  for (i = 0; i < 10; i++)
    list = g_list_append (list, &nums[i]);

  st = g_list_last (list);
  g_assert (*((gint*) st->data) == 9);
  st = g_list_nth_prev (st, 3);
  g_assert (*((gint*) st->data) == 6);
  st = g_list_first (st);
  g_assert (*((gint*) st->data) == 0);

  g_list_free (list);
}

static void
test_list_insert (void)
{
  GList *list = NULL;
  GList *st;
  gint   nums[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  gint   i;

  list = g_list_insert_before (NULL, NULL, &nums[1]);
  list = g_list_insert (list, &nums[3], 1);
  list = g_list_insert (list, &nums[4], -1);
  list = g_list_insert (list, &nums[0], 0);
  list = g_list_insert (list, &nums[5], 100);
  list = g_list_insert_before (list, NULL, &nums[6]);
  list = g_list_insert_before (list, list->next->next, &nums[2]);

  list = g_list_insert (list, &nums[9], 7);
  list = g_list_insert (list, &nums[8], 7);
  list = g_list_insert (list, &nums[7], 7);

  for (i = 0; i < 10; i++)
    {
      st = g_list_nth (list, i);
      g_assert (*((gint*) st->data) == i);
    }

  g_list_free (list);
}

typedef struct
{
  gboolean freed;
  int x;
} ListItem;

static void
free_func (gpointer data)
{
  ListItem *item = data;

  item->freed = TRUE;
}

static ListItem *
new_item (int x)
{
  ListItem *item;

  item = g_slice_new (ListItem);
  item->freed = FALSE;
  item->x = x;

  return item;
}

static void
test_free_full (void)
{
  ListItem *one, *two, *three;
  GSList *slist = NULL;
  GList *list = NULL;

  slist = g_slist_prepend (slist, one = new_item (1));
  slist = g_slist_prepend (slist, two = new_item (2));
  slist = g_slist_prepend (slist, three = new_item (3));
  g_assert (!one->freed);
  g_assert (!two->freed);
  g_assert (!three->freed);
  g_slist_free_full (slist, free_func);
  g_assert (one->freed);
  g_assert (two->freed);
  g_assert (three->freed);
  g_slice_free (ListItem, one);
  g_slice_free (ListItem, two);
  g_slice_free (ListItem, three);

  list = g_list_prepend (list, one = new_item (1));
  list = g_list_prepend (list, two = new_item (2));
  list = g_list_prepend (list, three = new_item (3));
  g_assert (!one->freed);
  g_assert (!two->freed);
  g_assert (!three->freed);
  g_list_free_full (list, free_func);
  g_assert (one->freed);
  g_assert (two->freed);
  g_assert (three->freed);
  g_slice_free (ListItem, one);
  g_slice_free (ListItem, two);
  g_slice_free (ListItem, three);
}

static void
test_list_copy (void)
{
  GList *l, *l2;
  GList *u, *v;

  l = NULL;
  l = g_list_append (l, GINT_TO_POINTER (1));
  l = g_list_append (l, GINT_TO_POINTER (2));
  l = g_list_append (l, GINT_TO_POINTER (3));

  l2 = g_list_copy (l);

  for (u = l, v = l2; u && v; u = u->next, v = v->next)
    {
      g_assert (u->data == v->data);
    }

  g_list_free (l);
  g_list_free (l2);
}

static gpointer
multiply_value (gconstpointer value, gpointer data)
{
  return GINT_TO_POINTER (GPOINTER_TO_INT (value) * GPOINTER_TO_INT (data));
}

static void
test_list_copy_deep (void)
{
  GList *l, *l2;
  GList *u, *v;

  l = NULL;
  l = g_list_append (l, GINT_TO_POINTER (1));
  l = g_list_append (l, GINT_TO_POINTER (2));
  l = g_list_append (l, GINT_TO_POINTER (3));

  l2 = g_list_copy_deep (l, multiply_value, GINT_TO_POINTER (2));

  for (u = l, v = l2; u && v; u = u->next, v = v->next)
    {
      g_assert_cmpint (GPOINTER_TO_INT (u->data) * 2, ==, GPOINTER_TO_INT (v->data));
    }

  g_list_free (l);
  g_list_free (l2);
}

static void
test_delete_link (void)
{
  GList *l, *l2;

  l = NULL;
  l = g_list_append (l, GINT_TO_POINTER (1));
  l = g_list_append (l, GINT_TO_POINTER (2));
  l = g_list_append (l, GINT_TO_POINTER (3));

  l2 = l->next;

  l = g_list_delete_link (l, l2);
  g_assert (l->data == GINT_TO_POINTER (1));
  g_assert (l->next->data == GINT_TO_POINTER (3));

  g_list_free (l);
}

static void
test_prepend (void)
{
  gpointer a = "a";
  gpointer b = "b";
  gpointer c = "c";
  GList *l, *l2;

  l = NULL;
  l = g_list_prepend (l, c);
  l = g_list_prepend (l, a);

  g_assert (l->data == a);
  g_assert (l->next->data == c);
  g_assert (l->next->next == NULL);

  l2 = l->next;
  l2 = g_list_prepend (l2, b);
  g_assert (l2->prev == l);

  g_assert (l->data == a);
  g_assert (l->next->data == b);
  g_assert (l->next->next->data == c);
  g_assert (l->next->next->next == NULL);

  g_list_free (l);
}

static void
test_position (void)
{
  GList *l, *ll;
  char *a = "a";
  char *b = "b";
  char *c = "c";
  char *d = "d";

  l = NULL;
  l = g_list_append (l, a);
  l = g_list_append (l, b);
  l = g_list_append (l, c);

  ll = g_list_find (l, a);
  g_assert_cmpint (g_list_position (l, ll), ==, 0);
  g_assert_cmpint (g_list_index (l, a), ==, 0);
  ll = g_list_find (l, b);
  g_assert_cmpint (g_list_position (l, ll), ==, 1);
  g_assert_cmpint (g_list_index (l, b), ==, 1);
  ll = g_list_find (l, c);
  g_assert_cmpint (g_list_position (l, ll), ==, 2);
  g_assert_cmpint (g_list_index (l, c), ==, 2);

  ll = g_list_append (NULL, d);
  g_assert_cmpint (g_list_position (l, ll), ==, -1);
  g_assert_cmpint (g_list_index (l, d), ==, -1);

  g_list_free (l);
  g_list_free (ll);
}

static void
test_double_free (void)
{
  GList *list, *link;
  // Casts to size_t first ensure compilers won't warn about pointer casts that change size
  // MSVC's C4312 warning with /Wp64
  GList  intruder = { NULL, (gpointer)(size_t)0xDEADBEEF, (gpointer)(size_t)0xDEADBEEF };

  if (g_test_subprocess ())
    {
      list = NULL;
      list = g_list_append (list, "a");
      link = list = g_list_append (list, "b");
      list = g_list_append (list, "c");

      list = g_list_remove_link (list, link);
      link->prev = list;
      link->next = &intruder;
      list = g_list_remove_link (list, link);

      g_list_free (list);
      return;
    }

  g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_DEFAULT);
  g_test_trap_assert_failed ();
  g_test_trap_assert_stderr ("*corrupted double-linked list detected*");
}

static void
test_list_insert_before_link (void)
{
  GList a = {0};
  GList b = {0};
  GList c = {0};
  GList d = {0};
  GList e = {0};
  GList *list;

  list = g_list_insert_before_link (NULL, NULL, &a);
  g_assert_nonnull (list);
  g_assert_true (list == &a);
  g_assert_null (a.prev);
  g_assert_null (a.next);
  g_assert_cmpint (g_list_length (list), ==, 1);

  list = g_list_insert_before_link (list, &a, &b);
  g_assert_nonnull (list);
  g_assert_true (list == &b);
  g_assert_null (b.prev);
  g_assert_true (b.next == &a);
  g_assert_true (a.prev == &b);
  g_assert_null (a.next);
  g_assert_cmpint (g_list_length (list), ==, 2);

  list = g_list_insert_before_link (list, &a, &c);
  g_assert_nonnull (list);
  g_assert_true (list == &b);
  g_assert_null (b.prev);
  g_assert_true (b.next == &c);
  g_assert_true (c.next == &a);
  g_assert_true (c.prev == &b);
  g_assert_true (a.prev == &c);
  g_assert_null (a.next);
  g_assert_cmpint (g_list_length (list), ==, 3);

  list = g_list_insert_before_link (list, &b, &d);
  g_assert_nonnull (list);
  g_assert_true (list == &d);
  g_assert_null (d.prev);
  g_assert_true (b.prev == &d);
  g_assert_true (c.prev == &b);
  g_assert_true (a.prev == &c);
  g_assert_true (d.next == &b);
  g_assert_true (b.next == &c);
  g_assert_true (c.next == &a);
  g_assert_null (a.next);
  g_assert_cmpint (g_list_length (list), ==, 4);

  list = g_list_insert_before_link (list, NULL, &e);
  g_assert_nonnull (list);
  g_assert_true (list == &d);
  g_assert_null (d.prev);
  g_assert_true (b.prev == &d);
  g_assert_true (c.prev == &b);
  g_assert_true (a.prev == &c);
  g_assert_true (d.next == &b);
  g_assert_true (b.next == &c);
  g_assert_true (c.next == &a);
  g_assert_true (a.next == &e);
  g_assert_true (e.prev == &a);
  g_assert_null (e.next);
  g_assert_cmpint (g_list_length (list), ==, 5);
}

int
main (int argc, char *argv[])
{
  gint i;

  g_test_init (&argc, &argv, NULL);

  /* Create an array of random numbers. */
  for (i = 0; i < SIZE; i++)
    array[i] = g_test_rand_int_range (NUMBER_MIN, NUMBER_MAX);

  g_test_add_func ("/list/sort", test_list_sort);
  g_test_add_func ("/list/sort-with-data", test_list_sort_with_data);
  g_test_add_func ("/list/sort/stable", test_list_sort_stable);
  g_test_add_func ("/list/insert-before-link", test_list_insert_before_link);
  g_test_add_func ("/list/insert-sorted", test_list_insert_sorted);
  g_test_add_func ("/list/insert-sorted-with-data", test_list_insert_sorted_with_data);
  g_test_add_func ("/list/reverse", test_list_reverse);
  g_test_add_func ("/list/nth", test_list_nth);
  g_test_add_func ("/list/concat", test_list_concat);
  g_test_add_func ("/list/remove", test_list_remove);
  g_test_add_func ("/list/remove-all", test_list_remove_all);
  g_test_add_func ("/list/first-last", test_list_first_last);
  g_test_add_func ("/list/insert", test_list_insert);
  g_test_add_func ("/list/free-full", test_free_full);
  g_test_add_func ("/list/copy", test_list_copy);
  g_test_add_func ("/list/copy-deep", test_list_copy_deep);
  g_test_add_func ("/list/delete-link", test_delete_link);
  g_test_add_func ("/list/prepend", test_prepend);
  g_test_add_func ("/list/position", test_position);
  g_test_add_func ("/list/double-free", test_double_free);

  return g_test_run ();
}
