///|
enum RegistryRoute {
  Root
  Catalog
  Blob(String, String)
  UploadStart(String)
  UploadChunk(String, String)
  Manifest(String, String)
  Referrers(String, String)
  Tags(String)
  Unknown
}

///|
fn without_query(path : String) -> String {
  match path.find("?") {
    Some(index) => path[:index].to_owned()
    None => path
  }
}

///|
fn query_value(path : String, key : String) -> String? {
  match path.find("?") {
    Some(index) => {
      let query : StringView = path[index + 1:]
      for field in query.split("&") {
        match field.find("=") {
          Some(equal) if field[:equal] == key =>
            return decode_query_value(field[equal + 1:].to_owned())
          _ => continue
        }
      }
      None
    }
    None => None
  }
}

///|
fn query_values(path : String, key : String) -> Array[String]? {
  let values : Array[String] = []
  match path.find("?") {
    Some(index) => {
      let query : StringView = path[index + 1:]
      for field in query.split("&") {
        match field.find("=") {
          Some(equal) if field[:equal] == key =>
            match decode_query_value(field[equal + 1:].to_owned()) {
              Some(value) => values.push(value)
              None => return None
            }
          _ => continue
        }
      }
      Some(values)
    }
    None => Some(values)
  }
}

///|
fn hex_nibble(value : Byte) -> Int? {
  if value >= b'0' && value <= b'9' {
    Some(value.to_int() - b'0'.to_int())
  } else if value >= b'a' && value <= b'f' {
    Some(value.to_int() - b'a'.to_int() + 10)
  } else if value >= b'A' && value <= b'F' {
    Some(value.to_int() - b'A'.to_int() + 10)
  } else {
    None
  }
}

///|
fn decode_query_value(value : String) -> String? {
  let input = @utf8.encode(value)
  let output = Buffer()
  let mut index = 0
  while index < input.length() {
    if input[index] == b'%' {
      guard index + 2 < input.length() else { return None }
      let high = match hex_nibble(input[index + 1]) {
        Some(nibble) => nibble
        None => return None
      }
      let low = match hex_nibble(input[index + 2]) {
        Some(nibble) => nibble
        None => return None
      }
      output.write_byte((high * 16 + low).to_byte())
      index += 3
    } else {
      output.write_byte(if input[index] == b'+' { b' ' } else { input[index] })
      index += 1
    }
  }
  Some(@utf8.decode(output.contents())) catch {
    _ => None
  }
}

///|
fn uppercase_hex_digit(value : Int) -> Byte {
  if value < 10 {
    (b'0'.to_int() + value).to_byte()
  } else {
    (b'A'.to_int() + value - 10).to_byte()
  }
}

///|
/// Encode one UTF-8 query value so pagination links remain valid for media
/// types containing reserved characters such as `+`, `&`, or `=`.
fn encode_query_value(value : String) -> String {
  let input = @utf8.encode(value)
  let output = Buffer()
  for byte in input {
    if (byte >= b'a' && byte <= b'z') ||
      (byte >= b'A' && byte <= b'Z') ||
      (byte >= b'0' && byte <= b'9') ||
      byte == b'-' ||
      byte == b'_' ||
      byte == b'.' ||
      byte == b'~' {
      output.write_byte(byte)
    } else {
      let number = byte.to_int()
      output.write_byte(b'%')
      output.write_byte(uppercase_hex_digit(number / 16))
      output.write_byte(uppercase_hex_digit(number % 16))
    }
  }
  @utf8.decode(output.contents()) catch {
    _ => ""
  }
}

///|
fn query_digest(path : String) -> String? {
  query_value(path, "digest")
}

///|
fn route_for(path : String) -> RegistryRoute {
  let clean = without_query(path)
  if clean == "/v2/" || clean == "/v2" {
    return Root
  }
  if clean == "/v2/_catalog" {
    return Catalog
  }
  guard clean.length() >= 4 && clean[:4] == "/v2/" else { return Unknown }
  let rest : StringView = clean[4:]
  if rest.rev_find("/blobs/uploads/") is Some(index) &&
    rest[index + 15:].find("/") is None {
    let repository = rest[:index].to_owned()
    let upload_id = rest[index + 15:].to_owned()
    if upload_id == "" {
      UploadStart(repository)
    } else {
      UploadChunk(repository, upload_id)
    }
  } else if rest.rev_find("/blobs/") is Some(index) &&
    rest[index + 7:].find("/") is None {
    Blob(rest[:index].to_owned(), rest[index + 7:].to_owned())
  } else if rest.rev_find("/manifests/") is Some(index) &&
    rest[index + 11:].find("/") is None {
    Manifest(rest[:index].to_owned(), rest[index + 11:].to_owned())
  } else if rest.rev_find("/referrers/") is Some(index) &&
    rest[index + 11:].find("/") is None {
    Referrers(rest[:index].to_owned(), rest[index + 11:].to_owned())
  } else if rest.rev_find("/tags/list") is Some(index) &&
    index + 10 == rest.length() {
    Tags(rest[:index].to_owned())
  } else {
    Unknown
  }
}

