///|
/// Image codec bridge powered by mizchi/image.
/// Keeps kagura's ImageSpec as the primary data shape.

///|
pub enum RasterResizeMethod {
  Nearest
  Bilinear
  Bicubic
} derive(Debug)

///|
pub impl Show for RasterResizeMethod with output(self, logger) {
  logger.write_object(to_repr(self))
}

///|
pub enum RasterImageFormat {
  Png
  Jpeg
  Bmp
} derive(Debug, Eq)

///|
pub impl Show for RasterImageFormat with output(self, logger) {
  logger.write_object(to_repr(self))
}

///|
pub fn raster_resize_nearest() -> RasterResizeMethod {
  Nearest
}

///|
pub fn raster_resize_bilinear() -> RasterResizeMethod {
  Bilinear
}

///|
pub fn raster_resize_bicubic() -> RasterResizeMethod {
  Bicubic
}

///|
pub fn raster_image_format_png() -> RasterImageFormat {
  RasterImageFormat::Png
}

///|
pub fn raster_image_format_jpeg() -> RasterImageFormat {
  RasterImageFormat::Jpeg
}

///|
pub fn raster_image_format_bmp() -> RasterImageFormat {
  RasterImageFormat::Bmp
}

///|
fn clamp_u8_channel_for_bytes(channel : Int) -> Int {
  if channel < 0 {
    0
  } else if channel > 255 {
    255
  } else {
    channel
  }
}

///|
fn bytes_to_rgba8_channels(data : Bytes) -> Array[Int] {
  let channels : Array[Int] = []
  for byte in data.to_array() {
    channels.push(byte.to_int())
  }
  channels
}

///|
fn rgba8_channels_to_bytes(channels : Array[Int]) -> Bytes {
  let out : Array[Byte] = []
  for channel in channels {
    out.push(clamp_u8_channel_for_bytes(channel).to_byte())
  }
  Bytes::from_array(out)
}

///|
fn image_data_to_image_spec(image_data : @image.ImageData) -> ImageSpec {
  image_spec_with_rgba8(
    image_data.width,
    image_data.height,
    bytes_to_rgba8_channels(image_data.data),
  )
}

