#include "config.h"

#include "rfg_regions.h"
#include "rfg_filter.h"
#include "rfg_groups.h"

#include "vt_inttypes.h"

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define CSTACK_BSIZE 0x80 /* call stack block size */
#define HASH_MAX 0x3fd    /* size of hash table */

/* data structure for call stack entry */

typedef struct RFG_RegionStackEntry_struct
{
  RFG_RegionInfo* rinf;          /* region info */
  int32_t         climitbypush;  /* call limit by push */
} RFG_RegionStackEntry;

/* data structure for call stack */

typedef struct RFG_RegionStack_struct
{
  RFG_RegionStackEntry* entries; /* call stack entries */

  int32_t  pos;                  /* stack position */
  uint32_t size;                 /* allocated stack size */
} RFG_RegionStack;

/* main data structure for RFG Regions */

struct RFG_Regions_struct
{
  RFG_Filter*      filter;    /* instance for region filter */
  RFG_Groups*      groups;    /* instance for region grouping */

  RFG_RegionStack* stack;     /* instance for call stack */

  RFG_RegionInfo*  htab[HASH_MAX];   /* hash table for mapping
					region id's and infos
					(call limit, group, region name,...) */
};

/* initializes call stack */
static int             stack_init( RFG_RegionStack** stack );
/* enlarges call stack */
static int             stack_enlarge( RFG_RegionStack* stack );

/* puts region info to hash table */
static void            hash_put( RFG_RegionInfo** htab, uint32_t h,
				 const char* g, const char* r,
				 int32_t l );
/* gets the region info by id */
static RFG_RegionInfo* hash_get( RFG_RegionInfo** htab, uint32_t h );
/* frees hash node */
static void            hash_free_node( RFG_RegionInfo* htab );
/* frees whole hash table */
static void            hash_free( RFG_RegionInfo** htab );

RFG_Regions* RFG_Regions_init()
{
  RFG_Regions* ret;

  uint32_t i;

  /* allocate memory for RFG regions object */

  ret = ( RFG_Regions* )malloc( sizeof( RFG_Regions ) );
  if( ret == NULL )
    return NULL;

  /* initalize hash table */

  for( i = 0; i < HASH_MAX; i++ )
    ret->htab[i] = NULL;

  /* initialize call stack */

  ret->stack = NULL;
  if( !stack_init( &(ret->stack) ) )
  {
    free( ret );
    return NULL;
  }

  /* initialize RFG filter object */
  
  ret->filter = NULL;
  ret->filter = RFG_Filter_init();
  if( !ret->filter )
  {
     free( ret );
     return NULL;
  }

  /* initialize RFG groups object */

  ret->groups = NULL;
  ret->groups = RFG_Groups_init();
  if( !ret->groups )
  {
     free( ret );
     return NULL;
  }

  return ret;
}

int RFG_Regions_free( RFG_Regions* regions )
{
  int ret = 1;
  
  if( !regions ) return 0;

  /* free objects for region filter and grouping */
 
  if( !RFG_Filter_free( regions->filter ) ) ret = 0;
  if( !RFG_Groups_free( regions->groups ) ) ret = 0;

  /* free call stack */

  free( regions->stack->entries );
  free( regions->stack );

  /* free hash table */

  hash_free( regions->htab );

  /* free self */

  free( regions );
  regions = NULL;

  return ret;
}

int RFG_Regions_setFilterDefFile( RFG_Regions* regions, const char* deffile )
{
  if( !regions || !regions->filter ) return 0;

  return RFG_Filter_setDefFile( regions->filter, deffile );
}

int RFG_Regions_setGroupsDefFile( RFG_Regions* regions, const char* deffile )
{
  if( !regions || !regions->groups ) return 0;

  return RFG_Groups_setDefFile( regions->groups, deffile );
}

