#include "defs.h"
#include "types.h"
#include "Map.h"

struct STypeSystem {
  STypeInfo** type_map;
  size_t last_item;
  size_t len;
};


STypeSystem * _type_system_object = NULL;

#define _INIT_TYPE_SYSTEM() {\
  if (_type_system_object == NULL) { \
    _s_internal_type_system_init();\
  }\
}

void
_s_internal_type_system_init () {
  _type_system_object = s_malloc (sizeof (STypeSystem));
  
  STypeInfo** type_map = _type_system_object->type_map =
               s_calloc (S_TYPE_MAX, sizeof (spointer));
  
  for (sint i; i < S_TYPE_MAX; i++) {
    type_map[i] = NULL;
  }
  /* The first time we run this we must make sure that we fill out the
   * fundamental types correctly */
  for (sint i = 0; i < S_TYPE_LAST_PREDEFINED; i++) {
    schar * name = STypeName[i];
    STypeInfo * ti = s_malloc (sizeof(STypeInfo));
    ti->name = s_string_new (name);
    ti->id = i;
    type_map[i] = ti;
  }
}

void
s_type_info_free (STypeInfo * ti) {
  if (ti) {
    s_free (ti->name);
    s_free (ti);
  }
}

const STypeInfo *
s_type_register_return_type_info (schar * name, SType parent) {
  _INIT_TYPE_SYSTEM();
  
  STypeInfo** type_map = _type_system_object->type_map;
  
  hash_t k = s_hash (name) % S_TYPE_MAX;
  
  if (k < S_TYPE_LAST_PREDEFINED) { /* Push it up a bit... */
    k += S_TYPE_LAST_PREDEFINED;
  }
  
  
  /* See if we have anyhing in that possision, if not, we just add it to that
   * possition.
   *
   * If the taken we look up through the array for a free spot.
   */
  if (type_map[k]) {
    while (type_map[k]) {
      if (s_string_is_equal (type_map[k]->name, name)) {
        return type_map[k];
      }
      k++;
    }
  }
  
  STypeInfo * ti = s_malloc (sizeof (STypeInfo));
  ti->name = NULL;
  
  ti->id = k;
  ti->parent = parent;
  ti->name = name;
  
  /*
   * Check if we are out-of-bounds and reallocs the array.
   */
  if (_type_system_object->len < k) {
    /* new size is k + 11, cus' why not? */
    _type_system_object->len = k + 11;
    type_map = s_realloc (type_map, _type_system_object->len);
  }
  
  type_map[k] = ti;
  
  if (_type_system_object->last_item < k) {
    _type_system_object->last_item = k;
  }
  
  return ti;
}

SType
s_type_register (schar * name, SType parent) {
  return s_type_register_return_type_info (name, parent)->id;
}

char *
s_type_get_name (SType k) {
  _INIT_TYPE_SYSTEM();
  
  if (k < S_TYPE_LAST_PREDEFINED) {
    return s_string_new(STypeName[k]);
  }
  
  if (!_type_system_object) {
    s_warn_print ("Typesystem not initialised.\n Returning.\n");
    return NULL;
  }
  
  STypeInfo** type_map = _type_system_object->type_map;
  if (type_map[k]) {
    return s_string_new (type_map[k]->name);
  } else {
    return NULL;
  }
  
}

SType
s_type_get_type (schar * name) {
  _INIT_TYPE_SYSTEM();
  /*
   * First do a dumb check if names are in the list of predefined.
   */
  for (size_t i; i < S_TYPE_LAST_PREDEFINED; i++) {
    if (s_string_is_equal (STypeName[i], name)) {
      return i;
    }
  }
  
  hash_t k = s_hash (name) % S_TYPE_MAX;
  
  STypeInfo ** type_map = _type_system_object->type_map;
  
  if (type_map[k]) {
    while (type_map[k]) {
      if (s_string_is_equal (type_map[k]->name, name)) {
        return type_map[k]->id;
      }
      k++;
    }
  }
  
  return S_TYPE_NONE;
  
}

SType
s_type_get_parent (SType type) {
  _INIT_TYPE_SYSTEM();
  if (type < S_TYPE_LAST_PREDEFINED) {
    return S_TYPE_NONE;
  }
  
  if (_type_system_object->type_map[type]) {
    return _type_system_object->type_map[type]->parent;
  }
  
  return S_TYPE_NONE;
  
}

SType *
s_type_get_array_of_parents (SType type, size_t * out_size) {
  _INIT_TYPE_SYSTEM();
  
  /* The array will hopefully not me longer than this... */
  SType * array = s_calloc (sizeof (SType), S_TYPE_MAX);
  
  STypeInfo ** type_map = _type_system_object->type_map;
  
  size_t len = 0;
  hash_t k = type;
  while (TRUE) {
    hash_t new_k = 0;
    if (type_map[k]) {
      new_k = type_map[k]->parent;
      array[len] = new_k;
      k = new_k;
    } else {
      break;
    }
    len++;
    if (k == S_TYPE_NONE || k == S_TYPE_INVALID) {
      break;
    }
  }
  
  *out_size = len;
  
  if (len == 0) {
    s_free (array);
    return NULL;
  }
  
  /* truncate array */
  array = s_realloc (array, sizeof(SType) * len);
  array[len] = (SType) NULL;
  
  return array;
}



void
s_type_system_teardown () {
  if (!_type_system_object) {
    return;
  }
  
  STypeInfo** type_map = _type_system_object->type_map;
  
  for (sint i = 0; i < _type_system_object->len; i++ ) {
    if (type_map[i]) {
      s_free (type_map[i]);
    }
  }
  s_free (_type_system_object);
}
