/*

  sshadt_map.c

  Author: Antti Huima <huima@ssh.fi>

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

  Created Thu Sep  9 15:41:50 1999.

  */

#include "sshincludes.h"
#include "sshadt_i.h"
#include "sshadt_map_i.h"
#include "sshadt_bag_i.h"
#include "sshadt_std_i.h"
#include "sshdebug.h"

#define SSH_DEBUG_MODULE "SshADTMap"

#define SSH_ADT_MAP_INITIAL_SLOTS 11

#define ROOT ((SshADTMapRoot *)(c->container_specific))
#define NODE(x) ((SshADTMapNode *)x)

static void *make_node(SshADTContainer c)
{
  SshADTMapENode *e;
  e = ssh_xmalloc(sizeof(*e));
  return e;
}

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

static void init_to_size(SshADTMapRoot *r, int size)
{
  int i;
  r->array_size = size;
  r->nodes = ssh_xmalloc(sizeof(r->nodes[0]) * r->array_size);
  for (i = 0; i < r->array_size; i++)
    {
      r->nodes[i] = NULL;
    }
}

static void init(SshADTContainer c)
{
  c->container_specific = ssh_xmalloc(sizeof(SshADTMapRoot));
  init_to_size(ROOT, SSH_ADT_MAP_INITIAL_SLOTS);
  ROOT->num_objects = 0;
}

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

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

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

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

static SshADTHandle next_rib_start(SshADTContainer c, int start_idx)
{
  int i;
  for (i = start_idx; i < ROOT->array_size; i++)
    {
      if (ROOT->nodes[i] != NULL)
        {
          return ROOT->nodes[i];
        }
    }  
  return SSH_ADT_INVALID;
}

/* $$METHOD(map, enumerate_start) */
/* $$METHOD(bag, enumerate_start) */
static SshADTHandle enum_start(SshADTContainer c)
{
  return next_rib_start(c, 0);
}

static SshADTHandle forward(SshADTContainer c, SshADTHandle h)
{
  SshADTMapNode *d = h;

  if (d->is_last_in_rib)
    {
      SshADTMapNode **ptr = d->u.rib_start;
      int idx = ((unsigned char *)ptr - (unsigned char *)(ROOT->nodes))
        / (sizeof(ROOT->nodes[0]));
      return next_rib_start(c, idx + 1);
    }
  else
    {
      return d->u.next;
    }
}

/* $$METHOD(map, enumerate_next) */
/* $$METHOD(bag, enumerate_next) */
static SshADTHandle enum_next(SshADTContainer c, SshADTHandle h)
{
  SSH_ASSERT (h != SSH_ADT_INVALID);
  return forward(c, h);
}

static void rehash(SshADTContainer c, size_t new_size);

static void insert(SshADTContainer c, SshADTMapNode *n)
{
  SshUInt32 hash_value;
  SSH_ADT_STD_HASH(c, n, hash_value);
  hash_value %= (ROOT->array_size);

  if (ROOT->nodes[hash_value] == NULL)
    {
      n->is_last_in_rib = TRUE;
      n->u.rib_start = &(ROOT->nodes[hash_value]);
      ROOT->nodes[hash_value] = n;
    }
  else
    {
      n->is_last_in_rib = FALSE;
      n->u.next = ROOT->nodes[hash_value];
      ROOT->nodes[hash_value] = n;
    }

  ROOT->num_objects++;

  if (ROOT->num_objects * 2 > ROOT->array_size)
    {
      rehash(c, ROOT->array_size * 2 + 1);
    }
}

static void rehash(SshADTContainer c, size_t new_size)
{
  SshADTMapNode **old;
  SshADTMapNode *n, *next;
  int old_size;
  int i;

  SSH_DEBUG(6, ("Rehashing, old size %ld, new size %ld, %d objects.",
                (long)ROOT->array_size, (long)new_size, ROOT->num_objects));
  old = ROOT->nodes;
  old_size = ROOT->array_size;
  init_to_size(ROOT, new_size);
  ROOT->num_objects = 0;

  for (i = 0; i < old_size; i++)
    {
      n = old[i];
      if (n == NULL) continue;
      while (1)
        {
          if (n->is_last_in_rib)
            {
              insert(c, n);       
              break;          
            }
          else
            {
              next = n->u.next;
              insert(c, n);
              n = next;
            }
        }
    }
  ssh_xfree(old);

  ROOT->array_size = new_size;

  SSH_DEBUG(6, ("Done rehash, %d objects present.", ROOT->num_objects));
}

static void my_insert_absolute(SshADTContainer c,
                               SshADTAbsoluteLocation location,
                               SshADTHandle object)
{
  SSH_ASSERT(location == SSH_ADT_DEFAULT);
  NODE(object)->image = NULL;
  insert(c, object);
}

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

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

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

/* $$METHOD(map, get) */
/* $$METHOD(bag, get) */
SSH_ADT_STD_GET(get)

/* $$METHOD(map, num_objects) */
/* $$METHOD(bag, num_objects) */
SSH_ADT_STD_NUM_OBJECTS(num_objects)

static SshADTHandle find_node(SshADTContainer c, void *object)
{
  SshADTMapNode *i;
  SshUInt32 hash_value = SSH_ADT_HASH_OBJECT(c, object) % (ROOT->array_size);
  void *result;

  if (ROOT->nodes[hash_value] == NULL) return SSH_ADT_INVALID;

  for (i = ROOT->nodes[hash_value]; ; i = i->u.next)
    {
      SSH_ADT_STD_GET_OBJECT_FROM_HANDLE(c, i, result);
      if (result == object)
        {
          return i;
        }

      if (i->is_last_in_rib)
        break;
    }
  return SSH_ADT_INVALID;
}

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

