///|
#cfg(target="native")
priv suberror QpackError {
  QpackBadPrefix
  QpackBadString
  QpackBadStaticIndex(Int)
  QpackDynamicUnsupported
} derive(Debug, ToJson)

///|
#cfg(target="native")
fn qpack_static_entry(index : Int) -> (String, String) raise {
  match index {
    0 => (":authority", "")
    1 => (":path", "/")
    4 => ("content-length", "0")
    14 => ("set-cookie", "")
    15 => (":method", "CONNECT")
    16 => (":method", "DELETE")
    17 => (":method", "GET")
    18 => (":method", "HEAD")
    19 => (":method", "OPTIONS")
    20 => (":method", "POST")
    21 => (":method", "PUT")
    22 => (":scheme", "http")
    23 => (":scheme", "https")
    25 => (":status", "200")
    31 => ("accept-encoding", "gzip, deflate, br")
    42 => ("content-encoding", "br")
    43 => ("content-encoding", "gzip")
    46 => ("content-type", "application/json")
    53 => ("content-type", "text/plain")
    63 => (":status", "100")
    64 => (":status", "204")
    65 => (":status", "206")
    66 => (":status", "302")
    67 => (":status", "400")
    68 => (":status", "403")
    69 => (":status", "421")
    70 => (":status", "425")
    71 => (":status", "500")
    84 => ("authorization", "")
    92 => ("server", "")
    95 => ("user-agent", "")
    _ => raise QpackBadStaticIndex(index)
  }
}

///|
#cfg(target="native")
fn qpack_static_name_index(name : String) -> Int? {
  match name {
    ":authority" => Some(0)
    ":path" => Some(1)
    "content-length" => Some(4)
    "set-cookie" => Some(14)
    ":method" => Some(17)
    ":scheme" => Some(22)
    ":status" => Some(25)
    "accept-encoding" => Some(31)
    "content-encoding" => Some(43)
    "content-type" => Some(53)
    "authorization" => Some(84)
    "server" => Some(92)
    "user-agent" => Some(95)
    _ => None
  }
}

///|
#cfg(target="native")
fn qpack_write_string(
  out : @buffer.Buffer,
  value : String,
  prefix_bits~ : Int,
  first_mask~ : Int,
) -> Unit {
  let bytes = @utf8.encode(value)
  hpack_write_int(out, bytes.length(), prefix_bits~, first_mask~)
  out.write_bytes(bytes)
}

///|
#cfg(target="native")
fn qpack_read_string(
  data : Bytes,
  offset : Int,
  prefix_bits~ : Int,
) -> (String, Int) raise {
  guard offset < data.length() else { raise QpackBadString }
  let huffman = (data[offset].to_int() & (1 << prefix_bits)) != 0
  let (len, pos) = hpack_read_int(data, offset, prefix_bits~)
  guard pos + len <= data.length() else { raise QpackBadString }
  let raw = @buffer.new()
  raw.write_bytes(data[pos:pos + len])
  let decoded = if huffman {
    hpack_huffman_decode(raw.contents())
  } else {
    raw.contents()
  }
  (@utf8.decode_lossy(decoded), pos + len)
}

///|
#cfg(target="native")
fn qpack_encode_header_block(headers : Array[(String, String)]) -> Bytes {
  let out = @buffer.new()
  out.write_byte(b'\x00')
  out.write_byte(b'\x00')
  for item in headers {
    let (name, value) = item
    let name = name.to_lower()
    match qpack_static_name_index(name) {
      Some(index) => {
        hpack_write_int(out, index, prefix_bits=4, first_mask=0x50)
        qpack_write_string(out, value, prefix_bits=7, first_mask=0)
      }
      None => {
        qpack_write_string(out, name, prefix_bits=3, first_mask=0x20)
        qpack_write_string(out, value, prefix_bits=7, first_mask=0)
      }
    }
  }
  out.contents()
}

///|
#cfg(target="native")
fn qpack_decode_header_block(block : Bytes) -> Array[(String, String)] raise {
  let (required_insert_count, pos) = hpack_read_int(block, 0, prefix_bits=8)
  guard required_insert_count == 0 else { raise QpackDynamicUnsupported }
  let (delta_base, pos) = hpack_read_int(block, pos, prefix_bits=7)
  guard delta_base == 0 else { raise QpackDynamicUnsupported }
  let headers = []
  for pos = pos; pos < block.length(); {
    let first = block[pos].to_int()
    if (first & 0x80) != 0 {
      guard (first & 0x40) != 0 else { raise QpackDynamicUnsupported }
      let (index, pos) = hpack_read_int(block, pos, prefix_bits=6)
      headers.push(qpack_static_entry(index))
      continue pos
    } else if (first & 0xc0) == 0x40 {
      guard (first & 0x10) != 0 else { raise QpackDynamicUnsupported }
      let (index, pos) = hpack_read_int(block, pos, prefix_bits=4)
      let (name, _) = qpack_static_entry(index)
      let (value, pos) = qpack_read_string(block, pos, prefix_bits=7)
      headers.push((name, value))
      continue pos
    } else if (first & 0xe0) == 0x20 {
      let (name, pos) = qpack_read_string(block, pos, prefix_bits=3)
      let (value, pos) = qpack_read_string(block, pos, prefix_bits=7)
      headers.push((name, value))
      continue pos
    } else {
      raise QpackBadPrefix
    }
  }
  headers
}