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

use anise::{
    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::EclipseLocator;
    use nyx::md::prelude::*;
    use nyx::od::GroundStation;

    let eme2k = almanac.frame_from_uid(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 = 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(StateParameter::TrueAnomaly, 35.1);
    let ta_event1 = Event::new(StateParameter::TrueAnomaly, 235.1);

    let events = vec![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 in &events {
        let found_events = traj.find(e, almanac.clone()).unwrap();
        let pretty = found_events
            .iter()
            .fold(String::new(), |mut output, orbit_event| {
                let _ = writeln!(
                    output,
                    "{:x}\tevent value: {}",
                    orbit_event.state, orbit_event.value
                );
                output
            });
        println!("[ta_tracker] {} =>\n{}", e, pretty);
    }

    // Find all eclipses!
    let e_loc = EclipseLocator {
        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.clone()).unwrap();
        if let Some(prev_state) = e_state {
            if prev_state.percentage != new_e_state.percentage {
                println!("{:x}\t{}", state, 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 {} degrees @ {}", min_el, min_dt);
    println!("Max elevation {} degrees @ {}", max_el, max_dt);

    let umbra_event_loc = e_loc.to_umbra_event();
    let umbra_events = traj.find(&umbra_event_loc, almanac.clone()).unwrap();

    let pretty = umbra_events
        .iter()
        .skip(1)
        .fold(String::new(), |mut output, orbit_event| {
            let orbit = orbit_event.state.orbit;
            let _ = writeln!(
                output,
                "{:x}\tevent value: {}\t(-10s: {}\t+10s: {})",
                orbit,
                &e_loc.compute(orbit, almanac.clone()).unwrap(),
                &e_loc
                    .compute(
                        traj.at(orbit.epoch - 10 * Unit::Second).unwrap().orbit,
                        almanac.clone()
                    )
                    .unwrap(),
                &e_loc
                    .compute(
                        traj.at(orbit.epoch + 10 * Unit::Second).unwrap().orbit,
                        almanac.clone()
                    )
                    .unwrap()
            );
            output
        });
    println!("[eclipses] {} =>\n{}", umbra_event_loc, pretty);

    let penumbra_event_loc = e_loc.to_penumbra_event();
    let penumbra_events = traj.find(&penumbra_event_loc, almanac.clone()).unwrap();

    let pretty = penumbra_events
        .iter()
        .fold(String::new(), |mut output, orbit_event| {
            let orbit = orbit_event.state.orbit;
            let _ = writeln!(
                output,
                "{:x}\tevent value: {}\t(-10s: {}\t+10s: {})",
                orbit,
                &e_loc.compute(orbit, almanac.clone()).unwrap(),
                &e_loc
                    .compute(
                        traj.at(orbit.epoch - 10 * Unit::Second).unwrap().orbit,
                        almanac.clone()
                    )
                    .unwrap(),
                &e_loc
                    .compute(
                        traj.at(orbit.epoch + 10 * Unit::Second).unwrap().orbit,
                        almanac.clone()
                    )
                    .unwrap()
            );
            output
        });
    println!("[eclipses] {} =>\n{}", penumbra_event_loc, pretty);
}
