///|
struct Wtf8(Bytes) derive(Eq, Show, Hash, Compare, Default)

///|
pub suberror Malformed Int derive(Show, ToJson)

///|
fn validate(bytes : BytesView) -> Int {
  loop bytes {
    [] => break -1
    [0x00..=0x7F, .. rest] => continue rest
    [0xC2..=0xDF, 0x80..=0xBF, .. rest] => continue rest
    [0xE0, 0xA0..=0xBF, 0x80..=0xBF, .. rest] => continue rest
    [0xED, 0xA0..=0xBF, 0x80..=0xBF, 0xED, 0xB0..=0xBF, 0x80..=0xBF, ..] as view =>
      return view.start_offset()
    [0xE1..=0xEF, 0x80..=0xBF, 0x80..=0xBF, .. rest] => continue rest
    [0xF0, 0x90..=0xBf, 0x80..=0xBF, 0x80..=0xBF, .. rest] => continue rest
    [0xF1..=0xF3, 0x80..=0xBF, 0x80..=0xBF, 0x80..=0xBF, .. rest] =>
      continue rest
    [0xF4, 0x80..=0x8F, 0x80..=0xBF, 0x80..=0xBF, .. rest] => continue rest
    [..] as view => return view.start_offset()
  }
}

///|
#alias(of_codeunits)
#as_free_fn
pub fn Wtf8::of_bytes(bytes : Bytes) -> Wtf8 raise Malformed {
  let error = validate(bytes)
  if error >= 0 {
    raise Malformed(error)
  }
  bytes
}

///|
pub fn Wtf8::of_codepoints(cps : FixedArray[@codepoint.CodePoint]) -> Wtf8 {
  let len = cps.length()
  let buf = buffer(capacity=len * 4)
  for i in 0.. Bytes {
  self.0
}

///|
pub fn Wtf8::codepoints(self : Wtf8) -> Iterator[@codepoint.CodePoint] {
  let mut view = self.0[:]
  Iterator::new(() => match view {
    [] => None
    [0x00..=0x7F as b0, .. rest] => {
      view = rest
      Some(@codepoint.of_int_unchecked(b0.to_int()))
    }
    [0xC2..=0xDF as b0, b1, .. rest] => {
      view = rest
      let b0 = b0.to_int()
      let b1 = b1.to_int()
      let c = ((b0 & 0x1F) << 6) | (b1 & 0x3F)
      Some(@codepoint.of_int_unchecked(c))
    }
    [0xE0..=0xEF as b0, b1, b2, .. rest] => {
      view = rest
      let b0 = b0.to_int()
      let b1 = b1.to_int()
      let b2 = b2.to_int()
      let c = ((b0 & 0x0F) << 12) | ((b1 & 0x3F) << 6) | (b2 & 0x3F)
      Some(@codepoint.of_int_unchecked(c))
    }
    [0xF0..=0xF4 as b0, b1, b2, b3, .. rest] => {
      view = rest
      let b0 = b0.to_int()
      let b1 = b1.to_int()
      let b2 = b2.to_int()
      let b3 = b3.to_int()
      let c = ((b0 & 0x07) << 18) |
        ((b1 & 0x3F) << 12) |
        ((b2 & 0x3F) << 6) |
        (b3 & 0x3F)
      Some(@codepoint.of_int_unchecked(c))
    }
    [..] => abort("Malformed WTF-8 sequence")
  })
}

///|
pub fn Wtf8::to_codepoints(self : Wtf8) -> FixedArray[Int] {
  let buf = []
  loop self.0[:] {
    [] => break
    [0x00..=0x7F as b0, .. rest] => {
      buf.push(b0.to_int())
      continue rest
    }
    [0xC2..=0xDF as b0, b1, .. rest] => {
      let b0 = b0.to_int()
      let b1 = b1.to_int()
      let c = ((b0 & 0x1F) << 6) | (b1 & 0x3F)
      buf.push(c)
      continue rest
    }
    [0xE0..=0xEF as b0, b1, b2, .. rest] => {
      let b0 = b0.to_int()
      let b1 = b1.to_int()
      let b2 = b2.to_int()
      let c = ((b0 & 0x0F) << 12) | ((b1 & 0x3F) << 6) | (b2 & 0x3F)
      buf.push(c)
      continue rest
    }
    [0xF0..=0xF4 as b0, b1, b2, b3, .. rest] => {
      let b0 = b0.to_int()
      let b1 = b1.to_int()
      let b2 = b2.to_int()
      let b3 = b3.to_int()
      let c = ((b0 & 0x07) << 18) |
        ((b1 & 0x3F) << 12) |
        ((b2 & 0x3F) << 6) |
        (b3 & 0x3F)
      buf.push(c)
      continue rest
    }
    [..] => abort("Malformed WTF-8 sequence")
  }
  FixedArray::from_array(buf)
}

///|
pub impl ToJson for Wtf8 with to_json(self) {
  self.to_bytes().to_array().to_json()
}

///|
pub fn Wtf8::to_utf8(self : Wtf8) -> @utf8.Utf8 raise @utf8.Malformed {
  @utf8.of_bytes(self.to_bytes())
}