static alias_Vector(struct engine_Variable *) _variable_all = ALIAS_VECTOR_INIT;
static alias_Vector(uint32_t) _variable_by_name = ALIAS_VECTOR_INIT;

static int _variable_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(_variable_all.data[a]->name, _variable_all.data[b]->name);
}

static struct engine_Variable ** _variable_find(const char * name) {
  if(_variable_all.length == 0) {
    return NULL;
  }
  struct engine_Variable _search_variable = {.name = name};
  uint32_t key = _variable_all.length;
  _variable_all.data[key] = &_search_variable;
  uint32_t *found = bsearch(&key, _variable_by_name.data, _variable_by_name.length, sizeof(*_variable_by_name.data), _variable_by_name_compare, NULL);
  if(found == NULL) {
    return NULL;
  }
  return &_variable_all.data[*found];
}

static void _variable_insert(struct engine_Variable *var) {
  uint32_t index = _variable_all.length;

  alias_Vector_space_for(&_variable_all, NULL, 2);
  alias_Vector_space_for(&_variable_by_name, NULL, 2);

  *alias_Vector_push(&_variable_all) = var;
  *alias_Vector_push(&_variable_by_name) = index;

  qsort(_variable_by_name.data, _variable_by_name.length, sizeof(*_variable_by_name.data), _variable_by_name_compare, NULL);
}

static const char * _variable_get_string(struct engine_Variable *var) {
  switch(var->tag) {
  case engine_Variable_undefined:
  default:
    engine_error("invalid variable type");
    return alias_str_clone(NULL, "");
  case engine_Variable_string:
    return alias_str_clone(NULL, *var->_string.ptr);
  case engine_Variable_boolean:
    return alias_str_clone(NULL, *var->_boolean.ptr ? "true" : "false");
  case engine_Variable_integer:
    return alias_str_format(NULL, "%ill", *var->_integer.ptr);
  case engine_Variable_real:
    return alias_str_format(NULL, "%d", *var->_real.ptr);
  case engine_Variable_enumerator:
    for(uint32_t i = 0; var->_enumerator.options[i].name; i++) {
      if(*var->_enumerator.ptr == var->_enumerator.options[i].value) {
        return alias_str_clone(NULL, var->_enumerator.options[i].name);
      }
    }
    return alias_str_format(NULL, "%ill", *var->_enumerator.ptr);
  } 
}

static void _variable_set_string(struct engine_Variable *var, const char *string) {
  uint32_t i;
  switch(var->tag) {
  case engine_Variable_undefined:
    alias_str_free(NULL, var->_value);
    var->_value = alias_str_clone(NULL, string);
    break;
  case engine_Variable_string:
    if(var->_string.is_cloned) {
      alias_str_free(NULL, *var->_string.ptr);
    }
    *var->_string.ptr = alias_str_clone(NULL, string);
    var->_value = *var->_string.ptr;
    var->_string.is_cloned = true;
    break;
  case engine_Variable_boolean:
    if(strcmp(string, "false") == 0 || atoi(string) == 0) {
      *var->_boolean.ptr = false;
    } else {
      *var->_boolean.ptr = true;
    }
    alias_str_free(NULL, var->_value);
    var->_value = _variable_get_string(var);
    break;
  case engine_Variable_integer:
    *var->_integer.ptr = atoi(string);
    alias_str_free(NULL, var->_value);
    var->_value = _variable_get_string(var);
    break;
  case engine_Variable_real:
    *var->_real.ptr = atof(string);
    alias_str_free(NULL, var->_value);
    var->_value = _variable_get_string(var);
    break;
  case engine_Variable_enumerator:
    for(i = 0; var->_enumerator.options[i].name; i++) {
      if(strcmp(var->_enumerator.options[i].name, string) == 0) {
        *var->_enumerator.ptr = var->_enumerator.options[i].value;
        break;
      }
    }
    if(var->_enumerator.options[i].name == NULL) {
      *var->_enumerator.ptr = atoi(string);
    }
    alias_str_free(NULL, var->_value);
    var->_value = _variable_get_string(var);
    break;
  default:
    engine_error("invalid variable type");
    break;
  } 
}

static void _variable_set_integer(struct engine_Variable *var, int64_t integer) {
  switch(var->tag) {
  case engine_Variable_undefined:
    alias_str_free(NULL, var->_value);
    var->_value = alias_str_format(NULL, "%ull", integer);
    break;
  case engine_Variable_string:
    if(var->_string.is_cloned) {
      alias_str_free(NULL, *var->_string.ptr);
    }
    *var->_string.ptr = alias_str_format(NULL, "%ull", integer);
    var->_value = *var->_string.ptr;
    var->_string.is_cloned = true;
    break;
  case engine_Variable_boolean:
    if(integer == 0) {
      *var->_boolean.ptr = false;
    } else {
      *var->_boolean.ptr = true;
    }
    alias_str_free(NULL, var->_value);
    var->_value = _variable_get_string(var);
    break;
  case engine_Variable_integer:
    *var->_integer.ptr = integer;
    alias_str_free(NULL, var->_value);
    var->_value = _variable_get_string(var);
    break;
  case engine_Variable_real:
    *var->_real.ptr = integer;
    alias_str_free(NULL, var->_value);
    var->_value = _variable_get_string(var);
    break;
  case engine_Variable_enumerator:
    *var->_enumerator.ptr = integer;
    alias_str_free(NULL, var->_value);
    var->_value = _variable_get_string(var);
    break;
  default:
    engine_error("invalid variable type");
    break;
  } 
}

