/*

  sshadt_list.c

  Author: Antti Huima <huima@ssh.fi>

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

  Created Thu Sep  9 11:57:24 1999.

  */

#include "sshincludes.h"
#include "sshadt_i.h"
#include "sshadt_list_i.h"
#include "sshadt_std_i.h"
#include "sshdebug.h"

#define SSH_DEBUG_MODULE "SshADTList"

#define ROOT     ((SshADTListRoot *)(c->container_specific))

#define NODE(x)  ((SshADTListNode *)x)

static void *make_node(SshADTContainer c)
{
  void *ptr = ssh_xmalloc(sizeof(SshADTListENode));
  return ptr;
}

static void free_node(SshADTContainer c, void *node)
{
  ssh_xfree(node);
}

static void init(SshADTContainer c)
{
  c->container_specific = ssh_xmalloc(sizeof(*ROOT));
  ROOT->first_node = ROOT->last_node = NULL;
}

/* $$METHOD(list, container_init) */
SSH_ADT_STD_INIT(container_init, init(c);)


static void uninit(SshADTContainer c)
{
  while (ROOT->first_node != NULL)
    ssh_adt_delete_from(c, SSH_ADT_BEGINNING);
  ssh_xfree(ROOT);
}

/* $$METHOD(list, destroy) */
SSH_ADT_STD_DESTROY(destroy, uninit(c);)

/* $$METHOD(list, clear) */
static void clear(SshADTContainer c)
{
  while (ROOT->first_node != NULL)
    ssh_adt_delete_from(c, SSH_ADT_BEGINNING);
}

#if 0
static void sanity_dump(SshADTListRoot *r)
{
  SshADTListNode *n = r->first_node;
  while (n != NULL)
    {
      fprintf(stderr, "%p next:%p prev:%p\n", 
              n, n->next, n->prev);
      n = n->next;
    }
}

static void sanity_check(SshADTListRoot *r)
{
  SshADTListNode *n = r->first_node;
  while (n != NULL)
    {
      if ((n->prev != NULL && n->prev->next != n)
          ||
          (n->next != NULL && n->next->prev != n)
          ||
          (n->prev == NULL && r->first_node != n)
          ||
          (n->next == NULL && r->last_node != n))
        {
          fprintf(stderr, "Sanity check failed for node %p\n", n);
          sanity_dump(r);
          abort();
        }
      n = n->next;
    }
}
#endif

static void insert_at_beginning(SshADTListRoot *r,
                                SshADTListNode *n)
{
  SSH_ADT__LIST_INSERT_TO_BEGINNING(r, n);
}

static void insert_at_end(SshADTListRoot *r,
                          SshADTListNode *n)
{
  SSH_ADT__LIST_INSERT_TO_END(r, n);
}

static void insert_prior_to_node(SshADTListRoot *r,
                                 SshADTListNode *p,
                                 SshADTListNode *n)
{
  if (p->prev == NULL)
    {
      insert_at_beginning(r, n);
    }
  else
    {
      n->prev = p->prev;
      n->prev->next = n;
      n->next = p;
      p->prev = n;
    }
}

static void insert_after_node(SshADTListRoot *r,
                              SshADTListNode *p,
                              SshADTListNode *n)
{
  if (p->next == NULL)
    {
      insert_at_end(r, n);
    }
  else
    {
      n->next = p->next;
      n->next->prev = n;
      n->prev = p;
      p->next = n;
    }
}

static void my_insert_relative(SshADTContainer c, SshADTRelativeLocation l,
                               SshADTHandle where, SshADTHandle new)
{
  switch (l)
    {
    case SSH_ADT_BEFORE:
      insert_prior_to_node(ROOT, where, new); break;
    case SSH_ADT_AFTER:
      insert_after_node(ROOT, where, new); break;
    default:
      SSH_NOTREACHED;
    }
}

/* $$METHOD(list, insert_at) */
SSH_ADT_STD_INSERT_AT(insert_relative,
                      my_insert_relative(c, location, handle, h);,
                      __handle = make_node(c);)

static void my_insert_absolute(SshADTContainer c, SshADTAbsoluteLocation l,
                               SshADTHandle new)
{
  switch (l)
    {
    case SSH_ADT_BEGINNING:
      insert_at_beginning(ROOT, new);
      break;

    case SSH_ADT_END:
    case SSH_ADT_DEFAULT:
      insert_at_end(ROOT, new);
      break;

    default:
      SSH_NOTREACHED;
    }
}

