use serde::{Deserialize};
use crate::{known_stars::{KNOWN_STARS, StarNotFoundError}, timeman::Second};
use std::error::Error;

const GRAVITATIONAL_CONSTANT: f64 = 6.67408e-11;

pub type Kilograms = f64;
pub type Kilometers = f64;
pub type Percentage = f64;
pub type Angle = f64;

pub type BodyId = usize;
pub type SystemId = usize;

#[derive(Debug, Deserialize)]
pub struct SerialOrbitalBody
{
    name: String,
    orbits: BodyId,
    mass: Kilograms,
    radius: Kilometers,

    eccentricity: Percentage,
    inclination: Angle,
    long_asc_node: Angle,
    long_periapsis: Angle,
    sgp: f64,
    mean_long: Angle,

    semi_major_axis: Kilometers,
}

pub struct OrbitalBody
{
    body: SerialOrbitalBody,
    position: Option<(Second, cgmath::Point3<Kilometers>)>
}

pub struct SolarSystem
{
    name: String,
    bodies: Vec<OrbitalBody>,
}

impl SolarSystem
{
    pub fn new_from_csv(
        data: &'static str)
    -> Result<Self, Box<dyn Error>> {
        let data_reader = stringreader::StringReader::new(data);
        let mut body_reader = csv::Reader::from_reader(data_reader);
        
        let mut bodies = Vec::<OrbitalBody>::new();

        for result in body_reader.deserialize() {
            let record: SerialOrbitalBody = result?;

            println!("New body: {:?}", record);

            bodies.push(OrbitalBody { body: record, position: None });
        }

        Ok(Self {
            name: bodies[0].name().clone(),
            bodies: bodies,
        })
    }

    pub fn new_from_known_star(
        star: &'static str)
    -> Result<Self, Box<dyn Error>> {
        let star_csv = match KNOWN_STARS.get(star).copied() {
            Some(csv) => csv,
            None => return Err(Box::new(StarNotFoundError { star: star }))
        };
        SolarSystem::new_from_csv(star_csv)
    }

    pub fn name(&self) -> &String { &self.name }

    pub fn bodies(&self)
        -> &[OrbitalBody]
    {
        self.bodies.as_slice()
    }
}

impl OrbitalBody
{
    pub fn name(&self) -> &String { &self.body.name }
    pub fn radius(&self) -> f32 { self.body.radius as f32 }

    pub fn position(&self, time: Second)
        -> Option<cgmath::Point3<Kilometers>>
    {
        match self.position {
            Some((cache_time, pos)) => {
                if time == cache_time {
                    return Some(pos);
                }
                return None;
            },
            None => None
        }
    }

    fn calculate_orbit(
        &self,
        time: i64)
    -> cgmath::Point3<Kilometers> {
        cgmath::Point3 { x: 0.0, y: 0.0, z: 0.0 }
        /*let arg_periapsis = self.long_periapsis - self.long_asc_node;


        let mean_angular_motion: f64 = (
            self.sgp as f64 / (self.semi_major_axis as f64).powf(3.0)
        ).sqrt();

        let mean_anomaly = (self.mean_long - self.long_periapsis) + (mean_angular_motion * time as f64) as f32;

        let mut eccentric_anomaly = mean_anomaly + self.eccentricity * mean_anomaly.sin();
        for _ in 0..100 {
            let new_eccentric = eccentric_anomaly + 
                (mean_anomaly - eccentric_anomaly + self.eccentricity * eccentric_anomaly.sin()) /
                (1.0 - self.eccentricity * eccentric_anomaly.cos());
            if (new_eccentric -  eccentric_anomaly).abs() < 1e-6 {
                eccentric_anomaly = new_eccentric;
                break;
            }
            eccentric_anomaly = new_eccentric;
        }

        let beta = self.eccentricity / 1.0 + (1.0 - self.eccentricity * self.eccentricity).sqrt();
        let true_anomaly = eccentric_anomaly + 2.0 * 
            ((beta * eccentric_anomaly.sin()) / (1.0 - beta * eccentric_anomaly.cos())).atan();

        let radius: f64 = self.semi_major_axis * (1.0 - self.eccentricity * eccentric_anomaly.cos()) as f64;

        let ohm_sin = self.long_asc_node.sin();
        let ohm_cos = self.long_asc_node.cos();

        let inc_sin = self.inclination.sin();
        let inc_cos = self.inclination.cos();

        let per_anom_sin = (arg_periapsis + true_anomaly).sin();
        let per_anom_cos = (arg_periapsis + true_anomaly).cos();

        let x: f32 = (radius as f32) * (
            (ohm_cos * per_anom_cos) -
            (ohm_sin * per_anom_sin * inc_cos));
        let y: f32 = (radius as f32) * (
            (ohm_sin * per_anom_cos) +
            (ohm_cos * per_anom_sin * inc_cos));
        let z: f32 = (radius as f32) * (inc_sin * per_anom_sin);

        OrbitState {
            position: cgmath::Vector3::new(x, y, z)
        }*/
    }
}

