/*

  sshadt_array.c

  Author: Antti Huima <huima@ssh.fi>

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

  Created Thu Oct 21 13:35:14 1999.

  */

#include "sshincludes.h"
#include "sshadt_i.h"
#include "sshadt_array.h"
#include "sshadt_array_i.h"
#include "sshadt_std_i.h"
#include "sshdebug.h"

#define SSH_DEBUG_MODULE "SshADTArray"

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

/* We need to add and subtract one because otherwise the index zero
   would actually correspond to SSH_ADT_INVALID, which would be
   confusing. */
#define PTRTOUINT(ptr) ((((unsigned char *)(ptr)) - ((unsigned char *)0)) - 1)
#define UINTTOPTR(i) (&(((unsigned char *)0)[i + 1]))

static void init(SshADTContainer c)
{
/* Handles are of type (void *)idx. Ugly, but who cares. */
  SSH_VERIFY(sizeof(void *) >= sizeof(unsigned int));
  /* The dynamic array type has zero-sized headers so they should
     not be `contained'. */
  SSH_ASSERT(!(c->flags & SSH_ADT_FLAG_CONTAINED_HEADER));
  c->container_specific = ssh_xmalloc(sizeof(*ROOT));
  ROOT->array = NULL;
  ROOT->array_size = 0;
}

/* $$METHOD(array, clear) */
static void clear(SshADTContainer c)
{
  int i;
  for (i = 0; i < ROOT->array_size; i++)
    {
      if (ROOT->array[i] != NULL)
        ssh_adt_delete(c, UINTTOPTR(i));
    }
}

/* $$METHOD(pq, clear) */
static void pq_clear(SshADTContainer c)
{
  SshADTHandle h = UINTTOPTR(0);

  while (c->ssh_adt_aux.num_objects > 0)
    {
      ssh_adt_delete(c, h);
    }
}

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

static void uninit(SshADTContainer c)
{
  clear(c);
  ssh_xfree(ROOT->array);
  ssh_xfree(ROOT);
}

static void pq_uninit(SshADTContainer c)
{
  pq_clear(c);
  ssh_xfree(ROOT->array);
  ssh_xfree(ROOT);
}

/* $$METHOD(array, destr) */
SSH_ADT_STD_DESTROY(destr, uninit(c);)

/* $$METHOD(pq, destr) */
SSH_ADT_STD_DESTROY(pq_destr, pq_uninit(c);)

/* After calling initialize_cell(c, n), the nth (0-based) cell is
   valid and empty. The array is expanded if necessary, and if there
   was an object at the nth cell, the object has been deleted. */
static void initialize_cell(SshADTContainer c, unsigned int idx)
{
  size_t i, old_size, new_size;
  void **array;

  if (ROOT->array_size <= idx)
    {
      old_size = ROOT->array_size;
      new_size = (idx + (idx / 4) + 1);
      ROOT->array = ssh_xrealloc(ROOT->array, new_size *
                                 sizeof(ROOT->array[0]));
      array = ROOT->array;
      for (i = old_size; i < new_size ; i++)
        {
          array[i] = NULL;
        } 
      ROOT->array_size = new_size;
      return;
    }
  else
    {
      if (ROOT->array[idx] != NULL)
        {
          ssh_adt_delete(c, UINTTOPTR(idx));
        }
    }
}

static unsigned int empty_idx(SshADTContainer c,
                              SshADTAbsoluteLocation location)
{
  unsigned int idx;
  SSH_ASSERT(SSH_ADT_IS_INDEX(location));
  idx = SSH_ADT_GET_INDEX(location);
  initialize_cell(c, idx);
  return idx;
}

/* $$METHOD(array, insert_to) */
static SshADTHandle insert_to(SshADTContainer c,
                              SshADTAbsoluteLocation location,
                              void *object)
{
  unsigned int idx;
  SshADTHandle h;
  idx = empty_idx(c, location);
  ROOT->array[idx] = object;
  c->ssh_adt_aux.num_objects++;
  h = UINTTOPTR(idx);
  SSH_ADT_CALL_HOOK(c, insert, h);
  return h;
}

