pub mod schedule;

use std::collections::{BinaryHeap, HashMap};
use std::error::Error;
use std::ops::{Range, RangeBounds};

use cgmath::Zero;

use crate::GameState;
use crate::fleet::schedule::{Schedule, ScheduleManager};
use crate::solar_system::body::BodyId;
use crate::solar_system::orbit::StaticOrbiter;
use crate::solar_system::{SystemId, GRAVITATIONAL_CONSTANT, Kilometers, SolarSystem, orbit::StaticOrbit};
use crate::timeman::{HOUR, Second};

pub type FleetId = usize;

pub struct Fleet
{
    id: FleetId,
    name: String,

    origin: cgmath::Vector3<Kilometers>,
    offset: cgmath::Vector3<Kilometers>,
    heading: cgmath::Vector2<cgmath::Rad<f32>>,

    velocity: cgmath::Vector3<f32>,
    acceleration: cgmath::Vector3<f32>,

    angular_velocity: cgmath::Vector2<f32>,
    angular_acceleration: cgmath::Vector2<f32>,

    system: SystemId,
    baked_orbit: Option<(f64, StaticOrbit)>,
}

pub struct FleetsManager
{
    next_id: FleetId,
    fleets: HashMap<FleetId, Fleet>,
}

impl StaticOrbiter for Fleet {
    fn orbit(&self) -> Option<&StaticOrbit> {
        match &self.baked_orbit {
            Some((_, orbit)) => {
                Some(orbit)
            },
            None => None
        }
    }

    fn sgp(&self) -> f64 {
        match &self.baked_orbit {
            Some((sgp, _)) => { *sgp },
            None => { 0.0 }
        }
    }
}

impl Fleet
{
    pub const SUBTICK_DURATION: Second = HOUR;

    pub fn new(
        id: FleetId,
        name: String,
        system: SystemId)
    -> Self
    {
        Self {
            id,
            name,
            origin: cgmath::vec3(0.0, 0.0, 0.0),
            offset: cgmath::vec3(0.0, 0.0, 0.0),
            heading: cgmath::vec2(
                cgmath::Rad::zero(), 
                cgmath::Rad::zero()),
            
            velocity: cgmath::vec3(0.0, 0.0, 0.0),
            acceleration: cgmath::vec3(0.0, 0.0, 0.0),

            angular_velocity: cgmath::vec2(0.0, 0.0),
            angular_acceleration: cgmath::vec2(0.0, 0.0),

            system: system,
            baked_orbit: None
        }
    }

    pub fn id(&self) -> FleetId { self.id }
    pub fn name(&self) -> &String { &self.name }
    pub fn origin_position(&self) -> &cgmath::Vector3<Kilometers>
    { &self.origin }
    pub fn offset_position(&self) -> &cgmath::Vector3<Kilometers>
    { &self.offset }
    pub fn heading(&self) -> &cgmath::Vector2<cgmath::Rad<f32>>
    { &self.heading }
    pub fn system(&self) -> SystemId
    { self.system }

    pub fn make_orbit(
        &mut self,
        star_system: &SolarSystem,
        body: BodyId,
        radius: Kilometers)
    {
        let body = star_system.body(body);
        let sgp = body.mass() * GRAVITATIONAL_CONSTANT;

        self.baked_orbit = Some((sgp, StaticOrbit::new_circular(body, radius)));
        self.system = star_system.id();
    }

    fn tick(
        &mut self,
        star_systems: &[SolarSystem],
        time: &Range<Second>)
    {
        if let Some((_sgp, orbit)) = &self.baked_orbit {
            let solar_system = &star_systems[self.system];
            let body = solar_system.body(orbit.parent());
            
            self.origin = body.absolute_position(solar_system, time.end);
            self.offset = orbit.calculate_position_at(self, time.end);
        }else{
            
        }
    }
} // impl Fleet

impl FleetsManager
{
    pub fn new()
    -> Self {
        Self {
            next_id: 0,
            fleets: HashMap::new()
        }
    }

    fn reserve_new_id(&mut self)
    -> FleetId
    {
        let new_id = self.next_id;
        self.next_id += 1;
        new_id
    }

    pub fn all(&self)
    -> Vec<usize>
    {
        self.fleets.keys().cloned().collect()
    }

    pub fn all_in_system(
        &self,
        system: SystemId)
    -> Vec<usize>
    {
        self.fleets.values().filter_map(|v| {
            if v.system() == system {
                Some(v.id())
            }else {
                None
            }
        }).collect()
    }

    pub fn fleet_mut(&mut self, id: FleetId) -> Option<&mut Fleet>
    { self.fleets.get_mut(&id) }
    pub fn fleet(&self, id: FleetId) -> Option<&Fleet>
    { self.fleets.get(&id) }

    pub fn new_fleet(&mut self,
        name: String,
        system: SystemId)
    -> FleetId
    {
        let id = self.reserve_new_id();
        self.fleets.insert(id, Fleet::new(id, name, system));
        id
    }

    fn subtick(
        &mut self,
        star_systems: &[SolarSystem],
        scheduler: &mut ScheduleManager,
        time: &Range<Second>)
    -> Result<(), Second>
    {
        let interrupt = scheduler.subtick(self, star_systems, time);
        let new_range = time.start..
            (if interrupt.is_err() 
                { interrupt.expect_err("Expect interrupt") } 
                else { time.end }
            );

        for (_, fleet) in self.fleets.iter_mut() {
            fleet.tick(star_systems, &new_range);
        }

        interrupt
    }

    pub fn tick(
        &mut self,
        star_systems: &[SolarSystem],
        scheduler: &mut ScheduleManager,
        time: Range<Second>)
    -> Result<(), Second>
    {
        let delta_time = time.end - time.start;
        let subticks = delta_time / Fleet::SUBTICK_DURATION;

        for tick in 0..subticks {
            let last_tick_time = time.start + tick * Fleet::SUBTICK_DURATION;
            let now_tick_time = last_tick_time + Fleet::SUBTICK_DURATION;
            
            let tick_time = last_tick_time..now_tick_time;
            let interrupt = self.subtick(star_systems, scheduler, &tick_time);
            if interrupt.is_err() {
                return interrupt;
            }
        }

        let last_tick_start = time.start + subticks * Fleet::SUBTICK_DURATION;
        let last_tick_time = last_tick_start..time.end;

        self.subtick(star_systems, scheduler, &last_tick_time)
    }
} // impl FleetsManager

impl SolarSystem
{
    pub fn fleets(
        &self,
        fleets_manager: &FleetsManager)
    -> Vec<usize>
    {
        fleets_manager.all_in_system(self.id())
    }
}
