// SPDX-FileCopyrightText: 2023 Angus Morrison <github@angus-morrison.com>
// SPDX-FileCopyrightText: 2024 Helge Eichhorn <git@helgeeichhorn.de>
//
// SPDX-License-Identifier: MPL-2.0

//! Functions for calculating fundamental astronomical parameters using the Mathews-Herring-Buffett
//! (MHB2000) nutation series. Note that these typically differ from their IERS03 equivalents by
//! less than 0.1 microarcseconds, but are retained as a faithful reproduction of the original
//! model.

use crate::{Moon, Neptune, Sun};

use lox_core::types::units::JulianCenturies;
use lox_units::{Angle, AngleUnits};

pub fn mean_moon_sun_elongation_mhb2000_luni_solar(
    centuries_since_j2000_tdb: JulianCenturies,
) -> Angle {
    Angle::arcseconds_normalized_signed(fast_polynomial::poly_array(
        centuries_since_j2000_tdb,
        &[
            1072260.70369,
            1602961601.2090,
            -6.3706,
            0.006593,
            -0.00003169,
        ],
    ))
}

pub fn mean_moon_sun_elongation_mhb2000_planetary(
    centuries_since_j2000_tdb: JulianCenturies,
) -> Angle {
    fast_polynomial::poly_array(centuries_since_j2000_tdb, &[5.198466741, 7771.3771468121])
        .rad()
        .mod_two_pi_signed()
}

impl Sun {
    pub fn mean_anomaly_mhb2000(&self, centuries_since_j2000_tdb: JulianCenturies) -> Angle {
        Angle::arcseconds_normalized_signed(fast_polynomial::poly_array(
            centuries_since_j2000_tdb,
            &[
                1287104.79305,
                129596581.0481,
                -0.5532,
                0.000136,
                -0.00001149,
            ],
        ))
    }
}

impl Moon {
    pub fn mean_anomaly_mhb2000(&self, centuries_since_j2000_tdb: JulianCenturies) -> Angle {
        fast_polynomial::poly_array(centuries_since_j2000_tdb, &[2.35555598, 8328.6914269554])
            .rad()
            .mod_two_pi_signed()
    }

    pub fn mean_longitude_minus_ascending_node_mean_longitude_mhb2000(
        &self,
        centuries_since_j2000_tdb: JulianCenturies,
    ) -> Angle {
        fast_polynomial::poly_array(centuries_since_j2000_tdb, &[1.627905234, 8433.466158131])
            .rad()
            .mod_two_pi_signed()
    }

    pub fn ascending_node_mean_longitude_mhb2000(
        &self,
        centuries_since_j2000_tdb: JulianCenturies,
    ) -> Angle {
        fast_polynomial::poly_array(centuries_since_j2000_tdb, &[2.18243920, -33.757045])
            .rad()
            .mod_two_pi_signed()
    }
}

impl Neptune {
    pub fn mean_longitude_mhb2000(&self, centuries_since_j2000_tdb: JulianCenturies) -> Angle {
        fast_polynomial::poly_array(centuries_since_j2000_tdb, &[5.3211590, 3.81277740])
            .rad()
            .mod_two_pi_signed()
    }
}

#[cfg(test)]
mod tests {

    use lox_core::types::units::JulianCenturies;
    use lox_test_utils::assert_approx_eq;

    use super::*;

    // Note that all expected values are outputs from the equivalent ERFA functions.

    // rtolative error tolerance for float_eq assertions.
    // This is somewhat loose, being based on observations of how closely our implementations
    // match ERFA outputs rather than any target tolerance.
    // See https://github.com/lox-space/lox/pull/23#discussion_r1398485509
    const TOLERANCE: f64 = 1e-11;

    // Test cases for t.
    const T_ZERO: JulianCenturies = 0.0;
    const T_POSITIVE: JulianCenturies = 1.23456789;
    const T_NEGATIVE: JulianCenturies = -1.23456789;

    #[test]
    fn test_mean_moon_sun_elongation_mhb2000_luni_solar() {
        assert_approx_eq!(
            mean_moon_sun_elongation_mhb2000_luni_solar(T_ZERO),
            5.198466588650503.rad(),
            rtol <= TOLERANCE
        );
        assert_approx_eq!(
            mean_moon_sun_elongation_mhb2000_luni_solar(T_POSITIVE),
            5.067140540624282.rad(),
            rtol <= TOLERANCE
        );
        assert_approx_eq!(
            mean_moon_sun_elongation_mhb2000_luni_solar(T_NEGATIVE),
            -0.953486820095515.rad(),
            rtol <= TOLERANCE
        );
    }