/* $$METHOD(list, insert_to) */
SSH_ADT_STD_INSERT_TO(insert_absolute,
                      my_insert_absolute(c, location, h);,
                      __handle = make_node(c);)

/* $$METHOD(list, alloc_n_at) */
SSH_ADT_STD_ALLOC_N_AT(alloc_n_at,
                       my_insert_relative(c, location, handle, h);)

/* $$METHOD(list, alloc_n_to) */
SSH_ADT_STD_ALLOC_N_TO(alloc_n_to,
                       my_insert_absolute(c, location, h);)

/* $$METHOD(list, put_n_at) */
SSH_ADT_STD_PUT_N_AT(put_n_at,
                     my_insert_relative(c, location, handle, h);)

/* $$METHOD(list, put_n_to) */
SSH_ADT_STD_PUT_N_TO(put_n_to,
                     my_insert_absolute(c, location, h);)

/* $$METHOD(list, get) */
SSH_ADT_STD_GET(get)

/* $$METHOD(list, num_objects) */
SSH_ADT_STD_NUM_OBJECTS(num_objects)

static SshADTHandle find_node(SshADTContainer c, void *object)
{
  SshADTListNode *n = ROOT->first_node;

  while (n != NULL)
    {
      if (SSH_ADT_OBJECT_AT_NODE(n) == object)
        {
          return n;
        }
      n = n->next;
    }
  return SSH_ADT_INVALID;
}

/* $$METHOD(list, get_handle_to) */
SSH_ADT_STD_GET_HANDLE_TO(get_handle_to, handle = find_node(c, object);)

/* $$METHOD(list, get_handle_to_location) */
static SshADTHandle get_location(SshADTContainer c,
                                 SshADTAbsoluteLocation location)
{
  switch (location)
    {
    case SSH_ADT_BEGINNING:
      if (ROOT->first_node == NULL) return SSH_ADT_INVALID;
      return ROOT->first_node;

    case SSH_ADT_END:
    case SSH_ADT_DEFAULT:
      if (ROOT->last_node == NULL) return SSH_ADT_INVALID;
      return ROOT->last_node;

    default:
      SSH_NOTREACHED;
      return SSH_ADT_INVALID;
    }
}

/* $$METHOD(list, enumerate_start) */
static SshADTHandle enum_start(SshADTContainer c)
{
  return ROOT->first_node;
}

/* $$METHOD(list, enumerate_next) */
/* $$METHOD(list, next) */
static SshADTHandle enum_next(SshADTContainer c, SshADTHandle h)
{
  SSH_PRECOND(h != SSH_ADT_INVALID);
  h = NODE(h)->next;
  return h;
}

/* $$METHOD(list, previous) */
static SshADTHandle to_previous(SshADTContainer c, SshADTHandle h)
{
  SSH_PRECOND(h != SSH_ADT_INVALID);
  h = NODE(h)->prev;
  return h;
}

static void detach_at_beginning(SshADTListRoot *r)
{
  SshADTListNode *n;
  n = r->first_node;

  if (r->last_node == r->first_node)
    {
      SSH_ASSERT(n->next == NULL);
      r->first_node = NULL;
      r->last_node = NULL;
    }
  else
    {
      SSH_ASSERT(n->next != NULL);
      r->first_node = n->next;
      n->next->prev = NULL;
    }
}

static void detach_at_end(SshADTListRoot *r)
{
  SshADTListNode *n;
  n = r->last_node;

  if (r->last_node == r->first_node)
    {
      SSH_ASSERT(n->prev == NULL);
      r->first_node = NULL;
      r->last_node = NULL;
    }
  else
    {
      SSH_ASSERT(n->prev != NULL);
      r->last_node = n->prev;
      n->prev->next = NULL;
    }
}

static void detach_at(SshADTListRoot *r,
                      SshADTListNode *n)
{
  if (n == r->last_node)  { detach_at_end(r);       return; }
  if (n == r->first_node) { detach_at_beginning(r); return; }

  SSH_ASSERT(n->next != NULL);
  SSH_ASSERT(n->prev != NULL);
  SSH_ASSERT(n->next->prev == n);
  SSH_ASSERT(n->prev->next == n);

  n->next->prev = n->prev;
  n->prev->next = n->next;
}

/* $$METHOD(list, detach) */
SSH_ADT_STD_DETACH(detach, detach_at(ROOT, handle);, free_node(c, node);)

static void reallocated(SshADTContainer c, SshADTHandle old, SshADTHandle new)
{
  SshADTListNode *n = new;
  if (n->prev == NULL) ROOT->first_node = n;
  else n->prev->next = n;
  if (n->next == NULL) ROOT->last_node = n;
  else n->next->prev = n;
}