///|
fn image_spec_to_image_data(spec : ImageSpec) -> @image.ImageData {
  let safe_width = if spec.width <= 0 { 1 } else { spec.width }
  let safe_height = if spec.height <= 0 { 1 } else { spec.height }
  let normalized = normalized_rgba8_channels(
    spec.pixels_rgba8,
    safe_width,
    safe_height,
  )
  let expected = expected_rgba8_channel_count(safe_width, safe_height)
  let channels = if normalized.length() >= expected {
    normalized
  } else {
    let fallback : Array[Int] = []
    for _ in 0.. @image.ResizeMethod {
  match resize_method {
    RasterResizeMethod::Nearest => @image.ResizeMethod::Nearest
    RasterResizeMethod::Bilinear => @image.ResizeMethod::Bilinear
    RasterResizeMethod::Bicubic => @image.ResizeMethod::Bicubic
  }
}

///|
fn bytes_has_prefix(bytes : Bytes, prefix : Array[Int]) -> Bool {
  if bytes.length() < prefix.length() {
    return false
  }
  for i in 0.. RasterImageFormat? {
  if bytes_has_prefix(bytes, [137, 80, 78, 71, 13, 10, 26, 10]) {
    return Some(RasterImageFormat::Png)
  }
  if bytes_has_prefix(bytes, [255, 216, 255]) {
    return Some(RasterImageFormat::Jpeg)
  }
  if bytes_has_prefix(bytes, [66, 77]) {
    return Some(RasterImageFormat::Bmp)
  }
  None
}

///|
pub fn decode_png_image_spec(
  bytes : Bytes,
) -> ImageSpec raise @image.DecodeError {
  image_data_to_image_spec(@image.decode_png(bytes))
}

///|
pub fn decode_jpeg_image_spec(
  bytes : Bytes,
) -> ImageSpec raise @image.DecodeError {
  image_data_to_image_spec(@image.decode_jpeg(bytes))
}

///|
pub fn decode_bmp_image_spec(
  bytes : Bytes,
) -> ImageSpec raise @image.DecodeError {
  image_data_to_image_spec(@image.decode_bmp(bytes))
}

///|
pub fn decode_image_spec(
  bytes : Bytes,
  format : RasterImageFormat,
) -> ImageSpec raise @image.DecodeError {
  match format {
    RasterImageFormat::Png => decode_png_image_spec(bytes)
    RasterImageFormat::Jpeg => decode_jpeg_image_spec(bytes)
    RasterImageFormat::Bmp => decode_bmp_image_spec(bytes)
  }
}

///|
pub fn decode_image_spec_auto(
  bytes : Bytes,
) -> ImageSpec? raise @image.DecodeError {
  match detect_raster_image_format(bytes) {
    Some(format) => Some(decode_image_spec(bytes, format))
    None => None
  }
}

///|
pub fn create_image_from_raster_bytes(
  repository : SimpleImageRepository,
  key : AssetKey,
  bytes : Bytes,
) -> @gfx.ImageHandle? raise {
  match decode_image_spec_auto(bytes) {
    Some(spec) => Some(repository.create_image(key, spec))
    None => None
  }
}

///|
pub fn update_image_from_raster_bytes(
  repository : SimpleImageRepository,
  key : AssetKey,
  bytes : Bytes,
) -> @gfx.ImageHandle? raise {
  match decode_image_spec_auto(bytes) {
    Some(spec) => update_image_spec(repository, key, spec)
    None => None
  }
}

///|
pub fn create_atlas_image_from_raster_bytes(
  repository : SimpleAtlasImageRepository,
  key : AssetKey,
  bytes : Bytes,
) -> @gfx.ImageHandle? raise {
  match decode_image_spec_auto(bytes) {
    Some(spec) => create_atlas_image(repository, key, spec)
    None => None
  }
}

///|
pub fn update_atlas_image_from_raster_bytes(
  repository : SimpleAtlasImageRepository,
  key : AssetKey,
  bytes : Bytes,
) -> @gfx.ImageHandle? raise {
  match decode_image_spec_auto(bytes) {
    Some(spec) => update_atlas_image_spec(repository, key, spec)
    None => None
  }
}

///|
pub fn encode_png_image_spec(
  spec : ImageSpec,
) -> Bytes raise @image.EncodeError {
  @image.encode_png(image_spec_to_image_data(spec))
}

///|
pub fn encode_jpeg_image_spec(
  spec : ImageSpec,
  quality? : Int,
) -> Bytes raise @image.EncodeError {
  @image.encode_jpeg(image_spec_to_image_data(spec), quality?)
}

///|
pub fn encode_bmp_image_spec(
  spec : ImageSpec,
) -> Bytes raise @image.EncodeError {
  @image.encode_bmp(image_spec_to_image_data(spec))
}

///|
pub fn encode_image_spec(
  spec : ImageSpec,
  format : RasterImageFormat,
) -> Bytes raise @image.EncodeError {
  match format {
    RasterImageFormat::Png => encode_png_image_spec(spec)
    RasterImageFormat::Jpeg => encode_jpeg_image_spec(spec, quality=95)
    RasterImageFormat::Bmp => encode_bmp_image_spec(spec)
  }
}

///|
pub fn resize_image_spec(
  spec : ImageSpec,
  width : Int,
  height : Int,
  resize_method : RasterResizeMethod,
) -> ImageSpec raise @image.EncodeError {
  let safe_width = if width <= 0 { 1 } else { width }
  let safe_height = if height <= 0 { 1 } else { height }
  let resized = @image.resize(
    image_spec_to_image_data(spec),
    safe_width,
    safe_height,
    to_image_resize_method(resize_method),
  )
  image_data_to_image_spec(resized)
}