The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include <string.h>
#include <stdlib.h>
#include <assert.h>

#include "spvm_hash.h"
#include "spvm_hash_entry.h"
#include "spvm_hash_func.h"
#include "spvm_util_allocator.h"

SPVM_HASH* SPVM_HASH_new(int32_t table_capacity) {
  
  assert(table_capacity >= 0);

  // Create hash
  SPVM_HASH* hash = SPVM_UTIL_ALLOCATOR_safe_malloc_i32(1, sizeof(SPVM_HASH));

  // Default table capacity
  if (table_capacity == 0) {
    hash->table_capacity = 101;
  }
  else {
    hash->table_capacity = table_capacity;
  }
  
  // Initialize table
  hash->table = SPVM_UTIL_ALLOCATOR_safe_malloc_i32(hash->table_capacity, sizeof(SPVM_HASH_ENTRY*));
  memset(hash->table, 0, hash->table_capacity * sizeof(SPVM_HASH_ENTRY*));
  
  // Initialize entries
  hash->entries_capacity = 255;
  hash->entries = SPVM_UTIL_ALLOCATOR_safe_malloc_i32(hash->entries_capacity, sizeof(SPVM_HASH_ENTRY));
  hash->entries_length = 0;
  
  // Initialize key buffer
  hash->key_buffer_capacity = 0xFF;
  hash->key_buffer = SPVM_UTIL_ALLOCATOR_safe_malloc_i32(hash->key_buffer_capacity, sizeof(char));
  hash->key_buffer_length = 0;
  
  return hash;
}

void SPVM_HASH_maybe_extend_entries(SPVM_HASH* hash) {
  
  assert(hash);
  
  int32_t entries_length = hash->entries_length;
  
  assert(entries_length >= 0);
  
  int32_t entries_capacity = hash->entries_capacity;
  
  if (entries_length >= entries_capacity) {
    int32_t new_entries_capacity = entries_capacity * 2;
    hash->entries = SPVM_UTIL_ALLOCATOR_safe_realloc_i32(hash->entries, new_entries_capacity, sizeof(SPVM_HASH_ENTRY));
    hash->entries_capacity = new_entries_capacity;
  }
}

void SPVM_HASH_maybe_extend_key_buffer(SPVM_HASH* hash, int32_t length) {
  
  assert(hash);
  
  int32_t key_buffer_length = hash->key_buffer_length;
  
  assert(key_buffer_length >= 0);
  
  int32_t key_buffer_capacity = hash->key_buffer_capacity;
  
  if (key_buffer_length + length >= key_buffer_capacity) {
    int32_t new_key_buffer_capacity = key_buffer_capacity * 2;
    hash->key_buffer = SPVM_UTIL_ALLOCATOR_safe_realloc_i32(hash->key_buffer, new_key_buffer_capacity, sizeof(SPVM_HASH_ENTRY));
    hash->key_buffer_capacity = new_key_buffer_capacity;
  }
}

void SPVM_HASH_free(SPVM_HASH* hash) {
  
  assert(hash);
  
  free(hash->table);
  free(hash->entries);
  free(hash);
}

int32_t SPVM_HASH_new_hash_entry(SPVM_HASH* hash, const char* key, void* value) {
  
  assert(hash);
  assert(key);
  
  int32_t index = hash->entries_length;
  
  SPVM_HASH_maybe_extend_entries(hash);
  SPVM_HASH_ENTRY* hash_entry = &hash->entries[index];
  
  int32_t key_length = strlen(key);
  SPVM_HASH_maybe_extend_key_buffer(hash, key_length);
  
  hash_entry->key_index = hash->key_buffer_length;
  
  strncpy(&hash->key_buffer[hash->key_buffer_length], key, key_length);
  hash->key_buffer[hash->key_buffer_length + key_length] = '\0';
  
  hash->key_buffer_length += key_length + 1;
  
  *(void**)&hash_entry->value = value;
  hash_entry->next_index = -1;
  
  hash->entries_length++;
  
  return index;
}

void SPVM_HASH_rehash(SPVM_HASH* hash, int32_t new_table_capacity) {
  
  assert(hash);
  assert(new_table_capacity > 0);
  
  // Create new hash
  SPVM_HASH* new_hash = SPVM_HASH_new(new_table_capacity);
  
  // Rehash
  {
    int32_t i;
    for (i = 0; i < hash->entries_length; i++) {
      SPVM_HASH_ENTRY* entry = &hash->entries[i];
      
      const char* key = &hash->key_buffer[entry->key_index];
      
      assert(key);
      
      void* value = *(void**)&entry->value;
      
      SPVM_HASH_insert_norehash(new_hash, key, strlen(key), value);
    }
  }
  
  // Replace hash fields
  free(hash->table);
  free(hash->entries);
  free(hash->key_buffer);
  hash->entries_length = new_hash->entries_length;
  hash->table_capacity = new_hash->table_capacity;
  hash->entries_capacity = new_hash->entries_capacity;
  hash->table = new_hash->table;
  hash->entries = new_hash->entries;
  
  hash->key_buffer_capacity = new_hash->key_buffer_capacity;
  hash->key_buffer_length = new_hash->key_buffer_length;
  hash->key_buffer = new_hash->key_buffer;
}

void SPVM_HASH_insert_norehash(SPVM_HASH* hash, const char* key, int32_t length, void* value) {
  
  assert(hash);
  assert(key);
  assert(length > 0);
  
  int32_t hash_value = SPVM_HASH_FUNC_calc_hash_for_index(key, length);
  int32_t index = hash_value % hash->table_capacity;
  
  if (hash->table[index]) {
    SPVM_HASH_ENTRY* next_entry = hash->table[index];
    while (1) {
      if (strncmp(key, &hash->key_buffer[next_entry->key_index], length) == 0) {
        *(void**)&next_entry->value = value;
        break;
      }
      else {
        if (next_entry->next_index != -1) {
          next_entry = &hash->entries[next_entry->next_index];
        }
        else {
          int32_t new_entry_index = SPVM_HASH_new_hash_entry(hash, key, value);
          next_entry->next_index = new_entry_index;
          break;
        }
      }
    }
  }
  else {
    int32_t new_entry_index = SPVM_HASH_new_hash_entry(hash, key, value);
    hash->table[index] = &hash->entries[new_entry_index];
  }
}

void SPVM_HASH_insert(SPVM_HASH* hash, const char* key, int32_t length, void* value) {
  
  assert(hash);
  assert(key);
  assert(length > 0);
  
  // Rehash
  if (hash->entries_length > hash->table_capacity * 0.75) {
    int32_t new_table_capacity = (hash->table_capacity * 2) + 1;
    
    SPVM_HASH_rehash(hash, new_table_capacity);
  }
  
  SPVM_HASH_insert_norehash(hash, key, length, value);
}

void* SPVM_HASH_search(SPVM_HASH* hash, const char* key, int32_t length) {
  
  assert(hash);
  assert(key);
  assert(length > 0);

  int32_t hash_value = SPVM_HASH_FUNC_calc_hash_for_index(key, length);
  int32_t index = hash_value % hash->table_capacity;
  
  SPVM_HASH_ENTRY* next_entry = hash->table[index];
  while (1) {
    if (next_entry) {
      
      if (strncmp(key, &hash->key_buffer[next_entry->key_index], length) == 0) {
        return *(void**)&next_entry->value;
      }
      else {
        if (next_entry->next_index == -1) {
          next_entry = NULL;
        }
        else {
          next_entry = &hash->entries[next_entry->next_index];
        }
      }
    }
    else {
      return NULL;
    }
  }
}