use crate::ephemeris::observe::{apparent, ecliptic_latlon, observe};
use crate::ephemeris::KernelSet;
use crate::error::EngineError;
use crate::frames::nutation::iau2000b_radians;
use crate::frames::Orientation;
use crate::instant::UtcInstant;
use crate::planets::require_kernel;
use crate::pyfloat::wrap180;
use crate::time::Time;
pub(crate) const SUN: i32 = 10;
pub(crate) const MOON: i32 = 301;
pub(crate) const TROPICAL_YEAR: f64 = 365.242_19;
pub(crate) const SYNODIC_MONTH: f64 = 29.530_589;
pub(crate) const SUN_RATE: f64 = 360.0 / TROPICAL_YEAR;
pub(crate) const ELONGATION_RATE: f64 = 360.0 / SYNODIC_MONTH;
pub(crate) const EPSILON_DAYS: f64 = 1e-7;
const UNIX_EPOCH_JD: f64 = 2_440_587.5;
pub(crate) fn instant_of(jd: f64) -> UtcInstant {
let seconds = ((jd - UNIX_EPOCH_JD) * 86_400.0).round() as i64;
UtcInstant::from_micros(seconds * 1_000_000)
}
pub(crate) struct Sky<'a> {
pub(crate) kernels: &'a KernelSet,
}
impl Sky<'_> {
pub(crate) fn longitude(&self, target: i32, jd: f64) -> Result<f64, EngineError> {
let kernel = require_kernel(self.kernels, jd)?;
let t = Time::from_ut1(jd);
let orientation = Orientation::with_nutation(&t, iau2000b_radians(t.tt()));
let position = apparent(kernel, &observe(kernel, target, &t)?, &t)?;
Ok(ecliptic_latlon(&orientation, &position).1)
}
pub(crate) fn sun(&self, jd: f64) -> Result<f64, EngineError> {
self.longitude(SUN, jd)
}
pub(crate) fn moon(&self, jd: f64) -> Result<f64, EngineError> {
self.longitude(MOON, jd)
}
pub(crate) fn elongation(&self, jd: f64) -> Result<f64, EngineError> {
Ok(self.moon(jd)? - self.sun(jd)?)
}
pub(crate) fn sun_at(&self, longitude: f64, guess: f64) -> Result<f64, EngineError> {
crossing(|jd| self.sun(jd), longitude, guess, SUN_RATE)
}
pub(crate) fn new_moon_near(&self, guess: f64) -> Result<f64, EngineError> {
crossing(|jd| self.elongation(jd), 0.0, guess, ELONGATION_RATE)
}
pub(crate) fn sun_before(&self, longitude: f64, jd: f64) -> Result<f64, EngineError> {
let behind = (self.sun(jd)? - longitude).rem_euclid(360.0);
let found = self.sun_at(longitude, jd - behind / SUN_RATE)?;
if found > jd {
self.sun_at(longitude, found - TROPICAL_YEAR)
} else {
Ok(found)
}
}
pub(crate) fn new_moon_before(&self, jd: f64) -> Result<f64, EngineError> {
let behind = self.elongation(jd)?.rem_euclid(360.0);
let found = self.new_moon_near(jd - behind / ELONGATION_RATE)?;
if found > jd {
self.new_moon_near(found - SYNODIC_MONTH)
} else {
Ok(found)
}
}
}
pub(crate) fn crossing(
f: impl Fn(f64) -> Result<f64, EngineError>,
target: f64,
guess: f64,
rate: f64,
) -> Result<f64, EngineError> {
let mut t0 = guess;
let mut d0 = wrap180(f(t0)? - target);
let mut t1 = t0 - d0 / rate;
for _ in 0..50 {
if (t1 - t0).abs() < EPSILON_DAYS {
break;
}
let d1 = wrap180(f(t1)? - target);
let slope = (d1 - d0) / (t1 - t0);
let slope = if slope.is_finite() && slope > rate / 4.0 {
slope
} else {
rate
};
(t0, d0) = (t1, d1);
t1 -= d1 / slope;
}
Ok(t1)
}