///|
pub(all) struct Rect {
  x : Int
  y : Int
  width : Int
  height : Int
} derive(Debug, Eq)

///|
pub struct Framebuffer {
  priv mut width : Int
  priv mut height : Int
  priv mut pixels : Array[Int]
  priv max_pixels : Int
}

///|
pub fn framebuffer(
  width : Int,
  height : Int,
  max_pixels? : Int = 4194304,
) -> Framebuffer raise RfbError {
  check_dimensions(width, height, max_pixels)
  { width, height, pixels: Array::make(width * height, 0), max_pixels, }
}

///|
fn check_dimensions(w : Int, h : Int, max : Int) -> Unit raise RfbError {
  if w <= 0 ||
    h <= 0 ||
    w > 65535 ||
    h > 65535 ||
    max <= 0 ||
    max > 16777216 ||
    w > max / h {
    raise Limit("framebuffer dimensions")
  }
}

///|
fn check_rect(rect : Rect, w : Int, h : Int) -> Unit raise RfbError {
  if rect.x < 0 ||
    rect.y < 0 ||
    rect.width <= 0 ||
    rect.height <= 0 ||
    rect.x > w ||
    rect.y > h ||
    rect.width > w - rect.x ||
    rect.height > h - rect.y {
    raise Invalid("rectangle outside framebuffer")
  }
}

///|
pub fn Framebuffer::size(self : Framebuffer) -> (Int, Int) {
  (self.width, self.height)
}

///|
/// Snapshot is owned by caller; mutating it cannot mutate framebuffer.
pub fn Framebuffer::snapshot(self : Framebuffer) -> Array[Int] {
  self.pixels.copy()
}

///|
pub fn Framebuffer::resize(
  self : Framebuffer,
  width : Int,
  height : Int,
) -> Unit raise RfbError {
  check_dimensions(width, height, self.max_pixels)
  self.width = width
  self.height = height
  self.pixels = Array::make(width * height, 0)
}

///|
pub fn Framebuffer::paint(
  self : Framebuffer,
  rect : Rect,
  pixels : Array[Int],
) -> Unit raise RfbError {
  check_rect(rect, self.width, self.height)
  if pixels.length() != rect.width * rect.height {
    raise Invalid("pixel count")
  }
  for p in pixels {
    if p < 0 || p > 0xffffff {
      raise Invalid("RGB pixel range")
    }
  }
  for y in 0.. Array[Int] raise RfbError {
  if w * h > (r.data.length() - r.pos) / (f.bits / 8) {
    raise NeedMore
  }
  Array::makei(w * h, _ => read_pixel(r, f, false))
}

///|
/// Decode exactly one Raw rectangle payload without needing a network session.
pub fn decode_raw(
  data : Array[Int],
  format : PixelFormat,
  width : Int,
  height : Int,
) -> Array[Int] raise RfbError {
  format.validate()
  check_dimensions(width, height, 4194304)
  let r = Reader::new(data)
  let pixels = raw_pixels(r, format, width, height)
  if r.pos != data.length() {
    raise Invalid("trailing raw bytes")
  }
  pixels
}

///|
/// CopyRect has memmove semantics even when source and destination overlap.
pub fn Framebuffer::copy_rect(
  self : Framebuffer,
  dest : Rect,
  source_x : Int,
  source_y : Int,
) -> Unit raise RfbError {
  check_rect(dest, self.width, self.height)
  check_rect({ ..dest, x: source_x, y: source_y, }, self.width, self.height)
  let saved = Array::makei(dest.width * dest.height, i => {
    self.pixels[(source_y + i / dest.width) * self.width +
    source_x +
    i % dest.width]
  })
  self.paint(dest, saved)
}