/*
 * %CopyrightBegin%
 * 
 * Copyright Ericsson AB 1998-2009. All Rights Reserved.
 * 
 * The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved online at http://www.erlang.org/.
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * %CopyrightEnd%
 *

 */
#include <stdlib.h>
#include <string.h>
#include "hash.h"

/* this function returns a bucket - from the freelist if one was found
 * there, or from malloc(). Only "small" buckets, i.e. those whose
 * keys are short enough to be stored in the bucket itself, are saved
 * on the freelist.
 */
static ei_bucket *ei_hash_bmalloc(ei_hash *tab)
{
  ei_bucket *new;
  
  if (tab->freelist) {
    new = tab->freelist;
    tab->freelist = new->next;
    /* fprintf(stderr,"getting bucket from freelist\n"); */
  }
  else {
    new = malloc(sizeof(*new));
    /* fprintf(stderr,"allocating new (small) bucket\n"); */
  }

  return new;
}

/* insert a new key-value pair. The old value (if any) is returned. If
 * the malloc fails the function returns NULL. This is potentially a
 * problem since the function returns the same thing when malloc fails
 * as when a item is inserted that did not previously exist in the
 * table. */
void *ei_hash_insert(ei_hash *tab, const char *key, const void *value) 
{
  const void *oldval=NULL;
  ei_bucket *b=NULL;
  int h, rh;

  rh = tab->hash(key);
  h =  rh % tab->size;

  b=tab->tab[h];
  while (b) {
    if ((rh == b->rawhash) && (!strcmp(key,b->key)))
      break;
    b=b->next;
  }

  if (b) {
    /* replace existing value, return old value */
    oldval = b->value;
    b->value = value;
  }
  else {
    int keylen = strlen(key);
    
    /* this element is new */
    if (keylen < EI_SMALLKEY) {
      /* short keys stored directly in bucket */
      /* try to get bucket from freelist */
      if ((b = ei_hash_bmalloc(tab)) == NULL) return NULL;
      b->key = b->keybuf;
    }
    else {
      /* for longer keys we allocate space */
      int keypos=sizeof(*b);

      ei_align(keypos);
      if ((b = malloc(keypos+keylen+1)) == NULL) return NULL;
      b->key = (char *)b + keypos;
      /* fprintf(stderr,"allocating new (large) bucket\n"); */
    }

    /* fill in the blanks */
    b->rawhash = rh;
    strcpy((char *)b->key,key);
    b->value = value;

    /* some statistiscs */
    if (!tab->tab[h]) tab->npos++;
    tab->nelem++;

    /* link in the new element */
    b->next = tab->tab[h];
    tab->tab[h] = b;
  }
  return (void *)oldval;
}

