///|
fn usage() -> String {
  (
    #|proxyproto-tool --help
    #|proxyproto-tool decode-v1 TEXT
    #|proxyproto-tool decode-v2 --hex HEX
    #|proxyproto-tool decode-auto --hex HEX
    #|proxyproto-tool encode-v1 UNKNOWN
    #|proxyproto-tool encode-v1 TCP4|TCP6 SOURCE DESTINATION SOURCE_PORT DESTINATION_PORT
    #|proxyproto-tool encode-v2 LOCAL
    #|proxyproto-tool encode-v2 TCP4|UDP4|TCP6|UDP6 SOURCE DESTINATION SOURCE_PORT DESTINATION_PORT
    #|proxyproto-tool inspect-tlv --hex HEX
    #|proxyproto-tool verify-crc32c --hex HEX
    #|proxyproto-tool split-test --hex HEX
    #|proxyproto-tool vector-check
    #|
    #|decode-auto 仅用于离线诊断,不应作为公开网络监听端的协议自动探测策略。
  )
}

///|
fn fail(message : String) -> Unit {
  println("{\"error\":\"" + message + "\"}")
  abort("proxyproto-tool failed")
}

///|
fn cli_port(text : String) -> Int {
  let bytes = @utf8.encode(text, bom=false)
  if bytes.length() == 0 {
    fail("invalid_port")
    return 0
  }
  let mut value = 0
  for byte in bytes {
    if byte < b'0' || byte > b'9' {
      fail("invalid_port")
      return 0
    }
    value = value * 10 + byte.to_int() - b'0'.to_int()
    if value > 65535 {
      fail("invalid_port")
      return 0
    }
  }
  value
}

///|
fn tlvs_json(tlvs : Array[@proxyproto.RawTlv]) -> String {
  let mut output = "["
  for index = 0; index < tlvs.length(); index = index + 1 {
    if index != 0 {
      output = output + ","
    }
    let tlv = tlvs[index]
    output = output +
      "{\"type\":\"" +
      @proxyproto.tlv_type_name(tlv.type_code) +
      "\",\"code\":" +
      tlv.type_code.to_int().to_string() +
      ",\"length\":" +
      tlv.value.length().to_string() +
      ",\"value_hex\":\"" +
      @proxyproto.hex_encode(tlv.value) +
      "\"}"
  }
  output + "]"
}

///|
fn frame_addresses(
  header : @proxyproto.ProxyHeader,
) -> (String, String, String, String, Int) {
  match header.address {
    NoAddress => ("", "", "null", "null", 0)
    RawUnsupported(raw) =>
      (@proxyproto.hex_encode(raw), "", "null", "null", raw.length())
    Ipv4(endpoints) =>
      (
        @proxyproto.format_ipv4(endpoints.source_address),
        @proxyproto.format_ipv4(endpoints.destination_address),
        endpoints.source_port.to_string(),
        endpoints.destination_port.to_string(),
        12,
      )
    Ipv6(endpoints) =>
      (
        @proxyproto.format_ipv6(endpoints.source_address),
        @proxyproto.format_ipv6(endpoints.destination_address),
        endpoints.source_port.to_string(),
        endpoints.destination_port.to_string(),
        36,
      )
    UnixAddress(endpoints) =>
      (
        @proxyproto.hex_encode(endpoints.source),
        @proxyproto.hex_encode(endpoints.destination),
        "null",
        "null",
        216,
      )
  }
}

