ENGINE_DEFINE_COMPONENT(engine_Camera)
ENGINE_DEFINE_COMPONENT(engine_DrawRectangle)
ENGINE_DEFINE_COMPONENT(engine_DrawCircle)
ENGINE_DEFINE_COMPONENT(engine_DrawText)
ENGINE_DEFINE_COMPONENT(engine_Sprite)

// ====================================================================================================================
static void _draw_sprite(const struct alias_LocalToWorld2D *t, const struct engine_Sprite *s) {
  struct LoadedResource *res = _resource_from_image(s->image);

  alias_R hw = res->image.width / 2, hh = res->image.height / 2, bl = -hw, br = hw, bt = -hh, bb = hh;

  alias_pga2d_Point box[] = {alias_pga2d_point(br, bb), alias_pga2d_point(br, bt), alias_pga2d_point(bl, bt),
                             alias_pga2d_point(bl, bb)};

  box[0] = alias_pga2d_sandwich_bm(box[0], t->motor);
  box[1] = alias_pga2d_sandwich_bm(box[1], t->motor);
  box[2] = alias_pga2d_sandwich_bm(box[2], t->motor);
  box[3] = alias_pga2d_sandwich_bm(box[3], t->motor);

  uint32_t *indexes;
  struct ScreenVertex *vertexes;
  _ScreenVertex_begin_draw(6, &indexes, 4, &vertexes);

  vertexes[0] = (struct ScreenVertex){.xy = {alias_pga2d_point_x(box[0]), alias_pga2d_point_y(box[0])},
                                      .rgba = {s->color.r, s->color.g, s->color.b, s->color.a},
                                      .st = {s->s1, s->t1}};
  vertexes[1] = (struct ScreenVertex){.xy = {alias_pga2d_point_x(box[1]), alias_pga2d_point_y(box[1])},
                                      .rgba = {s->color.r, s->color.g, s->color.b, s->color.a},
                                      .st = {s->s1, s->t0}};
  vertexes[2] = (struct ScreenVertex){.xy = {alias_pga2d_point_x(box[2]), alias_pga2d_point_y(box[2])},
                                      .rgba = {s->color.r, s->color.g, s->color.b, s->color.a},
                                      .st = {s->s0, s->t0}};
  vertexes[3] = (struct ScreenVertex){.xy = {alias_pga2d_point_x(box[3]), alias_pga2d_point_y(box[3])},
                                      .rgba = {s->color.r, s->color.g, s->color.b, s->color.a},
                                      .st = {s->s0, s->t1}};

  indexes[0] = 0;
  indexes[1] = 1;
  indexes[2] = 2;
  indexes[3] = 0;
  indexes[4] = 2;
  indexes[5] = 3;

  _ScreenVertex_draw(&res->image, 0, 6);
  _ScreenVertex_end_draw();
}

ENGINE_QUERY(_draw_sprites
  , read(alias_LocalToWorld2D, t)
  , read(engine_Sprite, s)
  , action(
    (void)state;
    _draw_sprite(t, s);
  )
)

// ====================================================================================================================
static void _draw_rectangle_action(const struct alias_LocalToWorld2D *t, const struct engine_DrawRectangle *r) {
 alias_R
      hw = r->width / 2
    , hh = r->height / 2
    , bl = -hw
    , br =  hw
    , bt = -hh
    , bb =  hh
    ;
  alias_pga2d_Point box[] = {
      alias_pga2d_point(br, bb)
    , alias_pga2d_point(br, bt)
    , alias_pga2d_point(bl, bt)
    , alias_pga2d_point(bl, bb)
    };
  box[0] = alias_pga2d_sandwich_bm(box[0], t->motor);
  box[1] = alias_pga2d_sandwich_bm(box[1], t->motor);
  box[2] = alias_pga2d_sandwich_bm(box[2], t->motor);
  box[3] = alias_pga2d_sandwich_bm(box[3], t->motor);
  uint32_t * indexes;
  struct ScreenVertex * vertexes;
  _ScreenVertex_begin_draw(6, &indexes, 4, &vertexes);
  vertexes[0] = (struct ScreenVertex) { .xy[0] = alias_pga2d_point_x(box[0]), .xy[1] = alias_pga2d_point_y(box[0]), .rgba = { r->color.r, r->color.g, r->color.b, r->color.a } };
  vertexes[1] = (struct ScreenVertex) { .xy[0] = alias_pga2d_point_x(box[1]), .xy[1] = alias_pga2d_point_y(box[1]), .rgba = { r->color.r, r->color.g, r->color.b, r->color.a } };
  vertexes[2] = (struct ScreenVertex) { .xy[0] = alias_pga2d_point_x(box[2]), .xy[1] = alias_pga2d_point_y(box[2]), .rgba = { r->color.r, r->color.g, r->color.b, r->color.a } };
  vertexes[3] = (struct ScreenVertex) { .xy[0] = alias_pga2d_point_x(box[3]), .xy[1] = alias_pga2d_point_y(box[3]), .rgba = { r->color.r, r->color.g, r->color.b, r->color.a } };
  indexes[0] = 0;
  indexes[1] = 1;
  indexes[2] = 2;
  indexes[3] = 0;
  indexes[4] = 2;
  indexes[5] = 3;
  _ScreenVertex_draw(NULL, 0, 6);
  _ScreenVertex_end_draw();
}

