// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
fn transform_image_point(
  base_x : Double,
  base_y : Double,
  transform : @smath.Transform,
  x : Double,
  y : Double,
) -> (Float, Float) {
  let world_x = base_x + transform.a * x + transform.c * y + transform.tx
  let world_y = base_y + transform.b * x + transform.d * y + transform.ty
  (to_float(world_x), to_float(world_y))
}

///|
fn transform_uv_point(
  transform : @smath.Transform,
  u : Double,
  v : Double,
) -> (Float, Float) {
  let uv = transform.apply_to_vec2(Vec2(u, v))
  (to_float(uv[X]), to_float(uv[Y]))
}

///|
fn can_use_texture_pro_path(transform : @smath.Transform) -> Bool {
  transform.b.abs() < 0.000001 && transform.c.abs() < 0.000001
}

///|
fn uv_transform_is_identity(transform : @smath.Transform) -> Bool {
  (transform.a - 1.0).abs() < 0.000001 &&
  transform.b.abs() < 0.000001 &&
  transform.c.abs() < 0.000001 &&
  (transform.d - 1.0).abs() < 0.000001 &&
  transform.tx.abs() < 0.000001 &&
  transform.ty.abs() < 0.000001
}

///|
fn draw_axis_aligned_textured_quad(
  texture : @raylib.Texture,
  src_x0 : Double,
  src_y0 : Double,
  src_x1 : Double,
  src_y1 : Double,
  local_x0 : Double,
  local_y0 : Double,
  local_x1 : Double,
  local_y1 : Double,
  base_x : Double,
  base_y : Double,
  transform : @smath.Transform,
  tint : @render.Color,
) -> Unit {
  let sx = transform.a
  let sy = transform.d
  if sx.abs() < 0.000001 || sy.abs() < 0.000001 {
    return
  }
  let x0 = base_x + transform.tx + sx * local_x0
  let y0 = base_y + transform.ty + sy * local_y0
  let x1 = base_x + transform.tx + sx * local_x1
  let y1 = base_y + transform.ty + sy * local_y1
  let dest_x = @cmp.minimum(x0, x1)
  let dest_y = @cmp.minimum(y0, y1)
  let dest_w = (x1 - x0).abs()
  let dest_h = (y1 - y0).abs()
  if dest_w <= 0.0 || dest_h <= 0.0 {
    return
  }
  let src_w = src_x1 - src_x0
  let src_h = src_y1 - src_y0
  // DrawTexturePro expects source.x to stay at the original left edge even when
  // source.width is negative (it applies horizontal flip internally).
  let src_x = src_x0
  let src_y = if sy >= 0.0 { src_y0 } else { src_y1 }
  let draw_src_w = if sx >= 0.0 { src_w } else { -src_w }
  let draw_src_h = if sy >= 0.0 { src_h } else { -src_h }
  @raylib.draw_texture_pro(
    texture,
    to_ray_rect(src_x, src_y, draw_src_w, draw_src_h),
    to_ray_rect(dest_x, dest_y, dest_w, dest_h),
    to_ray_vector2(0.0, 0.0),
    0.0,
    to_ray_color(tint),
  )
}

///|
fn draw_affine_textured_quad(
  texture : @raylib.Texture,
  uv00 : (Float, Float),
  uv10 : (Float, Float),
  uv11 : (Float, Float),
  uv01 : (Float, Float),
  local_x0 : Double,
  local_y0 : Double,
  local_x1 : Double,
  local_y1 : Double,
  base_x : Double,
  base_y : Double,
  transform : @smath.Transform,
  tint : @render.Color,
) -> Unit {
  if local_x1 <= local_x0 || local_y1 <= local_y0 {
    return
  }
  let p00 = transform_image_point(base_x, base_y, transform, local_x0, local_y0)
  let p10 = transform_image_point(base_x, base_y, transform, local_x1, local_y0)
  let p11 = transform_image_point(base_x, base_y, transform, local_x1, local_y1)
  let p01 = transform_image_point(base_x, base_y, transform, local_x0, local_y1)
  ignore(@rl.check_render_batch_limit(4))
  @rl.set_texture(@raylib.get_texture_id(texture))
  @rl.begin(@rl.Quads)
  let (r, g, b, a) = to_ray_color_bytes(tint)
  @rl.color4ub(r, g, b, a)
  @rl.normal3f(0.0, 0.0, 1.0)
  @rl.tex_coord2f(uv00.0, uv00.1)
  @rl.vertex2f(p00.0, p00.1)
  @rl.tex_coord2f(uv01.0, uv01.1)
  @rl.vertex2f(p01.0, p01.1)
  @rl.tex_coord2f(uv11.0, uv11.1)
  @rl.vertex2f(p11.0, p11.1)
  @rl.tex_coord2f(uv10.0, uv10.1)
  @rl.vertex2f(p10.0, p10.1)
  @rl.end_()
  @rl.set_texture(0U)
}

