///|
/// An RGBA color stored in the byte order used by GIF compositing.
pub(all) struct Rgba {
  r : Byte
  g : Byte
  b : Byte
  a : Byte
} derive(Eq, Debug)

///|
/// Construct an opaque RGB color.
pub fn rgb(r : Byte, g : Byte, b : Byte) -> Rgba {
  { r, g, b, a: 0xff }
}

///|
/// Construct an RGBA color.
pub fn rgba(r : Byte, g : Byte, b : Byte, a : Byte) -> Rgba {
  { r, g, b, a }
}

///|
pub fn Rgba::red(self : Rgba) -> Byte {
  self.r
}

///|
pub fn Rgba::green(self : Rgba) -> Byte {
  self.g
}

///|
pub fn Rgba::blue(self : Rgba) -> Byte {
  self.b
}

///|
pub fn Rgba::alpha(self : Rgba) -> Byte {
  self.a
}

///|
/// An image plane with one RGBA pixel for each coordinate.
///
/// Pixels are stored in row-major order: `x + y * width`.
pub(all) struct Canvas {
  width : Int
  height : Int
  pixels : Array[Rgba]
} derive(Debug)

///|
pub fn Canvas::new(width~ : Int, height~ : Int, fill~ : Rgba) -> Canvas {
  if width < 0 || height < 0 {
    panic()
  }
  { width, height, pixels: Array::make(width * height, fill) }
}

///|
pub fn Canvas::width(self : Canvas) -> Int {
  self.width
}

///|
pub fn Canvas::height(self : Canvas) -> Int {
  self.height
}

///|
pub fn Canvas::len(self : Canvas) -> Int {
  self.pixels.length()
}

///|
pub fn Canvas::get(self : Canvas, x~ : Int, y~ : Int) -> Rgba? {
  if x < 0 || y < 0 || x >= self.width || y >= self.height {
    None
  } else {
    Some(self.pixels[x + y * self.width])
  }
}

///|
pub fn Canvas::set(self : Canvas, x~ : Int, y~ : Int, color~ : Rgba) -> Bool {
  if x < 0 || y < 0 || x >= self.width || y >= self.height {
    false
  } else {
    self.pixels[x + y * self.width] = color
    true
  }
}

///|
pub fn Canvas::pixels(self : Canvas) -> ArrayView[Rgba] {
  self.pixels[:]
}

///|
/// A decoded or to-be-encoded animation frame.
pub(all) struct Frame {
  canvas : Canvas
  delay_cs : Int
  disposal : Disposal
  transparent : Rgba?
  left : Int
  top : Int
} derive(Debug)

///|
pub(all) enum Disposal {
  Keep
  Background
  Previous
} derive(Eq, Debug)

///|
pub fn Frame::new(canvas~ : Canvas, delay_cs~ : Int) -> Frame {
  {
    canvas,
    delay_cs: if delay_cs < 0 {
      0
    } else {
      delay_cs
    },
    disposal: Keep,
    transparent: None,
    left: 0,
    top: 0,
  }
}

///|
pub fn Frame::canvas(self : Frame) -> Canvas {
  self.canvas
}

///|
/// Return a frame with the same timing and GIF metadata but a new canvas.
pub fn Frame::with_canvas(self : Frame, canvas : Canvas) -> Frame {
  { ..self, canvas, }
}

///|
pub fn Frame::delay_cs(self : Frame) -> Int {
  self.delay_cs
}

///|
pub fn Frame::disposal(self : Frame) -> Disposal {
  self.disposal
}

///|
pub fn Frame::with_disposal(self : Frame, disposal~ : Disposal) -> Frame {
  { ..self, disposal, }
}

///|
pub fn Frame::with_transparent(self : Frame, color~ : Rgba) -> Frame {
  { ..self, transparent: Some(color) }
}

///|
pub fn Frame::with_offset(self : Frame, left~ : Int, top~ : Int) -> Frame {
  { ..self, left, top }
}

///|
pub fn Frame::offset(self : Frame) -> (Int, Int) {
  (self.left, self.top)
}

///|
/// A GIF animation. The encoder writes GIF89a when the animation has more than one frame.
pub(all) struct Animation {
  width : Int
  height : Int
  frames : Array[Frame]
  loop_count : Int?
  background : Rgba
} derive(Debug)

///|
pub fn Animation::new(width~ : Int, height~ : Int) -> Animation {
  if width < 0 || height < 0 {
    panic()
  }
  { width, height, frames: [], loop_count: Some(0), background: rgb(0, 0, 0) }
}

///|
pub fn Animation::width(self : Animation) -> Int {
  self.width
}

///|
pub fn Animation::height(self : Animation) -> Int {
  self.height
}

///|
pub fn Animation::frames(self : Animation) -> ArrayView[Frame] {
  self.frames[:]
}

///|
pub fn Animation::frame_count(self : Animation) -> Int {
  self.frames.length()
}

///|
pub fn Animation::loop_count(self : Animation) -> Int? {
  self.loop_count
}

///|
pub fn Animation::with_loop_count(
  self : Animation,
  loop_count~ : Int?,
) -> Animation {
  { ..self, loop_count, }
}

///|
pub fn Animation::with_background(
  self : Animation,
  background~ : Rgba,
) -> Animation {
  { ..self, background, }
}

///|
pub fn Animation::add_frame(self : Animation, frame : Frame) -> Animation {
  self.frames.push(frame)
  self
}

///|
pub fn Animation::with_frame(
  self : Animation,
  index~ : Int,
  frame : Frame,
) -> Animation {
  if index < 0 || index >= self.frames.length() {
    panic()
  }
  self.frames[index] = frame
  self
}

///|
pub(all) suberror GifError {
  InvalidSignature
  UnexpectedEnd(context~ : String)
  InvalidDimensions
  InvalidBlock(context~ : String)
  UnsupportedFeature(feature~ : String)
  InvalidLzw(context~ : String)
  InvalidPalette(context~ : String)
} derive(Eq, Debug)