///|
fn print_frame(frame : @proxyproto.DecodedFrame) -> Unit {
  let version = match frame.header.version {
    V1 => "v1"
    V2 => "v2"
  }
  let command = match frame.header.command {
    Local => "local"
    Proxy => "proxy"
  }
  let family = match frame.header.family {
    Unspec => "unspec"
    Inet => "inet"
    Inet6 => "inet6"
    Unix => "unix"
  }
  let transport = match frame.header.transport {
    Unspec => "unspec"
    Stream => "stream"
    Datagram => "datagram"
  }
  let addresses = frame_addresses(frame.header)
  let crc_status = if @proxyproto.find_tlv(
      frame.header.tlvs,
      @proxyproto.TLV_CRC32C,
    )
    is Some(_) {
    "valid"
  } else {
    "not_present"
  }
  println(
    "{\"version\":\"" +
    version +
    "\",\"command\":\"" +
    command +
    "\",\"family\":\"" +
    family +
    "\",\"transport\":\"" +
    transport +
    "\",\"source_address\":\"" +
    addresses.0 +
    "\",\"destination_address\":\"" +
    addresses.1 +
    "\",\"source_port\":" +
    addresses.2 +
    ",\"destination_port\":" +
    addresses.3 +
    ",\"address_length\":" +
    addresses.4.to_string() +
    ",\"header_bytes\":" +
    frame.consumed.to_string() +
    ",\"remaining_payload_bytes\":" +
    frame.remaining.length().to_string() +
    ",\"remaining_payload_hex_prefix\":\"" +
    @proxyproto.hex_encode(frame.remaining) +
    "\",\"crc32c_status\":\"" +
    crc_status +
    "\",\"tlvs\":" +
    tlvs_json(frame.header.tlvs) +
    "}",
  )
}

///|
fn decode_hex_arg(args : Array[String]) -> Bytes {
  if args.length() != 4 || args[2] != "--hex" {
    fail("expected_--hex_HEX")
    return Bytes::new(0)
  }
  match @proxyproto.hex_decode(args[3]) {
    Ok(value) => value
    Err(_) => {
      fail("invalid_hex")
      Bytes::new(0)
    }
  }
}

///|
fn run_decode_v1(args : Array[String]) -> Unit {
  if args.length() != 3 {
    fail("decode-v1_requires_TEXT")
    return
  }
  let wire = @utf8.encode(args[2] + "\r\n", bom=false)
  match @proxyproto.decode_v1(wire, @proxyproto.DecodePolicy::v1_only()) {
    Ok(frame) => print_frame(frame)
    Err(_) => fail("decode_v1_failed")
  }
}

///|
fn run_decode_v2(args : Array[String]) -> Unit {
  let wire = decode_hex_arg(args)
  match @proxyproto.decode_v2(wire, @proxyproto.DecodePolicy::v2_only()) {
    Ok(frame) => print_frame(frame)
    Err(_) => fail("decode_v2_failed")
  }
}

///|
fn run_decode_auto(args : Array[String]) -> Unit {
  let wire = decode_hex_arg(args)
  if wire.length() == 0 {
    fail("empty_input")
    return
  }
  let result = if wire[0] == b'P' {
    @proxyproto.decode_v1(wire, @proxyproto.DecodePolicy::v1_only())
  } else {
    @proxyproto.decode_v2(wire, @proxyproto.DecodePolicy::v2_only())
  }
  match result {
    Ok(frame) => print_frame(frame)
    Err(_) => fail("decode_auto_failed")
  }
}

///|
fn run_encode_v1(args : Array[String]) -> Unit {
  let text = if args.length() == 3 && args[2] == "UNKNOWN" {
    "PROXY UNKNOWN\r\n"
  } else if args.length() == 7 && (args[2] == "TCP4" || args[2] == "TCP6") {
    "PROXY " +
    args[2] +
    " " +
    args[3] +
    " " +
    args[4] +
    " " +
    args[5] +
    " " +
    args[6] +
    "\r\n"
  } else {
    fail("invalid_encode_v1_arguments")
    return
  }
  match
    @proxyproto.decode_v1(
      @utf8.encode(text, bom=false),
      @proxyproto.DecodePolicy::v1_only(),
    ) {
    Ok(frame) =>
      match @proxyproto.encode_v1(frame.header) {
        Ok(encoded) =>
          println("{\"hex\":\"" + @proxyproto.hex_encode(encoded) + "\"}")
        Err(_) => fail("encode_v1_failed")
      }
    Err(_) => fail("invalid_v1_model")
  }
}