int RFG_Regions_readFilterDefFile( RFG_Regions* regions )
{
  if( !regions || !regions->filter ) return 0;

  return RFG_Filter_readDefFile( regions->filter );
}

int RFG_Regions_readGroupsDefFile( RFG_Regions* regions )
{
  if( !regions || !regions->groups ) return 0;

  return RFG_Groups_readDefFile( regions->groups );
}

int RFG_Regions_setDefaultCallLimit( RFG_Regions* regions,
				     const uint32_t limit )
{
  if( !regions || !regions->filter ) return 0;

  return RFG_Filter_setDefaultCallLimit( regions->filter, limit );
}

int RFG_Regions_setDefaultGroup( RFG_Regions* regions,
				 const char* name )
{
  if( !regions || !regions->groups ) return 0;

  return RFG_Groups_setDefaultGroup( regions->groups, name );
}

int RFG_Regions_addGroupAssign( RFG_Regions* regions,
				const char* gname, int n, ... )
{
  va_list ap;
  int i;

  if( !regions || !regions->groups || !gname ) return 0;

  va_start(ap, n);

  for( i = 0; i < n; i++ )
  {
    if( !RFG_Groups_addAssign( regions->groups, gname, va_arg(ap, char*) ) )
    {
      va_end(ap);
      return 0;
    }
  }

  va_end(ap);

  return 1;
}

int RFG_Regions_getFilteredRegions( RFG_Regions* regions,
				    uint32_t* r_nrinfs, RFG_RegionInfo*** r_rinfs )
{
  uint32_t i;

  if( !regions ) return 0;

  *r_nrinfs = 0;
  *r_rinfs = NULL;

  for( i = 0; i < HASH_MAX; i++ )
  {
    if( regions->htab[i] )
    {
      RFG_RegionInfo* curr = regions->htab[i];
      while( curr )
      {
	if( curr->callLimitCD == 0 )
	{
	  if( *r_nrinfs == 0 )
	    *r_rinfs = ( RFG_RegionInfo** )malloc( sizeof( RFG_RegionInfo* ) );
	  else
	    *r_rinfs = ( RFG_RegionInfo** )realloc( *r_rinfs, ( *r_nrinfs + 1 )
					   * sizeof( RFG_RegionInfo* ) );

	  (*r_rinfs)[(*r_nrinfs)++] = curr;
	}

	curr = curr->next;
      }
    }
  }

  return 1;
}

int RFG_Regions_stackPush( RFG_Regions* regions,
			   const uint32_t rid, const uint8_t decr,
			   RFG_RegionInfo** r_rinf )
{
  if( !regions || !regions->stack ) return 0;

  /* get region info by region id */

  *r_rinf = RFG_Regions_get( regions, rid );
  if( !(*r_rinf) ) return 0;

  /* enlarge call stack if necessary */

  if( regions->stack->pos+1 == (int32_t)regions->stack->size )
  {
    if( !stack_enlarge( regions->stack ) )
    {       
      fprintf( stderr, "RFG_Regions_stackPush(): Error: Could not enlarge stack size\n" );
      return 0;
    }
  }

  /* decrement call limit of region, if desired */

  if( decr && (*r_rinf)->callLimitCD > 0 ) 
    (*r_rinf)->callLimitCD--;

  /* push pointer new call stack entry to top of the call stack */

  regions->stack->
     entries[++regions->stack->pos].rinf = *r_rinf;
  regions->stack->
     entries[regions->stack->pos].climitbypush = (*r_rinf)->callLimitCD;

  return 1;
}

int RFG_Regions_stackPop( RFG_Regions* regions,
			  RFG_RegionInfo** r_rinf, int32_t* r_climitbypush )
{
  if( !regions || !regions->stack ) return 0;

  if( regions->stack->pos == -1 )
  {
    fprintf( stderr, "RFG_Regions_stackPop(): Error: Stack underflow\n" );
    return 0;
  }

  *r_rinf =
     regions->stack->entries[regions->stack->pos].rinf;
  *r_climitbypush =
     regions->stack->entries[regions->stack->pos--].climitbypush;

  return 1;
}

