/**************************************************************************/
/*                                                                        */
/*                                 OCaml                                  */
/*                                                                        */
/*                 Stephen Dolan, University of Cambridge                 */
/*                                                                        */
/*   Copyright 2015 University of Cambridge                               */
/*                                                                        */
/*   All rights reserved.  This file is distributed under the terms of    */
/*   the GNU Lesser General Public License version 2.1, with the          */
/*   special exception on linking described in the file LICENSE.          */
/*                                                                        */
/**************************************************************************/

#define CAML_INTERNALS

#include "caml/config.h"
#include "caml/memory.h"
#include "caml/addrmap.h"

#define MAX_CHAIN 100

Caml_inline uintnat pos_initial(struct addrmap* t, value key)
{
  uintnat pos = (uintnat)key;
  pos *= 0xcc9e2d51;
  pos ^= (pos >> 17);

  CAMLassert(Is_power_of_2(t->size));
  return pos & (t->size - 1);
}

Caml_inline uintnat pos_next(struct addrmap* t, uintnat pos)
{
  return (pos + 1) & (t->size - 1);
}

int caml_addrmap_contains(struct addrmap* t, value key)
{
  CAMLassert(Is_block(key));
  if (!t->entries) return 0;

  for (uintnat i = 0, pos = pos_initial(t, key);
       i < MAX_CHAIN;
       i++, pos = pos_next(t, pos)) {
    if (t->entries[pos].key == ADDRMAP_INVALID_KEY) break;
    if (t->entries[pos].key == key) return 1;
  }
  return 0;
}

value caml_addrmap_lookup(struct addrmap* t, value key)
{
  CAMLassert(Is_block(key));
  CAMLassert(t->entries);

  for (uintnat pos = pos_initial(t, key); ; pos = pos_next(t, pos)) {
    CAMLassert(t->entries[pos].key != ADDRMAP_INVALID_KEY);
    if (t->entries[pos].key == key)
      return t->entries[pos].value;
  }
}

static void addrmap_alloc(struct addrmap* t, uintnat sz)
{
  CAMLassert(Is_power_of_2(sz));
  t->entries = caml_stat_alloc(sizeof(struct addrmap_entry) * sz);
  t->size = sz;
  for (uintnat i = 0; i < sz; i++) {
    t->entries[i].key = ADDRMAP_INVALID_KEY;
    t->entries[i].value = ADDRMAP_NOT_PRESENT;
  }
}

void caml_addrmap_init(struct addrmap* t) {
  t->entries = NULL;
  t->size = 0;
}

void caml_addrmap_clear(struct addrmap* t) {
  caml_stat_free(t->entries);
  t->entries = NULL;
  t->size = 0;
}



value* caml_addrmap_insert_pos(struct addrmap* t, value key) {
  CAMLassert(Is_block(key));
  if (!t->entries) {
    /* first call, initialise table with a small initial size */
    addrmap_alloc(t, 256);
  }
  for (uintnat i = 0, pos = pos_initial(t, key);
       i < MAX_CHAIN;
       i++,   pos = pos_next(t, pos)) {
    if (t->entries[pos].key == ADDRMAP_INVALID_KEY) {
      t->entries[pos].key = key;
    }
    if (t->entries[pos].key == key) {
      return &t->entries[pos].value;
    }
  }
  /* failed to insert, rehash and try again */
  {
    struct addrmap_entry* old_table = t->entries;
    uintnat old_size = t->size;
    addrmap_alloc(t, old_size * 2);
    for (uintnat i = 0; i < old_size; i++) {
      if (old_table[i].key != ADDRMAP_INVALID_KEY) {
        value* p = caml_addrmap_insert_pos(t, old_table[i].key);
        CAMLassert(*p == ADDRMAP_NOT_PRESENT);
        *p = old_table[i].value;
      }
    }
    caml_stat_free(old_table);
  }
  return caml_addrmap_insert_pos(t, key);
}

void caml_addrmap_insert(struct addrmap* t, value k, value v) {
  value* p = caml_addrmap_insert_pos(t, k);
  CAMLassert(*p == ADDRMAP_NOT_PRESENT);
  *p = v;
}

void caml_addrmap_iter(struct addrmap* t, void (*f)(value, value)) {
  for (addrmap_iterator i = caml_addrmap_iterator(t);
       caml_addrmap_iter_ok(t, i);
       i = caml_addrmap_next(t, i)) {
    f(caml_addrmap_iter_key(t, i),
      caml_addrmap_iter_value(t, i));
  }
}
