/** 
 * Cyclone Scheme
 * https://github.com/justinethier/cyclone
 *
 * Copyright (c) 2020, Justin Ethier
 * All rights reserved.
 *
 * An optional module that can be used to compile Cyclone when
 * libck is not available. Note there will be a performance impact!
 * It is recommended to compile with libck where possible.
 */

#include "cyclone/types.h"
#include "cyclone/runtime.h"
#include "ck-polyfill.h"
#include <unistd.h>

static pthread_mutex_t glock;

void ck_polyfill_init()
{
  // will need to call this as soon as possible, perhaps from main()
  if (pthread_mutex_init(&(glock), NULL) != 0) {
    fprintf(stderr, "Unable to initialize global ck mutex\n");
    exit(1);
  }
}

// CK Hashset section
bool ck_hs_init(ck_hs_t *hs, unsigned int mode, ck_hs_hash_cb_t *hash_func,
    ck_hs_compare_cb_t *cmp, struct ck_malloc *alloc, unsigned long capacity, unsigned long seed)
{
  (*hs).hs = simple_hashset_create();
  if (pthread_mutex_init(&((*hs).lock), NULL) != 0) {
    fprintf(stderr, "Unable to initialize ck hashset mutex\n");
    exit(1);
  }
  return true;
}

void *ck_hs_get(ck_hs_t *_hs, unsigned long hash, const void *key)
{
  void *result = NULL;
  int index = -1;
  simple_hashset_t set = (*_hs).hs;

  pthread_mutex_lock(&((*_hs).lock));

  index = simple_hashset_is_member(set, (symbol_type *)key);
  if (index > 0) {
    result = (void *)(set->items[index].item);
  }

  pthread_mutex_unlock(&((*_hs).lock));
  return result;
}

bool ck_hs_put(ck_hs_t *_hs, unsigned long hash, const void *key)
{
  bool result = false;
  int rv, index;
  simple_hashset_t hs = (*_hs).hs;

  pthread_mutex_lock(&((*_hs).lock));

  //index = simple_hashset_is_member(hs, (symbol_type *)key);
  //if (index == 0) {
    rv = simple_hashset_add(hs, (symbol_type *)key);
    if (rv >= 0) {
      result = true;
    }
  //}

  pthread_mutex_unlock(&((*_hs).lock));
  return result;
}

// CK Array section
bool
ck_array_init(ck_array_t *array, unsigned int mode,
         struct ck_malloc *allocator, unsigned int initial_length)
{
  (*array).hs = hashset_create();
  if (pthread_mutex_init(&((*array).lock), NULL) != 0) {
    fprintf(stderr, "Unable to initialize ck array mutex\n");
    exit(1);
  }
  return true;
}

// DESCRIPTION
//      The ck_array_put_unique(3) function will attempt to insert the value of
//      pointer into the array pointed to by array.  This function may incur
//      additional memory allocations if not enough memory has been allocated in
//      the array for a new entry. The operation is also free to apply the opera-
//      tion immediately if there is an opportunity for elimination with a pend-
//      ing (uncommitted) remove operation. The function will not make any modi-
//      fications if the pointer already exists in the array.
// 
// RETURN VALUES
//      This function returns 1 if the pointer already exists in the array.  It
//      returns 0 if the put operation succeeded. It returns -1 on error due to
//      internal memory allocation failures.
int
ck_array_put_unique(ck_array_t *array, void *pointer)
{
  pthread_mutex_lock(&(array->lock));
  hashset_add(array->hs, pointer);
  pthread_mutex_unlock(&(array->lock));
  return true;
}

// DESCRIPTION
//      The ck_array_remove(3) function will attempt to remove the value of
//      pointer into the array pointed to by array. The operation is also free to
//      apply the operation immediately if there is an opportunity for elimina-
//      tion with a pending (uncommitted) put operation. If no elimination was
//      possible, the function may require to allocate more memory.
// 
// RETURN VALUES
//      This function returns true if the remove operation succeeded. It will
//      return false otherwise due to internal allocation failures or because the
//      value did not exist.
bool
ck_array_remove(ck_array_t *array, void *pointer){
  pthread_mutex_lock(&(array->lock));
  hashset_remove(array->hs, pointer);
  pthread_mutex_unlock(&(array->lock));
  return true;
}

// DESCRIPTION
//      The ck_array_commit(3) function will commit any pending put or remove
//      operations associated with the array. The function may end up requesting
//      the safe reclamation of memory actively being iterated upon by other
//      threads.
// 
// RETURN VALUES
//      This function returns true if the commit operation succeeded. It will
//      return false otherwise, and pending operations will not be applied.
bool ck_array_commit(ck_array_t *array) {
  // Nothing to do in this polyfill
  return true;
}


// TODO: global pthread mutex lock for this? obviously not ideal but the
// whole purpose of this module is a minimal interface for compatibility
// not speed

bool ck_pr_cas_int(int *target, int old_value, int new_value)
{
  bool result = false;
  pthread_mutex_lock(&glock);
  if (*target == old_value) {
    *target = new_value;
    result = true;
  }
  pthread_mutex_unlock(&glock);
  return result;
}

bool ck_pr_cas_ptr(void *target, void *old_value, void *new_value)
{
  bool result = false;
  pthread_mutex_lock(&glock);
  if ( *(void **)target == old_value ) {
    *(void **)target = new_value;
    result = true;
  }
  pthread_mutex_unlock(&glock);
  return result;
  // *(void **)v = set;                                                             
}

