pub mod body;
pub mod orbit;

use self::body::*;
use self::orbit::*;

use serde::{Deserialize};
use crate::known_stars::*;
use crate::ntree::NTreeNode;
use crate::timeman::Second;
use std::error::Error;

pub const GRAVITATIONAL_CONSTANT: f64 = 6.67408e-20;

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

pub type SystemId = usize;

#[derive(Debug, Deserialize)]
pub struct CSVOrbitalBody
{
    name: String,
    orbits: Option<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 SolarSystem
{
    id: SystemId,
    name: String,
 
    bodies: Vec<OrbitalBody>,
    heirarchy: NTreeNode<BodyId>
}

impl SolarSystem
{
    fn rebuild_system_heirarchy(
        bodies: &[OrbitalBody],
        root: &mut NTreeNode<BodyId>)
    {
        for body in bodies {
            if let Some(orbit) = body.orbit() {
                if orbit.parent() == *root.value() {
                    let mut subtree = NTreeNode::new(body.id());
                    SolarSystem::rebuild_system_heirarchy(bodies, &mut subtree);
                    root.insert_node(subtree);
                }
            }
        }
    }

    pub fn new_from_csv(
        id: SystemId,
        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 mut record: CSVOrbitalBody = result?;

            match record.orbits {
                Some(orbits) => {
                    if record.sgp == 0.0 {
                        record.sgp = (bodies[orbits].mass() + record.mass) * GRAVITATIONAL_CONSTANT;
                    }
                }
                None => {}
            }
            //if record.radius == 0.0 { continue; }
            println!("New body: {:?}", record);

            bodies.push(OrbitalBody::new_from_record(bodies.len(), id, record));
        }

        let mut heirarchy_root = NTreeNode::new(0);
        SolarSystem::rebuild_system_heirarchy(&bodies, &mut heirarchy_root);

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

    pub fn new_from_known_star(
        id: SystemId,
        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(id, star_csv)
    }

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

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

    pub fn body(&self, id: BodyId) -> &OrbitalBody
    { &self.bodies[id] }

    pub fn heirarchy(&self) -> &NTreeNode<BodyId>
    { &self.heirarchy }

    pub fn body_position(
        &self,
        body: &OrbitalBody,
        time: Second)
    -> cgmath::Vector3<Kilometers>
    {
        body.absolute_position(self, time)
    }

    /*fn tick_tickables<T: Tickable>(
        to_tick: &mut [T],
        time_then: Second,
        time_now: Second)
    {
        let tick_interval = T::subtick_interval();

        let time_then_offset = time_then % tick_interval;
        let time_now_offset = time_now % tick_interval;

        let tick_start = time_then + tick_interval - time_then_offset;
        let tick_end = time_now - time_now_offset;

        let num_ticks = (tick_end - tick_start) / tick_interval;
        for tick_i in 0..num_ticks+1 {
            let tick_second = tick_start + tick_i * tick_interval;
            to_tick.iter_mut().for_each(|v| { v.tick(tick_second); });
        }

        if !T::tick_only_on_subtick() && tick_end != time_now {
            to_tick.iter_mut().for_each(|v| { v.tick(time_now); });
        }
    }*/
}
