///|
/// A runway and the wind across it.
///
/// A runway is named for its magnetic direction rounded to the nearest ten
/// degrees and cut to two digits: runway 27 lies along 270 degrees, runway 09
/// is the same strip of concrete approached from the other end. What a
/// take-off or landing cares about is the wind across the runway, which a
/// crosswind limit is written against, and the wind along it, which shortens
/// or lengthens the ground roll.

///|
/// One runway, by its designator and magnetic heading.
pub struct Runway {
  /// The designator as written, such as `27` or `09L`.
  designator : String
  /// The magnetic heading of the runway, in degrees: two digits times ten.
  heading : Int32
}

///|
pub fn Runway::designator(self : Runway) -> String {
  self.designator
}

///|
pub fn Runway::heading(self : Runway) -> Int32 {
  self.heading
}

///|
/// The runway at the other end of the same strip, if it has one: runway 09 is
/// the reciprocal of runway 27, and the other way round. Every runway pair
/// has one, but some one-way strips carry no designator.
pub fn Runway::reciprocal(self : Runway) -> Runway? {
  let mut heading = (self.heading + 180) % 360
  if heading == 0 {
    // The other end of runway 18 is runway 36, heading 360 degrees.
    heading = 360
  }
  let digits = two_runway_digits(heading)
  let side = side_of(self.designator)
  match (digits, side) {
    (Some(designator), None) => Some({ designator, heading, })
    (Some(designator), Some(side)) =>
      Some({ designator: designator + side, heading, })
    _ => None
  }
}

///|
/// The `L`, `C` or `R` of a parallel-runway designator, if it carries one.
fn side_of(designator : String) -> String? {
  let n = designator.length()
  if n > 0 &&
    (
      designator[n - 1] == 'L' ||
      designator[n - 1] == 'C' ||
      designator[n - 1] == 'R'
    ) {
    Some(designator[n - 1:n].to_owned())
  } else {
    None
  }
}

///|
/// Two decimal digits, zero padded, for a runway heading.
fn two_runway_digits(heading : Int32) -> String? {
  if heading % 10 != 0 {
    return None
  }
  let number = heading / 10
  if number < 1 || number > 36 {
    return None
  }
  let tens = number / 10
  let ones = number % 10
  Some(
    String::from_array([
      ('0'.to_int() + tens).unsafe_to_char(),
      ('0'.to_int() + ones).unsafe_to_char(),
    ]),
  )
}

///|
/// A runway from its designator, such as `27`, `09L` or `36`.
///
/// The designator is a runway number from 01 to 36 — its heading in tens of
/// degrees — optionally followed by `L`, `C` or `R` for one of a set of
/// parallel runways. Anything else is not a runway, and parses to `None`.
pub fn Runway::parse(designator : String) -> Runway? {
  let n = designator.length()
  let mut digits = 0
  while digits < n && designator[digits] >= '0' && designator[digits] <= '9' {
    digits += 1
  }
  if digits == 0 || digits > 2 {
    return None
  }
  let number = match parse_digits(designator[0:digits].to_owned()) {
    Some(value) => value
    None => return None
  }
  if number < 1 || number > 36 {
    return None
  }
  match side_of(designator) {
    Some(_) => if digits + 1 != n { return None }
    None => if digits != n { return None }
  }
  Some({ designator, heading: number * 10, })
}

///|
/// The value of a string of one or two digits.
fn parse_digits(text : String) -> Int32? {
  if text.length() == 0 {
    return None
  }
  let mut value = 0
  for i = 0; i < text.length(); i = i + 1 {
    let c = text[i]
    if c < '0' || c > '9' {
      return None
    }
    value = value * 10 + (c.to_int() - '0'.to_int())
  }
  Some(value)
}

///|
/// The wind along the runway, in knots, positive against the landing or
/// take-off direction.
pub fn Runway::headwind(self : Runway, wind : Wind) -> Double {
  WindVector::from_wind(wind).headwind_component(self.heading.to_double())
}

///|
/// The wind across the runway, in knots, positive from the right of the
/// landing or take-off direction.
pub fn Runway::crosswind(self : Runway, wind : Wind) -> Double {
  WindVector::from_wind(wind).crosswind_component(self.heading.to_double())
}