#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/mm.h>

#include "runtime_internal.h"
#define TLS_TABLE_SIZE 100


//first attempt hash table -- revisit
static tls_slot_t **tls_table;

static void init_tls_table() {
  tls_table = (tls_slot_t**)kmalloc(TLS_TABLE_SIZE*sizeof(tls_slot_t*), GFP_KERNEL);
  int i=0;
  for(i =0;i<TLS_TABLE_SIZE;i++)
    tls_table[i] = 0;
}

static void free_tls_table() {
  if(tls_table)
    kfree(tls_table);
  /* stack allocated elements of table should not be freed
    int i=0;
    for(i =0;i<TLS_TABLE_SIZE;i++) {
    if(tls_table[i]) {
    deallocate_slot(tls_table[i]);
    }
    }
  */
}

static tls_slot_t *init_slot(tls_slot_t *s, unsigned int pid) {
  s->pid = pid;
  s->usage_count = 1;
  s->record->current_frame = 0;
  s->record->exn_thrown = 0;
  s->record->exn_filename = "?";
  s->record->exn_lineno = 0;
  return s;
}

//should not be used
#if 0
static tls_slot_t* allocate_slot(unsigned int pid) {
  tls_slot_t *s = (tls_slot_t*)kmalloc(sizeof(tls_slot_t), GFP_KERNEL);
  s->record = (tls_record_t*)kmalloc( sizeof(tls_record_t), GFP_KERNEL);
  return init_slot(s,pid);
}

static void deallocate_slot(tls_slot_t *slot) {
  if(slot)
    kfree(slot);
}
#endif


static tls_slot_t* get_tls_slot(int *slot_index) {
  *slot_index = -1;
  unsigned int pid_hash = pid_hashfn(current->pid);
  int start = pid_hash % TLS_TABLE_SIZE;
  //  printk(KERN_INFO "cyc_runtime: get_tls_slot for pid %d ...", pid_hash);
  int i=start;
  //--- guard with spinlock
  do {
    int ix = i % TLS_TABLE_SIZE;
    if(tls_table[ix] && tls_table[ix]->pid == current->pid){
      *slot_index = ix;
      //      printk(KERN_INFO "found at slot %d\n", ix);
      return tls_table[ix];
    }
  } while(((++i) % TLS_TABLE_SIZE) != start);
  //--- done guard with spinlock
  //  printk(KERN_ERR "\ncyc_runtime: Unable to to find tls_slot -- fatal\n");
  //not found
  return 0;
}


//should be called by the cyc_call macros before calling a Cyclone function
int cyc_runtime_register_thread(tls_slot_t *slot) {
  if(!slot) {
    printk(KERN_ERR "cyc_runtime: register thread expects a stack allocated slot\n");
    return -EFAULT;
  }
  unsigned int pid_hash = pid_hashfn(current->pid);
  int start = pid_hash % TLS_TABLE_SIZE;
  //--- guard with spinlock
  while(tls_table[pid_hash % TLS_TABLE_SIZE]) {
    if((++pid_hash) % TLS_TABLE_SIZE == start) {
      //table is full
      printk(KERN_ERR "cyc_runtime: tls table reached capacity\n");
      return -EFAULT;
    }
  }
  int ix = pid_hash % TLS_TABLE_SIZE;
  tls_table[ix] = init_slot(slot, current->pid);
  //--- doneguard with spinlock
  printk(KERN_INFO "cyc_runtime: register thread (pid,hash) (%d,%d) allocd at %d\n", 
	 current->pid, pid_hashfn(current->pid), ix);
  return 0;
}

int cyc_runtime_register_thread_dbg(tls_slot_t *slot, const char *fname) {
  printk(KERN_INFO "cyc_runtime: register thread (%d,%d,%s)\n", 
	 current->pid, pid_hashfn(current->pid), fname);
  if(!slot) {
    printk(KERN_ERR "cyc_runtime: register thread expects a stack allocated slot\n");
    return -EFAULT;
  }
  int exists=0;
  tls_slot_t *ex_slot = get_tls_slot(&exists);
  if(ex_slot){
    ex_slot->usage_count++;
    printk(KERN_ERR "cyc_runtime: re-register ignored\n");
    return 0;
  }
  unsigned int pid_hash = pid_hashfn(current->pid);
  int start = pid_hash % TLS_TABLE_SIZE;
  //--- guard with spinlock
  while(tls_table[pid_hash % TLS_TABLE_SIZE]) {
    if((++pid_hash) % TLS_TABLE_SIZE == start) {
      //table is full
      printk(KERN_ERR "cyc_runtime: tls table reached capacity\n");
      return -EFAULT;
    }
  }
  int ix = pid_hash % TLS_TABLE_SIZE;
  tls_table[ix] = init_slot(slot, current->pid);
  printk(KERN_INFO "cyc_runtime: register thread (%d,%d,%s) --> %d\n", 
	 current->pid, pid_hashfn(current->pid), fname, ix);
  //--- doneguard with spinlock
  return 0;
}


//called by runtime_exception, runtime_stack to get record data
tls_record_t* cyc_runtime_lookup_tls_record() {
  int ix = -1;
  tls_slot_t *slot =get_tls_slot(&ix);
  if(slot)
    return slot->record;
  else 
    printk(KERN_ERR "lookup_tls_record failed for pid %d\n", current->pid);
  return 0;
}

//should be called by the cyc_call macros before returning
int cyc_runtime_unregister_thread() {
  printk(KERN_INFO "cyc_runtime: unregister thread for pid,hash (%d,%d) \n", 
	 current->pid, pid_hashfn(current->pid));
  int slot_index=-1;
  tls_slot_t *slot = get_tls_slot(&slot_index);
  if(!slot) {//not foun
    printk(KERN_ERR "Unregister_thread unable to find thread\n");
    return -1;
  }
  //stack allocated slot -- no need to free
  printk(KERN_ALERT "cyc_runtime: unregister_thread at slot_index %d\n", slot_index);
  //--- guard with spinlock
  tls_table[slot_index] = 0;
  //  deallocate_slot(slot);
  return 0;
}

int cyc_runtime_unregister_thread_dbg(const char *fname) {
  printk(KERN_INFO "cyc_runtime: unregister thread (%d,%d,%s) \n", 
	 current->pid, pid_hashfn(current->pid), fname);
  int slot_index=-1;
  tls_slot_t *slot = get_tls_slot(&slot_index);
  if(!slot) {//not foun
    printk(KERN_ERR "Unregister_thread unable to find thread\n");
    return -1;
  }
  slot->usage_count--;
  if(!slot->usage_count) {
    //stack allocated slot -- no need to free
    printk(KERN_ALERT "cyc_runtime: unregister_thread at slot_index %d\n", slot_index);
    //--- guard with spinlock
    tls_table[slot_index] = 0;
  }
  //  deallocate_slot(slot);
  return 0;
}


int init_module() {
  printk(KERN_ALERT "Loading module cyc_runtime\n");
  init_tls_table();
  return 0;
}

int cleanup_module() { //check for dependencies, usages
  printk(KERN_ALERT "unloading module cyc_runtime\n");
  free_tls_table();
  return 0;
}