///|
async fn send_error(
  conn : @http.ServerConnection,
  code : Int,
  reason : String,
) -> Unit {
  let body = "{\"errors\":[{\"code\":\"" +
    reason +
    "\",\"message\":\"" +
    reason +
    "\"}]}"
  conn
  ..send_response(code, reason, extra_headers={
    "Content-Type": "application/json",
  })
  ..write(@utf8.encode(body))
  .end_response()
}

///|
async fn send_empty(
  conn : @http.ServerConnection,
  code : Int,
  reason : String,
) -> Unit {
  conn.send_response(code, reason, extra_headers={
    "Docker-Distribution-API-Version": "registry/2.0",
  })
  conn.end_response()
}

///|
async fn send_blob(
  conn : @http.ServerConnection,
  code : Int,
  reason : String,
  body : Bytes,
  digest : String,
) -> Unit {
  conn
  ..send_response(code, reason, extra_headers={
    "Content-Type": "application/octet-stream",
    "Content-Length": body.length().to_string(),
    "Docker-Content-Digest": digest,
    "Accept-Ranges": "bytes",
  })
  ..write(body)
  .end_response()
}

///|
enum BlobRange {
  Full
  Partial(Int, Int)
  Unsatisfiable
}

///|
/// A registry can ignore unsupported or malformed ranges. A valid range
/// beyond the end of the blob gets a 416 response instead.
fn select_blob_range(headers : @http.Headers, size : Int) -> BlobRange {
  let value = match headers.get("Range") {
    Some(value) => value
    None => return Full
  }
  guard value.length() >= 6 && value[:6] == "bytes=" else { return Full }
  let spec : StringView = value[6:]
  guard spec.find(",") is None else { return Full }
  let dash = match spec.find("-") {
    Some(index) => index
    None => return Full
  }
  if dash == 0 {
    let suffix = @string.parse_int(spec[1:]) catch { _ => return Full }
    guard suffix > 0 && size > 0 else { return Unsatisfiable }
    let start = if suffix >= size { 0 } else { size - suffix }
    return Partial(start, size - 1)
  }
  let start = @string.parse_int(spec[:dash]) catch { _ => return Full }
  guard start >= 0 else { return Full }
  if dash + 1 == spec.length() {
    guard start < size else { return Unsatisfiable }
    return Partial(start, size - 1)
  }
  let end = @string.parse_int(spec[dash + 1:]) catch { _ => return Full }
  guard end >= start else { return Full }
  guard start < size else { return Unsatisfiable }
  Partial(start, if end >= size { size - 1 } else { end })
}

///|
async fn send_manifest(
  conn : @http.ServerConnection,
  code : Int,
  reason : String,
  body : Bytes,
  content_type : String,
) -> Unit {
  let digest = sha256_digest(body)
  conn
  ..send_response(code, reason, extra_headers={
    "Content-Type": content_type,
    "Content-Length": body.length().to_string(),
    "Docker-Content-Digest": digest,
  })
  ..write(body)
  .end_response()
}

///|
/// OCI manifests and indexes are JSON objects. Decode as UTF-8 first so
/// malformed payloads cannot be persisted as apparently valid manifests.
fn valid_manifest_json(body : Bytes) -> Bool {
  let text = @utf8.decode(body) catch { _ => return false }
  let value = @json.parse(text) catch { _ => return false }
  match value {
    Object(object) => {
      let has_schema_v2 = match object.get("schemaVersion") {
        Some(Number(version, ..)) => version == 2.0
        _ => false
      }
      has_schema_v2 && valid_manifest_fields(object)
    }
    _ => false
  }
}

