pub mod components;
pub mod constants;
pub mod debug;
pub mod error;
pub mod misc;
pub mod render;
pub mod resources;
pub mod systems;

use specs::{Dispatcher, DispatcherBuilder, Entity, World};

pub use error::Error;

use debug::{Fleets as DebugFleets, Ships as DebugShips, Summary as DebugSummary};
use misc::{Events, TextManager, Window};
use render::{Asteroids, Init, Planets, Ships};
use resources::{Camera, Config, Geometry, InputState, PlayerState, Uniform};
use systems::{FleetInfoUpdate, StateUpdate};

pub struct App<'a, 'b> {
    is_running: bool,
    events: Events,
    window: Window,
    dispatcher: Dispatcher<'a, 'b>,
}

impl<'a, 'b> App<'a, 'b> {
    pub fn new(world: &mut World, player_id: Entity) -> Result<Self, Error> {
        let config = Config::new(world)?;
        let events = Events::new(world)?;
        let window = Window::new(events.handle(), &config)?;

        let camera = Camera::new()?;
        let uniform = Uniform::new()?;
        let geometry = Geometry::new(world)?;
        let input_state = InputState::default();
        let player_state = PlayerState::new(player_id);

        world.insert(config);
        world.insert(camera);
        world.insert(uniform);
        world.insert(geometry);
        world.insert(input_state);
        world.insert(player_state);

        let text_manager = TextManager::new(world)?;

        let mut dispatcher = DispatcherBuilder::new()
            .with(StateUpdate::new(world)?, "state_update", &[])
            .with(FleetInfoUpdate::new(world), "fleet_info_update", &[])
            .with_thread_local(Init::new(world)?)
            .with_thread_local(Planets::new(world)?)
            .with_thread_local(Asteroids::new(world)?)
            .with_thread_local(Ships::new(world)?)
            .with_thread_local(DebugShips::default())
            .with_thread_local(DebugFleets::default())
            .with_thread_local(DebugSummary::new(&text_manager)?)
            .build();
        dispatcher.setup(world);

        Ok(Self {
            is_running: true,
            events,
            window,
            dispatcher,
        })
    }

    pub fn is_running(&self) -> bool {
        self.is_running
    }

    pub fn process(&mut self, world: &World) -> Result<(), Error> {
        self.events.process(world, &self.window);
        self.dispatcher.dispatch(world);

        self.window.swap_buffers()?;

        self.is_running = !world.fetch::<InputState>().close_requested;

        Ok(())
    }
}