///|
fn draw_textured_quad(
  texture : @raylib.Texture,
  texture_width : Double,
  texture_height : Double,
  src_x0 : Double,
  src_y0 : Double,
  src_x1 : Double,
  src_y1 : Double,
  local_x0 : Double,
  local_y0 : Double,
  local_x1 : Double,
  local_y1 : Double,
  base_x : Double,
  base_y : Double,
  transform : @smath.Transform,
  uv_transform : @smath.Transform,
  tint : @render.Color,
  flip_y? : Bool = false,
) -> Unit {
  let base_u0 = src_x0 / texture_width
  let base_v0_raw = src_y0 / texture_height
  let base_u1 = src_x1 / texture_width
  let base_v1_raw = src_y1 / texture_height
  let (base_v0, base_v1) = if flip_y {
    (1.0 - base_v0_raw, 1.0 - base_v1_raw)
  } else {
    (base_v0_raw, base_v1_raw)
  }
  let uv00 = transform_uv_point(uv_transform, base_u0, base_v0)
  let uv10 = transform_uv_point(uv_transform, base_u1, base_v0)
  let uv11 = transform_uv_point(uv_transform, base_u1, base_v1)
  let uv01 = transform_uv_point(uv_transform, base_u0, base_v1)
  if can_use_texture_pro_path(transform) &&
    uv_transform_is_identity(uv_transform) {
    draw_axis_aligned_textured_quad(
      texture, src_x0, src_y0, src_x1, src_y1, local_x0, local_y0, local_x1, local_y1,
      base_x, base_y, transform, tint,
    )
    return
  }
  draw_affine_textured_quad(
    texture, uv00, uv10, uv11, uv01, local_x0, local_y0, local_x1, local_y1, base_x,
    base_y, transform, tint,
  )
}

///|
fn draw_image_repeat_transform(
  texture : @raylib.Texture,
  command : @render2d_types.ImageDrawCommand2D,
  flip_y? : Bool = false,
) -> Unit {
  let texture_width = @raylib.get_texture_width(texture).to_double()
  let texture_height = @raylib.get_texture_height(texture).to_double()
  if texture_width <= 0.0 || texture_height <= 0.0 {
    return
  }
  let (src_x, src_y, src_w, src_h) = match command.source {
    Some(source) =>
      (source.position[X], source.position[Y], source.size[X], source.size[Y])
    None => (0.0, 0.0, texture_width, texture_height)
  }
  let dest_w = command.destination.size[X]
  let dest_h = command.destination.size[Y]
  if src_w <= 0.0 || src_h <= 0.0 || dest_w <= 0.0 || dest_h <= 0.0 {
    return
  }
  let base_x = command.destination.position[X]
  let base_y = command.destination.position[Y]
  if command.repeat is NoRepeat {
    draw_textured_quad(
      texture,
      texture_width,
      texture_height,
      src_x,
      src_y,
      src_x + src_w,
      src_y + src_h,
      0.0,
      0.0,
      dest_w,
      dest_h,
      base_x,
      base_y,
      command.transform,
      command.uv_transform,
      command.color,
      flip_y~,
    )
    return
  }
  let tile_w = src_w
  let tile_h = src_h
  let repeat_x = command.repeat is Repeat || command.repeat is RepeatX
  let repeat_y = command.repeat is Repeat || command.repeat is RepeatY
  let span_x = if repeat_x { dest_w } else { @cmp.minimum(dest_w, tile_w) }
  let span_y = if repeat_y { dest_h } else { @cmp.minimum(dest_h, tile_h) }
  let mut ox = 0.0
  while ox < span_x {
    let mut oy = 0.0
    while oy < span_y {
      let x0 = ox
      let y0 = oy
      let x1 = @cmp.minimum(ox + tile_w, span_x)
      let y1 = @cmp.minimum(oy + tile_h, span_y)
      let local_w = x1 - x0
      let local_h = y1 - y0
      draw_textured_quad(
        texture,
        texture_width,
        texture_height,
        src_x,
        src_y,
        src_x + local_w,
        src_y + local_h,
        x0,
        y0,
        x1,
        y1,
        base_x,
        base_y,
        command.transform,
        command.uv_transform,
        command.color,
        flip_y~,
      )
      oy += tile_h
    }
    ox += tile_w
  }
}