///|
fn run_encode_v2(args : Array[String]) -> Unit {
  let header = if args.length() == 3 && args[2] == "LOCAL" {
    @proxyproto.ProxyHeader::new(V2, Local, Unspec, Unspec, NoAddress, []).unwrap()
  } else if args.length() == 7 {
    let kind = args[2]
    let transport : @proxyproto.Transport = if kind == "UDP4" || kind == "UDP6" {
      Datagram
    } else {
      Stream
    }
    if kind == "TCP4" || kind == "UDP4" {
      @proxyproto.ProxyHeader::new(
        V2,
        Proxy,
        Inet,
        transport,
        Ipv4({
          source_address: @proxyproto.parse_ipv4_text(args[3]).unwrap(),
          destination_address: @proxyproto.parse_ipv4_text(args[4]).unwrap(),
          source_port: cli_port(args[5]),
          destination_port: cli_port(args[6]),
        }),
        [],
      ).unwrap()
    } else if kind == "TCP6" || kind == "UDP6" {
      @proxyproto.ProxyHeader::new(
        V2,
        Proxy,
        Inet6,
        transport,
        Ipv6({
          source_address: @proxyproto.parse_ipv6_text(args[3]).unwrap(),
          destination_address: @proxyproto.parse_ipv6_text(args[4]).unwrap(),
          source_port: cli_port(args[5]),
          destination_port: cli_port(args[6]),
        }),
        [],
      ).unwrap()
    } else {
      fail("invalid_v2_kind")
      return
    }
  } else {
    fail("invalid_encode_v2_arguments")
    return
  }
  match @proxyproto.encode_v2(header) {
    Ok(encoded) =>
      println("{\"hex\":\"" + @proxyproto.hex_encode(encoded) + "\"}")
    Err(_) => fail("encode_v2_failed")
  }
}

///|
fn run_inspect_tlv(args : Array[String]) -> Unit {
  let wire = decode_hex_arg(args)
  match @proxyproto.parse_tlvs(wire) {
    Ok(tlvs) =>
      match @proxyproto.interpret_all_tlvs(tlvs) {
        Ok(_) => println("{\"tlvs\":" + tlvs_json(tlvs) + "}")
        Err(_) => fail("tlv_semantic_validation_failed")
      }
    Err(_) => fail("tlv_parse_failed")
  }
}

///|
fn run_verify_crc(args : Array[String]) -> Unit {
  let wire = decode_hex_arg(args)
  match @proxyproto.verify_proxy_crc32c(wire) {
    Ok(_) => println("{\"crc32c_status\":\"valid\"}")
    Err(_) => fail("crc32c_invalid")
  }
}

///|
fn run_split_test(args : Array[String]) -> Unit {
  let wire = decode_hex_arg(args)
  let policy = if wire.length() > 0 && wire[0] == b'P' {
    @proxyproto.DecodePolicy::v1_only()
  } else {
    @proxyproto.DecodePolicy::v2_only()
  }
  let count = @proxyproto.assert_all_single_splits(wire, policy)
  println("{\"single_split_cases\":" + count.to_string() + "}")
}

///|
fn main {
  let args = @env.args()
  if args.length() <= 1 || args[1] == "--help" || args[1] == "-h" {
    println(usage())
    return
  }
  match args[1] {
    "decode-v1" => run_decode_v1(args)
    "decode-v2" => run_decode_v2(args)
    "decode-auto" => run_decode_auto(args)
    "encode-v1" => run_encode_v1(args)
    "encode-v2" => run_encode_v2(args)
    "inspect-tlv" => run_inspect_tlv(args)
    "verify-crc32c" => run_verify_crc(args)
    "split-test" => run_split_test(args)
    "vector-check" =>
      match @proxyproto.verify_generated_vectors() {
        Ok(count) =>
          println(
            "{\"vector_count\":" +
            count.to_string() +
            ",\"status\":\"verified\"}",
          )
        Err(_) => fail("vector_verification_failed")
      }
    _ => fail("unknown_command")
  }
}