////////////////////////////////
// NOTE(allen): Render Helper Functions

//// render wrappers ////

// init selected graphics API & the renderer backend

function B32
r_init(void){
  B32 backend = r_backend_init();
  B32 renderer = r_renderer_init();
  B32 result = (backend && renderer);
  return(result);
}

// flushing a list (submitting all batches)

function void
r_flush(R_List *list){
  // api check
  if (!r_renderer_is_inited()){
    er_emit(str8_lit("renderer is not initialized"));
  }
  // flush
  else{
    for (R_Batch *batch = list->first_batch;
         batch != 0;
         batch = batch->next){
      r_submit_quads(batch->first, batch->total_count, batch->texture);
    }
  }
}

// font preperation

function R_Font*
r_font_bake_and_build_texture(Arena *arena, FONT_Loose *loose,
                              FONT_PackParams *packp){
  R_Font *result = 0;
  
  // api check
  if (!r_renderer_is_inited()){
    er_emit(str8_lit("renderer is not initialized"));
  }
  
  // main path
  else{
    ArenaTemp scratch = arena_get_scratch(0, 0);
    
    FONT_Baked *baked = font_bake(arena, loose, packp);
    
    R_Texture *tex = r_texture_create(0, baked->atlas_w, baked->atlas_h);
    
    U32 min_index = baked->min_index;
    for (FONT_GlyphLoose *node = loose->first_glyph;
         node != 0;
         node = node->next){
      U32 raw_index = node->index;
      Assert(raw_index >= min_index);
      U32 adj_index = raw_index - min_index;
      
      FONT_GlyphCoords *coords = baked->glyph_coords + adj_index;
      r_texture_update(tex, node->bitmap,
                       coords->dim.x, coords->dim.y,
                       coords->p.x, coords->p.y);
    }
    
    arena_release_scratch(&scratch);
    
    // fill result
    result = push_array(arena, R_Font, 1);
    result->baked = baked;
    result->texture = tex;
  }
  
  return(result);
}


//// render batch helpers ////

// IMPORTANT: The helpers for the render batch data structure
// should not depend on the render abstraction at all. That way
// they can be used as pure data without having an initialized
// or actively selected renderer.

function void
r_batch_push_quad(Arena *arena, R_Batch *batch, R_Quad *quad){
  // add first node
  if (batch->last == 0){
    R_QuadNode *new_node = push_array(arena, R_QuadNode, 1);
    batch->first = batch->last = new_node;
    batch->node_count = 1;
  }
  
  // add another node
  R_QuadNode *tail = batch->last;
  if (tail->count >= R_BATCH_CHUNK_MAX){
    R_QuadNode *new_node = push_array(arena, R_QuadNode, 1);
    SLLQueuePush(batch->first, batch->last, new_node);
    batch->node_count += 1;
    tail = new_node;
  }
  
  // initialize the tail's quad buffer
  if (tail->quads == 0){
    tail->quads = push_array(arena, R_Quad, R_BATCH_CHUNK_MAX);
  }
  
  // fill next quad slot
  {
    R_Quad *new_quad = tail->quads + tail->count;
    MemoryCopyStruct(new_quad, quad);
  }
  
  // increment counters
  tail->count += 1;
  batch->total_count += 1;
}

function void
r_batch_push_string(Arena *arena, R_Batch *batch,
                    FONT_Baked *font, String8 string,
                    V2F32 p, U32 c, RectF32 *clip_maybe){
  Assert(font != 0);
  
  // fixed values
  U32 fallback_index = font_glyph_index_from_codepoint(font, '?');
  U32 min_index = font->min_index;
  FONT_GlyphLayout *layout_array = font->glyph_layout;
  FONT_GlyphUVCoords *uv_coords_array = font->glyph_uv_coords;
  
  // layout state
  V2F32 pos = p;
  
  // quad prototype
  R_Quad quad = {0};
  quad.c[0] = c;
  quad.c[1] = c;
  if (clip_maybe != 0){
    quad.flags |= R_QuadFlag_Clipped;
    quad.clip = *clip_maybe;
  }
  
  // layout loop
  U8 *ptr = string.str;
  U8 *opl = string.str + string.size;
  for (; ptr < opl; ptr += 1){
    // determine glyph index to render
    U32 index = 0;
    {
      U32 codepoint = *ptr;
      if (codepoint <= 0x7F){
        index = font_glyph_index_from_codepoint(font, codepoint);
      }
      if (index == 0){
        index = fallback_index;
      }
      index -= min_index;
    }
    
    // get layout & uv coords
    FONT_GlyphLayout   *layout = layout_array + index;
    FONT_GlyphUVCoords *uv_coords = uv_coords_array + index;
    
    quad.xy.x0 = layout->xy_off.x0 + pos.x;
    quad.xy.y0 = layout->xy_off.y0 + pos.y;
    quad.xy.x1 = layout->xy_off.x1 + pos.x;
    quad.xy.y1 = layout->xy_off.y1 + pos.y;
    quad.uv = uv_coords->uv;
    
    r_batch_push_quad(arena, batch, &quad);
    pos.x += layout->advance;
  }
}

function R_Ctx
r_ctx_make(Arena *out_arena, R_List *out_list){
  R_Ctx result = {0};
  result.arena = out_arena;
  result.list = out_list;
  return(result);
}

function void
r_ctx_font(R_Ctx *ctx, R_Font *font){
  if (font != 0){
    ctx->font = font->baked;
    ctx->texture = font->texture;
  }
}