static void _variable_set_real(struct engine_Variable *var, double real) {
  switch(var->tag) {
  case engine_Variable_undefined:
    alias_str_free(NULL, var->_value);
    var->_value = alias_str_format(NULL, "%d", real);
    break;
  case engine_Variable_string:
    if(var->_string.is_cloned) {
      alias_str_free(NULL, *var->_string.ptr);
    }
    *var->_string.ptr = alias_str_format(NULL, "%d", real);
    var->_value = *var->_string.ptr;
    var->_string.is_cloned = true;
    break;
  case engine_Variable_boolean:
    if(real == 0) {
      *var->_boolean.ptr = false;
    } else {
      *var->_boolean.ptr = true;
    }
    alias_str_free(NULL, var->_value);
    var->_value = _variable_get_string(var);
    break;
  case engine_Variable_integer:
    *var->_integer.ptr = real;
    alias_str_free(NULL, var->_value);
    var->_value = _variable_get_string(var);
    break;
  case engine_Variable_real:
    *var->_real.ptr = real;
    alias_str_free(NULL, var->_value);
    var->_value = _variable_get_string(var);
    break;
  case engine_Variable_enumerator:
    *var->_enumerator.ptr = real;
    alias_str_free(NULL, var->_value);
    var->_value = _variable_get_string(var);
    break;
  default:
    engine_error("invalid variable type");
    break;
  } 
}

void engine_variable_register(struct engine_Variable *var) {
  struct engine_Variable **var_ptr = _variable_find(var->name);
  if(var_ptr == NULL) {
    var->_value = _variable_get_string(var);
    _variable_insert(var);
  } else if((*var_ptr)->tag == engine_Variable_undefined) {
    alias_str_free(NULL, (*var_ptr)->name);
    _variable_set_string(var, (*var_ptr)->_value);
    alias_str_free(NULL, (*var_ptr)->_value);
    alias_free(NULL, *var_ptr, sizeof(**var_ptr), alignof(**var_ptr));
    *var_ptr = var;
  } else {
    engine_error("attempting to register engine variable %s more than once", var->name);
    return;
  }
}

const char *engine_variable_get(const char *name, const char *_default) {
  struct engine_Variable **var_ptr = _variable_find(name);
  if(var_ptr == NULL) {
    return _default;
  }
  return (*var_ptr)->_value;
}

int64_t engine_variable_getInteger(const char *name, int64_t _default) {
  struct engine_Variable **var_ptr = _variable_find(name);
  if(var_ptr == NULL) {
    return _default;
  }
  switch((*var_ptr)->tag) {
  case engine_Variable_boolean:
    return *(*var_ptr)->_boolean.ptr;
  case engine_Variable_integer:
    return *(*var_ptr)->_integer.ptr;
  case engine_Variable_real:
    return *(*var_ptr)->_real.ptr;
  case engine_Variable_enumerator:
    return *(*var_ptr)->_enumerator.ptr;
  default:
    return atoi((*var_ptr)->_value);
  }
}

double engine_variable_getReal(const char *name, double _default) {
  struct engine_Variable **var_ptr = _variable_find(name);
  if(var_ptr == NULL) {
    return _default;
  }
  switch((*var_ptr)->tag) {
  case engine_Variable_boolean:
    return *(*var_ptr)->_boolean.ptr;
  case engine_Variable_integer:
    return *(*var_ptr)->_integer.ptr;
  case engine_Variable_real:
    return *(*var_ptr)->_real.ptr;
  case engine_Variable_enumerator:
    return *(*var_ptr)->_enumerator.ptr;
  default:
    return atoi((*var_ptr)->_value);
  }
}

static void _variable_undefined(const char *name, const char *value) {
  struct engine_Variable *var = alias_malloc(NULL, sizeof(*var), alignof(*var));
  var->tag = engine_Variable_undefined;
  var->name = alias_str_clone(NULL, name);
  var->_value = alias_str_clone(NULL, value);
  _variable_insert(var);
}

void engine_variable_set(const char *name, const char *value) {
  struct engine_Variable **var_ptr = _variable_find(name);
  if(var_ptr == NULL) {
    _variable_undefined(name, value);
  } else {
    _variable_set_string(*var_ptr, value);
  }
}

void engine_variable_setInteger(const char *name, int64_t value) {
  struct engine_Variable **var_ptr = _variable_find(name);
  if(var_ptr == NULL) {
    const char *value_as_string;
    alias_str_stack_format(value_as_string, PRIi64, value);
    _variable_undefined(name, value_as_string);
  } else {
    _variable_set_integer(*var_ptr, value);
  }
}

void engine_variable_setReal(const char *name, double value) {
  struct engine_Variable **var_ptr = _variable_find(name);
  if(var_ptr == NULL) {
    const char *value_as_string;
    alias_str_stack_format(value_as_string, "%f", value);
    _variable_undefined(name, value_as_string);
  } else {
    _variable_set_real(*var_ptr, value);
  }
}

ENGINE_COMMAND(set, "set <name> <value>") {
  if(argc < 3) {
    engine_info("missing arguments");
    return;
  }
  engine_variable_set(argv[1], argv[2]);
}