#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "prof.h"
#include "prof_internal.h"

Prof_Define(_global);

Prof_Zone_Stack Prof_dummy  ;  // impossible parent
Prof_Zone_Stack Prof_dummy2 ; 
Prof_Zone_Stack *Prof_stack = &Prof_dummy2;

int Prof_num_zones;
Prof_Zone *Prof_zones[];

#define MAX_HASH_SIZE     65536   // not unlimited, to catch unbalanced BEGIN/END_PROF
#define INIT_HASH_SIZE    256     // balance resource usage and avoid initial growth

static Prof_Zone_Stack *init_hash[] = { &Prof_dummy };
static Prof_Zone_Stack **zone_hash = init_hash;
static int zone_hash_count = 1;
static int zone_hash_max   = 1;
static int zone_hash_mask  = 0;

static int hash(Prof_Zone *z, Prof_Zone_Stack *s)
{
   int n = (int) z + (int) s;
   return n + (n >> 8);
}

static void insert_node(Prof_Zone_Stack *q)
{
   int h = hash(q->zone, q->parent);
   int x = h & zone_hash_mask;
   int s = ((h << 4) + (h >> 4)) | 1;

   while (zone_hash[x] != &Prof_dummy)
      x = (x + s) & zone_hash_mask;

   zone_hash[x] = q;

   ++zone_hash_count;
}

static void init_zone(Prof_Zone *zone)
{
   Prof_zones[Prof_num_zones++] = zone;

   zone->initialized = 1;
}

static int count_recursion_depth(Prof_Zone_Stack *stack, Prof_Zone *zone)
{
   int n=0;
   while (stack) {
      if (stack->zone == zone)
         ++n;
      stack = stack->parent;
   }
   return n;
}

static Prof_Zone_Stack *createStackNode(Prof_Zone *zone, Prof_Zone_Stack *parent)
{
   // create a new node
   Prof_Zone_Stack *z = (Prof_Zone_Stack *) malloc(sizeof(*z));
   z->zone = zone;
   z->parent = parent;
   z->total_entry_count = 0;
   z->total_hier_ticks = 0;
   z->total_self_ticks = 0;
   z->t_self_start = 0;
   z->highlevel = NULL;
   z->recursion_depth = count_recursion_depth(parent, zone);
   return z;
}

static void init_zone_hash(int size)
{
   int i;
   assert(size <= MAX_HASH_SIZE);
   zone_hash_max   = size;
   zone_hash_count = 0;
   zone_hash       = (Prof_Zone_Stack **) malloc(sizeof(*zone_hash) * zone_hash_max);
   zone_hash_mask  = size-1;
   for (i=0; i < zone_hash_max; ++i)
      zone_hash[i] = &Prof_dummy;
}

static void Prof_init_lowlevel(void);

// this code is structured to minimize computation
// assuming there's a hit in the very first slot
Prof_extern_C Prof_Zone_Stack *Prof_StackAppend(Prof_Zone *zone)
{
   int h = hash(zone, Prof_stack), s;
   int x = h & zone_hash_mask;
   Prof_Zone_Stack *z = zone_hash[x];
   if (z->parent == Prof_stack && z->zone == zone) return z;
   if (z != &Prof_dummy) {

      // compute a secondary hash function; force it to be odd
      // so it's relatively prime to the power-of-two table size
      s = ((h << 4) + (h >> 4)) | 1;
      for(;;) {
         x = (x + s) & zone_hash_mask;
         z = zone_hash[x];
         if (z->parent == Prof_stack && z->zone == zone) return z;
         if (z == &Prof_dummy) break;
      }
      // loop is guaranteed to terminate because the hash table is never full
   }

   // now's as good a time as any to initialize this zone
   if (!zone->initialized) {
      if (zone_hash_max == 1) {
         Prof_init_lowlevel();
         // the above is reentrant since it initializes _global
         // so now invariants are broken, so start over
         return Prof_StackAppend(zone);
      }
      init_zone(zone);
   }

   // check if we need to grow the table
   // we keep it at most 1/2 full to be very fast
   if (zone_hash_count*2 > zone_hash_max) {
      Prof_Zone_Stack **old_hash = zone_hash, *z;
      int i,n = zone_hash_max;

      init_zone_hash(zone_hash_max*2);

      for (i=0; i < n; ++i)
         if (old_hash[i] != &Prof_dummy)
            insert_node(old_hash[i]);

      z = createStackNode(zone, Prof_stack);
      insert_node(z);
      return z;
   }

   // insert new entry in hash table
   ++zone_hash_count;
   return zone_hash[x] = createStackNode(zone, Prof_stack);
}

void Prof_traverse(void (*func)(Prof_Zone_Stack *z))
{
   int i;
   for (i=0; i < zone_hash_max; ++i)
      if (zone_hash[i] != &Prof_dummy)
         func(zone_hash[i]);
}

static void Prof_init_lowlevel(void)
{
   init_zone_hash(INIT_HASH_SIZE);

   Prof_init_highlevel();

   // intentionally unbalanced, this wraps everything else
   {
      Prof_Region(_global)
   }
}

