// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

// The program the guide walks through, in order.
//
// It is here so that every snippet on that page has been run: the output printed
// below each one on the page is the output this prints. `moon run` it and the
// numbers should be the ones the page shows.

///|
/// A picture made from nothing: four columns, three rows, five colours.
///
/// Written out pixel by pixel so that the guide has a file whose contents are
/// known — every later number on the page comes from this one.
fn picture() -> @raster.Image raise @raster.Refused {
  let palette : Array[(Int32, Int32, Int32, Int32)] = [
    (214, 178, 92, 255),
    (122, 58, 44, 255),
    (79, 106, 84, 255),
    (36, 28, 21, 255),
    (244, 237, 222, 128),
  ]
  let bytes : FixedArray[Byte] = FixedArray::make(4 * 3 * 4, b'\x00')
  for y in 0..<3 {
    for x in 0..<4 {
      let (r, g, b, a) = palette[(y * 4 + x) % palette.length()]
      let at = (y * 4 + x) * 4
      bytes[at] = r.to_byte()
      bytes[at + 1] = g.to_byte()
      bytes[at + 2] = b.to_byte()
      bytes[at + 3] = a.to_byte()
    }
  }
  @raster.Image::of(
    4,
    3,
    @raster.PixelFormat::Rgba8,
    Bytes::from_array(bytes.clamped_view()),
  )
}

///|
/// The whole of a small copy in one function: read, scale, write.
///
/// This is the function the homepage shows, and the shape most callers end up
/// with — the format is discovered rather than passed in, and the error is the
/// one value that can go wrong.
fn small_copy(file : Bytes) -> Bytes raise @moonimage.Refused {
  let image = @moonimage.decode(file)
  let small = image.scale(320, 200)
  @moonimage.encode(small, @moonimage.Format::Webp)
}

///|
fn main {
  // A picture, and what it says about itself.
  let image = picture() catch {
    error => abort("the picture is well formed: \{error}")
  }
  println(
    "\{image.width} x \{image.height}, \{image.format}, \{image.pixels.length()} bytes",
  )

  // Writing it out as a PNG, and reading it back.
  let file = @moonimage.encode(image, @moonimage.Format::Png)
  let back = @moonimage.decode(file) catch {
    error => abort("the file is one this library wrote: \{error}")
  }
  println(
    "\{file.length()} bytes of PNG, read back as \{back.width} x \{back.height} \{back.format}",
  )

  // One pixel, as four numbers between nought and one.
  let pixel = back.pixel(1, 0) catch {
    error => abort("inside the picture: \{error}")
  }
  let eighth = fn(value : Double) -> Int32 { (value * 255.0 + 0.5).to_int() }
  println(
    "pixel (1, 0), eight bits a sample: \{eighth(pixel.r)} \{eighth(pixel.g)} \{eighth(pixel.b)} \{eighth(pixel.a)}",
  )

  // The same pixel after a change of layout.
  let grey = back.convert(@raster.PixelFormat::Gray8)
  let shade = grey.pixel(1, 0) catch {
    error => abort("inside the picture: \{error}")
  }
  println("as grey: \{grey.format}, \{eighth(shade.r)}")

  // The same picture as a thumbnail, through the one function the homepage
  // shows.
  let thumb = small_copy(file) catch {
    error => abort("the copy is well formed: \{error}")
  }
  println("as a 320 x 200 WebP: \{thumb.length()} bytes")

  // Geometry: each call gives a new picture.
  let turned = back.rotate(quarters=1)
  let mirrored = back.flip_horizontal()
  let cut = back.crop(0, 0, 2, 2) catch {
    error => abort("the rectangle fits: \{error}")
  }
  let small = back.scale(8, 6) catch {
    error => abort("the size is legal: \{error}")
  }
  println(
    "turned \{turned.width} x \{turned.height}, mirrored \{mirrored.width} x \{mirrored.height}, cut \{cut.width} x \{cut.height}, scaled \{small.width} x \{small.height}",
  )

  // Every format this library writes, with the size it came to.
  let formats : Array[(@moonimage.Format, String)] = [
    (@moonimage.Format::Png, "png"),
    (@moonimage.Format::Qoi, "qoi"),
    (@moonimage.Format::Bmp, "bmp"),
    (@moonimage.Format::Tga, "tga"),
    (@moonimage.Format::Jpeg, "jpeg"),
    (@moonimage.Format::Gif, "gif"),
    (@moonimage.Format::Webp, "webp"),
  ]
  for entry in formats {
    let (format, name) = entry
    let written = @moonimage.encode(back, format)
    println("\{name}: \{written.length()} bytes")
  }

  // Two frames, written as an animated GIF and read back.
  let animation = @moonimage.Animation::new(
    4,
    3,
    [
      @moonimage.Frame::new(back, delay=100),
      @moonimage.Frame::new(turned, delay=50),
    ],
    loops=0,
  )
  let gif = @moonimage.encode_animation(animation, @moonimage.Format::Gif) catch {
    error => abort("the animation is well formed: \{error}")
  }
  let again = @moonimage.decode_animation(gif) catch {
    error => abort("the file is one this library wrote: \{error}")
  }
  let frames = again.compose()
  println(
    "\{gif.length()} bytes of GIF, \{again.frames.length()} frames, first delay \{again.frames[0].delay}, \{frames.length()} composed",
  )

  // A file that is not one: the error says where and what.
  let broken = file.clamped_view(start=0, end=file.length() / 2).to_owned()
  let refused = try {
    ignore(@moonimage.decode(broken))
    "it did not"
  } catch {
    error => error.to_string()
  }
  println("half a file: \{refused}")

  // And a ceiling on what a file is allowed to ask for.
  let limits = @raster.Limits::{ width: 2, height: 2, pixels: 4, bytes: 1024, }
  let capped = try {
    ignore(@moonimage.decode(file, limits~))
    "it did not"
  } catch {
    error => error.to_string()
  }
  println("two by two limit: \{capped}")
}