#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>

#include "doubly-linked-list.h"

#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif

#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1
#endif

/* Implementation */

typedef int T;

typedef struct ListNodeType
{
  T value;
  struct ListNodeType *next;
  struct ListNodeType *prev;
} ListNodeType;

ListNodeType * l_new_node (T value)
{
  ListNodeType *n = malloc (sizeof (ListNodeType));
  n->next = NULL;
  n->prev = NULL;
  n->value = value;
  return n;
}

typedef struct LinkedListWrapperType
{
  ListNodeType *first;
  ListNodeType *last;
  size_t size;
} LinkedListWrapperType;

int compare_nodes (const ListNodeType *n1, const ListNodeType *n2)
{
  if (n1->value == n2->value)
    return 0;
  else if (n1->value < n2->value)
    return -1;
  else
    return 1;
}

LINKED_LIST_MUTATIVE_OPS_PROTOTYPE (LinkedListWrapperType, ListNodeType, static);
LINKED_LIST_MERGE_SORT_PROTOTYPE (LinkedListWrapperType, ListNodeType, static);

LINKED_LIST_MUTATIVE_OPS_DECL (LinkedListWrapperType, ListNodeType, static)
LINKED_LIST_MERGE_SORT_DECL (LinkedListWrapperType, ListNodeType, static)

ListNodeType * find_last_node (ListNodeType *head)
{
  if (head == NULL)
    return NULL;

  ListNodeType *n = head;
  while (n->next != NULL)
    n = n->next;

  return n;
}

void l_print (ListNodeType *node)
{
  for (ListNodeType *l = node; l != NULL; l = l->next)
    printf ("%d ", l->value);
  printf ("\n");
}

void l_reverse_print (ListNodeType *last_node)
{
  for (ListNodeType *l = last_node; l != NULL; l = l->prev)
    printf ("%d ", l->value);
  printf ("\n");
}

struct test_data_t
{
  T const *content;
  size_t size;
};

bool run_test (const struct test_data_t *expect,
	       LinkedListWrapperType *current,
	       bool reversed)
{
  ListNodeType *node = (reversed) ? current->last : current->first;
  bool passed = true;
  for (int i=0; i<expect->size && node != NULL; ++i)
    {
      if (reversed)
	{
	  if (expect->content[expect->size - 1 - i] != node->value)
	    {
	      printf ("FAIL: mismatching expected (%d) VS current (%d).\n",
		      expect->content[expect->size - 1 - i], node->value);
	      passed = false;
	    }
	  if (node->prev == NULL && current->first != node)
	    {
	      printf ("FAIL: first is not matching the first node.\n");
	      passed = false;
	    }
	}
      else
	{
	  if (expect->content[i] != node->value)
	    {
	      printf ("FAIL: mismatching expected (%d) VS current (%d).\n",
		      expect->content[i], node->value);
	      passed = false;
	    }
	  if (node->next == NULL && current->last != node)
	    {
	      printf ("FAIL: last_ is not matching the last node.\n");
	      passed = false;
	    }
	}

      if (!passed)
	return false;

      if (reversed)
	node = node->prev;
      else
	node = node->next;
    }

  if (node != NULL)
    {
      printf ("FAIL: the list is longer than expected.\n");
      passed = false;
    }
  if (expect->size != current->size)
    {
      printf ("FAIL: size (%ld) is not matching the real size of the list (%ld).\n",
	      current->size, expect->size);
      passed = false;
    }

  return passed;
}

bool check(const char *op,
	  const struct test_data_t *expect,
	  LinkedListWrapperType *wrapper)
{
  bool success = true;
  bool res;

#define DUMP_LIST 0

  if (DUMP_LIST)
    l_print (wrapper->first);

  res = run_test (expect, wrapper, false);
  printf ("%s: test-linked-list::%s: check forward conformity\n",
	  res ? "PASS": "FAIL", op);
  success &= res;

  if (DUMP_LIST)
    l_reverse_print (wrapper->last);

  res = run_test (expect, wrapper, true);
  printf ("%s: test-linked-list::%s: check backward conformity\n",
	  res ? "PASS": "FAIL", op);
  success &= res;

  if (DUMP_LIST)
    printf("\n");

  return success;
}

