///|
pub struct Ipv4Address {
  octet_values : Array[Int]
} derive(Eq, Debug)

///|
pub struct Ipv6Address {
  group_values : Array[Int]
} derive(Eq, Debug)

///|
fn address_error(message : String, column? : Int = 0) -> ZoneError {
  ZoneError::new(InvalidRecordData, message, SourceSpan::point(0, column))
}

///|
fn split_address_text(text : String, separator : Int) -> Array[String] {
  let result : Array[String] = []
  let mut current = ""
  for index = 0; index < text.length(); index = index + 1 {
    let code = text[index].to_int()
    if code == separator {
      result.push(current)
      current = ""
    } else {
      current = current + ascii_character(code)
    }
  }
  result.push(current)
  result
}

///|
fn parse_decimal_octet(text : String) -> Result[Int, ZoneError] {
  if text.length() == 0 || text.length() > 3 {
    return Err(address_error("IPv4 octet has an invalid length"))
  }
  if text.length() > 1 && text[0].to_int() == 48 {
    return Err(address_error("IPv4 octet cannot contain a leading zero"))
  }
  let mut value = 0
  for index = 0; index < text.length(); index = index + 1 {
    let code = text[index].to_int()
    if code < 48 || code > 57 {
      return Err(address_error("IPv4 octet must be decimal", column=index))
    }
    value = value * 10 + code - 48
  }
  if value > 255 {
    Err(address_error("IPv4 octet exceeds 255"))
  } else {
    Ok(value)
  }
}

///|
pub fn parse_ipv4(text : String) -> Result[Ipv4Address, ZoneError] {
  let parts = split_address_text(text, 46)
  if parts.length() != 4 {
    return Err(address_error("IPv4 address must contain four octets"))
  }
  let octets : Array[Int] = []
  for part in parts {
    match parse_decimal_octet(part) {
      Ok(value) => octets.push(value)
      Err(error) => return Err(error)
    }
  }
  Ok({ octet_values: octets })
}

///|
pub fn Ipv4Address::octets(self : Ipv4Address) -> Array[Int] {
  self.octet_values.copy()
}

///|
pub fn Ipv4Address::to_text(self : Ipv4Address) -> String {
  self.octet_values[0].to_string() +
  "." +
  self.octet_values[1].to_string() +
  "." +
  self.octet_values[2].to_string() +
  "." +
  self.octet_values[3].to_string()
}

///|
fn hex_digit_value(code : Int) -> Int? {
  if code >= 48 && code <= 57 {
    Some(code - 48)
  } else if code >= 65 && code <= 70 {
    Some(code - 65 + 10)
  } else if code >= 97 && code <= 102 {
    Some(code - 97 + 10)
  } else {
    None
  }
}

///|
fn parse_ipv6_group(text : String) -> Result[Int, ZoneError] {
  if text.length() == 0 || text.length() > 4 {
    return Err(address_error("IPv6 group must contain one to four hex digits"))
  }
  let mut value = 0
  for index = 0; index < text.length(); index = index + 1 {
    match hex_digit_value(text[index].to_int()) {
      Some(digit) => value = value * 16 + digit
      None =>
        return Err(
          address_error("IPv6 group contains a non-hex digit", column=index),
        )
    }
  }
  Ok(value)
}

///|
fn parse_ipv6_side(text : String) -> Result[Array[Int], ZoneError] {
  if text.length() == 0 {
    return Ok([])
  }
  let parts = split_address_text(text, 58)
  let groups : Array[Int] = []
  for part in parts {
    if part.length() == 0 {
      return Err(address_error("IPv6 address contains an empty group"))
    }
    match parse_ipv6_group(part) {
      Ok(value) => groups.push(value)
      Err(error) => return Err(error)
    }
  }
  Ok(groups)
}

///|
fn find_double_colon(text : String) -> Result[Int?, ZoneError] {
  let mut found : Int? = None
  for index = 0; index + 1 < text.length(); index = index + 1 {
    if text[index].to_int() == 58 && text[index + 1].to_int() == 58 {
      if found is Some(_) {
        return Err(address_error("IPv6 address contains multiple :: markers"))
      }
      found = Some(index)
    }
  }
  Ok(found)
}

///|
pub fn parse_ipv6(text : String) -> Result[Ipv6Address, ZoneError] {
  if text.length() == 0 {
    return Err(address_error("IPv6 address is empty"))
  }
  let marker = match find_double_colon(text) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let groups : Array[Int] = []
  match marker {
    Some(position) => {
      let left_text = text[0:position].to_owned()
      let right_text = text[position + 2:].to_owned()
      let left = match parse_ipv6_side(left_text) {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      let right = match parse_ipv6_side(right_text) {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      if left.length() + right.length() >= 8 {
        return Err(address_error("IPv6 :: must replace at least one group"))
      }
      for value in left {
        groups.push(value)
      }
      let zero_count = 8 - left.length() - right.length()
      for zero_index = 0; zero_index < zero_count; zero_index = zero_index + 1 {
        groups.push(0)
      }
      for value in right {
        groups.push(value)
      }
    }
    None => {
      let parsed = match parse_ipv6_side(text) {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      if parsed.length() != 8 {
        return Err(address_error("IPv6 address must contain eight groups"))
      }
      for value in parsed {
        groups.push(value)
      }
    }
  }
  Ok({ group_values: groups })
}

///|
pub fn Ipv6Address::groups(self : Ipv6Address) -> Array[Int] {
  self.group_values.copy()
}

///|
fn longest_zero_run(groups : Array[Int]) -> (Int, Int) {
  let mut best_start = -1
  let mut best_length = 0
  let mut index = 0
  while index < groups.length() {
    if groups[index] != 0 {
      index = index + 1
      continue
    }
    let start = index
    while index < groups.length() && groups[index] == 0 {
      index = index + 1
    }
    let length = index - start
    if length >= 2 && length > best_length {
      best_start = start
      best_length = length
    }
  }
  (best_start, best_length)
}

///|
pub fn Ipv6Address::to_text(self : Ipv6Address) -> String {
  let (zero_start, zero_length) = longest_zero_run(self.group_values)
  let mut result = ""
  let mut index = 0
  while index < 8 {
    if index == zero_start {
      result = result + "::"
      index = index + zero_length
    } else {
      if result.length() > 0 && result[result.length() - 1].to_int() != 58 {
        result = result + ":"
      }
      result = result + self.group_values[index].to_string(radix=16)
      index = index + 1
    }
  }
  result
}