use crate::*;

use winit::application::ApplicationHandler;
use winit::dpi::{PhysicalPosition, PhysicalSize};
use winit::event::{MouseButton as WinitMouseButton, MouseScrollDelta, StartCause, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::keyboard::PhysicalKey;
use winit::window::WindowId;
use winit::error::EventLoopError;

use std::collections::HashMap;
use std::time::{Duration, Instant};


pub struct Phosphor {
    event_loop: EventLoop<()>,
    builders: Vec<WindowBuilder>,
}

impl Phosphor {
    pub fn new() -> Result<Self, ()> {
        if let Ok(event_loop) = EventLoop::new() {
            Ok( Self {
                event_loop,
                builders: Vec::new(),
            } )
        } else {
            Err(())
        }
    }

    pub fn create_window(&mut self, window: WindowBuilder) {
        self.builders.push(window);
    }

    pub fn run(self) -> Result<(), EventLoopError> {
        let mut application = PhosphorApplication {
            builders: self.builders,
            windows: HashMap::new(),
            frame_start: Instant::now(),
        };
        self.event_loop.run_app(&mut application)
    }
}


struct PhosphorApplication {
    builders: Vec<WindowBuilder>,
    windows: HashMap<WindowId, PhosphorWindow>,
    frame_start: Instant,
}

impl PhosphorApplication {
    pub fn handle_builders_and_destructors(&mut self, event_loop: &ActiveEventLoop) {
        // Find marked windows, handle final requests and destroy.
        let mut marked_ids = Vec::new();
        for (id, window) in &mut self.windows {
            if window.marked_for_destruction {
                window.handle_requests(&mut self.builders);
                marked_ids.push(id.clone());
            }
        }
        for id in marked_ids {
            self.windows.remove(&id);
        }

        // Build any newly-requested windows.
        while !self.builders.is_empty() {
            for builder in std::mem::take(&mut self.builders) {
                let mut window = PhosphorWindow::from_builder(builder, event_loop);
                window.program.handle_event(Event::Initialise, &mut window.requests.as_writer());
                window.handle_requests(&mut self.builders);
                self.windows.insert(window.winit.id(), window);
            }
        }

        // If all windows have been destroyed, close the event loop.
        if self.windows.is_empty() {
            event_loop.exit();
        }
    }

    pub fn handle_window_event(&mut self, event: WindowEvent, id: WindowId) {
        let window = match self.windows.get_mut(&id) {
            Some(w) => w,
            None => return,
        };
        macro_rules! handle { ($event:expr) => {
                window.program.handle_event($event, &mut window.requests.as_writer())
        }; }

        match event {
            WindowEvent::Resized( PhysicalSize { width, height } ) => {
                window.update_buffer_size(Dimensions::new(width, height));
            }
            WindowEvent::CloseRequested => {
                handle!(Event::CloseRequest);
            }
            WindowEvent::Destroyed => {
                handle!(Event::Close);
            }
            WindowEvent::DroppedFile(path) => {
                handle!(Event::FileDrop(path));
            }
            WindowEvent::Focused(focused) => {
                handle!(Event::FocusChange(focused));
            }
            WindowEvent::KeyboardInput { event, .. }  => {
                if let Some(smol_str) = event.text {
                    if event.state.is_pressed() {
                        for c in smol_str.chars() {
                            handle!(Event::CharacterInput(c));
                        }
                    }
                }
                if let PhysicalKey::Code(code) = event.physical_key {
                    handle!(Event::KeyboardInput {
                        key: code,
                        action: event.state.into(),
                    });
                }
            }
            WindowEvent::ModifiersChanged(modifiers) => {
                handle!(Event::ModifierChange(modifiers.state()));
            }
            WindowEvent::CursorMoved { position, .. } => {
                let pointer = Position::new(
                    position.x as i32 / window.scale() as i32,
                    position.y as i32 / window.scale() as i32,
                );
                if window.pointer != Some(pointer) {
                    window.pointer = Some(pointer);
                    handle!(Event::CursorMove(pointer));
                }
            }
            WindowEvent::CursorEntered { .. } => {
                handle!(Event::CursorEnter);
            }
            WindowEvent::CursorLeft { .. } => {
                handle!(Event::CursorExit);
            }
            WindowEvent::MouseWheel { delta, .. } => match delta {
                MouseScrollDelta::LineDelta(x, y) => {
                    if x != 0.0 { handle!(Event::ScrollLines { axis: Axis::Horizontal, distance: -x }); }
                    if y != 0.0 { handle!(Event::ScrollLines { axis: Axis::Vertical,   distance: -y }); }
                }
                MouseScrollDelta::PixelDelta(PhysicalPosition {x, y}) => {
                    if x != 0.0 { handle!(Event::ScrollPixels { axis: Axis::Horizontal, distance: -x as f32 }); }
                    if y != 0.0 { handle!(Event::ScrollPixels { axis: Axis::Vertical,   distance: -y as f32 }); }
                }
            }
            WindowEvent::MouseInput { state, button, .. } => {
                let action = state.into();
                match button {
                    WinitMouseButton::Left   => handle!(Event::MouseButton { button: MouseButton::Left,   action }),
                    WinitMouseButton::Middle => handle!(Event::MouseButton { button: MouseButton::Middle, action }),
                    WinitMouseButton::Right  => handle!(Event::MouseButton { button: MouseButton::Right,  action }),
                    _ => (),
                }
            }
            WindowEvent::RedrawRequested => {
                window.redraw();
            },
            _ => (),
        }
    }
}

impl ApplicationHandler for PhosphorApplication {
    fn resumed(&mut self, _event_loop: &ActiveEventLoop) {}

    fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
        if let StartCause::Init = cause {
            // Ensure a minimum duration between frames.
            const MINIMUM_WAIT: Duration = Duration::from_millis(1);
            std::thread::sleep(MINIMUM_WAIT.saturating_sub(self.frame_start.elapsed()));
            self.frame_start = Instant::now();

            event_loop.set_control_flow(ControlFlow::Poll);
            self.handle_builders_and_destructors(event_loop);
        }
    }

    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
        for (_, window) in &mut self.windows {
            window.program.process(&mut window.requests.as_writer());
            window.handle_requests(&mut self.builders);
        }
        self.handle_builders_and_destructors(event_loop);
    }

    fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
        self.handle_window_event(event, id);
        if let Some(window) = self.windows.get_mut(&id) {
            window.handle_requests(&mut self.builders);
        }
        self.handle_builders_and_destructors(event_loop);
    }
}