///|
/// Winds as vectors, for the arithmetic that a report cannot carry.
///
/// A bulletin gives a wind as a direction and a speed, which is what a
/// forecaster reads but not what a calculation wants: the wind between two
/// levels is not the average of two directions and two speeds. Interpolation
/// is done on the two components of the wind, and the direction and speed of
/// the result are read back off the interpolated vector.

///|
/// A horizontal wind as a velocity vector: `u` is the eastward component and
/// `v` the northward one, both in knots.
///
/// This is the direction the air is moving *towards*, the opposite of the
/// direction a bulletin reports, since a wind direction names where the wind
/// comes from.
pub struct WindVector {
  u : Double
  v : Double
}

///|
pub fn WindVector::u(self : WindVector) -> Double {
  self.u
}

///|
pub fn WindVector::v(self : WindVector) -> Double {
  self.v
}

///|
/// The wind speed, in knots: the length of the vector.
pub fn WindVector::speed(self : WindVector) -> Double {
  (self.u * self.u + self.v * self.v).sqrt()
}

///|
/// The direction the wind comes from, in degrees true, in `[0, 360)`.
///
/// Undefined for a vector of zero length, where it returns 0.
pub fn WindVector::direction(self : WindVector) -> Double {
  let degrees = @math.atan2(-self.u, -self.v) * 180.0 / @math.PI
  // atan2 returns (-180, 180]; a reported direction is in [0, 360).
  if degrees < 0.0 {
    degrees + 360.0
  } else {
    degrees
  }
}

///|
/// A wind vector from its eastward and northward components, in knots.
pub fn WindVector::from_components(u : Double, v : Double) -> WindVector {
  { u, v, }
}

///|
/// The vector of a reported wind.
///
/// A light and variable wind has no direction, and becomes the zero vector:
/// it contributes nothing to an interpolated wind.
pub fn WindVector::from_wind(wind : Wind) -> WindVector {
  match wind.direction {
    None => { u: 0.0, v: 0.0, }
    Some(direction) => {
      let radians = direction.to_double() * @math.PI / 180.0
      let speed = wind.speed.to_double()
      { u: -speed * @math.sin(radians), v: -speed * @math.cos(radians), }
    }
  }
}

///|
/// A vector weaker than this, in knots, has no direction worth reporting.
let calm_threshold : Double = 0.5

///|
/// The wind as a `Wind`, rounded to the nearest knot and degree, with 360°
/// folded back to 0°.
///
/// A vector below half a knot rounds to zero speed, and is reported the way
/// the bulletins report a negligible wind: light and variable.
pub fn WindVector::to_wind(self : WindVector) -> Wind {
  let speed = self.speed()
  if speed < calm_threshold {
    { direction: None, speed: 0, }
  } else {
    {
      direction: Some(@math.round(self.direction()).to_int() % 360),
      speed: @math.round(speed).to_int(),
    }
  }
}

///|
/// The vector `fraction` of the way from `a` to `b`, component by component.
///
/// A wind forecast for two levels is taken to vary linearly between them, the
/// same way its temperature does.
pub fn WindVector::interpolate(
  a : WindVector,
  b : WindVector,
  fraction : Double,
) -> WindVector {
  { u: a.u + (b.u - a.u) * fraction, v: a.v + (b.v - a.v) * fraction, }
}