static void percolate_up(SshADTContainer c, int idx)
{
  int parent;
  int cmp;
  void *tmp;

  while (idx > 0)
    {
      parent = (idx - 1) >> 1;
      cmp = SSH_ADT_CALL_APP_MANDATORY(c, compare,
                                       (ROOT->array[idx],
                                        ROOT->array[parent],
                                        SSH_ADT_APPCTX(c)));
      SSH_DEBUG(9, ("Comp %d %d == %d\n", idx, parent, cmp));
      if (cmp >= 0) return;
      tmp = ROOT->array[parent];
      ROOT->array[parent] = ROOT->array[idx];
      ROOT->array[idx] = tmp;
      idx = parent;
    }
}

static void percolate_down(SshADTContainer c, int idx)
{
  int left, right, child;
  int cmp, child_cmp;
  void *tmp;

  while (left = (idx << 1) + 1,
         right = left + 1,
         left < c->ssh_adt_aux.num_objects)
    {
      if (right < c->ssh_adt_aux.num_objects)
        {
          child_cmp =
            SSH_ADT_CALL_APP_MANDATORY(c, compare,
                                       (ROOT->array[left],
                                        ROOT->array[right],
                                        SSH_ADT_APPCTX(c)));
        }
      else
        {
          child_cmp = -1; /* use the left branch anyway */
        }
      if (child_cmp < 0) child = left; else child = right;

      cmp = SSH_ADT_CALL_APP_MANDATORY(c, compare,
                                       (ROOT->array[idx],
                                        ROOT->array[child],
                                        SSH_ADT_APPCTX(c)));
      if (cmp <= 0) return;
      tmp = ROOT->array[child];
      ROOT->array[child] = ROOT->array[idx];
      ROOT->array[idx] = tmp;

      idx = child;
    }
}

/* $$METHOD(pq, insert_to) */
static SshADTHandle pq_insert_to(SshADTContainer c,
                                 SshADTAbsoluteLocation location,
                                 void *object)
{
  unsigned int idx;

  SSH_ASSERT(location == SSH_ADT_DEFAULT);

  idx = c->ssh_adt_aux.num_objects; /* Add to the end. */
  initialize_cell(c, idx);
  ROOT->array[idx] = object;
  c->ssh_adt_aux.num_objects++;
  percolate_up(c, idx);
  return SSH_ADT_INVALID;       /* No handle actually. */
}

/* $$METHOD(array, get_handle_to_location) */
static SshADTHandle get_handle_to_location(SshADTContainer c,
                                           SshADTAbsoluteLocation location)
{
  SshADTHandle h = UINTTOPTR(SSH_ADT_GET_INDEX(location));
  return h;
}

/* $$METHOD(pq, get_handle_to_location) */
static SshADTHandle pq_get_handle_to_location(SshADTContainer c,
                                              SshADTAbsoluteLocation location)
{
  SSH_ASSERT(location == SSH_ADT_DEFAULT);
  return UINTTOPTR(0);
}

/* $$METHOD(array, alloc_n_to) */
static SshADTHandle alloc_n_to(SshADTContainer c,
                               SshADTAbsoluteLocation location,
                               size_t size)
{
  unsigned int idx;
  SshADTHandle h;
  void *newp;
  idx = empty_idx(c, location);
  ROOT->array[idx] = (newp = ssh_xmalloc(size));
  c->ssh_adt_aux.num_objects++;
  h = UINTTOPTR(idx);
  SSH_ADT_CALL_APP(c, init, (newp, size, SSH_ADT_APPCTX(c)));
  SSH_ADT_CALL_HOOK(c, insert, h);
  return h;
}

/* $$METHOD(array, put_n_to) */
static SshADTHandle put_n_to(SshADTContainer c,
                             SshADTAbsoluteLocation location,
                             size_t size,
                             void *object)
{
  unsigned int idx;
  SshADTHandle h;
  void *newp;
  idx = empty_idx(c, location);
  ROOT->array[idx] = (newp = ssh_xmalloc(size));
  c->ssh_adt_aux.num_objects++;
  h = UINTTOPTR(idx);
  SSH_ADT_CALL_APP(c, copy, (newp, size, object, SSH_ADT_APPCTX(c)));
  SSH_ADT_CALL_HOOK(c, insert, h);
  return h;
}

