use std::collections::HashMap;

use winit::{application::ApplicationHandler, window::WindowAttributes};

use crate::{
    event::WindowEvent,
    util::{CVec, from_ptr, into_ptr},
};

/// Provides a way to retrieve events from the system and from the windows that were registered to
/// the events loop.
///
/// An `EventLoop` can be seen more or less as a "context". Calling [`event_loop_new`]
/// initializes everything that will be required to create windows. For example on Linux creating
/// an event loop opens a connection to the X or Wayland server.
///
/// To wake up an `EventLoop` from a another thread, see the [`EventLoopProxy`] docs.
///
/// Note that this cannot be shared across threads (due to platform-dependant logic
/// forbidding it), as such it is neither [`Send`] nor [`Sync`]. If you need cross-thread access,
/// the [`Window`] created from this _can_ be sent to an other thread, and the
/// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread.
#[repr(transparent)]
pub struct EventLoop(pub *mut winit::event_loop::EventLoop);

/// Builds a new event loop.
///
/// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread,
/// and only once per application.***
///
/// Calling this function will result in display backend initialisation.
///
/// ## Panics
///
/// Attempting to create the event loop off the main thread will panic. This
/// restriction isn't strictly necessary on all platforms, but is imposed to
/// eliminate any nasty surprises when porting to platforms that require it.
#[unsafe(no_mangle)]
pub extern "C" fn event_loop_new() -> EventLoop {
    let raw = winit::event_loop::EventLoop::builder().build().unwrap();
    EventLoop(into_ptr(raw))
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn event_loop_drop(event_loop: EventLoop) {
    let EventLoop(ptr) = event_loop;
    unsafe { from_ptr(ptr) };
}

// #[derive(Debug)]
// #[repr(transparent)]
// pub struct Window(Box<dyn winit::window::Window>);

#[derive(Debug)]
#[repr(transparent)]
pub struct WindowId(usize);

#[derive(Debug)]
struct App {
    init: extern "C" fn() -> InitActions,
    on_window_event: extern "C" fn(WindowId, WindowEvent) -> EventActions,
    windows: HashMap<winit::window::WindowId, Box<dyn winit::window::Window>>,
}

#[derive(Debug)]
#[repr(C)]
pub struct InitActions {
    pub fst: InitAction,
    pub rest: CVec<InitAction>,
}

#[derive(Debug)]
#[repr(u8)]
pub enum InitAction {
    // TODO add `WindowAttributes`
    CreateWindow,
}

pub type EventActions = CVec<EventAction>;

#[derive(Debug)]
#[repr(u8)]
pub enum EventAction {
    // TODO add `WindowAttributes`
    CreateWindow,
    CloseWindow(WindowId),
    Exit,
}

#[unsafe(no_mangle)]
pub extern "C" fn run_app(
    event_loop: EventLoop,
    init: extern "C" fn() -> InitActions,
    on_window_event: extern "C" fn(WindowId, WindowEvent) -> EventActions,
) {
    let EventLoop(ptr) = event_loop;
    let event_loop: winit::event_loop::EventLoop = unsafe { from_ptr(ptr) };
    let app = App {
        init,
        on_window_event,
        windows: Default::default(),
    };
    event_loop.run_app(app).expect("TODO");
}

impl ApplicationHandler for App {
    fn can_create_surfaces(&mut self, event_loop: &dyn winit::event_loop::ActiveEventLoop) {
        let InitActions { fst, rest } = (self.init)();
        // TODO callback when ready to allow window creation on demand?
        for action in [fst].into_iter().chain(rest.into_vec().into_iter()) {
            match action {
                InitAction::CreateWindow => {
                    let window = event_loop
                        .create_window(WindowAttributes::default())
                        .unwrap();
                    let id = window.id();
                    self.windows.insert(id, window);
                }
            }
        }
    }

    fn window_event(
        &mut self,
        event_loop: &dyn winit::event_loop::ActiveEventLoop,
        window_id: winit::window::WindowId,
        event: winit::event::WindowEvent,
    ) {
        // Convert to C repr structures
        let window_id = WindowId::from(window_id);
        let event = WindowEvent::from(event);

        let actions = (self.on_window_event)(window_id, event);
        for action in actions.into_vec() {
            match action {
                EventAction::CreateWindow => {
                    let window = event_loop
                        .create_window(WindowAttributes::default())
                        .unwrap();
                    let id = window.id();
                    self.windows.insert(id, window);
                }
                EventAction::CloseWindow(WindowId(id)) => {
                    let id = winit::window::WindowId::from_raw(id);
                    // Drop closes the window
                    let _window = self.windows.remove(&id);
                }
                EventAction::Exit => event_loop.exit(),
            }
        }
    }
}

impl From<winit::window::WindowId> for WindowId {
    fn from(value: winit::window::WindowId) -> Self {
        Self(value.into_raw())
    }
}