struct ScreenVertex {
  float xy[2];
  float rgba[4];
  float st[2];
};

enum Sampler { Sampler_NEAREST, Sampler_LINEAR, NUM_BACKEND_SAMPLERS };

ENGINE_ENUM(r_window_mode, 0, "",
  (windowed, 0, ""),
  (fullscreen, 1, ""),
  (borderless, 2, "")
)
ENGINE_INTEGER(r_monitor, -1, "Chosen monitor index for fullscreen, -1 gets the primary monitor")
ENGINE_INTEGER(r_window_width, 0, "desired width of window")
ENGINE_INTEGER(r_window_height, 0, "desired height of window")
static uint32_t _r_width;
static uint32_t _r_height;
static GLFWwindow * _r_window;

static inline void prepare_model_view(void) {
  alias_matrix_multiply(engine_r_view_matrix.uniform.data.mat, engine_r_model_matrix.uniform.data.mat,
                     engine_r_model_view_matrix.uniform.data.mat);
}

static inline void prepare_view_projection(void) {
  alias_matrix_multiply(engine_r_projection_matrix.uniform.data.mat, engine_r_view_matrix.uniform.data.mat,
                     engine_r_view_projection_matrix.uniform.data.mat);
}

static inline void prepare_model_view_projection(void) {
  alias_gl_ShaderResource_prepare(&engine_r_view_projection_matrix);
  alias_matrix_multiply(engine_r_view_projection_matrix.uniform.data.mat, engine_r_model_matrix.uniform.data.mat,
                     engine_r_model_view_projection_matrix.uniform.data.mat);
}

struct alias_gl_ShaderResource engine_r_time = {.type = alias_gl_Type_float, .name = "time"};
struct alias_gl_ShaderResource engine_r_model_matrix = {.type = alias_gl_Type_float4x4, .name = "model_matrix"};
struct alias_gl_ShaderResource engine_r_view_matrix = {.type = alias_gl_Type_float4x4, .name = "view_matrix"};
struct alias_gl_ShaderResource engine_r_model_view_matrix = {
    .type = alias_gl_Type_float4x4, .name = "model_view_matrix", .uniform.prepare = prepare_model_view};
struct alias_gl_ShaderResource engine_r_projection_matrix = {.type = alias_gl_Type_float4x4, .name = "projection_matrix"};
struct alias_gl_ShaderResource engine_r_view_projection_matrix = {
    .type = alias_gl_Type_float4x4, .name = "view_projection_matrix", .uniform.prepare = prepare_view_projection};
struct alias_gl_ShaderResource engine_r_model_view_projection_matrix = {
    .type = alias_gl_Type_float4x4, .name = "model_view_projection_matrix", .uniform.prepare = prepare_model_view_projection};

ALIAS_GL_SHADER(_ScreenVertex_vertex,
  code(
    layout(location = 0) out vec2 out_st;
    layout(location = 1) out vec4 out_rgba;
  ),
  main(
    gl_Position = u_view_projection_matrix * vec4(in_xy, 0, 1);
    out_st = in_st;
    out_rgba = in_rgba;
  )
)

ALIAS_GL_SHADER(_ScreenVertex_fragment,
  code(
    layout(location = 0) in vec2 in_st;
    layout(location = 1) in vec4 in_rgba;

    layout(location = 0) out vec4 out_color;
  ),
  main(
    out_color = texture(u_img, in_st) * in_rgba;
  )
)

static struct alias_gl_Buffer _ScreenVertex_element_buffer;
static struct alias_gl_Buffer _ScreenVertex_vertexes_buffer;
static struct alias_gl_DrawState _ScreenVertex_draw_state = {
  .primitive = GL_TRIANGLES,
  .attribute[0] = {0, alias_memory_Format_Float32, 2, "xy", 0},
  .attribute[1] = {0, alias_memory_Format_Float32, 4, "rgba", 8},
  .attribute[2] = {0, alias_memory_Format_Float32, 2, "st", 24},
  .binding[0] = {sizeof(struct ScreenVertex)},
  .global[0] = {ALIAS_GL_VERTEX_BIT, &engine_r_view_projection_matrix},
  .image[0] = {ALIAS_GL_FRAGMENT_BIT, alias_gl_Type_sampler2D, "img"},
  .vertex_shader = &_ScreenVertex_vertex_shader,
  .fragment_shader = &_ScreenVertex_fragment_shader,
  .depth_range_min = 0,
  .depth_range_max = 1,
  .blend_enable = true,
  .blend_src_factor = GL_SRC_ALPHA,
  .blend_dst_factor = GL_ONE_MINUS_SRC_ALPHA};

