//! Provides a graphical application over pkecs.

pub mod component;
pub mod config;
pub mod event;
mod metrics;
pub mod schedule;

use log::debug;
use winit::{
    application::ApplicationHandler,
    dpi::PhysicalSize,
    error::EventLoopError,
    event::{DeviceEvent, DeviceId, ElementState, KeyEvent, MouseButton, WindowEvent},
    event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
    keyboard::PhysicalKey,
    window::{WindowAttributes, WindowId},
};
use pkecs::{
    app::{Application, plugin::Plugin},
    core::{
        entity::{Entities},
        event::{Events, EventHandler},
        schedule::{Schedule, System},
    },
};
use component::{ApplicationLifecycle, Window};
use config::GraphicalApplicationOptions;
use event::{Resized, Key, MouseMotion};
use metrics::Metrics;
use schedule::{Startup, Update, FixedUpdate, Shutdown};

/// An application with a graphical interface.
pub struct GraphicalApplication {
    entities: Entities,
    events: Events,
    schedule: Schedule,
    options: GraphicalApplicationOptions,
    metrics: Metrics,
}

impl GraphicalApplication {
    /// Initializes [`self`] from [`GraphicalApplicationOptions`].
    pub fn from_config(options: impl Into<GraphicalApplicationOptions>) -> Self {
        let entities = Self::new_entities();
        let events = Events::default();
        let schedule = Schedule::default();
        let options = options.into();
        let metrics = Metrics::default();

        Self { schedule, entities, events, options, metrics }
    }

    /// Initializes [`Entities`] with preconfigured entities.
    fn new_entities() -> Entities {
        let mut entities = Entities::default();
        entities.spawn(ApplicationLifecycle::default());

        entities
    }

    /// Runs the application.
    ///
    /// # Panics
    /// Will panics if the event loop fails.
    pub fn run(&mut self) {
        debug!("Running application...");

        self.run_inner()
            .expect("Application failed unexpectedly!");
    }

    /// Runs a winit event loop.
    ///
    /// Configures the event loop to poll as this is ideal for real-time graphics.
    fn run_inner(&mut self) -> Result<(), EventLoopError> {
        let event_loop = EventLoop::new()?;
        event_loop.set_control_flow(ControlFlow::Poll);
        event_loop.run_app(self)?;

        Ok(())
    }

    /// Closes the window.
    fn close(&mut self, event_loop: &ActiveEventLoop) {
        event_loop.exit();

        // Run after the event loop closes for the window to shut properly.
        self.schedule.run_schedule(Shutdown, &mut self.entities, &mut self.events);
    }

    /// Executes systems and requests a redraw.
    fn redraw(&mut self) {
        self.schedule.run_schedule(Update, &mut self.entities, &mut self.events);

        self.metrics.measure();
        if self.metrics.elapsed() > self.options.tick_rate {
            self.schedule.run_schedule(FixedUpdate, &mut self.entities, &mut self.events);
            self.metrics.reset_elapsed();
        }

        let window_query = self.entities.query::<Window>();
        let window_component = window_query.first();

        if let Some(window) = window_component {
            window.redraw();
        };
    }

    /// Executed when the window resizes.
    fn resize(&mut self, width: u32, height: u32) {
        self.options.with_size(width, height);

        let resized = Resized::from_size(width, height);
        self.events.trigger(&resized, &mut self.entities);
    }

    /// Handles keyboard input.
    fn keyboard_input(&mut self, event: &KeyEvent) {
        if !event.state.is_pressed() {
            return;
        }

        if let PhysicalKey::Code(code) = event.physical_key {
            let key = Into::<Key>::into(code);
            self.events.trigger(&key, &mut self.entities);
        }
    }

    /// Handles mouse input.
    fn mouse_input(&mut self, state: &ElementState, button: &MouseButton) {
        if !state.is_pressed() {
            return;
        }

        let mouse = Into::<MouseButton>::into(*button);
        self.events.trigger(&mouse, &mut self.entities);
    }

    /// Handles mouse motion.
    fn mouse_motion(&mut self, delta_x: f64, delta_y: f64) {
        let motion = MouseMotion::from_delta(delta_x, delta_y);
        self.events.trigger(&motion, &mut self.entities)
    }
}

impl Default for GraphicalApplication {
    fn default() -> Self {
        let config = GraphicalApplicationOptions::default();

        Self::from_config(config)
    }
}

impl ApplicationHandler for GraphicalApplication {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        debug!("Window resumed...");

        let Ok(window) = event_loop.create_window(WindowAttributes::default()) else {
            panic!("Failed to initialize the window!");
        };

        let window_component = Window::from_window(window);
        self.entities.spawn(window_component);

        self.schedule.run_schedule(Startup, &mut self.entities, &mut self.events);
    }

    fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
        match event {
            WindowEvent::CloseRequested => self.close(event_loop),
            WindowEvent::RedrawRequested => self.redraw(),
            WindowEvent::Resized(PhysicalSize { width, height }) => self.resize(width, height),
            WindowEvent::KeyboardInput { event, .. } => self.keyboard_input(&event),
            WindowEvent::MouseInput { state, button, .. } => self.mouse_input(&state, &button),
            _ => { },
        };

        let lifecycle_query = self.entities.query::<ApplicationLifecycle>();
        if lifecycle_query.iter().any(|l| l.is_exiting()) {
            self.close(event_loop);
        }
    }

    fn device_event(&mut self, event_loop: &ActiveEventLoop, _id: DeviceId, event: DeviceEvent) {
        match event {
            DeviceEvent::MouseMotion { delta: (delta_x, delta_y) } => self.mouse_motion(delta_x, delta_y),
            _ => { }
        };

        let lifecycle_query = self.entities.query::<ApplicationLifecycle>();
        if lifecycle_query.iter().any(|l| l.is_exiting()) {
            self.close(event_loop);
        }
    }
}

impl Application for GraphicalApplication {
    /// Adds a system to the application.
    fn add_system<TSchedule, TSystem>(&mut self, schedule: TSchedule, system: TSystem) -> &mut Self
        where
            TSchedule: 'static,
            TSystem: System + 'static,
    {
        self.schedule.add_system(schedule, system);

        self
    }

    fn handle_event<TEvent, TEventHandler>(&mut self, handler: TEventHandler) -> &mut Self
        where
            TEvent: 'static,
            TEventHandler: EventHandler<TEvent> + 'static,
    {
        self.events.register(handler);

        self
    }

    fn add_plugin<TPlugin>(&mut self) -> &mut Self
        where
            TPlugin: Plugin,
    {
        TPlugin::attach(&mut self.events, &mut self.schedule);

        self
    }
}