///|
fn valid_media_type_token(value : StringView) -> Bool {
  guard value.length() > 0 else { return false }
  for index in 0.. Bool {
  guard value.find("\r") is None && value.find("\n") is None else {
    return false
  }
  let slash = match value.find("/") {
    Some(index) => index
    None => return false
  }
  guard value[slash + 1:].find("/") is None else { return false }
  valid_media_type_token(value[:slash]) &&
  valid_media_type_token(value[slash + 1:])
}

///|
fn valid_descriptor(value : Json) -> Bool {
  guard value is Object(object) else { return false }
  let valid_type = match object.get("mediaType") {
    Some(String(value)) => valid_media_type(value)
    _ => false
  }
  let valid_digest_value = match object.get("digest") {
    Some(String(value)) => valid_digest(value)
    _ => false
  }
  let valid_size = match object.get("size") {
    Some(Number(value, ..)) => value >= 0.0 && value.floor() == value
    _ => false
  }
  valid_type && valid_digest_value && valid_size
}

///|
fn valid_optional_descriptor(object : Map[String, Json], key : String) -> Bool {
  match object.get(key) {
    Some(value) => valid_descriptor(value)
    None => true
  }
}

///|
fn valid_optional_descriptor_list(
  object : Map[String, Json],
  key : String,
) -> Bool {
  match object.get(key) {
    Some(Array(values)) => {
      for value in values {
        if !valid_descriptor(value) {
          return false
        }
      }
      true
    }
    None => true
    _ => false
  }
}

///|
fn valid_manifest_fields(object : Map[String, Json]) -> Bool {
  match object.get("mediaType") {
    Some(String(value)) if valid_media_type(value) => ()
    None => ()
    _ => return false
  }
  match object.get("artifactType") {
    Some(String(value)) if valid_media_type(value) => ()
    None => ()
    _ => return false
  }
  valid_optional_descriptor(object, "config") &&
  valid_optional_descriptor(object, "subject") &&
  valid_optional_descriptor_list(object, "layers") &&
  valid_optional_descriptor_list(object, "blobs") &&
  valid_optional_descriptor_list(object, "manifests")
}

///|
fn descriptor_digest(value : Json) -> String? {
  guard value is Object(object) else { return None }
  match object.get("digest") {
    Some(String(digest)) if valid_digest(digest) => Some(digest)
    _ => None
  }
}

///|
fn append_descriptor_digests(value : Json, output : Array[String]) -> Bool {
  match value {
    Array(values) => {
      for descriptor in values {
        match descriptor_digest(descriptor) {
          Some(digest) => output.push(digest)
          None => return false
        }
      }
      true
    }
    _ => false
  }
}

///|
/// Separate blob dependencies from nested manifests; subject is a relationship
/// target and is intentionally not required to exist for an artifact push.
fn manifest_dependencies(body : Bytes) -> (Array[String], Array[String])? {
  let text = @utf8.decode(body) catch { _ => return None }
  let value = @json.parse(text) catch { _ => return None }
  guard value is Object(object) else { return None }
  let blobs : Array[String] = []
  let manifests : Array[String] = []
  match object.get("config") {
    Some(descriptor) =>
      match descriptor_digest(descriptor) {
        Some(digest) => blobs.push(digest)
        None => return None
      }
    None => ()
  }
  for key in ["layers", "blobs"] {
    match object.get(key) {
      Some(value) if append_descriptor_digests(value, blobs) => ()
      Some(_) => return None
      None => ()
    }
  }
  match object.get("manifests") {
    Some(value) if append_descriptor_digests(value, manifests) => ()
    Some(_) => return None
    None => ()
  }
  Some((blobs, manifests))
}

///|
async fn manifest_dependencies_exist(
  store : RegistryStore,
  repository : String,
  body : Bytes,
) -> Bool {
  let (blobs, manifests) = match manifest_dependencies(body) {
    Some(value) => value
    None => return false
  }
  for digest in blobs {
    if !store.has_repository_blob(repository, digest) {
      return false
    }
  }
  for digest in manifests {
    if store.get_manifest(repository, digest) is None {
      return false
    }
  }
  true
}

///|
fn manifest_reference_matches(reference : String, digest : String) -> Bool {
  !valid_digest(reference) || reference == digest
}

///|
fn trim_http_ows(value : StringView) -> String {
  let mut start = 0
  let mut end = value.length()
  while start < end &&
        (value[start].to_int() == 32 || value[start].to_int() == 9) {
    start += 1
  }
  while end > start &&
        (value[end - 1].to_int() == 32 || value[end - 1].to_int() == 9) {
    end -= 1
  }
  value[start:end].to_owned()
}

///|
fn manifest_body_media_type(body : Bytes) -> String? {
  let text = @utf8.decode(body) catch { _ => return None }
  let value = @json.parse(text) catch { _ => return None }
  guard value is Object(object) else { return None }
  match object.get("mediaType") {
    Some(String(value)) if valid_media_type(value) => Some(value)
    _ => None
  }
}

///|
fn valid_manifest_content_type(headers : @http.Headers, body : Bytes) -> Bool {
  match headers.get("Content-Type") {
    Some(raw) => {
      guard raw.find("\r") is None && raw.find("\n") is None else {
        return false
      }
      let base : StringView = match raw.find(";") {
        Some(index) => raw[:index]
        None => raw[:]
      }
      let normalized = trim_http_ows(base)
      guard valid_media_type(normalized) else { return false }
      match manifest_body_media_type(body) {
        Some(media_type) => normalized == media_type
        None => normalized != ""
      }
    }
    None => true
  }
}

///|
fn request_manifest_content_type(
  headers : @http.Headers,
  body : Bytes,
) -> String {
  let default_type = "application/vnd.oci.image.manifest.v1+json"
  let raw = match headers.get("Content-Type") {
    Some(value) => value
    None =>
      return match manifest_body_media_type(body) {
        Some(value) => value
        None => default_type
      }
  }
  guard raw.find("\r") is None && raw.find("\n") is None else {
    return default_type
  }
  let base : StringView = match raw.find(";") {
    Some(index) => raw[:index]
    None => raw[:]
  }
  let normalized = trim_http_ows(base)
  if normalized == "" {
    default_type
  } else {
    normalized
  }
}

///|
fn tag_header_value(tags : Array[String]) -> String {
  let mut value = ""
  for index, tag in tags {
    if index > 0 {
      value += ", "
    }
    value += tag
  }
  value
}

///|
fn manifest_subject(body : Bytes) -> String? {
  let text = @utf8.decode(body) catch { _ => return None }
  let value = @json.parse(text) catch { _ => return None }
  guard value is Object(object) else { return None }
  guard object.get("subject") is Some(Object(subject)) else { return None }
  match subject.get("digest") {
    Some(String(digest)) if valid_digest(digest) => Some(digest)
    _ => None
  }
}

///|
fn referrer_descriptor(
  body : Bytes,
  digest : String,
  media_type : String,
) -> (String, String?, Json)? {
  let text = @utf8.decode(body) catch { _ => return None }
  let value = @json.parse(text) catch { _ => return None }
  guard value is Object(object) else { return None }
  let subject = match object.get("subject") {
    Some(Object(subject)) =>
      match subject.get("digest") {
        Some(String(value)) if valid_digest(value) => value
        _ => return None
      }
    _ => return None
  }
  let artifact_type = match object.get("artifactType") {
    Some(String(value)) if value != "" => Some(value)
    _ if media_type == "application/vnd.oci.image.index.v1+json" ||
      media_type == "application/vnd.docker.distribution.manifest.list.v2+json" =>
      None
    _ =>
      match object.get("config") {
        Some(Object(config)) =>
          match config.get("mediaType") {
            Some(String(value)) if value != "" => Some(value)
            _ => None
          }
        _ => None
      }
  }
  let descriptor : Map[String, Json] = {
    "mediaType": @json.to_json(media_type),
    "digest": @json.to_json(digest),
    "size": @json.to_json(body.length()),
  }
  if artifact_type is Some(value) {
    descriptor["artifactType"] = @json.to_json(value)
  }
  if object.get("annotations") is Some(Object(_) as annotations) {
    descriptor["annotations"] = annotations
  }
  Some((subject, artifact_type, Json::object(descriptor)))
}

///|
async fn handle_blob(
  store : RegistryStore,
  repository : String,
  digest : String,
  request_method : @http.RequestMethod,
  headers : @http.Headers,
  conn : @http.ServerConnection,
) -> Unit {
  guard valid_repository(repository) && valid_digest(digest) else {
    send_error(conn, 400, "DIGEST_INVALID")
    return
  }
  if request_method is Delete {
    guard store.delete_repository_blob(repository, digest) else {
      send_error(conn, 404, "BLOB_UNKNOWN")
      return
    }
    send_empty(conn, 202, "Accepted")
    return
  }
  let body = match store.get_repository_blob(repository, digest) {
    Some(value) => value
    None => {
      send_error(conn, 404, "BLOB_UNKNOWN")
      return
    }
  }
  match request_method {
    Head => {
      conn.send_response(200, "OK", extra_headers={
        "Content-Length": body.length().to_string(),
        "Docker-Content-Digest": digest,
        "Accept-Ranges": "bytes",
      })
      conn.end_response()
    }
    Get =>
      match select_blob_range(headers, body.length()) {
        Full => send_blob(conn, 200, "OK", body, digest)
        Partial(start, end) =>
          conn
          ..send_response(206, "Partial Content", extra_headers={
            "Content-Type": "application/octet-stream",
            "Content-Length": (end - start + 1).to_string(),
            "Content-Range": "bytes " +
            start.to_string() +
            "-" +
            end.to_string() +
            "/" +
            body.length().to_string(),
            "Docker-Content-Digest": digest,
            "Accept-Ranges": "bytes",
          })
          ..write(body[start:end + 1])
          .end_response()
        Unsatisfiable => {
          conn.send_response(416, "Range Not Satisfiable", extra_headers={
            "Content-Range": "bytes */" + body.length().to_string(),
            "Accept-Ranges": "bytes",
          })
          conn.end_response()
        }
      }
    _ => send_error(conn, 405, "UNSUPPORTED")
  }
}

///|
async fn handle_manifest(
  store : RegistryStore,
  repository : String,
  reference : String,
  request_method : @http.RequestMethod,
  path : String,
  conn : @http.ServerConnection,
  headers : @http.Headers,
  body_reader : &@io.Reader,
) -> Unit {
  guard valid_repository(repository) && valid_manifest_reference(reference) else {
    send_error(conn, 400, "MANIFEST_INVALID")
    return
  }
  if request_method is Put {
    let requested_tags : Array[String] = if valid_digest(reference) {
      match query_values(path, "tag") {
        Some(values) => values
        None => {
          send_error(conn, 400, "TAG_INVALID")
          return
        }
      }
    } else {
      []
    }
    for tag in requested_tags {
      guard valid_reference(tag) else {
        send_error(conn, 400, "TAG_INVALID")
        return
      }
    }
    let body = body_reader.read_all().binary()
    guard valid_manifest_json(body) else {
      send_error(conn, 400, "MANIFEST_INVALID")
      return
    }
    guard valid_manifest_content_type(headers, body) else {
      send_error(conn, 400, "MANIFEST_INVALID")
      return
    }
    let body_digest = sha256_digest(body)
    guard manifest_reference_matches(reference, body_digest) else {
      send_error(conn, 400, "DIGEST_INVALID")
      return
    }
    guard manifest_dependencies_exist(store, repository, body) else {
      send_error(conn, 400, "MANIFEST_BLOB_UNKNOWN")
      return
    }
    let content_type = request_manifest_content_type(headers, body)
    let digest = store.put_manifest_with_type(
      repository, reference, body, content_type,
    )
    for tag in requested_tags {
      ignore(store.tag_manifest(repository, tag, digest))
    }
    let response_headers : @http.Headers = {
      "Location": "/v2/" + repository + "/manifests/" + digest,
      "Docker-Content-Digest": digest,
    }
    if requested_tags.length() > 0 {
      response_headers["OCI-Tag"] = tag_header_value(requested_tags)
    }
    if manifest_subject(body) is Some(subject) {
      response_headers["OCI-Subject"] = subject
    }
    conn.send_response(201, "Created", extra_headers=response_headers)
    conn.end_response()
    return
  }
  if request_method is Delete {
    guard store.delete_manifest(repository, reference) else {
      send_error(conn, 404, "MANIFEST_UNKNOWN")
      return
    }
    send_empty(conn, 202, "Accepted")
    return
  }
  let value = match store.get_manifest(repository, reference) {
    Some(value) => value
    None => {
      send_error(conn, 404, "MANIFEST_UNKNOWN")
      return
    }
  }
  match request_method {
    Head => {
      conn.send_response(200, "OK", extra_headers={
        "Content-Type": store.manifest_content_type(
          repository,
          sha256_digest(value),
        ),
        "Content-Length": value.length().to_string(),
        "Docker-Content-Digest": sha256_digest(value),
      })
      conn.end_response()
    }
    Get =>
      send_manifest(
        conn,
        200,
        "OK",
        value,
        store.manifest_content_type(repository, sha256_digest(value)),
      )
    _ => send_error(conn, 405, "UNSUPPORTED")
  }
}

///|
async fn handle_referrers(
  store : RegistryStore,
  repository : String,
  subject : String,
  request_method : @http.RequestMethod,
  path : String,
  conn : @http.ServerConnection,
) -> Unit {
  guard valid_repository(repository) else {
    send_error(conn, 400, "NAME_INVALID")
    return
  }
  guard valid_digest(subject) else {
    send_error(conn, 400, "DIGEST_INVALID")
    return
  }
  guard request_method is Get else {
    send_error(conn, 405, "UNSUPPORTED")
    return
  }
  let filter : String? = match query_values(path, "artifactType") {
    Some(values) if values.length() == 0 => None
    Some(values) if values.length() == 1 && values[0] != "" => Some(values[0])
    _ => {
      send_error(conn, 400, "UNSUPPORTED")
      return
    }
  }
  let requested_limit : Int? = match query_values(path, "n") {
    Some(values) if values.length() == 0 => None
    Some(values) if values.length() == 1 => {
      let parsed = @string.parse_int(values[0]) catch { _ => -1 }
      guard parsed >= 0 else {
        send_error(conn, 400, "UNSUPPORTED")
        return
      }
      Some(parsed)
    }
    _ => {
      send_error(conn, 400, "UNSUPPORTED")
      return
    }
  }
  let last : String? = match query_values(path, "last") {
    Some(values) if values.length() == 0 => None
    Some(values) if values.length() == 1 && valid_digest(values[0]) =>
      Some(values[0])
    _ => {
      send_error(conn, 400, "UNSUPPORTED")
      return
    }
  }
  let limit = Some(
    match requested_limit {
      Some(value) => value
      None => 1000
    },
  )
  let matching_digests : Array[String] = []
  let descriptor_by_digest : Map[String, Json] = Map([])
  for
    (digest, artifact_type, descriptor) in store.list_referrer_descriptors(
      repository, subject,
    ) {
    if filter is None || filter == artifact_type {
      matching_digests.push(digest)
      descriptor_by_digest[digest] = descriptor
    }
  }
  let (page_digests, next) = list_page(matching_digests, limit, last)
  let descriptors : Array[Json] = []
  for digest in page_digests {
    match descriptor_by_digest.get(digest) {
      Some(descriptor) => descriptors.push(descriptor)
      None => ()
    }
  }
  let response = Json::object({
    "schemaVersion": @json.to_json(2),
    "mediaType": @json.to_json("application/vnd.oci.image.index.v1+json"),
    "manifests": Json::array(descriptors),
  }).stringify()
  let response_headers : @http.Headers = {
    "Content-Type": "application/vnd.oci.image.index.v1+json",
  }
  if filter is Some(_) {
    response_headers["OCI-Filters-Applied"] = "artifactType"
  }
  match next {
    Some(digest) => {
      let page_size = match limit {
        Some(value) => value
        None => 1000
      }
      let filter_query = match filter {
        Some(value) => "&artifactType=" + encode_query_value(value)
        None => ""
      }
      response_headers["Link"] = "; rel=\"next\""
    }
    None => ()
  }
  conn
  ..send_response(200, "OK", extra_headers=response_headers)
  ..write(@utf8.encode(response))
  .end_response()
}

///|
async fn handle_tags(
  store : RegistryStore,
  repository : String,
  request_method : @http.RequestMethod,
  path : String,
  conn : @http.ServerConnection,
) -> Unit {
  guard valid_repository(repository) else {
    send_error(conn, 400, "NAME_INVALID")
    return
  }
  guard request_method is Get else {
    send_error(conn, 405, "UNSUPPORTED")
    return
  }
  let limit = match query_value(path, "n") {
    Some(value) => {
      let parsed = @string.parse_int(value) catch { _ => -1 }
      guard parsed >= 0 else {
        send_error(conn, 400, "UNSUPPORTED")
        return
      }
      Some(parsed)
    }
    None => None
  }
  let last = query_value(path, "last")
  if last is Some(value) && !valid_reference(value) {
    send_error(conn, 400, "UNSUPPORTED")
    return
  }
  let (tags, next) = list_page(store.list_tags(repository), limit, last)
  let mut body = "{\"name\":\"" + repository + "\",\"tags\":["
  for i, tag in tags {
    if i > 0 {
      body += ","
    }
    body += "\"" + tag + "\""
  }
  body += "]}"
  let response_headers : @http.Headers = { "Content-Type": "application/json" }
  match next {
    Some(tag) => {
      let n = match limit {
        Some(value) => value
        None => 0
      }
      response_headers["Link"] = "; rel=\"next\""
    }
    None => ()
  }
  conn
  ..send_response(200, "OK", extra_headers=response_headers)
  ..write(@utf8.encode(body))
  .end_response()
}

///|
async fn handle_catalog(
  store : RegistryStore,
  request_method : @http.RequestMethod,
  path : String,
  conn : @http.ServerConnection,
) -> Unit {
  guard request_method is Get else {
    send_error(conn, 405, "UNSUPPORTED")
    return
  }
  let limit = match query_value(path, "n") {
    Some(value) => {
      let parsed = @string.parse_int(value) catch { _ => -1 }
      guard parsed >= 0 else {
        send_error(conn, 400, "UNSUPPORTED")
        return
      }
      Some(parsed)
    }
    None => None
  }
  let last = query_value(path, "last")
  if last is Some(value) && !valid_repository(value) {
    send_error(conn, 400, "UNSUPPORTED")
    return
  }
  let (repositories, next) = list_page(store.list_repositories(), limit, last)
  let mut body = "{\"repositories\":["
  for index, repository in repositories {
    if index > 0 {
      body += ","
    }
    body += "\"" + repository + "\""
  }
  body += "]}"
  let response_headers : @http.Headers = { "Content-Type": "application/json" }
  match next {
    Some(repository) => {
      let n = match limit {
        Some(value) => value
        None => 0
      }
      response_headers["Link"] = "; rel=\"next\""
    }
    None => ()
  }
  conn
  ..send_response(200, "OK", extra_headers=response_headers)
  ..write(@utf8.encode(body))
  .end_response()
}

///|
fn parse_content_range(headers : @http.Headers) -> (Int, Int)? {
  match headers.get("Content-Range") {
    Some(value) =>
      match value.find("-") {
        Some(index) => {
          let start = @string.parse_int(value[:index]) catch { _ => -1 }
          let end = @string.parse_int(value[index + 1:]) catch { _ => -1 }
          if start >= 0 && end >= start {
            Some((start, end))
          } else {
            None
          }
        }
        None => None
      }
    None => None
  }
}

///|
fn content_range_matches(
  headers : @http.Headers,
  current : Int,
  chunk_length : Int,
) -> Bool {
  match headers.get("Content-Range") {
    None => true
    Some(_) =>
      match parse_content_range(headers) {
        Some((start, end)) =>
          start == current && end - start + 1 == chunk_length
        None => false
      }
  }
}

///|
fn upload_range(size : Int) -> String {
  if size == 0 {
    "0-0"
  } else {
    "0-" + (size - 1).to_string()
  }
}

///|
async fn send_upload_created(
  conn : @http.ServerConnection,
  repository : String,
  digest : String,
) -> Unit {
  conn.send_response(201, "Created", extra_headers={
    "Location": "/v2/" + repository + "/blobs/" + digest,
    "Docker-Content-Digest": digest,
  })
  conn.end_response()
}

///|
async fn handle_upload(
  store : RegistryStore,
  repository : String,
  upload_id : String?,
  request_method : @http.RequestMethod,
  path : String,
  headers : @http.Headers,
  body_reader : &@io.Reader,
  conn : @http.ServerConnection,
) -> Unit {
  guard valid_repository(repository) else {
    send_error(conn, 400, "NAME_INVALID")
    return
  }
  match (request_method, upload_id) {
    (Post, None) => {
      match (query_value(path, "mount"), query_value(path, "from")) {
        (Some(digest), Some(source)) =>
          if store.mount_blob(repository, source, digest) {
            send_upload_created(conn, repository, digest)
            return
          }
        _ => ()
      }
      let id = store.start_upload(repository)
      let data = body_reader.read_all().binary()
      if data.length() > 0 {
        ignore(store.append_upload(repository, id, data))
      }
      match query_digest(path) {
        Some(expected) => {
          let (status, digest) = store.finalize_upload(repository, id, expected)
          match (status, digest) {
            (201, Some(value)) => send_upload_created(conn, repository, value)
            (400, _) => send_error(conn, 400, "DIGEST_INVALID")
            _ => send_error(conn, 404, "BLOB_UPLOAD_UNKNOWN")
          }
        }
        None => {
          conn.send_response(202, "Accepted", extra_headers={
            "Location": "/v2/" + repository + "/blobs/uploads/" + id,
            "Docker-Upload-UUID": id,
            "Range": upload_range(data.length()),
          })
          conn.end_response()
        }
      }
    }
    (Patch, Some(id)) => {
      let current = match store.upload_size(repository, id) {
        Some(value) => value
        None => {
          send_error(conn, 404, "BLOB_UPLOAD_UNKNOWN")
          return
        }
      }
      let data = body_reader.read_all().binary()
      guard content_range_matches(headers, current, data.length()) else {
        send_error(conn, 416, "RANGE_INVALID")
        return
      }
      guard store.append_upload(repository, id, data) else {
        send_error(conn, 404, "BLOB_UPLOAD_UNKNOWN")
        return
      }
      conn.send_response(202, "Accepted", extra_headers={
        "Location": "/v2/" + repository + "/blobs/uploads/" + id,
        "Docker-Upload-UUID": id,
        "Range": upload_range(current + data.length()),
      })
      conn.end_response()
    }
    (Get, Some(id)) => {
      let current = match store.upload_size(repository, id) {
        Some(value) => value
        None => {
          send_error(conn, 404, "BLOB_UPLOAD_UNKNOWN")
          return
        }
      }
      conn.send_response(204, "No Content", extra_headers={
        "Location": "/v2/" + repository + "/blobs/uploads/" + id,
        "Docker-Upload-UUID": id,
        "Range": upload_range(current),
      })
      conn.end_response()
    }
    (Delete, Some(id)) => {
      guard store.delete_upload(repository, id) else {
        send_error(conn, 404, "BLOB_UPLOAD_UNKNOWN")
        return
      }
      conn.send_response(204, "No Content")
      conn.end_response()
    }
    (Put, Some(id)) => {
      let expected = match query_digest(path) {
        Some(value) => value
        None => {
          send_error(conn, 400, "DIGEST_INVALID")
          return
        }
      }
      let current = match store.upload_size(repository, id) {
        Some(value) => value
        None => {
          send_error(conn, 404, "BLOB_UPLOAD_UNKNOWN")
          return
        }
      }
      let data = body_reader.read_all().binary()
      guard content_range_matches(headers, current, data.length()) else {
        send_error(conn, 416, "RANGE_INVALID")
        return
      }
      if data.length() > 0 {
        guard store.append_upload(repository, id, data) else {
          send_error(conn, 404, "BLOB_UPLOAD_UNKNOWN")
          return
        }
      }
      let (status, digest) = store.finalize_upload(repository, id, expected)
      match (status, digest) {
        (201, Some(value)) => send_upload_created(conn, repository, value)
        (400, Some(_)) => send_error(conn, 400, "DIGEST_INVALID")
        (400, None) => send_error(conn, 400, "DIGEST_INVALID")
        _ => send_error(conn, 404, "BLOB_UPLOAD_UNKNOWN")
      }
    }
    _ => send_error(conn, 404, "BLOB_UPLOAD_UNKNOWN")
  }
}

///|
pub async fn serve(store : RegistryStore, host : String, port : Int) -> Unit {
  store.ensure()
  let server = @http.Server(@socket.Addr::parse(host + ":" + port.to_string()))
  server.run_forever() <| ((request, body, conn) => {
    let route = route_for(request.path)
    match route {
      Root =>
        match request.meth {
          Get | Head => send_empty(conn, 200, "OK")
          _ => send_error(conn, 405, "UNSUPPORTED")
        }
      Catalog => handle_catalog(store, request.meth, request.path, conn)
      Blob(repository, digest) =>
        handle_blob(
          store,
          repository,
          digest,
          request.meth,
          request.headers,
          conn,
        )
      UploadStart(repository) =>
        handle_upload(
          store,
          repository,
          None,
          request.meth,
          request.path,
          request.headers,
          body,
          conn,
        )
      UploadChunk(repository, id) =>
        handle_upload(
          store,
          repository,
          Some(id),
          request.meth,
          request.path,
          request.headers,
          body,
          conn,
        )
      Manifest(repository, reference) =>
        handle_manifest(
          store,
          repository,
          reference,
          request.meth,
          request.path,
          conn,
          request.headers,
          body,
        )
      Referrers(repository, digest) =>
        handle_referrers(
          store,
          repository,
          digest,
          request.meth,
          request.path,
          conn,
        )
      Tags(repository) =>
        handle_tags(store, repository, request.meth, request.path, conn)
      Unknown => send_error(conn, 404, "NAME_UNKNOWN")
    }
  })
}