///|
fn ascii_string(input : Bytes, end : Int) -> Result[String, ProxyError] {
  let chars = Array::make(0, ' ')
  for i = 0; i < end; i = i + 1 {
    if input[i].to_int() > 127 {
      return Err(proxy_error(InvalidV1Line, i, "v1 line must be ASCII"))
    }
    chars.push(input[i].to_int().to_char().unwrap())
  }
  Ok(String::from_array(chars))
}

///|
fn v1_header(
  command : String,
  source : String,
  destination : String,
  source_port : String,
  destination_port : String,
) -> Result[ProxyHeader, ProxyError] {
  let sp = match parse_port_text(source_port) {
    Err(err) => return Err(err)
    Ok(v) => v
  }
  let dp = match parse_port_text(destination_port) {
    Err(err) => return Err(err)
    Ok(v) => v
  }
  if command == "TCP4" {
    let src = match parse_ipv4_text(source) {
      Err(err) => return Err(err)
      Ok(v) => v
    }
    let dst = match parse_ipv4_text(destination) {
      Err(err) => return Err(err)
      Ok(v) => v
    }
    ProxyHeader::new(
      V1,
      Proxy,
      Inet,
      Stream,
      Ipv4({
        source_address: src,
        destination_address: dst,
        source_port: sp,
        destination_port: dp,
      }),
      [],
    )
  } else if command == "TCP6" {
    let src = match parse_ipv6_text(source) {
      Err(err) => return Err(err)
      Ok(v) => v
    }
    let dst = match parse_ipv6_text(destination) {
      Err(err) => return Err(err)
      Ok(v) => v
    }
    ProxyHeader::new(
      V1,
      Proxy,
      Inet6,
      Stream,
      Ipv6({
        source_address: src,
        destination_address: dst,
        source_port: sp,
        destination_port: dp,
      }),
      [],
    )
  } else {
    Err(
      proxy_error(InvalidV1Line, 6, "v1 command must be TCP4, TCP6, or UNKNOWN"),
    )
  }
}

///|
pub fn decode_v1(
  input : Bytes,
  policy : DecodePolicy,
) -> Result[DecodedFrame, ProxyError] {
  if policy.expected_protocol == V2Only {
    return Err(proxy_error(PolicyViolation, 0, "policy accepts only v2"))
  }
  let mut end = -1
  let mut i = 0
  while i < input.length() && i < policy.max_v1_line_bytes {
    if input[i] == b'\r' {
      if i + 1 >= input.length() {
        if i + 1 >= policy.max_v1_line_bytes {
          return Err(
            proxy_error(
              V1LineTooLong,
              i,
              "CRLF would exceed the configured v1 line bound",
            ),
          )
        }
        return Err(proxy_error(NeedMoreData, i, "CRLF is incomplete"))
      }
      if input[i + 1] != b'\n' {
        return Err(proxy_error(MissingCrlf, i, "v1 requires CRLF"))
      }
      if i + 2 > policy.max_v1_line_bytes {
        return Err(
          proxy_error(V1LineTooLong, i + 2, "v1 line exceeded configured bound"),
        )
      }
      end = i
      break
    }
    if input[i] == b'\n' {
      return Err(proxy_error(MissingCrlf, i, "v1 requires CRLF"))
    }
    i = i + 1
  }
  if end < 0 {
    if input.length() >= policy.max_v1_line_bytes {
      Err(
        proxy_error(
          V1LineTooLong,
          policy.max_v1_line_bytes,
          "v1 line exceeded configured bound",
        ),
      )
    } else {
      Err(proxy_error(NeedMoreData, input.length(), "CRLF not received"))
    }
  } else {
    let line = match ascii_string(input, end) {
      Err(err) => return Err(err)
      Ok(v) => v
    }
    let parts = line.split(" ").to_array()
    if parts.length() < 2 || parts[0].to_owned() != "PROXY" {
      return Err(
        proxy_error(
          InvalidPrefix,
          0,
          "v1 must start with PROXY followed by one space",
        ),
      )
    }
    let proto = parts[1].to_owned()
    let header = if proto == "UNKNOWN" {
      ProxyHeader::new(V1, Proxy, Unspec, Unspec, NoAddress, [])
    } else if parts.length() == 6 {
      v1_header(
        proto,
        parts[2].to_owned(),
        parts[3].to_owned(),
        parts[4].to_owned(),
        parts[5].to_owned(),
      )
    } else {
      Err(
        proxy_error(InvalidV1Line, 0, "TCP4/TCP6 requires exactly four fields"),
      )
    }
    match header {
      Err(err) => Err(err)
      Ok(parsed) =>
        Ok({
          header: parsed,
          remaining: Bytes::from_array(input.to_array()[end + 2:]),
          consumed: end + 2,
        })
    }
  }
}