/*
   File:        skid.c
   Authors:     Andrew W. Moore
   Created:     Wed Apr 17 16:09:44 EDT 2002
   Description: Sparse Kd Array

   Copyright 2002, Andrew W. Moore
*/

#include "skid.h"

skelt *mk_zero_skelt(ivec *indexes)
{
  skelt *x = AM_MALLOC(skelt);

  x -> indexes = mk_copy_ivec(indexes);
  x -> count = 0;

  return x;
}

void free_skelt(skelt *x)
{
  free_ivec(x->indexes);
  AM_FREE(x,skelt);
}

void fprintf_skelt(FILE *s,char *m1,skelt *x,char *m2)
{
  char *buff;

  buff = mk_printf("%s->indexes",m1);
  fprintf_ivec(s,buff,x->indexes,m2);
  free_string(buff);

  buff = mk_printf("%s->count",m1);
  fprintf_int(s,buff,x->count,m2);
  free_string(buff);
}

skelt *mk_copy_skelt(skelt *old)
{
  skelt *nu = AM_MALLOC(skelt);

  nu->indexes = mk_copy_ivec(old->indexes);
  nu->count = old->count;

  return nu;
}

/*** Begin skelts auto definition ***/

void void_free_skelt(void *data)
{
  free_skelt((skelt *)data);
}

void *void_mk_copy_skelt(void *data)
{
  return (void *) mk_copy_skelt((skelt *)data);
}

void void_fprintf_skelt(FILE *s,char *m1,void *data,char *m2)
{
  fprintf_skelt(s,m1,(skelt *) data,m2);
}

skelts *mk_empty_skelts(void)
{
  skelts *array = AM_MALLOC(skelts);
  array -> genarr = mk_empty_generic_array(void_free_skelt,
					   void_mk_copy_skelt,
					   void_fprintf_skelt);
  return array;
}

void add_to_skelts(skelts *array,skelt *element)
{
  add_to_generic_array(array->genarr,(void *)element);
}

void skelts_remove(skelts *array,int i)
{
  generic_array_remove(array->genarr,i);
}

void add_pointer_to_skelts(skelts *array,skelt *element)
{
  add_pointer_to_generic_array(array->genarr,(void *)element);
}

int skelts_size(skelts *array)
{
  return(generic_array_size(array->genarr));
}

skelt *skelts_ref(skelts *array,int index)
{
  return (skelt *) generic_array_ref(array->genarr,index);
}
  
void fprintf_skelts(FILE *s,char *m1,skelts *array,char *m2)
{
  fprintf_generic_array(s,m1,array->genarr,m2);
}

void pskelts(skelts *array)
{
  fprintf_skelts(stdout,"skelts",array,"\n");
}

void free_skelts(skelts *array)
{
  free_generic_array(array->genarr);
  AM_FREE(array,skelts);
}

skelts *mk_copy_skelts(skelts *array)
{
  skelts *new_skelts = AM_MALLOC(skelts);
  new_skelts -> genarr = mk_copy_generic_array(array->genarr);
  return new_skelts;
}

/*** End skelts auto definition ***/

int skelts_indexes_to_skeltnum(skelts *sks,ivec *indexes)
{
  int result = -1;
  int i;
  for ( i = 0 ; result < 0 && i < skelts_size(sks) ; i++ )
  {
    skelt *sk = skelts_ref(sks,i);
    if ( ivec_equal(sk->indexes,indexes) )
      result = i;
  }
  return result;
}

skid *mk_empty_skid(int num_dims)
{
  skid *x = AM_MALLOC(skid);
  int i;

  x -> num_dims = num_dims;
  x -> num_elements = 0;
  x -> num_buckets = 10;
  x -> buckets = AM_MALLOC_ARRAY(skelts *,x->num_buckets);

  for ( i = 0 ; i < x->num_buckets ; i++ )
    x -> buckets[i] = mk_empty_skelts();

  return x;
}

void free_skid(skid *x)
{
  int i;

  for ( i = 0 ; i < x->num_buckets ; i++ )
    free_skelts(x->buckets[i]);

  AM_FREE_ARRAY(x->buckets,skelts *,x->num_buckets);
  AM_FREE(x,skid);
}

void fprintf_skid(FILE *s,char *m1,skid *x,char *m2)
{
  char *buff;
  int i;

  buff = mk_printf("%s->num_dims",m1);
  fprintf_int(s,buff,x->num_dims,m2);
  free_string(buff);

  buff = mk_printf("%s->num_elements",m1);
  fprintf_int(s,buff,x->num_elements,m2);
  free_string(buff);

  buff = mk_printf("%s->num_buckets",m1);
  fprintf_int(s,buff,x->num_buckets,m2);
  free_string(buff);

  for ( i = 0 ; i < x->num_buckets ; i++ )
  {
    buff = mk_printf("%s->buckets[%d]",m1,i);
    fprintf_skelts(s,buff,x->buckets[i],m2);
    free_string(buff);
  }
}

skid *mk_copy_skid(skid *old)
{
  skid *nu = AM_MALLOC(skid);
  int i;

  nu->num_dims = old->num_dims;
  nu->num_elements = old->num_elements;
  nu->num_buckets = old->num_buckets;
  nu->buckets = AM_MALLOC_ARRAY(skelts *,old->num_buckets);
  for ( i = 0 ; i < old->num_buckets ; i++ )
    nu->buckets[i] = mk_copy_skelts(old->buckets[i]);

  return nu;
}