/* $$METHOD(pq, alloc_n_to) */
static SshADTHandle pq_alloc_n_to(SshADTContainer c,
                                  SshADTAbsoluteLocation location,
                                  size_t size)
{
  unsigned int idx;
  SshADTHandle h;
  void *newp;

  SSH_ASSERT(location == SSH_ADT_DEFAULT);

  idx = c->ssh_adt_aux.num_objects;
  initialize_cell(c, idx);

  ROOT->array[idx] = (newp = ssh_xmalloc(size));
  c->ssh_adt_aux.num_objects++;
  h = UINTTOPTR(idx);
  SSH_ADT_CALL_APP(c, init, (newp, size, SSH_ADT_APPCTX(c)));
  percolate_up(c, idx);
  return SSH_ADT_INVALID;
}

/* $$METHOD(pq, put_n_to) */
static SshADTHandle pq_put_n_to(SshADTContainer c,
                                SshADTAbsoluteLocation location,
                                size_t size,
                                void *object)
{
  unsigned int idx;
  SshADTHandle h;
  void *newp;

  SSH_ASSERT(location == SSH_ADT_DEFAULT);

  idx = c->ssh_adt_aux.num_objects;
  initialize_cell(c, idx);

  ROOT->array[idx] = (newp = ssh_xmalloc(size));
  c->ssh_adt_aux.num_objects++;
  h = UINTTOPTR(idx);
  SSH_ADT_CALL_APP(c, copy, (newp, size, object, SSH_ADT_APPCTX(c)));
  percolate_up(c, idx);
  return SSH_ADT_INVALID;
}

/* $$METHOD(array, get) */
/* $$METHOD(pq, get) */
static void *get(SshADTContainer c, SshADTHandle h)
{
  unsigned int idx = PTRTOUINT(h);
  SSH_ASSERT(idx < ROOT->array_size);
  return ROOT->array[idx];
}

/* $$METHOD(array, num_objects) */
/* $$METHOD(pq,    num_objects) */
SSH_ADT_STD_NUM_OBJECTS(num_objects)

/* $$METHOD(array, get_handle_to) */
static SshADTHandle get_handle_to(SshADTContainer c, void *object)
{
  unsigned int i;
  for (i = 0; i < ROOT->array_size; i++)
    {
      if (ROOT->array[i] == object)
        return UINTTOPTR(i);
    }
  return SSH_ADT_INVALID;
}

/* $$METHOD(array, enumerate_start) */
/* $$METHOD(pq, enumerate_start) */
static SshADTHandle enum_start(SshADTContainer c)
{
  SshADTHandle h = UINTTOPTR(0);
  if (ROOT->array_size == 0) return SSH_ADT_INVALID;
  return h;
}

/* $$METHOD(array, enumerate_next) */
static SshADTHandle enum_next(SshADTContainer c, SshADTHandle h)
{
  unsigned int idx = PTRTOUINT(h);
  SSH_ASSERT(h != SSH_ADT_INVALID);
  idx++;
  if (idx >= ROOT->array_size) return SSH_ADT_INVALID;
  return UINTTOPTR(idx);
}

/* $$METHOD(pq, enumerate_next) */
static SshADTHandle pq_enum_next(SshADTContainer c, SshADTHandle h)
{
  unsigned int idx = PTRTOUINT(h);
  SSH_ASSERT(h != SSH_ADT_INVALID);
  idx++;
  if (idx >= ROOT->array_size) return SSH_ADT_INVALID;
  if (ROOT->array[idx] == NULL) return SSH_ADT_INVALID;
  return UINTTOPTR(idx);
}

