extern crate nyx_space as nyx;
use std::{fmt::Write, sync::Arc};

use anise::{
    analysis::{expr::ScalarExpr, prelude::Condition},
    astro::Occultation,
    constants::frames::{EARTH_J2000, IAU_EARTH_FRAME, SUN_J2000},
    prelude::Almanac,
};
use rstest::*;

#[fixture]
fn almanac() -> Arc<Almanac> {
    use crate::test_almanac_arcd;
    test_almanac_arcd()
}

#[rstest]
fn event_tracker_true_anomaly(almanac: Arc<Almanac>) {
    use nyx::cosmic::eclipse::ShadowModel;
    use nyx::md::prelude::*;
    use nyx::od::GroundStation;

    let eme2k = almanac.frame_info(EARTH_J2000).unwrap();

    let dt = Epoch::from_gregorian_tai_at_noon(2020, 1, 1);
    let state = Orbit::cartesian(
        -2436.45, -2436.45, 6891.037, 5.088_611, -5.088_611, 0.0, dt, eme2k,
    );

    let prop_time = 200 * state.period().unwrap();

    // Track how many times we've passed by that TA again
    let peri_event = Event::periapsis(); // Special event shortcut!
    let apo_event = Event::apoapsis(); // Special event shortcut!
    let ta_event0 = Event::new(
        ScalarExpr::Element(OrbitalElement::TrueAnomaly),
        Condition::Equals(35.1),
    );
    let ta_event1 = Event::new(
        ScalarExpr::Element(OrbitalElement::TrueAnomaly),
        Condition::Equals(235.1),
    );

    let events = &[peri_event, apo_event, ta_event0, ta_event1];

    let dynamics = SpacecraftDynamics::new(OrbitalDynamics::two_body());
    let setup = Propagator::rk89(dynamics, IntegratorOptions::with_tolerance(1e-9));
    let mut prop = setup.with(state.into(), almanac.clone());
    let (_, traj) = prop.for_duration_with_traj(prop_time).unwrap();

    println!("Initial state {state:x}");

    println!("{traj}");

    // Find all of the events
    for (e_num, e) in events.iter().enumerate() {
        let found_events = almanac
            .report_events(&traj, e, traj.first().epoch(), traj.last().epoch())
            .unwrap();
        let pretty = found_events
            .iter()
            .fold(String::new(), |mut output, orbit_event| {
                let _ = writeln!(
                    output,
                    "{:x}\tevent value: {:.6}",
                    orbit_event.orbit, orbit_event.value
                );
                output
            });

        // We expect one more apsis because we start at periapasis and prop for a fixed number of orbits.
        assert_eq!(
            found_events.len(),
            if e_num == 0 { 199 } else { 200 },
            "wrong number of true anomaly events for #{e_num}"
        );
        println!("[ta_tracker] {e} =>\n{pretty}");
    }

    // Find all eclipses!
    let e_loc = ShadowModel {
        light_source: SUN_J2000,
        shadow_bodies: vec![eme2k],
    };

    // Adding this print to confirm that the penumbra calculation continuously increases and then decreases.
    let mut e_state: Option<Occultation> = None;
    // Also see what is the max elevation of this spacecraft over the Grand Canyon
    let gc = GroundStation::from_point(
        "Grand Canyon".to_string(),
        36.0544,
        112.1402,
        0.0,
        IAU_EARTH_FRAME,
    );
    let mut min_el = f64::INFINITY;
    let mut max_el = f64::NEG_INFINITY;
    let mut min_dt = dt;
    let mut max_dt = dt;
    for state in traj.every(10 * Unit::Second) {
        let new_e_state = e_loc.compute(state.orbit, &almanac).unwrap();
        if let Some(prev_state) = e_state {
            if prev_state.percentage != new_e_state.percentage {
                println!("{state:x}\t{new_e_state}");
                e_state = Some(new_e_state);
            }
        } else {
            e_state = Some(new_e_state);
        }

        // Compute the elevation
        let aer = gc
            .azimuth_elevation_of(state.orbit, None, &almanac)
            .unwrap();
        let elevation = aer.elevation_deg;
        if elevation > max_el {
            max_el = elevation;
            max_dt = state.epoch();
        }

        if elevation < min_el {
            min_el = elevation;
            min_dt = state.epoch();
        }
    }

    println!("Min elevation {min_el} degrees @ {min_dt}");
    println!("Max elevation {max_el} degrees @ {max_dt}");

    let umbra_event_loc = &e_loc.to_umbra_events()[0];
    let umbra_events = almanac
        .report_event_arcs(&traj, umbra_event_loc, traj.start_epoch(), traj.end_epoch())
        .unwrap();

    let pretty = umbra_events
        .iter()
        .skip(1)
        .fold(String::new(), |mut output, orbit_event| {
            let orbit = orbit_event.rise.orbit;
            let _ = writeln!(output, "{orbit:x}\t{orbit_event}",);
            output
        });
    println!("[eclipses] {umbra_event_loc} =>\n{pretty}");

    let penumbra_event_loc = &e_loc.to_penumbra_events()[0];
    let penumbra_events = almanac
        .report_event_arcs(
            &traj,
            penumbra_event_loc,
            traj.start_epoch(),
            traj.end_epoch(),
        )
        .unwrap();

    let pretty = penumbra_events
        .iter()
        .fold(String::new(), |mut output, orbit_event| {
            let orbit = orbit_event.rise.orbit;
            let _ = writeln!(output, "{orbit:x}\t{orbit_event}",);
            output
        });

    println!("[eclipses] {penumbra_event_loc} =>\n{pretty}");
}