int indexes_to_bucket_num(ivec *indexes,int num_buckets)
{
  int n = 0;
  int k = num_buckets;

  int i;

  for ( i = 0 ; i < ivec_size(indexes) ; i++ )
  {
    int prime = (i==0) ? 2 : (i==1) ? 3 : (i==2) ? 5 : -77;
    my_assert(prime >= 0);
    n += prime * ivec_ref(indexes,i);
  }

  while ( k % 2 == 0 || k % 3 == 0 || k % 5 == 0 )
  {
    k -= 1;
    my_assert(k > 1);
  }
  
  return n % k;
}

skelt *skid_indexes_bucket_num_to_skelt(skid *ski,ivec *indexes,int bucket_num)
{
  int skeltnum = skelts_indexes_to_skeltnum(ski->buckets[bucket_num],
					    indexes);
  return ( skeltnum < 0 ) ? NULL :
                            skelts_ref(ski->buckets[bucket_num],skeltnum);
}

skelts *skid_bucket_to_skelts(skid *sk,int bucket)
{
  my_assert(bucket >= 0 && bucket < sk->num_buckets);
  return sk->buckets[bucket];
}

bool Message_given = FALSE;

skelt *skid_indexes_to_skelt_without_creation(skid *ski,ivec *indexes)
{
  int bucket_num = indexes_to_bucket_num(indexes,ski->num_buckets);
  if ( skelts_size(skid_bucket_to_skelts(ski,bucket_num)) > 50 &&
       !Message_given )
  {
    int i;
    for ( i = 0 ; i < 30 ; i++ )
      printf("*** Warning! Imbalanced Hash Table!!!\n");
    Message_given = TRUE;
  }

  return skid_indexes_bucket_num_to_skelt(ski,indexes,bucket_num);
}

int skid_indexes_to_count(skid *ski,ivec *indexes)
{
  skelt *ske = skid_indexes_to_skelt_without_creation(ski,indexes);
  return ( ske == NULL ) ? 0 : ske->count;
}

skelt *skid_indexes_to_skelt_with_creation(skid *ski,ivec *indexes)
{
  int bucket_num = indexes_to_bucket_num(indexes,ski->num_buckets);
  skelt *ske = skid_indexes_bucket_num_to_skelt(ski,indexes,bucket_num);

  if ( ske == NULL )
  {
    skelts *sks = ski->buckets[bucket_num];
    skelt *temp = mk_zero_skelt(indexes);
    add_to_skelts(sks,temp);
    free_skelt(temp);

    ski -> num_elements += 1;
    if ( ski->num_elements > ski->num_buckets )
    {
      int new_num_buckets = 2 * ski->num_buckets;
      skelts **new_buckets = AM_MALLOC_ARRAY(skelts *,new_num_buckets);
      int i;
      for ( i = 0 ; i < new_num_buckets ; i++ )
	new_buckets[i] = mk_empty_skelts();
      for ( i = 0 ; i < ski->num_buckets ; i++ )
      {
	skelts *sks = ski->buckets[i];
	int j;
	for ( j = 0 ; j < skelts_size(sks) ; j++ )
	{
	  skelt *ske_j = skelts_ref(sks,j);
	  int new_bucket_num = 
	    indexes_to_bucket_num(ske_j->indexes,new_num_buckets);
	  add_to_skelts(new_buckets[new_bucket_num],ske_j);
	}
	free_skelts(sks);
      }
      AM_FREE_ARRAY(ski->buckets,skelts *,ski->num_buckets);
      ski->buckets = new_buckets;
      ski->num_buckets = new_num_buckets;
    }
      
    ske = skid_indexes_to_skelt_without_creation(ski,indexes);
    my_assert(ske != NULL);
  }
  my_assert(ske != NULL);
  my_assert(ivec_equal(ske->indexes,indexes));
  return ske;
}

void skid_set(skid *ski,ivec *indexes,int count)
{
  skelt *ske = skid_indexes_to_skelt_with_creation(ski,indexes);
  my_assert(count > 0);
  ske -> count = count;
}

void skid_increment(skid *ski,ivec *indexes)
{
  skelt *ske = skid_indexes_to_skelt_with_creation(ski,indexes);
  ske -> count += 1;
}

void skid_decrement(skid *ski,ivec *indexes)
{
  int bucket_num = indexes_to_bucket_num(indexes,ski->num_buckets);
  skelts *sks = ski->buckets[bucket_num];
  int skeltnum = skelts_indexes_to_skeltnum(sks,indexes);

  if ( skeltnum < 0 )
    my_error("skid_decrement: count already zero (ie not in skid)");
  else
  {
    skelt *ske = skelts_ref(sks,skeltnum);
    ske -> count -= 1;

    if ( ske -> count == 0 )
      skelts_remove(sks,skeltnum);
  }    
}


int skid_num_buckets(skid *sk)
{
  return sk->num_buckets;
}

int skid_bucket_to_num_members(skid *sk,int bucket)
{
  return skelts_size(skid_bucket_to_skelts(sk,bucket));
}

skelt *skid_bucket_member_to_skelt(skid *sk,int bucket,int member)
{
  return skelts_ref(skid_bucket_to_skelts(sk,bucket),member);
}

ivec *skid_bucket_member_to_indexes(skid *sk,int bucket,int member)
{
  return skid_bucket_member_to_skelt(sk,bucket,member)->indexes;
}

int skid_bucket_member_to_count(skid *sk,int bucket,int member)
{
  return skid_bucket_member_to_skelt(sk,bucket,member)->count;
}
