///|
fn hex_value(c : UInt16) -> UInt32 {
  if c >= 48 && c <= 57 {
    c.to_uint() - 48U
  } else if c >= 97 && c <= 102 {
    c.to_uint() - 97U + 10U
  } else if c >= 65 && c <= 70 {
    c.to_uint() - 65U + 10U
  } else {
    abort("invalid hex")
  }
}

///|
fn hex_to_bytes(input : String) -> Bytes {
  if input.length() % 2 != 0 {
    abort("hex input must have even length")
  }
  Bytes::makei(input.length() / 2, fn(i) {
    let hi = hex_value(input[2 * i])
    let lo = hex_value(input[2 * i + 1])
    ((hi << 4) | lo).to_byte()
  })
}

///|
fn hex_nibble(value : Int32) -> String {
  match value {
    0 => "0"
    1 => "1"
    2 => "2"
    3 => "3"
    4 => "4"
    5 => "5"
    6 => "6"
    7 => "7"
    8 => "8"
    9 => "9"
    10 => "a"
    11 => "b"
    12 => "c"
    13 => "d"
    14 => "e"
    15 => "f"
    _ => abort("invalid nibble")
  }
}

///|
fn bytes_to_hex(bytes : Bytes) -> String {
  let out = StringBuilder(size_hint=bytes.length() * 2)
  for byte in bytes {
    let v = byte.to_int()
    out.write_string(hex_nibble(v >> 4))
    out.write_string(hex_nibble(v & 15))
  }
  out.to_string()
}

///|
fn arg(matches : @argparse.Matches, name : String) -> String {
  match matches.values.get(name) {
    Some([value]) => value
    _ => abort("missing argument \{name}")
  }
}

///|
/// Collapse a verification call into the (ok, detail) pair printed for the
/// interop script; `Show` on `Ed25519Error` keeps the detail strings stable.
fn verify_report(
  run : () -> Bool raise @ed25519.Ed25519Error,
) -> (Bool, String) {
  try run() catch {
    err => (false, err.to_string())
  } noraise {
    true => (true, "ok")
    false => (false, "invalid")
  }
}

///|
fn main raise {
  let command = @argparse.Command(
    "openssl-interop",
    positionals=[
      PositionArg("seed_hex"),
      PositionArg("message_hex"),
      PositionArg("openssl_public_hex"),
      PositionArg("openssl_signature_hex"),
    ],
    about="Compute Ed25519 public key/signature and verify OpenSSL vector",
  )
  let matches = command.parse() catch {
    err => {
      println(err.to_string())
      abort("argument parsing failed")
    }
  }
  let seed = hex_to_bytes(arg(matches, "seed_hex"))
  let message = hex_to_bytes(arg(matches, "message_hex"))
  let openssl_public = hex_to_bytes(arg(matches, "openssl_public_hex"))
  let openssl_signature = hex_to_bytes(arg(matches, "openssl_signature_hex"))
  let moon_public = @ed25519.derive_public_key(seed)
  let moon_signature = @ed25519.sign(seed, message)
  let (moon_verifies_openssl_signature, moon_verify_openssl_result) = verify_report(() => {
      @ed25519.verify_result(openssl_public, message, openssl_signature)
    },
  )
  println("moon_public_hex=\{bytes_to_hex(moon_public)}")
  println("moon_signature_hex=\{bytes_to_hex(moon_signature)}")
  println("moon_verifies_openssl_signature=\{moon_verifies_openssl_signature}")
  println("moon_verify_openssl_result=\{moon_verify_openssl_result}")
  println(
    "moon_verifies_moon_signature=\{@ed25519.verify(moon_public, message, moon_signature)}",
  )
}