use sciforge::hub::prelude::constants::G;

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum OrbitType {
    Low,
    Medium,
    High,
    Custom,
}

pub struct ArtificialSatellite {
    pub name: String,
    pub masskg: f64,
    pub altitudem: f64,
    pub semimajoraxism: f64,
    pub eccentricity: f64,
    pub inclinationrad: f64,
    pub raanrad: f64,
    pub argperigeerad: f64,
    pub orbitalanglerad: f64,
    pub orbittype: OrbitType,
}

impl ArtificialSatellite {
    pub fn new(
        name: &str,
        masskg: f64,
        altitudem: f64,
        eccentricity: f64,
        inclinationrad: f64,
    ) -> Self {
        let semimajoraxism = crate::NEPTUNEEQUATORIALRADIUS + altitudem;
        let orbittype = match altitudem {
            a if a < 5000000.0 => OrbitType::Low,
            a if a < 50000000.0 => OrbitType::Medium,
            a if a > 50000000.0 => OrbitType::High,
            a if a.is_finite() => OrbitType::Custom,
            a => {
                debug_assert!(!a.is_finite());
                OrbitType::Custom
            }
        };
        Self {
            name: name.to_string(),
            masskg,
            altitudem,
            semimajoraxism,
            eccentricity,
            inclinationrad,
            raanrad: 0.0,
            argperigeerad: 0.0,
            orbitalanglerad: 0.0,
            orbittype,
        }
    }

    pub fn loworbit(name: &str, masskg: f64, altitudem: f64) -> Self {
        Self::new(name, masskg, altitudem, 0.0, 0.0)
    }

    pub fn polarorbit(name: &str, masskg: f64, altitudem: f64) -> Self {
        Self::new(name, masskg, altitudem, 0.0, std::f64::consts::FRAC_PI_2)
    }

    pub fn orbitalperiods(&self) -> f64 {
        2.0 * std::f64::consts::PI * (self.semimajoraxism.powi(3) / (G * crate::NEPTUNEMASS)).sqrt()
    }

    pub fn orbitalvelocityms(&self) -> f64 {
        (G * crate::NEPTUNEMASS / self.semimajoraxism).sqrt()
    }

    pub fn step(&mut self, dts: f64) {
        let period = self.orbitalperiods();
        let n = 2.0 * std::f64::consts::PI / period;
        let pi2 = 2.0 * std::f64::consts::PI;
        self.orbitalanglerad = (self.orbitalanglerad + n * dts) % pi2;
        let j2 = crate::J2NEPTUNE;
        let p = self.semimajoraxism * (1.0 - self.eccentricity * self.eccentricity);
        let rratiosq = (crate::NEPTUNEEQUATORIALRADIUS / p).powi(2);
        let cosi = self.inclinationrad.cos();
        let sini = self.inclinationrad.sin();
        let raandot = -1.5 * n * j2 * rratiosq * cosi;
        self.raanrad = (self.raanrad + raandot * dts) % pi2;
        let omegadot = 1.5 * n * j2 * rratiosq * (2.0 - 2.5 * sini * sini);
        self.argperigeerad = (self.argperigeerad + omegadot * dts) % pi2;
    }

    pub fn position(&self) -> (f64, f64, f64) {
        let e = self.eccentricity;
        let nu = self.orbitalanglerad;
        let r = self.semimajoraxism * (1.0 - e * e) / (1.0 + e * nu.cos());
        let xorb = r * nu.cos();
        let yorb = r * nu.sin();
        let w = self.argperigeerad;
        let xw = xorb * w.cos() - yorb * w.sin();
        let yw = xorb * w.sin() + yorb * w.cos();
        let i = self.inclinationrad;
        let xi = xw;
        let yi = yw * i.cos();
        let zi = yw * i.sin();
        let omega = self.raanrad;
        let x = xi * omega.cos() - yi * omega.sin();
        let y = xi * omega.sin() + yi * omega.cos();
        let z = zi;
        (x, y, z)
    }

    pub fn gravityatsurface(&self) -> f64 {
        G * crate::NEPTUNEMASS / self.semimajoraxism.powi(2)
    }
}

pub struct Constellation {
    pub name: String,
    pub satellites: Vec<ArtificialSatellite>,
}

impl Constellation {
    pub fn new(name: &str) -> Self {
        Self {
            name: name.to_string(),
            satellites: Vec::new(),
        }
    }

    pub fn add(&mut self, sat: ArtificialSatellite) {
        self.satellites.push(sat);
    }

    pub fn stepall(&mut self, dts: f64) {
        for sat in &mut self.satellites {
            sat.step(dts);
        }
    }

    pub fn positions(&self) -> Vec<(f64, f64, f64)> {
        self.satellites.iter().map(|s| s.position()).collect()
    }
}