ENGINE_QUERY(_draw_rectangles
  , read(alias_LocalToWorld2D, t)
  , read(engine_DrawRectangle, r)
  , action(
    (void)state;
    _draw_rectangle_action(t, r);
  )
)

// ====================================================================================================================
static void _draw_circle(float x, float y, float radius, alias_Color color) {
#define NUM_CIRCLE_SEGMENTS 32

  struct ScreenVertex * vertexes;
  uint32_t * indexes;

  _ScreenVertex_begin_draw(3 * (NUM_CIRCLE_SEGMENTS - 2), &indexes, NUM_CIRCLE_SEGMENTS, &vertexes);

  for(uint32_t i = 0; i < NUM_CIRCLE_SEGMENTS; i++) {
    alias_R angle = (alias_R)i / NUM_CIRCLE_SEGMENTS * alias_R_PI * 2;
    alias_R s = alias_R_sin(angle);
    alias_R c = alias_R_cos(angle);
    vertexes[i].xy[0] = x + s * radius;
    vertexes[i].xy[1] = y + c * radius;
    vertexes[i].rgba[0] = color.r;
    vertexes[i].rgba[1] = color.g;
    vertexes[i].rgba[2] = color.b;
    vertexes[i].rgba[3] = color.a;
    vertexes[i].st[0] = 0;
    vertexes[i].st[1] = 0;

    if(i >= 2) {
      indexes[(i - 2) * 3 + 0] = 0;
      indexes[(i - 2) * 3 + 1] = i - 1;
      indexes[(i - 2) * 3 + 2] = i - 0;
    }
  }

  _ScreenVertex_draw(NULL, 0, 3 * (NUM_CIRCLE_SEGMENTS - 2));
  _ScreenVertex_end_draw();

#undef NUM_CIRCLE_SEGMENTS
}

ENGINE_QUERY(_draw_circles
  , read(alias_LocalToWorld2D, transform)
  , read(engine_DrawCircle, c)
  , action(
    (void)state;
    _draw_circle(alias_pga2d_point_x(transform->position), alias_pga2d_point_y(transform->position), c->radius, c->color);
  )
)

// ====================================================================================================================
ENGINE_QUERY(_draw_text
  , read(alias_LocalToWorld2D, w)
  , read(engine_DrawText, t)
  , action(
    (void)state;
    _font_draw(&BreeSerif, t->text, alias_pga2d_point_x(w->position), alias_pga2d_point_y(w->position), t->size, 1.0, t->color);
  )
)

// ====================================================================================================================
static void _draw_frame_action(const struct alias_LocalToWorld2D * transform, const struct engine_Camera * camera) {
  int x = alias_pga2d_point_x(camera->viewport_min) * _r_width;
  int y = alias_pga2d_point_y(camera->viewport_min) * _r_height;
  int width = (alias_pga2d_point_x(camera->viewport_max) * _r_width) - x;
  int height = (alias_pga2d_point_y(camera->viewport_max) * _r_height) - y;

  alias_pga2d_Point center = alias_pga2d_sandwich_bm(alias_pga2d_point(0, 0), transform->motor);

  float offset_x = width / 2.0f;
  float offset_y = height / 2.0f;
  float target_x = alias_pga2d_point_x(center);
  float target_y = alias_pga2d_point_y(center);
  float rotation = 0;

  alias_matrix_ortho(0, width, height, 0, -99999, 99999, engine_r_projection_matrix.uniform.data.mat);

  {
    float origin_m[16], rotation_m[16], scale_m[16], translation_m[16], transform_m[16];
    alias_matrix_translation(-target_x, -target_y, 0, origin_m);
    alias_matrix_rotation_z(rotation * alias_R_PI / 180.0f, rotation_m);
    alias_matrix_scale(camera->zoom, camera->zoom, 1, scale_m);
    alias_matrix_translation(offset_x, offset_y, 0, translation_m);
    alias_matrix_multiply(scale_m, rotation_m, transform_m);
    alias_matrix_multiply(origin_m, transform_m, transform_m);
    alias_matrix_multiply(transform_m, translation_m, engine_r_view_matrix.uniform.data.mat);
  }

  alias_matrix_identity(engine_r_model_matrix.uniform.data.mat);

  glViewport(x, y, width, height);

  glClearColor(0, 0, 0, 1);
  glClear(GL_COLOR_BUFFER_BIT);

  _draw_sprites();
  _draw_rectangles();
  _draw_circles();
  _draw_text();
}

ENGINE_QUERY(_draw_frame
  , read(alias_LocalToWorld2D, transform)
  , read(engine_Camera, camera)
  , pre(
    int w, h;
    glfwGetFramebufferSize(_r_window, &w, &h);
    _r_width = w;
    _r_height = h;

    glViewport(0, 0, _r_width, _r_height);
    glClearColor(0.9, 0.9, 0.9, 1);
    glClear(GL_COLOR_BUFFER_BIT);

    alias_gl_resetTemporaryBuffers();
  )
  , action(
    (void)state;
    _draw_frame_action(transform, camera);
  )
  , post(
    alias_matrix_ortho(0, _r_width, _r_height, 0, -99999, 99999, engine_r_projection_matrix.uniform.data.mat);
    alias_matrix_identity(engine_r_view_matrix.uniform.data.mat);
    glViewport(0, 0, _r_width, _r_height);

    _ui_frame();

    glfwSwapBuffers(_r_window);
  )
)