/* $$METHOD(list, reallocate) */
SSH_ADT_STD_REALLOC(reallocate, SSH_ADT_STD_NOTHING,
                    reallocated(c, oldh, newh);)

/* $$METHOD(list, delete) */
SSH_ADT_STD_DELETE(delete)

SshADTStaticData ssh_adt_list_static_data =
{
  {
    /* $$METHODS(list) */
    /* DO NOT EDIT THIS, edit METHODS.h and 
       the method implementations above instead. */
    container_init, /* container_init */
    clear, /* clear */
    destroy, /* destroy */
    insert_relative, /* insert_at */
    insert_absolute, /* insert_to */
    alloc_n_at, /* alloc_n_at */
    alloc_n_to, /* alloc_n_to */
    put_n_at, /* put_n_at */
    put_n_to, /* put_n_to */
    get, /* get */
    num_objects, /* num_objects */
    get_handle_to, /* get_handle_to */
    get_location, /* get_handle_to_location */
    enum_next, /* next */
    to_previous, /* previous */
    enum_start, /* enumerate_start */
    enum_next, /* enumerate_next */
    NULL, /* get_handle_to_equal */
    reallocate, /* reallocate */
    detach, /* detach */
    delete, /* delete */
    /* $$ENDMETHODS */
  },
  sizeof(SshADTListNode),
  0
};

const SshADTContainerType ssh_adt_list_type = &ssh_adt_list_static_data;

/* (The sorting code below was originally written by Mika Kojo.) */

#define MERGE(extra)                                                          \
  for (tail = NULL; t1 && t2; tail = t1, t1 = t1->next)                       \
    {                                                                         \
      SSH_ADT_STD_COMPARE(c, t1, t2, result);                                 \
      if (result > 0)                                                         \
        {                                                                     \
          /* Remove. */                                                       \
          t = t2;                                                             \
          t2 = t->next;                                                       \
                                                                              \
          /* Join. */                                                         \
          t->next = t1;                                                       \
          if (tail == NULL)                                                   \
            extra = t;                                                        \
          else                                                                \
            tail->next = t;                                                   \
          t1 = t;                                                             \
          continue;                                                           \
        }                                                                     \
    }                                                                         \
  if (t1 == NULL && tail)                                                     \
    tail->next = t2;

void ssh_adt_list_sort(SshADTContainer c)
{
  SshADTListNode *table[64];    /* Enough to sort around 2^64 entries... */
  SshADTListNode *cursor;
  SshADTListNode *t1, *t2, *t, *tail;
  int i;
  int result;

  SSH_ASSERT(c->static_data == ssh_adt_list_type);

  cursor = ROOT->first_node;

  if (cursor == NULL || cursor->next == NULL) return;
  
  /* Clear the table. */
  for (i = 0; i < 64; i++)
    table[i] = NULL;

  while (cursor != NULL)
    {
      SshADTListNode *op1, *op2;

      op1 = cursor;
      op2 = cursor->next;

      if (op2 != NULL)
        {
          cursor = op2->next; op2->next = NULL;
          SSH_ADT_STD_COMPARE(c, op1, op2, result);
          if (result > 0)
            {
              SshADTListNode *t;
              
              /* Swap. */
              t = op2; op2 = op1; op1 = t;

              /* Now op1 is smaller but later in the list. Must link. */
              op1->next = op2; op2->next = NULL;
            }
        }
      else
        {
          cursor = NULL;
        }

      if (table[0] == NULL)
        {
          table[0] = op1;
          continue;
        }

      for (i = 0; i < 64; i++)
        {
          if (table[i] == NULL)
            {
              table[i] = op1;
              break;
            }

          t1 = table[i];
          t2 = op1;
          op1 = t1;
          
          MERGE(op1); 
          
          table[i] = NULL;

          SSH_ASSERT(i < 64);
        }
    }

  /* Merge left-overs. */
  cursor = NULL;
  for (i = 0; i < 64; i++)
    {
      if (table[i] != NULL && cursor != NULL)
        {
          t1 = table[i];
          t2 = cursor;
          cursor = t1;
          
          MERGE(cursor);
          
          table[i] = NULL;
        }
      else
        {
          if (cursor == NULL)
            cursor = table[i];
        }
    }

  /* `cursor' now points to the first element. */

  ROOT->first_node = cursor;
  cursor->prev = NULL;
  while ((t1 = cursor->next) != NULL)
    {
      t1->prev = cursor;
      cursor = t1;
    }
  ROOT->last_node = cursor;
}
