#include "local.h"

// --------------------------------------------------------------------------------------------------------------------
static alias_str _boolean_read(const void *data_pointer, const void *ud) {
  bool data = *(const bool *)data_pointer;
  return alias_str_clone(NULL, data ? "true" : "false");
}

static void _boolean_write(void *data_pointer, const void *ud, alias_str string) {
  bool * data = (bool *)data_pointer;
  if(*string >= '0' && *string <= '9') { 
    *data = !!atoi(string);
  }
  *data = strcmp(string, "true") == 0;
}

struct EngineVariableType EngineVariableType_boolean = {
  .read = _boolean_read,
  .write = _boolean_write
};

// --------------------------------------------------------------------------------------------------------------------
static alias_str _integer_read(const void *data_pointer, const void *ud) {
  int64_t data = *(const int64_t *)data_pointer;
  return alias_str_format(NULL, "%ill", data);
}

static void _integer_write(void *data_pointer, const void *ud, alias_str string) {
  int64_t * data = (int64_t *)data_pointer;
  *data = atoi(string);
}

struct EngineVariableType EngineVariableType_integer = {
  .read = _integer_read,
  .write = _integer_write
};

// --------------------------------------------------------------------------------------------------------------------
static alias_str _real_read(const void *data_pointer, const void *ud) {
  float data = *(const float *)data_pointer;
  return alias_str_format(NULL, "%f", data);
}

static void _real_write(void *data_pointer, const void *ud, alias_str string) {
  float * data = (float *)data_pointer;
  *data = atof(string);
}

struct EngineVariableType EngineVariableType_real = {
  .read = _real_read,
  .write = _real_write
};

// --------------------------------------------------------------------------------------------------------------------
alias_str EngineVariableTypeEnum_read(const void *data_pointer, const void *ud) {
  int64_t data = *(const int64_t *)data_pointer;
  const struct EngineVariableTypeEnumOption *options = ud;
  for(; options->name != NULL; options++) {
    if(options->value == data) {
      return alias_str_clone(NULL, options->name);
    }
  }
  return NULL;
}

void EngineVariableTypeEnum_write(void *data_pointer, const void *ud, alias_str string) {
  int64_t * data = (int64_t *)data_pointer;
  if(*string >= '0' && *string <= '9') { 
    *data = atoi(string);
  }
  const struct EngineVariableTypeEnumOption *options = ud;
  for(; options->name != NULL; options++) {
    if(strcmp(options->name, string) == 0) {
      *data = options->value;
    }
  }
}

// --------------------------------------------------------------------------------------------------------------------
static alias_Vector(struct EngineVariable *) _variables = ALIAS_VECTOR_INIT;
static alias_Vector(uint32_t) _by_name = ALIAS_VECTOR_INIT;

static int _by_name_compare(const void *ap, const void *bp, void *ud) {
  uint32_t a = *(uint32_t *)ap;
  uint32_t b = *(uint32_t *)bp;
  return strcmp(_variables.data[a]->name, _variables.data[b]->name);
}

static struct EngineVariable ** _find(alias_str name) {
  if(_variables.length == 0) {
    return NULL;
  }
  struct EngineVariable _search_variable = {.name = name};
  uint32_t key = _variables.length;
  _variables.data[key] = &_search_variable;
  uint32_t *found = bsearch(&key, _by_name.data, _by_name.length, sizeof(*_by_name.data), _by_name_compare, NULL);
  if(found == NULL) {
    return NULL;
  }
  return &_variables.data[*found];
}

static void _insert(struct EngineVariable *var) {
  uint32_t index = _variables.length;

  alias_Vector_space_for(&_variables, NULL, 2);
  alias_Vector_space_for(&_by_name, NULL, 2);

  *alias_Vector_push(&_variables) = var;
  *alias_Vector_push(&_by_name) = index;

  qsort(_by_name.data, _by_name.length, sizeof(*_by_name.data), _by_name_compare, NULL);
}

// --------------------------------------------------------------------------------------------------------------------
void Engine_variable_register(struct EngineVariable *var) {
  struct EngineVariable **var_ptr = _find(var->name);
  if(var_ptr == NULL) {
    var->default_value = var->variable_type->read(var->data_pointer, var->variable_type->ud);
    var->current_value = var->variable_type->read(var->data_pointer, var->variable_type->ud);
    _insert(var);
    return;
  }
  if((*var_ptr)->default_value == NULL && (*var_ptr)->variable_type == NULL) {
    alias_str_free(NULL, (*var_ptr)->name);
    alias_str_free(NULL, (*var_ptr)->default_value);
    alias_free(NULL, (*var_ptr), sizeof(*(*var_ptr)), alignof(*(*var_ptr)));
    *var_ptr = var;
    var->default_value = var->variable_type->read(var->data_pointer, var->variable_type->ud);
    var->current_value = var->variable_type->read(var->data_pointer, var->variable_type->ud);
  } else {
    ALIAS_ERROR("Attempting to register Engine variable %s more than once", var->name);
    return;
  }
}

alias_str Engine_variable_get(alias_str name) {
  struct EngineVariable **var_ptr = _find(name);
  if(var_ptr == NULL) return "";
  return (*var_ptr)->current_value;
}

void Engine_variable_set(alias_str name, alias_str value) {
  struct EngineVariable **var_ptr = _find(name);
  if(var_ptr == NULL) {
    struct EngineVariable *var = alias_malloc(NULL, sizeof(*var), alignof(*var));
    var->name = alias_str_clone(NULL, name);
    var->default_value = NULL;
    var->current_value = alias_str_clone(NULL, value);
    var->data_pointer = NULL;
    var->variable_type = NULL;
    _insert(var);
    return;
  }
  struct EngineVariable *var = *var_ptr;
  alias_str_free(NULL, var->current_value);
  var->current_value = alias_str_clone(NULL, value);
  if(var->variable_type != NULL && var->data_pointer != NULL) {
    var->variable_type->write(var->data_pointer, var->variable_type->ud, value);
  }
}