function void
r_ctx_clip(R_Ctx *ctx, B32 enable, RectF32 rect){
  ctx->clip_enabled = enable;
  ctx->clip_rect = rect;
}

function void
r_quad(R_Ctx *ctx, R_Quad *quad, R_Texture *tex){
  r_prep_batch(ctx->arena, ctx->list, tex);
  r_batch_push_quad(ctx->arena, ctx->list->last_batch, quad);
}

function void
r_rect(R_Ctx *ctx, RectF32 xy, F32 r, U32 c_lin){
  r_prep_batch(ctx->arena, ctx->list, 0);
  
  R_Quad quad = {0};
  quad.xy = xy;
  quad.radius = r;
  quad.c[0] = c_lin;
  quad.c[1] = c_lin;
  r_ctx_apply_clip(ctx, &quad);
  r_batch_push_quad(ctx->arena, ctx->list->last_batch, &quad);
}

function void
r_vrt_line(R_Ctx *ctx, F32 x, RangeF32 y, U32 c_lin){
  r_prep_batch(ctx->arena, ctx->list, 0);
  
  R_Quad quad = {0};
  quad.xy = rectf32(x, y.min, x + 1.f, y.max);
  quad.c[0] = c_lin;
  quad.c[1] = c_lin;
  r_ctx_apply_clip(ctx, &quad);
  r_batch_push_quad(ctx->arena, ctx->list->last_batch, &quad);
}

function void
r_segment(R_Ctx *ctx, V2F32 p0, V2F32 p1, F32 r, U32 c_lin){
  // translate the input geometry into quad geometry
  V2F32 center = v2f32_scalar_mul(0.5f, v2f32_add(p0, p1));
  V2F32 v = v2f32_sub(p1, p0);
  F32 length = sqrt_F32(v.x*v.x + v.y*v.y);
  F32 x_hdim = length*0.5f + r;
  F32 y_hdim = r;
  F32 theta = angle_from_v2f32(v);
  
  // push
  r_prep_batch(ctx->arena, ctx->list, 0);
  R_Quad quad = {0};
  quad.xy = rectf32(center.x - x_hdim, center.y - y_hdim,
                    center.x + x_hdim, center.y + y_hdim);
  quad.radius = r;
  quad.theta = theta;
  quad.c[0] = c_lin;
  quad.c[1] = c_lin;
  r_ctx_apply_clip(ctx, &quad);
  r_batch_push_quad(ctx->arena, ctx->list->last_batch, &quad);
}

function void
r_string(R_Ctx *ctx, String8 string, V2F32 p, U32 c_lin){
  if (ctx->font == 0){
    er_emit(str8_lit("r_string: no font set in render context"));
  }
  else{
    r_prep_batch(ctx->arena, ctx->list, ctx->texture);
    RectF32 *clip_ptr = 0;
    if (ctx->clip_enabled){
      clip_ptr = &ctx->clip_rect;
    }
    r_batch_push_string(ctx->arena, ctx->list->last_batch, ctx->font,
                        string, p, c_lin, clip_ptr);
  }
}

function void
r_char(R_Ctx *ctx, U64 cp, V2F32 p, U32 c_lin){
  if (ctx->font == 0){
    er_emit(str8_lit("r_char: no font set in render context"));
  }
  else{
    r_prep_batch(ctx->arena, ctx->list, ctx->texture);
    
    // determine glyph index to render
    FONT_Baked *font = ctx->font;
    U32 raw_index = font_glyph_index_from_codepoint(font, cp);
    if (raw_index >= font->min_index){
      // get layout & uv coords
      U32 index = raw_index - font->min_index;
      FONT_GlyphLayout   *layout = font->glyph_layout + index;
      FONT_GlyphUVCoords *uv_coords = font->glyph_uv_coords + index;
      
      // quad
      R_Quad quad = {0};
      quad.xy.x0 = p.x + layout->xy_off.x0;
      quad.xy.y0 = p.y + layout->xy_off.y0;
      quad.xy.x1 = p.x + layout->xy_off.x1;
      quad.xy.y1 = p.y + layout->xy_off.y1;
      quad.uv = uv_coords->uv;
      quad.thick = 0.f;
      quad.c[0] = c_lin;
      quad.c[1] = c_lin;
      r_ctx_apply_clip(ctx, &quad);
      
      r_batch_push_quad(ctx->arena, ctx->list->last_batch, &quad);
    }
  }
}

function void
r_ctx_apply_clip(R_Ctx *ctx, R_Quad *quad){
  if (ctx->clip_enabled){
    quad->flags |= R_QuadFlag_Clipped;
    quad->clip = ctx->clip_rect;
  }
  else{
    quad->flags &= ~R_QuadFlag_Clipped;
  }
}

function void
r_new_batch(Arena *arena, R_List *list){
  R_Batch *batch = push_array(arena, R_Batch, 1);
  SLLQueuePush(list->first_batch, list->last_batch, batch);
  list->batch_count += 1;
}

function void
r_prep_batch(Arena *arena, R_List *list, R_Texture *texture){
  // add first batch
  if (list->last_batch == 0){
    r_new_batch(arena, list);
  }
  
  // only track texture changes for non-zero textures
  if (texture != 0){
    
    // if texture change then add batch
    if (list->last_batch->texture != 0 &&
        list->last_batch->texture != texture){
      r_new_batch(arena, list);
    }
    
    // do texture change
    list->last_batch->texture = texture;
  }
}

