// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

///|
/// The address an IPv4 dotted-quad or IPv6 literal (RFC 4291 ยง2.2, brackets allowed) spells, as
/// the four or sixteen octets a subjectAltName iPAddress holds; `None` when `s` is not one, which
/// makes it a DNS name.
fn ip_literal(s : String) -> Bytes? {
  let t = match s.strip_prefix("[") {
    Some(rest) =>
      match rest.strip_suffix("]") {
        Some(inner) => inner.to_owned()
        None => return None
      }
    None => s
  }
  if t.contains(":") {
    ipv6(t)
  } else {
    ipv4(t)
  }
}

///|
/// A dotted-quad: four decimal octets, no leading zeros (a leading zero reads as octal in some
/// parsers, so accepting one would let two readers see two addresses).
fn ipv4(s : String) -> Bytes? {
  let parts = s.split(".").to_array()
  if parts.length() != 4 {
    return None
  }
  let out : Array[Byte] = []
  for p in parts {
    if p.length() == 0 ||
      p.length() > 3 ||
      (p.length() > 1 && p.has_prefix("0")) {
      return None
    }
    let mut n = 0
    for c in p {
      if c < '0' || c > '9' {
        return None
      }
      n = n * 10 + (c.to_int() - '0'.to_int())
    }
    if n > 255 {
      return None
    }
    out.push(n.to_byte())
  }
  Some(Bytes::from_array(out))
}

///|
/// One hexadecimal group of an IPv6 address.
fn hex_group(g : StringView) -> Int32? {
  if g.length() == 0 || g.length() > 4 {
    return None
  }
  let mut n = 0
  for c in g {
    let d = match c {
      '0'..<='9' => c.to_int() - '0'.to_int()
      'a'..<='f' => c.to_int() - 'a'.to_int() + 10
      'A'..<='F' => c.to_int() - 'A'.to_int() + 10
      _ => return None
    }
    n = n * 16 + d
  }
  Some(n)
}

///|
/// An IPv6 literal: eight groups, one `::` standing for a run of zero groups, and an optional
/// dotted-quad in the last thirty-two bits. A zone identifier (`%eth0`) names an interface, not
/// an address a certificate can carry, so it is refused.
fn ipv6(s : String) -> Bytes? {
  if s.contains("%") {
    return None
  }
  let halves = s.split("::").to_array()
  if halves.length() > 2 {
    return None
  }
  fn groups(part : StringView) -> Array[Int32]? {
    if part.length() == 0 {
      return Some([])
    }
    let out = []
    let pieces = part.split(":").to_array()
    for i, g in pieces {
      if i == pieces.length() - 1 && g.contains(".") {
        guard ipv4(g.to_owned()) is Some(v4) else { return None }
        out.push(v4[0].to_int() * 256 + v4[1].to_int())
        out.push(v4[2].to_int() * 256 + v4[3].to_int())
      } else {
        guard hex_group(g) is Some(n) else { return None }
        out.push(n)
      }
    }
    Some(out)
  }
  guard groups(halves[0]) is Some(head) else { return None }
  let words = if halves.length() == 2 {
    guard groups(halves[1]) is Some(tail) else { return None }
    if head.length() + tail.length() > 7 {
      return None
    }
    let zeros = Array::make(8 - head.length() - tail.length(), 0)
    head + zeros + tail
  } else {
    head
  }
  if words.length() != 8 {
    return None
  }
  let out = []
  for w in words {
    out.push((w >> 8).to_byte())
    out.push((w & 0xff).to_byte())
  }
  Some(Bytes::from_array(out))
}