#include <stdlib.h>
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include "eztrace-core/eztrace_htable.h"

hashkey_t hash_function_str(char* key) {
  hashkey_t ret = 0;
  for(int i=0; key[i]!='\0' ; i++) {
    ret += key[i] << (i%4);
  }
  return ret;
}

hashkey_t hash_function_int32(uint32_t key) {
  hashkey_t k = key;
  /* Robert Jenkins' 32 bit Mix Function */
  k += (k << 12);
  k ^= (k >> 22);
  k += (k << 4);
  k ^= (k >> 9);
  k += (k << 10);
  k ^= (k >> 2);
  k += (k << 7);
  k ^= (k >> 12);

  /* Knuth's Multiplicative Method */
  k = (k >> 3) * 2654435761;

  return k;
}

hashkey_t hash_function_int64(uint64_t key) {
  uint32_t key1 = (uint32_t) key;
  uint32_t key2 = (uint32_t) (key>>32);
  return hash_function_int32(key1) ^ hash_function_int32(key2);
}

hashkey_t hash_function_ptr(void* ptr_key) {
  if(sizeof(ptr_key) == 8) {
    uint64_t k = (uintptr_t)ptr_key;
    return hash_function_int64(k);
  }
  uint32_t k = (uintptr_t) ptr_key;
  return hash_function_int32(k);
}

/* initialize a hashtable */
void ezt_hashtable_init(struct ezt_hashtable *table,
			int table_len) {
  table->table_len = table_len;
  table->table = malloc(sizeof(struct ezt_hashtable_list)*table_len);
  for(int i=0; i<table_len; i++) {
    table->table[i].entries = NULL;
    ezt_spin_init(&table->table[i].lock);
  }
}

/* delete a hashtable */
void ezt_hashtable_finalize(struct ezt_hashtable *table) {
  for(int i=0; i<table->table_len; i++) {
    ezt_spin_lock(&table->table[i].lock);
    struct ezt_hashtable_entry *entry = table->table[i].entries;
    while(entry) {
      struct ezt_hashtable_entry *next_entry = entry->next;
      free(entry);
      entry = next_entry;
    }
    table->table[i].entries = NULL;
    ezt_spin_unlock(&table->table[i].lock);
  }
  free(table->table);
  table->table = NULL;
  table->table_len = 0;
}

/* get the entry associated with a key
 *
 * WARNING: the list lock has to be held when calling this function !
 */
static struct ezt_hashtable_entry*
_ezt_ht_get_from_list(hashkey_t key,
		       struct ezt_hashtable_list* list) {

  struct ezt_hashtable_entry *e = list->entries;

  while(e) {
    if(key == e->key) {
      return e;
    }

    e = e->next;
  }
  return NULL;
}

/* return the data associated with key */
void* ezt_hashtable_get(struct ezt_hashtable *table,
			hashkey_t key) {
  int index = key % table->table_len;
  assert(index < table->table_len);
  void *retval = NULL;
  ezt_spin_lock(&table->table[index].lock);
  struct ezt_hashtable_entry *e = _ezt_ht_get_from_list(key, &table->table[index]);
  if(e)
    retval = e->data;

  ezt_spin_unlock(&table->table[index].lock);
  return retval;
}

/* insert data in the hashtable. If key is already in the hashtable, its data is overwritten*/
void ezt_hashtable_insert(struct ezt_hashtable*table,
			  hashkey_t key,
			  void* data) {
  int index = key % table->table_len;
  assert(index < table->table_len);

  ezt_spin_lock(&table->table[index].lock);
  struct ezt_hashtable_entry *e = _ezt_ht_get_from_list(key, &table->table[index]);
  if(e) {
    /* the key already exists. Just replace its data */
    e->data = data;
  } else {
    /* the key does not exist */
    e = malloc(sizeof(struct ezt_hashtable_entry));
    e->key = key;
    e->data = data;
    e->next = table->table[index].entries;
    table->table[index].entries = e;
  }
  ezt_spin_unlock(&table->table[index].lock);
}

/* remove an entry from the hashtable */
void ezt_hashtable_remove(struct ezt_hashtable*table,
			  hashkey_t key) {
  if(table->table_len == 0) return;
  int index = key % table->table_len;
  assert(index < table->table_len);

  ezt_spin_lock(&table->table[index].lock);

  struct ezt_hashtable_entry *e = table->table[index].entries;
  if(!e)
    goto out;
  if(key == e->key) {
    /* we need to remove the first entry */
    table->table[index].entries = e->next;
    free(e);
  } else {
    while(e->next && key != e->next->key) {
      e = e->next;
    }
    if(e->next) {
      /* remove e->next */
      struct ezt_hashtable_entry *to_remove = e->next;
      e->next = to_remove->next;
      free(to_remove);
    }
  }
 out:
  ezt_spin_unlock(&table->table[index].lock);
}


void ezt_hashtable_print(struct ezt_hashtable *table) {
  for(int i=0; i<table->table_len; i++) {
    if(table->table[i].entries) {
      printf("List %d :\n", i);
      struct ezt_hashtable_entry *e = table->table[i].entries;
      while(e) {
	printf("\t{%x, %p}", e->key, e->data);
	e = e->next;
      }
      printf("\n");
    }
  }
}