    #[test]
    fn test_mean_moon_sun_elongation_mhb2000_planetary() {
        assert_approx_eq!(
            mean_moon_sun_elongation_mhb2000_planetary(T_ZERO),
            5.1984667410.rad(),
            rtol <= TOLERANCE
        );
        assert_approx_eq!(
            mean_moon_sun_elongation_mhb2000_planetary(T_POSITIVE),
            5.06718921180569.rad(),
            rtol <= TOLERANCE
        );
        assert_approx_eq!(
            mean_moon_sun_elongation_mhb2000_planetary(T_NEGATIVE),
            -0.953441036985836.rad(),
            rtol <= TOLERANCE
        );
    }

    #[test]
    fn test_sun_mean_anomaly_mhb2000() {
        assert_approx_eq!(
            Sun.mean_anomaly_mhb2000(T_ZERO),
            6.24006012692298.rad(),
            rtol <= TOLERANCE
        );
        assert_approx_eq!(
            Sun.mean_anomaly_mhb2000(T_POSITIVE),
            2.806497028816457.rad(),
            rtol <= TOLERANCE
        );
        assert_approx_eq!(
            Sun.mean_anomaly_mhb2000(T_NEGATIVE),
            -2.892755565138653.rad(),
            rtol <= TOLERANCE
        );
    }

    #[test]
    fn test_moon_mean_anomaly_mhb2000() {
        assert_approx_eq!(
            Moon.mean_anomaly_mhb2000(T_ZERO),
            2.35555598.rad(),
            rtol <= TOLERANCE
        );
        assert_approx_eq!(
            Moon.mean_anomaly_mhb2000(T_POSITIVE),
            5.399394871613055.rad(),
            rtol <= TOLERANCE
        );
        assert_approx_eq!(
            Moon.mean_anomaly_mhb2000(T_NEGATIVE),
            -0.688282911613584.rad(),
            rtol <= TOLERANCE
        );
    }

    #[test]
    fn test_moon_mean_longitude_minus_ascending_node_mean_longitude_mhb2000() {
        assert_approx_eq!(
            Moon.mean_longitude_minus_ascending_node_mean_longitude_mhb2000(T_ZERO),
            1.627905234.rad(),
            rtol <= TOLERANCE
        );
        assert_approx_eq!(
            Moon.mean_longitude_minus_ascending_node_mean_longitude_mhb2000(T_POSITIVE),
            2.07637146761946.rad(),
            rtol <= TOLERANCE
        );
        assert_approx_eq!(
            Moon.mean_longitude_minus_ascending_node_mean_longitude_mhb2000(T_NEGATIVE),
            -5.103746306797973.rad(),
            rtol <= TOLERANCE
        );
    }

    #[test]
    fn test_moon_ascending_node_mean_longitude_mhb2000() {
        assert_approx_eq!(
            Moon.ascending_node_mean_longitude_mhb2000(T_ZERO),
            2.18243920.rad(),
            rtol <= TOLERANCE
        );
        assert_approx_eq!(
            Moon.ascending_node_mean_longitude_mhb2000(T_POSITIVE),
            -1.793812775207527.rad(),
            rtol <= TOLERANCE
        );
        assert_approx_eq!(
            Moon.ascending_node_mean_longitude_mhb2000(T_NEGATIVE),
            6.15869117520753.rad(),
            rtol <= TOLERANCE
        );
    }

    #[test]
    fn test_neptune_mean_longitude_mhb2000() {
        assert_approx_eq!(
            Neptune.mean_longitude_mhb2000(T_ZERO),
            5.3211590.rad(),
            rtol <= TOLERANCE
        );
        assert_approx_eq!(
            Neptune.mean_longitude_mhb2000(T_POSITIVE),
            3.7451062425781.rad(),
            rtol <= TOLERANCE
        );
        assert_approx_eq!(
            Neptune.mean_longitude_mhb2000(T_NEGATIVE),
            0.614026450242314.rad(),
            rtol <= TOLERANCE
        );
    }
}