RFG_RegionInfo* RFG_Regions_add( RFG_Regions* regions,
				 const char* rname, uint32_t rid )
{
  RFG_RegionInfo* rinf;
  char*    gname;
  int32_t  climit;

  if( !regions ) return NULL;

  if( !rname )
  {
    fprintf( stderr, "RFG_Regions_add(): Error: Empty region name\n" );
    return NULL;
  }

  /* look for already existing hash node of this region */

  rinf = hash_get( regions->htab, rid );
  if( !rinf )
  {
    /* get group information of this region */

    if( !RFG_Groups_get( regions->groups, rname, &gname ) )
      return NULL;

    /* get filter information of this region */

    if( !RFG_Filter_get( regions->filter, rname, &climit ) )
      return NULL;

    /* add region information to hash table */

    hash_put( regions->htab,
	      rid,
	      gname,
	      rname,
	      climit );

    rinf = hash_get( regions->htab, rid );
  }

  return rinf;
}

RFG_RegionInfo* RFG_Regions_get( RFG_Regions* regions, const uint32_t rid )
{
  RFG_RegionInfo* rinf;

  if( !regions ) return NULL;

  /* look for region informations in hash table */

  rinf = hash_get( regions->htab, rid );

  return rinf;
}

static int stack_init( RFG_RegionStack** stack )
{
  /* allocate memory for call stack object */

  *stack = ( RFG_RegionStack* )malloc( sizeof( RFG_RegionStack ) );
  if( *stack == NULL )
    return 0;

  /* allocatte memory for the call stack entries */
  (*stack)->entries =
    ( RFG_RegionStackEntry* )malloc( CSTACK_BSIZE * sizeof( RFG_RegionStack ) );
  if( (*stack)->entries == NULL )
  {
    free( *stack );
    return 0;
  }

  /* initialize allocated stack size and position */

  (*stack)->size = CSTACK_BSIZE;
  (*stack)->pos = -1;

  return 1;
}

static int stack_enlarge( RFG_RegionStack* stack )
{
  if( !stack ) return 0;

  /* reallocate memory for call stack (size + CSTACK_BSIZE) */

  stack->entries =
    ( RFG_RegionStackEntry* )realloc( stack->entries,
				      ( stack->size + CSTACK_BSIZE )
				      * sizeof( RFG_RegionStackEntry ) );
  if( stack->entries == NULL )
    return 0;
  
  stack->size += CSTACK_BSIZE;

  return 1;
}

static void hash_put( RFG_RegionInfo** htab, uint32_t h,
		      const char* g, const char* r,
		      int32_t l )
{
  uint32_t id         = h % HASH_MAX;
  RFG_RegionInfo* add = ( RFG_RegionInfo* )malloc( sizeof( RFG_RegionInfo ) );
  add->regionId       = h;
  add->groupName      = strdup( g );
  add->regionName     = strdup( r );
  add->callLimit      = l;
  add->callLimitCD    = l;
  add->next           = htab[id];
  htab[id]            = add;
}

static RFG_RegionInfo* hash_get(RFG_RegionInfo** htab, uint32_t h)
{
  uint32_t id = h % HASH_MAX;
  RFG_RegionInfo* curr = htab[id];
  while( curr )
  {
    if( curr->regionId == h )
      return curr;

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

static void hash_free_node(RFG_RegionInfo* htab)
{
  if( htab->next )
    hash_free_node( htab->next );   /* call recursive */

  free( htab->groupName );
  free( htab->regionName );
  free( htab );
}

static void hash_free(RFG_RegionInfo** htab)
{
  uint32_t i;
  
  /* free hash nodes */

  for( i = 0; i < HASH_MAX; i++ )
  {
    if( htab[i] )
      hash_free_node( htab[i] );
  }
}