/* $$METHOD(map, get_handle_to_equal) */
/* $$METHOD(bag, get_handle_to_equal) */
static SshADTHandle to_equal(SshADTContainer c, void *object)
{
  SshADTMapNode *i;
  SshUInt32 hash_value = SSH_ADT_HASH_OBJECT(c, object) % (ROOT->array_size);
  int result;

  if (ROOT->nodes[hash_value] == NULL) return SSH_ADT_INVALID;

  for (i = ROOT->nodes[hash_value]; ; i = i->u.next)
    {      
      SSH_ADT_STD_COMPARE_H_O(c, i, object, result);
      if (!result)
        {
          return i;
        }

      if (i->is_last_in_rib)
        break;
    }
  return SSH_ADT_INVALID;  
}

static void my_detach(SshADTContainer c, SshADTHandle handle)
{
  SshADTMapNode *target;
  SshADTMapNode *prior;
  target = handle;

  /* We need to find the node prior to that being detached and changed
     that. This is a muddy algorithm. */

  prior = target;

  while (1)
    {
      /* If `prior' does not point to the last item in the chain... */
      if (!(prior->is_last_in_rib))
        {
          /* If the next item is the target... */
          if (prior->u.next == target)
            {
              /* If the target is last in the chain... */
              if (target->is_last_in_rib)
                {
                  /* ... then set the `prior' to be the last item in
                     the chain and copy the rib start backpointer from
                     `target'. */
                  prior->is_last_in_rib = TRUE;
                  prior->u.rib_start = target->u.rib_start;
                }
              else
                {
                  /* Otherwise `prior' won't be the last in the chain
                     and the next pointer is just copied. */
                  prior->u.next = target->u.next;             
                }
              break;            /* Stop scanning. */
            }
          /* Otherwise move to the next item, which exists because
             `prior' wasn't the last one in the chain. */
          prior = prior->u.next;
          continue;             /* Continue scanning. */
        }
      else
        {
          /* When `prior' IS the last item in the chain. */
          /* If `target' is actually the first one in the chain... */
          if (*(prior->u.rib_start) == target)
            {
              /* If `target' is also the last... */
              if (target->is_last_in_rib)
                {
                  /* ... then the chain is now empty. */
                  *(prior->u.rib_start) = NULL;
                }
              else
                {
                  /* Otherwise the chain start pointer must be changed
                     to point to the item after `target', which now
                     must exist. */
                  *(prior->u.rib_start) = target->u.next;
                }
              break;            /* Stop scanning. */
            }
          /* Otherwise set `prior' to be the first item in the chain... */
          prior = *(prior->u.rib_start);
          continue;             /* Continue scanning. */
        }
    }

  ROOT->num_objects--;
}

/* $$METHOD(map, detach) */
/* $$METHOD(bag, detach) */
SSH_ADT_STD_DETACH(detach, my_detach(c, handle);, free_node(c, node);)

static void realloc_prepare(SshADTContainer c, SshADTHandle old_handle)
{
  /* Remove temporarily from the map. */
  detach(c, old_handle);
}

static void reallocated(SshADTContainer c, SshADTHandle old_handle,
                        SshADTHandle new_handle)
{
  /* Insert back. This recomputes the hash because in general
     the hash value can have been changed. */
  insert(c, new_handle);
}

/* $$METHOD(map, reallocate) */
/* $$METHOD(bag, reallocate) */
SSH_ADT_STD_REALLOC(reallocate, realloc_prepare(c, oldh);,
                    reallocated(c, oldh, newh);)

/* $$METHOD(map, delet) */
/* $$METHOD(bag, delet) */
SSH_ADT_STD_DELETE(delet)

SshADTStaticData ssh_adt_map_static_data =
{
  {
    /* $$METHODS(map) */
    /* 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_absolute, /* 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 */
    NULL, /* get_handle_to_location */
    NULL, /* next */
    NULL, /* previous */
    enum_start, /* enumerate_start */
    enum_next, /* enumerate_next */
    to_equal, /* get_handle_to_equal */
    reallocate, /* reallocate */
    detach, /* detach */
    delet, /* delet */
    /* $$ENDMETHODS */
  },
  sizeof(SshADTMapNode),
  0
};

SshADTStaticData *ssh_adt_type_map = &ssh_adt_map_static_data;

SshADTStaticData ssh_adt_bag_static_data =
{
  {
    /* $$METHODS(bag) */
    /* 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_absolute, /* 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 */
    NULL, /* get_handle_to_location */
    NULL, /* next */
    NULL, /* previous */
    enum_start, /* enumerate_start */
    enum_next, /* enumerate_next */
    to_equal, /* get_handle_to_equal */
    reallocate, /* reallocate */
    detach, /* detach */
    delet, /* delet */
    /* $$ENDMETHODS */
  },
  sizeof(SshADTMapNode),
  0
};

SshADTStaticData *ssh_adt_type_bag = &ssh_adt_bag_static_data;

void ssh_adt_map_set_map(SshADTContainer c, SshADTHandle h,
                         void *image)
{
  SSH_ADT_CALL_HOOK(c, unmap, h);
  NODE(h)->image = image;
  SSH_ADT_CALL_HOOK(c, map, h);
}

void *ssh_adt_map_map(SshADTContainer c, SshADTHandle h)
{
  return NODE(h)->image;
}

void ssh_adt_map_changed(SshADTContainer c, SshADTHandle h)
{
  detach(c, h);
  insert(c, h);
}
