///|
let image_material_fragment_shader : String =
  #|#version 330
  #|in vec2 fragTexCoord;
  #|in vec4 fragColor;
  #|uniform sampler2D texture0;
  #|uniform vec4 effect;
  #|uniform vec4 shadowColor;
  #|uniform vec4 shadowData;
  #|uniform vec4 uvBounds;
  #|out vec4 finalColor;
  #|bool insideBounds(vec2 uv) {
  #|  return all(greaterThanEqual(uv, uvBounds.xy)) &&
  #|         all(lessThanEqual(uv, uvBounds.zw));
  #|}
  #|float sourceAlpha(vec2 uv) {
  #|  return insideBounds(uv) ? texture(texture0, uv).a : 0.0;
  #|}
  #|void main() {
  #|  float shadowAlpha = 0.0;
  #|  if (effect.w > 0.5) {
  #|    vec2 center = fragTexCoord - shadowData.xy;
  #|    for (int y = -2; y <= 2; y++) {
  #|      for (int x = -2; x <= 2; x++) {
  #|        vec2 delta = vec2(float(x), float(y)) * shadowData.zw * 0.5;
  #|        shadowAlpha += sourceAlpha(center + delta);
  #|      }
  #|    }
  #|    shadowAlpha *= 0.04 * shadowColor.a * effect.x * fragColor.a;
  #|  }
  #|  vec4 source = vec4(0.0);
  #|  if (insideBounds(fragTexCoord)) {
  #|    source = texture(texture0, fragTexCoord) * fragColor;
  #|    float luma = dot(source.rgb, vec3(0.2126, 0.7152, 0.0722));
  #|    source.rgb = mix(vec3(luma), source.rgb, effect.z) * effect.y;
  #|    source.a *= effect.x;
  #|  }
  #|  vec4 shadow = vec4(shadowColor.rgb, shadowAlpha);
  #|  float outAlpha = source.a + shadow.a * (1.0 - source.a);
  #|  vec3 outRgb = outAlpha > 0.000001
  #|    ? (source.rgb * source.a + shadow.rgb * shadow.a * (1.0 - source.a)) / outAlpha
  #|    : vec3(0.0);
  #|  finalColor = vec4(outRgb, outAlpha);
  #|}

///|
fn get_image_material_shader_state() -> ImageMaterialShaderState {
  if backend.image_material_shader is Some(state) {
    return state
  }
  let shader = @raylib.load_shader_from_memory(
    "", image_material_fragment_shader,
  )
  let state : ImageMaterialShaderState = {
    shader,
    effect_loc: @raylib.get_shader_location(shader, "effect"),
    shadow_color_loc: @raylib.get_shader_location(shader, "shadowColor"),
    shadow_data_loc: @raylib.get_shader_location(shader, "shadowData"),
    uv_bounds_loc: @raylib.get_shader_location(shader, "uvBounds"),
  }
  backend.image_material_shader = Some(state)
  state
}

///|
fn image_material_vec4(
  x : Double,
  y : Double,
  z : Double,
  w : Double,
) -> @raylib.ShaderUniformData {
  Vec4(@raylib.Vector4::new(to_float(x), to_float(y), to_float(z), to_float(w)))
}

///|
fn draw_image_material_quad(
  texture : @raylib.Texture,
  command : @render2d_types.ImageMaterialDrawCommand2D,
  flip_y : Bool,
) -> Unit {
  let image = command.image
  let texture_width = @raylib.get_texture_width(texture).to_double()
  let texture_height = @raylib.get_texture_height(texture).to_double()
  let (src_x, src_y, src_w, src_h) = match image.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 = image.destination.size[X]
  let dest_h = image.destination.size[Y]
  if src_w <= 0.0 || src_h <= 0.0 || dest_w <= 0.0 || dest_h <= 0.0 {
    return
  }
  let shadow = command.drop_shadow
  let offset = shadow
    .map(fn(value) { value.offset })
    .unwrap_or(@smath.Vec2::zero())
  let blur = shadow.map(fn(value) { value.blur_radius }).unwrap_or(0.0)
  let left = blur * 2.0 + (-offset[X]).max(0.0)
  let top = blur * 2.0 + (-offset[Y]).max(0.0)
  let right = blur * 2.0 + offset[X].max(0.0)
  let bottom = blur * 2.0 + offset[Y].max(0.0)
  let u0 = src_x / texture_width
  let raw_v0 = src_y / texture_height
  let u1 = (src_x + src_w) / texture_width
  let raw_v1 = (src_y + src_h) / texture_height
  let (v0, v1) = if flip_y {
    (1.0 - raw_v0, 1.0 - raw_v1)
  } else {
    (raw_v0, raw_v1)
  }
  let uv_width = u1 - u0
  let uv_height = v1 - v0
  let eu0 = u0 - left / dest_w * uv_width
  let ev0 = v0 - top / dest_h * uv_height
  let eu1 = u1 + right / dest_w * uv_width
  let ev1 = v1 + bottom / dest_h * uv_height
  let (bu0, bv0) = transform_uv_point(image.uv_transform, u0, v0)
  let (bu1, bv1) = transform_uv_point(image.uv_transform, u1, v1)
  let uv00 = transform_uv_point(image.uv_transform, eu0, ev0)
  let uv10 = transform_uv_point(image.uv_transform, eu1, ev0)
  let uv11 = transform_uv_point(image.uv_transform, eu1, ev1)
  let uv01 = transform_uv_point(image.uv_transform, eu0, ev1)
  let state = get_image_material_shader_state()
  set_shader_uniform(
    state.shader,
    state.effect_loc,
    image_material_vec4(
      command.opacity,
      command.brightness,
      command.saturation,
      if shadow is Some(_) {
        1.0
      } else {
        0.0
      },
    ),
  )
  set_shader_uniform(
    state.shader,
    state.shadow_color_loc,
    shadow
    .map(fn(value) { uniform_color4(value.color) })
    .unwrap_or(image_material_vec4(0.0, 0.0, 0.0, 0.0)),
  )
  set_shader_uniform(
    state.shader,
    state.shadow_data_loc,
    image_material_vec4(
      offset[X] / dest_w * uv_width,
      offset[Y] / dest_h * uv_height,
      blur / dest_w * uv_width,
      blur / dest_h * uv_height,
    ),
  )
  set_shader_uniform(
    state.shader,
    state.uv_bounds_loc,
    image_material_vec4(
      bu0.to_double().min(bu1.to_double()),
      bv0.to_double().min(bv1.to_double()),
      bu0.to_double().max(bu1.to_double()),
      bv0.to_double().max(bv1.to_double()),
    ),
  )
  state.shader.begin_mode()
  draw_affine_textured_quad(
    texture,
    uv00,
    uv10,
    uv11,
    uv01,
    -left,
    -top,
    dest_w + right,
    dest_h + bottom,
    image.destination.position[X],
    image.destination.position[Y],
    image.transform,
    image.color,
  )
  @raylib.end_shader_mode()
}