const int EXPECT_0 [] = { 10, 4, 3, 1, 9, 2 };
const int EXPECT_1 [] = { 1, 2, 3, 4, 9, 10 };
const int EXPECT_2 [] = { 11, 1, 2, 3, 4, 9, 10 };
const int EXPECT_3 [] = { 11, 1, 2, 3, 4, 9, 8, 10 };
const int EXPECT_4 [] = { 11, 2, 3, 4, 9, 8, 10 };
const int EXPECT_5 [] = { 10, 2, 3, 4, 9, 8, 11 };
const int EXPECT_6 [] = { 10, 3, 2, 4, 9, 8, 11 };
const int EXPECT_7 [] = { 10, 9, 2, 4, 3, 8, 11 };
const int EXPECT_8 [] = { 2, 3, 4, 8, 9, 10, 11 };
const int EXPECT_9 [] = { 3, 4, 8, 9, 10, 11 };
const int EXPECT_10 [] = { 3, 4, 8, 9, 10 };
const int EXPECT_11 [] = { 20, 21, 22 };
const int EXPECT_12 [] = { 3, 4, 8, 9, 10 };
const struct test_data_t test_data[] = {
  { .content = EXPECT_0, .size = sizeof(EXPECT_0) / sizeof(EXPECT_0[0]) },
  { .content = EXPECT_1, .size = sizeof(EXPECT_1) / sizeof(EXPECT_1[0]) },
  { .content = EXPECT_2, .size = sizeof(EXPECT_2) / sizeof(EXPECT_2[0]) },
  { .content = EXPECT_3, .size = sizeof(EXPECT_3) / sizeof(EXPECT_3[0]) },
  { .content = EXPECT_4, .size = sizeof(EXPECT_4) / sizeof(EXPECT_4[0]) },
  { .content = EXPECT_5, .size = sizeof(EXPECT_5) / sizeof(EXPECT_5[0]) },
  { .content = EXPECT_6, .size = sizeof(EXPECT_6) / sizeof(EXPECT_6[0]) },
  { .content = EXPECT_7, .size = sizeof(EXPECT_7) / sizeof(EXPECT_7[0]) },
  { .content = EXPECT_8, .size = sizeof(EXPECT_8) / sizeof(EXPECT_8[0]) },
  { .content = EXPECT_9, .size = sizeof(EXPECT_9) / sizeof(EXPECT_9[0]) },
  { .content = EXPECT_10, .size = sizeof(EXPECT_10) / sizeof(EXPECT_10[0]) },
  { .content = EXPECT_11, .size = sizeof(EXPECT_11) / sizeof(EXPECT_11[0]) },
  { .content = EXPECT_12, .size = sizeof(EXPECT_12) / sizeof(EXPECT_12[0]) },
};

int main (void)
{
  int failures = 0;

  LinkedListWrapperType wrapper = {
    .first = NULL,
    .last = NULL,
    .size = 0,
  };

  /* Append nodes.  */
  LINKED_LIST_APPEND(ListNodeType) (&wrapper, l_new_node (10));
  LINKED_LIST_APPEND(ListNodeType) (&wrapper, l_new_node (4));
  LINKED_LIST_APPEND(ListNodeType) (&wrapper, l_new_node (3));
  LINKED_LIST_APPEND(ListNodeType) (&wrapper, l_new_node (1));
  LINKED_LIST_APPEND(ListNodeType) (&wrapper, l_new_node (9));
  LINKED_LIST_APPEND(ListNodeType) (&wrapper, l_new_node (2));

  failures += ! check ("append", &test_data[0], &wrapper);

  /* Sort nodes (without updating wrapper).  */
  wrapper.first =
    LINKED_LIST_MERGE_SORT_(ListNodeType) (wrapper.first, compare_nodes);
  wrapper.last = find_last_node (wrapper.first);

  failures += ! check ("sort", &test_data[1], &wrapper);

  /* Save a reference to this node for later.  */
  ListNodeType *n_to_remove = wrapper.first;

  /* Prepend node.  */
  LINKED_LIST_PREPEND(ListNodeType) (&wrapper, l_new_node (11));
  failures += ! check ("prepend", &test_data[2], &wrapper);

  /* Insert node.  */
  LINKED_LIST_INSERT_BEFORE(ListNodeType) (&wrapper, l_new_node (8), wrapper.last);
  failures += ! check ("insert_before", &test_data[3], &wrapper);

  /* Remove a node.  */
  LINKED_LIST_REMOVE(ListNodeType) (&wrapper, n_to_remove);
  failures += ! check ("remove", &test_data[4], &wrapper);

  /* Swap first and last.  */
  LINKED_LIST_SWAP(ListNodeType) (&wrapper, wrapper.first, wrapper.last);
  failures += ! check ("swap first and last", &test_data[5], &wrapper);

  /* Swap adjacent nodes.  */
  LINKED_LIST_SWAP(ListNodeType) (&wrapper, wrapper.first->next,
				  wrapper.first->next->next);
  failures += ! check ("swap adjacent nodes", &test_data[6], &wrapper);

  /* Swap non-adjacent nodes, but neither first nor last.  */
  LINKED_LIST_SWAP(ListNodeType) (&wrapper, wrapper.first->next,
				  wrapper.first->next->next->next->next);
  failures += ! check ("swap non-adjacent nodes", &test_data[7], &wrapper);

  /* Sort nodes.  */
  LINKED_LIST_MERGE_SORT(ListNodeType) (&wrapper, compare_nodes);
  failures += ! check ("sort", &test_data[8], &wrapper);

  /* Pop front.  */
  LINKED_LIST_POP_FRONT(ListNodeType) (&wrapper);
  failures += ! check ("pop_front", &test_data[9], &wrapper);

  /* Pop back.  */
  LINKED_LIST_POP_BACK(ListNodeType) (&wrapper);
  failures += ! check ("pop_back", &test_data[10], &wrapper);

  LinkedListWrapperType other_wrapper = {
    .first = NULL,
    .last = NULL,
    .size = 0,
  };

  LINKED_LIST_APPEND(ListNodeType) (&other_wrapper, l_new_node (20));
  LINKED_LIST_APPEND(ListNodeType) (&other_wrapper, l_new_node (21));
  LINKED_LIST_APPEND(ListNodeType) (&other_wrapper, l_new_node (22));

  /* Swap wrappers once.  */
  LINKED_LIST_SWAP_LISTS (LinkedListWrapperType) (&wrapper, &other_wrapper);
  failures += ! check ("swap wrappers once", &test_data[11], &wrapper);

  /* Swap wrappers twice.  */
  LINKED_LIST_SWAP_LISTS (LinkedListWrapperType) (&wrapper, &other_wrapper);
  failures += ! check ("swap wrappers twice", &test_data[12], &wrapper);

  exit (failures ? EXIT_FAILURE : EXIT_SUCCESS);
}