bool ck_pr_cas_8(uint8_t *target, uint8_t old_value, uint8_t new_value)
{
  bool result = false;
  pthread_mutex_lock(&glock);
  if (*target == old_value) {
    *target = new_value;
    result = true;
  }
  pthread_mutex_unlock(&glock);
  return result;
}

void
ck_pr_add_ptr(void *target, uintptr_t delta)
{
  pthread_mutex_lock(&glock);
  size_t value = (size_t) target;
  size_t d = (size_t) delta;
  size_t result = value + d;
  *(void **)target = (void *)result;
  // *(void **)v = set;                                                             
  pthread_mutex_unlock(&glock);
}

void
ck_pr_add_int(int *target, int delta)
{
  pthread_mutex_lock(&glock);
  (*target) += delta;
  pthread_mutex_unlock(&glock);
}

void
ck_pr_add_8(uint8_t *target, uint8_t delta)
{
  pthread_mutex_lock(&glock);
  (*target) += delta;
  pthread_mutex_unlock(&glock);
}

void *
ck_pr_load_ptr(const void *target)
{
  void *result;
  pthread_mutex_lock(&glock);
  result = *(void **)target;
  pthread_mutex_unlock(&glock);
  return result;
}

int
ck_pr_load_int(const int *target)
{
  int result;
  pthread_mutex_lock(&glock);
  result = *target;
  pthread_mutex_unlock(&glock);
  return result;
}

uint8_t
ck_pr_load_8(const uint8_t *target)
{
  uint8_t result;
  pthread_mutex_lock(&glock);
  result = *target;
  pthread_mutex_unlock(&glock);
  return result;
}

void ck_pr_store_ptr(void *target, void *value)
{
  pthread_mutex_lock(&glock);
  *(void **)target = value;
  pthread_mutex_unlock(&glock);
}


// Simple hashset

static const size_t prime_1 = 73;
static const size_t prime_2 = 5009;

size_t hash_function(const char* str, size_t len) {
    unsigned long hash = 5381;
    int c;

    while (c = *str++) {
        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
    }

    return hash;
}

simple_hashset_t simple_hashset_create()
{
    simple_hashset_t set = (simple_hashset_t)calloc(1, sizeof(struct simple_hashset_st));

    if (set == NULL) {
        return NULL;
    }

    set->hash_func = hash_function;
    set->nbits = 3;
    set->capacity = (size_t)(1 << set->nbits);
    set->mask = set->capacity - 1;
    set->items = (struct simple_hashset_item_st*)calloc(set->capacity, sizeof(struct simple_hashset_item_st));
    if (set->items == NULL) {
        simple_hashset_destroy(set);
        return NULL;
    }
    set->nitems = 0;
    set->n_deleted_items = 0;
    return set;
}

void simple_hashset_destroy(simple_hashset_t set)
{
    if (set) {
        free(set->items);
    }
    free(set);
}

void simple_hashset_set_hash_function(simple_hashset_t set, hash_func_t func)
{
    set->hash_func = func;
}

static int simple_hashset_add_member(simple_hashset_t set, symbol_type* key, size_t hash)
{
    size_t index;

    if (hash < 2) {
        return -1;
    }

    index = set->mask & (prime_1 * hash);

    while (set->items[index].hash != 0 && set->items[index].hash != 1) {
        if (set->items[index].hash == hash) {
            return 0;
        }
        else {
            /* search free slot */
            index = set->mask & (index + prime_2);
        }
    }

    ++set->nitems;
    if (set->items[index].hash == 1) {
        --set->n_deleted_items;
    }

    set->items[index].hash = hash;
    set->items[index].item = key;
    return 1;
}

static void set_maybe_rehash(simple_hashset_t set)
{
    struct simple_hashset_item_st *old_items;
    size_t old_capacity, index;


    if (set->nitems + set->n_deleted_items >= (double)set->capacity * 0.85) {
        old_items = set->items;
        old_capacity = set->capacity;
        ++set->nbits;
        set->capacity = (size_t)(1 << set->nbits);
        set->mask = set->capacity - 1;
        set->items = (struct simple_hashset_item_st*)calloc(set->capacity, sizeof(struct simple_hashset_item_st));
        set->nitems = 0;
        set->n_deleted_items = 0;
        //assert(set->items);
        for (index = 0; index < old_capacity; ++index) {
            simple_hashset_add_member(set, old_items[index].item, old_items[index].hash);
        }
        free(old_items);
    }
}

int simple_hashset_add(simple_hashset_t set, symbol_type* key)
{
    size_t key_len = strlen(key->desc);
    size_t hash = set->hash_func(key->desc, key_len);
    int rv = simple_hashset_add_member(set, key, hash);
    set_maybe_rehash(set);
    return rv;
}

int simple_hashset_is_member(simple_hashset_t set, symbol_type* key)
{
    size_t key_len = strlen(key->desc);
    size_t hash = set->hash_func(key->desc, key_len);
    size_t index = set->mask & (prime_1 * hash);

    while (set->items[index].hash != 0) {
        if (set->items[index].hash == hash) {
            return index;
        } else {
            index = set->mask & (index + prime_2);
        }
    }
    return 0;
}


