use std::collections::{BinaryHeap, HashMap, VecDeque};
use std::ops::Range;
use std::sync::RwLock;

use crate::fleet::{Fleet, FleetId, FleetsManager};
use crate::timeman::Second;
use crate::solar_system::{Kilometers, SolarSystem};

pub trait ScheduleCommand
{
    //Remaining time interval for this command.
    fn time_range(&self, time: &Range<Second>) -> Range<Second>;

    fn tick(
        &self, 
        star_systems: &[SolarSystem], 
        fleet: &mut Fleet,
        time: &Range<Second>) 
    -> Result<(), Second>;
}

pub struct Schedule
{
    commands: Vec<Box<dyn ScheduleCommand>>,
    groups: Vec<Range<usize>>,
    start_time: Second
}

pub struct ScheduleManager
{
    schedules: HashMap<FleetId, Schedule>
}

impl Schedule
{
    pub fn new()
    -> Self {
        Self {
            commands: vec![],
            groups: vec![],
            start_time: 0
        }
    }

    pub fn commands(&self) -> &[Box<dyn ScheduleCommand>] { &self.commands }

    pub fn top(&self) -> Option<&Box<dyn ScheduleCommand>> { self.commands.first() }
    pub fn top_mut(&mut self) -> Option<&mut Box<dyn ScheduleCommand>> { self.commands.first_mut() }

    pub fn pop(&mut self) -> Option<Box<dyn ScheduleCommand>> { self.commands.pop() }

    pub fn add_command<T: ScheduleCommand + 'static>(
        &mut self,
        command: T)
    {
        self.commands.push(Box::new(command));
    }

    pub(crate) fn tick(
        &mut self, 
        star_systems: &[SolarSystem], 
        fleet: &mut Fleet,
        time: &Range<Second>)
    -> Result<(), Second>
    {
        while let Some(command) = self.top() {
            if command.time_range(time).end > time.start {
                self.pop();
                continue;
            }

            return command.tick(star_systems, fleet, time);
        };
        Ok(())
    }
} // impl Schedule

impl ScheduleManager
{
    pub fn new() -> Self
    {
        Self {
            schedules: HashMap::new()
        }
    }

    pub fn schedules_mut(
        &mut self)
        -> std::collections::hash_map::IterMut<'_, FleetId, Schedule>
    {
        self.schedules.iter_mut()
    }

    pub fn schedules(
        &self)
        -> std::collections::hash_map::Iter<'_, FleetId, Schedule>
    {
        self.schedules.iter()
    }

    pub fn add_command_to_schedule
        <T: ScheduleCommand + 'static>(
        &mut self,
        id: FleetId,
        command: T)
    {
        if let Some(schedule) = self.schedules.get_mut(&id) {
            schedule.add_command(command);
        }
    }

    pub fn schedule_mut(
        &mut self,
        id: FleetId)
    -> Option<&mut Schedule>
    {
        self.schedules.get_mut(&id)
    }

    pub fn schedule(
        &self,
        id: FleetId)
    -> Option<&Schedule>
    {
        self.schedules.get(&id)
    }

    pub fn remove_schedule(
        &mut self,
        id: FleetId)
    {
        self.schedules.remove_entry(&id);
    }

    fn next_schedule_tick(
        &self,
        time: &Range<Second>)
    -> Option<(FleetId, Second)> {
        let mut least_cmd: Option<(FleetId, Second)> = None;
        for (id, schedule) in self.schedules.iter() {
            let top_cmd = match schedule.top() {
                Some(cmd) => cmd.time_range(time).end,
                None => { continue; }
            };

            if let Some(least_val) = least_cmd {
                if least_val.1 > top_cmd {
                    least_cmd = Some((*id, top_cmd));
                }
            }else{
                least_cmd = Some((*id, top_cmd));
            }
        }
        least_cmd
    }

    pub fn subtick(
        &mut self,
        fleets_man: &mut FleetsManager,
        star_systems: &[SolarSystem],
        time: &Range<Second>)
    -> Result<(), Second>
    {
        let mut last_time = time.start;
        while let Some((fleet_id, fleet_tick)) = self.next_schedule_tick(time) {
            if fleet_tick > time.end {
                break;
            }
            let fleet = match fleets_man.fleet_mut(fleet_id) {
                Some(v) => v,
                None => {
                    self.remove_schedule(fleet_id);
                    continue;
                }
            };
            
            let schedule = self.schedules.get_mut(&fleet_id).unwrap();
            let tick_range = last_time..fleet_tick;
            let schedule_tick_interrupt = schedule.tick(
                star_systems, fleet, &tick_range);

            if schedule_tick_interrupt.is_err() {
                return schedule_tick_interrupt;
            }

            last_time = fleet_tick;
        };
        Ok(())
    }
}