/* $$METHOD(array, detach) */
static void *detach(SshADTContainer c, SshADTHandle h)
{
  void *object;
  unsigned int idx = PTRTOUINT(h);

  SSH_ASSERT(h != SSH_ADT_INVALID);
  SSH_ASSERT(idx < ROOT->array_size);
  SSH_ASSERT(ROOT->array[idx] != NULL);

  SSH_ADT_CALL_HOOK(c, detach, h);

  c->ssh_adt_aux.num_objects--;

  object = ROOT->array[idx];
  ROOT->array[idx] = NULL;
  return object;
}

/* $$METHOD(pq, detach) */
static void *pq_detach(SshADTContainer c, SshADTHandle h)
{
  void *object;
  unsigned int idx = PTRTOUINT(h);

  SSH_ASSERT(h != SSH_ADT_INVALID);
  SSH_ASSERT(idx == 0);
  SSH_ASSERT(idx < ROOT->array_size);
  SSH_ASSERT(ROOT->array[idx] != NULL);

  SSH_ADT_CALL_HOOK(c, detach, h);
  
  c->ssh_adt_aux.num_objects--;
  object = ROOT->array[idx];
  ROOT->array[idx] = ROOT->array[c->ssh_adt_aux.num_objects];
  ROOT->array[c->ssh_adt_aux.num_objects] = NULL;
  percolate_down(c, idx);
  return object;
}

/* $$METHOD(array, reallocate) */
/* $$METHOD(pq, reallocate) */
static void *reallocate(SshADTContainer c, void *object, size_t new_size)
{
  ssh_fatal("Realloc does not work with arrays now.");
  return NULL;
}

/* $$METHOD(array, delet) */
/* $$METHOD(pq, delet) */
static void delet(SshADTContainer c, SshADTHandle handle)
{
  void *object = ssh_adt_detach_i(c, handle);
  SSH_ADT_CALL_APP(c, destr, (object, SSH_ADT_APPCTX(c)));  
  if (c->flags & SSH_ADT_FLAG_ALLOCATE)
    {
      ssh_xfree(object);
    }
}

SshADTStaticData ssh_adt_array_static_data = 
{
  {
    /* $$METHODS(array) */
    /* DO NOT EDIT THIS, edit METHODS.h and 
       the method implementations above instead. */
    container_init, /* container_init */
    clear, /* clear */
    destr, /* destr */
    NULL, /* insert_at */
    insert_to, /* insert_to */
    NULL, /* alloc_n_at */
    alloc_n_to, /* alloc_n_to */
    NULL, /* put_n_at */
    put_n_to, /* put_n_to */
    get, /* get */
    num_objects, /* num_objects */
    get_handle_to, /* get_handle_to */
    get_handle_to_location, /* get_handle_to_location */
    NULL, /* next */
    NULL, /* previous */
    enum_start, /* enumerate_start */
    enum_next, /* enumerate_next */
    NULL, /* get_handle_to_equal */
    reallocate, /* reallocate */
    detach, /* detach */
    delet, /* delet */
    /* $$ENDMETHODS */
  },
  0,
  0
};

SshADTStaticData ssh_adt_pq_static_data =
{
  {
    /* $$METHODS(pq) */
    /* DO NOT EDIT THIS, edit METHODS.h and 
       the method implementations above instead. */
    container_init, /* container_init */
    pq_clear, /* clear */
    pq_destr, /* destr */
    NULL, /* insert_at */
    pq_insert_to, /* insert_to */
    NULL, /* alloc_n_at */
    pq_alloc_n_to, /* alloc_n_to */
    NULL, /* put_n_at */
    pq_put_n_to, /* put_n_to */
    get, /* get */
    num_objects, /* num_objects */
    NULL, /* get_handle_to */
    pq_get_handle_to_location, /* get_handle_to_location */
    NULL, /* next */
    NULL, /* previous */
    enum_start, /* enumerate_start */
    pq_enum_next, /* enumerate_next */
    NULL, /* get_handle_to_equal */
    reallocate, /* reallocate */
    pq_detach, /* detach */
    delet, /* delet */
    /* $$ENDMETHODS */
  },
  0,
  0
};

const SshADTContainerType ssh_adt_array_type = &ssh_adt_array_static_data;
const SshADTContainerType ssh_adt_priority_queue_type = &ssh_adt_pq_static_data;

