///|
struct Buffer {
  mut buf : FixedArray[UInt16]
  mut len : Int
}

///|
pub fn buffer(capacity? : Int = 0) -> Buffer {
  Buffer::new(capacity~)
}

///|
pub fn Buffer::new(capacity? : Int = 0) -> Buffer {
  let cap = if capacity < 0 { 0 } else { capacity }
  Buffer::{ buf: FixedArray::make(cap, 0), len: 0 }
}

///|
pub impl Default for Buffer with default() -> Buffer {
  Buffer::new()
}

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

///|
pub fn Buffer::capacity(self : Buffer) -> Int {
  self.buf.length()
}

///|
fn Buffer::grow(self : Buffer, by~ : Int) -> Unit {
  let new_len = self.len + by
  let mut cap = self.buf.length()
  if new_len <= cap {
    return
  }
  if cap == 0 {
    cap = 8
  }
  while new_len > cap {
    cap *= 2
  }
  let new_buf : FixedArray[UInt16] = FixedArray::make(cap, 0)
  new_buf.unsafe_blit(0, self.buf, 0, self.len)
  self.buf = new_buf
}

///|
pub fn Buffer::write_codeunit(self : Buffer, value : UInt16) -> Unit {
  self.grow(by=1)
  self.buf.unsafe_set(self.len, value)
  self.len += 1
}

///|
pub fn Buffer::write_codepoint(self : Buffer, value : @codepoint.CodePoint) -> Unit {
  let value = value.to_int()
  self.grow(by=2)
  match value {
    0x0000..=0xFFFF => {
      self.buf.unsafe_set(self.len, value.to_uint16())
      self.len += 1
    }
    0x10000..=0x10FFFF => {
      let v = value - 0x10000
      let high = 0xD800 + ((v >> 10) & 0x3FF)
      let low = 0xDC00 + (v & 0x3FF)
      self.buf.unsafe_set(self.len, high.to_uint16())
      self.buf.unsafe_set(self.len + 1, low.to_uint16())
      self.len += 2
    }
    _ => abort("Code point out of range")
  }
}

///|
pub fn Buffer::write_char(self : Buffer, value : Char) -> Unit {
  match value {
    '\u{0000}'..='\u{FFFF}' => {
      self.grow(by=1)
      self.buf.unsafe_set(self.len, value.to_int().to_uint16())
      self.len += 1
    }
    '\u{10000}'..='\u{10FFFF}' => {
      let v = value.to_int() - 0x10000
      let high = 0xD800 + ((v >> 10) & 0x3FF)
      let low = 0xDC00 + (v & 0x3FF)
      self.grow(by=2)
      self.buf.unsafe_set(self.len, high.to_uint16())
      self.buf.unsafe_set(self.len + 1, low.to_uint16())
      self.len += 2
    }
  }
}

///|
pub fn Buffer::write_string(self : Buffer, value : String) -> Unit {
  let len = value.length()
  self.grow(by=len)
  for i in 0.. Wtf16 {
  let len = self.len
  let buf : FixedArray[UInt16] = FixedArray::make(len, 0)
  buf.unsafe_blit(0, self.buf, 0, len)
  Wtf16(buf)
}