///|
/// RGBA8 image data in row-major order.
pub(all) struct Image {
  width : Int
  height : Int
  pixels : Bytes
} derive(Eq)

///|
/// Limits applied before allocating decoded image buffers.
pub(all) struct DecodeLimits {
  max_input_bytes : Int
  max_dimension : Int
  max_pixels : Int
}

///|
/// Default limits: 64 MiB compressed input, 16,384 pixels per edge, 64 MiB RGBA.
pub fn default_decode_limits() -> DecodeLimits {
  {
    max_input_bytes: 64 * 1024 * 1024,
    max_dimension: 16384,
    max_pixels: 16 * 1024 * 1024,
  }
}

///|
pub(all) enum ImageErrorKind {
  InvalidInput
  ResourceLimit
  UnsupportedFormat
  InvalidDimensions
  NotSquare
} derive(Eq, Debug)

///|
pub suberror ImageError {
  ImageError(ImageErrorKind, String)
}

///|
/// Construct a checked RGBA8 image.
pub fn Image::new(
  width : Int,
  height : Int,
  pixels : Bytes,
) -> Image raise ImageError {
  if width <= 0 || height <= 0 || width > 0x3fffffff / height {
    raise ImageError(InvalidDimensions, "image dimensions are invalid")
  }
  let expected = width * height
  if expected > 0x3fffffff / 4 || pixels.length() != expected * 4 {
    raise ImageError(
      InvalidDimensions,
      "RGBA8 buffer length does not match dimensions",
    )
  }
  { width, height, pixels }
}

///|
pub fn Image::is_square(self : Image) -> Bool {
  self.width == self.height
}

///|
fn checked_square(image : Image) -> Unit raise ImageError {
  if !image.is_square() {
    raise ImageError(NotSquare, "icon encoders require a square image")
  }
}

///|
fn u32_be(data : BytesView, offset : Int) -> Int {
  (data[offset].to_int() << 24) |
  (data[offset + 1].to_int() << 16) |
  (data[offset + 2].to_int() << 8) |
  data[offset + 3].to_int()
}

///|
fn write_u16_le(out : Buffer, value : Int) -> Unit {
  out.write_byte((value & 0xff).to_byte())
  out.write_byte(((value >> 8) & 0xff).to_byte())
}

///|
fn write_u32_le(out : Buffer, value : Int) -> Unit {
  for shift = 0; shift < 32; shift = shift + 8 {
    out.write_byte(((value >> shift) & 0xff).to_byte())
  }
}

///|
fn write_u32_be(out : Buffer, value : Int) -> Unit {
  out.write_byte(((value >> 24) & 0xff).to_byte())
  out.write_byte(((value >> 16) & 0xff).to_byte())
  out.write_byte(((value >> 8) & 0xff).to_byte())
  out.write_byte((value & 0xff).to_byte())
}

///|
fn paeth(a : Int, b : Int, c : Int) -> Int {
  let p = a + b - c
  let pa = (p - a).abs()
  let pb = (p - b).abs()
  let pc = (p - c).abs()
  if pa <= pb && pa <= pc {
    a
  } else if pb <= pc {
    b
  } else {
    c
  }
}