static void _ScreenVertex_begin_draw(uint32_t num_indexes, uint32_t **indexes_ptr, uint32_t num_vertexes,
                                     struct ScreenVertex **vertexes_ptr) {
  _ScreenVertex_element_buffer =
      alias_gl_allocateTemporaryBuffer(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32_t) * num_indexes);
  _ScreenVertex_vertexes_buffer =
      alias_gl_allocateTemporaryBuffer(GL_ARRAY_BUFFER, sizeof(struct ScreenVertex) * num_vertexes);

  *indexes_ptr = _ScreenVertex_element_buffer.mapping;
  *vertexes_ptr = _ScreenVertex_vertexes_buffer.mapping;
}

static uint32_t _render_white_image(void) {
  static bool init = false;
  static struct Image image;
  if(!init) {
    init = true;
    uint8_t * data = alias_stack_allocation(16 * 16 * 3, 1);
    alias_memory_set(data, 16 * 16 * 3, 255, 16 * 16 * 3);
    _image_upload_2d(&image, 16, 16, 3, data, true);
  }
  return image.gl.image;
}

static void _ScreenVertex_draw(struct Image * image, uint32_t index_offset, uint32_t num_indexes) {
  alias_gl_drawElements(&_ScreenVertex_draw_state, &(struct alias_gl_DrawAssets){
    .image[0] = image ? image->gl.image : _render_white_image(),
    .element_buffer = &_ScreenVertex_element_buffer,
    .element_buffer_offset = index_offset,
    .vertex_buffers[0] = &_ScreenVertex_vertexes_buffer
  }, num_indexes, 1, 0, 0);
}

static void _ScreenVertex_end_draw(void) {
}

#define FONT_DRAW_CHARACTERS_PER_BATCH 1024

static void _font_draw(struct Font *font, const char *text, float x, float y, float size, float spacing, alias_Color color) {
  struct ScreenVertex *vertexes = NULL;
  uint32_t *indexes = NULL;
  uint32_t i = 0;

  size_t length = alias_str_length(text);

  struct Image *image = &_resource_from_image(&font->atlas)->image;
  float s_scale = alias_R_ONE / image->width;
  float t_scale = alias_R_ONE / image->height;

  while(*text) {
    if(i == 0) {
      _ScreenVertex_begin_draw(6 * alias_min(FONT_DRAW_CHARACTERS_PER_BATCH, length), &indexes,
                               4 * alias_min(FONT_DRAW_CHARACTERS_PER_BATCH, length), &vertexes);
    }

    const struct Font_Glyph *glyph = _font_findGlyph(font, text);

    // TODO utf8 advance
    text++;

#define EMIT(I, V, H)                                                                                                  \
  vertexes[i * 4 + I].xy[0] = x + glyph->plane_##H * size;                                                             \
  vertexes[i * 4 + I].xy[1] = y + (1.0f - glyph->plane_##V) * size;                                                    \
  vertexes[i * 4 + I].rgba[0] = color.r;                                                                               \
  vertexes[i * 4 + I].rgba[1] = color.g;                                                                               \
  vertexes[i * 4 + I].rgba[2] = color.b;                                                                               \
  vertexes[i * 4 + I].rgba[3] = color.a;                                                                               \
  vertexes[i * 4 + I].st[0] = glyph->atlas_##H * s_scale;                                                              \
  vertexes[i * 4 + I].st[1] = 1.0f - (glyph->atlas_##V * t_scale);
    EMIT(0, bottom, left)
    EMIT(1, bottom, right)
    EMIT(2, top, right)
    EMIT(3, top, left)
#undef EMIT

    indexes[i * 6 + 0] = i * 4 + 0;
    indexes[i * 6 + 1] = i * 4 + 2;
    indexes[i * 6 + 2] = i * 4 + 1;
    indexes[i * 6 + 3] = i * 4 + 2;
    indexes[i * 6 + 4] = i * 4 + 0;
    indexes[i * 6 + 5] = i * 4 + 3;

    x += glyph->advance * size + spacing;
    i++;
    length--;

    if(i >= FONT_DRAW_CHARACTERS_PER_BATCH) {
      _ScreenVertex_draw(image, 0, 6 * FONT_DRAW_CHARACTERS_PER_BATCH);
      _ScreenVertex_end_draw();
      i = 0;
    }
  }

  if(i >= 0) {
    _ScreenVertex_draw(image, 0, 6 * i);
    _ScreenVertex_end_draw();
  }
}