static float _physics_speed = 1.0f;
static float _physics_time;
static float _physics_frame_time;

static void _update_physics(void) {
  const float timestep = 1.0f / 60.0f;

  static float p_time = 0.0f;
  static float s_time = 0.0f;

  float time = _physics_time;
  _physics_time = (float)((double)uv_hrtime() / 1000000000.0);
  _physics_frame_time = _physics_time - time;

  s_time += _physics_frame_time * _physics_speed;

  if(p_time >= s_time) {
    alias_transform_update2d_serial(engine_ecs(), engine_ecs_transform());
  }

  while(p_time < s_time) {
    alias_physics_update2d_serial_pre_transform(engine_ecs(), engine_ecs_physics_2d(), timestep);

    alias_transform_update2d_serial(engine_ecs(), engine_ecs_transform());

    alias_physics_update2d_serial_post_transform(engine_ecs(), engine_ecs_physics_2d(), timestep);

    p_time += timestep;
  }
}

static void __timer_cb(uv_timer_t *timer) {
  if(glfwWindowShouldClose(_r_window)) {
    uv_timer_stop(timer);
    return;
  }

  glfwPollEvents();

  _input_frame();

  if(!_state_frame()) {
    glfwSetWindowShouldClose(_r_window, GLFW_TRUE);
    uv_timer_stop(timer);
    return;
  }

  _update_physics();

  _draw_frame();
}

void _gl_debug_message_callback(uint32_t source, uint32_t type, uint32_t id, uint32_t severity, int length, const char *message, const void *ud) {
  const char *source_str = source == GL_DEBUG_SOURCE_API               ? "API"
                           : source == GL_DEBUG_SOURCE_WINDOW_SYSTEM   ? "Window System"
                           : source == GL_DEBUG_SOURCE_SHADER_COMPILER ? "Shader Compiler"
                           : source == GL_DEBUG_SOURCE_THIRD_PARTY     ? "Third Party"
                           : source == GL_DEBUG_SOURCE_APPLICATION     ? "Application"
                           : source == GL_DEBUG_SOURCE_OTHER           ? "Other"
                                                                  : "Invalid";

  const char *type_str = type == GL_DEBUG_TYPE_ERROR                 ? "Error"
                         : type == GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR ? "Deprecated Behavior"
                         : type == GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR  ? "Undefined Behavior"
                         : type == GL_DEBUG_TYPE_PORTABILITY         ? "Portability"
                         : type == GL_DEBUG_TYPE_PERFORMANCE         ? "Performance"
                         : type == GL_DEBUG_TYPE_MARKER              ? "Marker"
                         : type == GL_DEBUG_TYPE_PUSH_GROUP          ? "Push Group"
                         : type == GL_DEBUG_TYPE_POP_GROUP           ? "Pop Group"
                         : type == GL_DEBUG_TYPE_OTHER               ? "Other"
                                                                  : "Invalid";

  switch(severity) {
  case GL_DEBUG_SEVERITY_HIGH:
  default:
    engine_error("%s from %s: %.*s", type_str, source_str, length, message);
    break;
  case GL_DEBUG_SEVERITY_MEDIUM:
    engine_warning("%s from %s: %.*s", type_str, source_str, length, message);
    break;
  case GL_DEBUG_SEVERITY_LOW:
    engine_debug("%s from %s: %.*s", type_str, source_str, length, message);
    break;
  case GL_DEBUG_SEVERITY_NOTIFICATION:
    engine_info("%s from %s: %.*s", type_str, source_str, length, message);
    break;
  }
}

int main(int argc, char * argv []) {
  uint32_t i;

  argv = uv_setup_args(argc, argv);

  alias_libuv_init();
  alias_ecs_create_instance(NULL, &_ecs);

  // process -set arguments
  i = 1;
  while(i < argc) {
    if(argv[i] == NULL) {
      i++;
      continue;
    }
    if(argv[i][0] == '-' && argv[i][1] == 's' && argv[i][2] == 'e' && argv[i][3] == 't' && argv[i][4] == 0) {
      uint32_t start = i;
      i++;
      while(i < argc && argv[i][0] != '-' && argv[i][0] != '+') i++;
      argv[start] += 1;
      _command_execute(i - start, (char const * const *)(argv + start));
      for(uint32_t j = start; j < i; j++) {
        argv[j] = NULL;
      }
    } else {
      i++;
    }
  }

  _fs_init();

  _state = engine_application_entryPoint();

  alias_libuv_run_forever();

  // now files commands have been run, files are loaded. we can loop again for the rendering timer.
  // but first, process the + arguments
  i = 1;
  while(i < argc) {
    if(argv[i] == NULL) {
      i++;
      continue;
    }
    if(argv[i][0] == '-' || argv[i][0] == '+') {
      uint32_t start = i;
      i++;
      while(i < argc && argv[i][0] != '-' && argv[i][0] != '+') i++;
      argv[start] += 1;
      _command_execute(i - start, (char const * const *)(argv + start));
      for(uint32_t j = start; j < i; j++) {
        argv[j] = NULL;
      }
    } else {
      engine_error("unused argument %s", argv[i]);
      i++;
    }
  }

  if(!glfwInit()) {
    engine_error("glfw: failed to initialize");
    return 0;
  }

  glfwDefaultWindowHints();
  glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

  int width = r_window_width;
  int height = r_window_height;

  GLFWmonitor *primary = glfwGetPrimaryMonitor();

  int count;
  GLFWmonitor **monitors = glfwGetMonitors(&count);

  GLFWmonitor *monitor = r_monitor == -1 ? primary : monitors[alias_min(alias_max(r_monitor, 0), count)];

  const GLFWvidmode *mode = glfwGetVideoMode(monitor);

  if(r_window_mode == r_window_mode_windowed) {
    monitor = NULL;

    if(width == 0) {
      width = mode->width / 2;
    }

    if(height == 0) {
      height = mode->height / 2;
    }
  } else if(r_window_mode == r_window_mode_fullscreen) {
  } else if(r_window_mode == r_window_mode_borderless) {
    glfwWindowHint(GLFW_RED_BITS, mode->redBits);
    glfwWindowHint(GLFW_GREEN_BITS, mode->greenBits);
    glfwWindowHint(GLFW_BLUE_BITS, mode->blueBits);
    glfwWindowHint(GLFW_REFRESH_RATE, mode->refreshRate);
    width = mode->width;
    height = mode->height;
  }

  _r_window = glfwCreateWindow(width, height, "alias engine", monitor, NULL);
  if(_r_window == NULL) {
    engine_error("glfw: could not create window");
    return 0;
  }

  glfwMakeContextCurrent(_r_window);

  if(gladLoadGL(glfwGetProcAddress) == 0) {
    engine_error("glad: could not load");
    return 0;
  }

  glDebugMessageCallback(_gl_debug_message_callback, NULL);

  uv_timer_t timer;
  uv_timer_init(alias_libuv_event_loop(), &timer);
  uv_timer_start(&timer, __timer_cb, 1000 / 60, 1000 / 60);

  if(_state != NULL && _state->begin != NULL) {
    _state->begin(_state->ud);
  }

  alias_libuv_run_forever();

  return 0;
}