#include "array.h"

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

static size_t array_Count    = 0;
static size_t array_Buckets  = 0;
static size_t array_Bytes    = 0;
static size_t array_MaxIndex = 0;

struct bucket EmptyBucket= { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } };

void array_defaultElement_(void *element)
{
  size_t i;
  for (i= 0;  i < 32;  ++i)
    EmptyBucket.elements[i]= element;
}

array_t array_new(size_t size)
{
  size_t  bucketCount, i;
  array_t array= xmalloc(sizeof(struct array));

  if (0 == size) size= 1;
  bucketCount= ((size - 1) / BucketSize) + 1;

  array->capacity= BucketSize * bucketCount;
  array->buckets= (bucket_t *)xmalloc(sizeof(bucket_t) * bucketCount);

  for (i= 0;  i < bucketCount;  ++i)
    array->buckets[i]= &EmptyBucket;

  array_Count += 1;
  array_Bytes += sizeof(struct array);
  array_Bytes += sizeof(bucket_t) * bucketCount;

  return array;
}

void array_free(array_t self)
{
  size_t    bucketCount= self->capacity / BucketSize;
  bucket_t *buckets= self->buckets;
  size_t    i;

  for (i= 0;  i < bucketCount;  ++i)
    if (buckets[i] != &EmptyBucket)
      {
	xfree(buckets[i]);
	array_Buckets -= 1;
	array_Bytes -= sizeof(bucket_t);
      }

  xfree(buckets);
  xfree(self);

  array_Bytes -= sizeof(bucket_t) * bucketCount;
  array_Bytes -= sizeof(struct array);
  array_Count -= 1;
}

array_t array_grow(array_t array, size_t index)
{
  if (index >= array->capacity)
    {
      size_t    oldBucketCount= array->capacity / BucketSize;
      size_t    newBucketCount= (index / BucketSize) + 1;
      bucket_t *oldBuckets= array->buckets;
      bucket_t *newBuckets= xmalloc(sizeof(bucket_t) * newBucketCount);
      size_t    i;

      for (i= 0;  i < oldBucketCount;  ++i)
	newBuckets[i]= oldBuckets[i];
      for (i= oldBucketCount;  i < newBucketCount;  ++i)
	newBuckets[i]= &EmptyBucket;

      array->buckets= newBuckets;
      array->capacity= BucketSize * newBucketCount;
      xfree(oldBuckets);

      array_Bytes += sizeof(bucket_t) * newBucketCount;
      array_Bytes -= sizeof(bucket_t) * oldBucketCount;
      if (index > array_MaxIndex)
	array_MaxIndex= index;
    }
  return array;
}

void *array_uncheckedAt_put_(array_t array, size_t index, void *element)
{
  size_t   bucketIndex= index >> BucketShift;
  size_t   elementIndex= index & ElementMask;
  bucket_t bucket;

  assert(index < array->capacity);

  bucket= array->buckets[bucketIndex];
  
  if (bucket == &EmptyBucket)
    {
      bucket= (bucket_t)xmalloc(sizeof(struct bucket));
      memcpy(bucket, &EmptyBucket, sizeof(struct bucket));
      array->buckets[bucketIndex]= bucket;

      array_Buckets += 1;
      array_Bytes += sizeof(struct bucket);
    }

  return bucket->elements[elementIndex]= element;
}


void *array_at_put_(array_t array, size_t index, void *element)
{
  if (index >= array->capacity)
    array_grow(array, index);
  return array_uncheckedAt_put_(array, index, element);
}

#if (ARRAY_TEST)

int main()
{
  array_t a= array_new(1);
  
  array_at_put_(a, 1, "one");
  array_at_put_(a, 10, "ten");
  array_at_put_(a, 100, "hundred");
  array_at_put_(a, 1000, "thousand");

  printf("%s\n", (char *)array_at_(a, 1));
  printf("%s\n", (char *)array_at_(a, 10));
  printf("%s\n", (char *)array_at_(a, 100));
  printf("%s\n", (char *)array_at_(a, 1000));
  printf("%s\n", (char *)array_at_(a, 10000));

  printf("max index %5ld\n", array_MaxIndex);
  printf("arrays    %5ld\n", array_Count);
  printf("buckets   %5ld\n", array_Buckets);
  printf("bytes     %5ld\n", array_Bytes);

  return 0;
}

#endif
