#![deny(unused_results)]

use std::{
    cell::{RefCell, RefMut},
    collections::HashSet,
    mem,
    os::raw::c_void,
    ptr,
    time::Instant,
};

use objc::runtime::{BOOL, YES};

use crate::{
    dpi::LogicalSize,
    event::{Event, StartCause, WindowEvent},
    event_loop::ControlFlow,
    platform_impl::platform::{
        event_loop::{EventHandler, EventProxy, EventWrapper, Never},
        ffi::{
            id, kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRelease, CFRunLoopAddTimer,
            CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
            CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CGRect, CGSize, NSInteger,
            NSOperatingSystemVersion, NSUInteger,
        },
    },
    window::WindowId as RootWindowId,
};

macro_rules! bug {
    ($($msg:tt)*) => {
        panic!("winit iOS bug, file an issue: {}", format!($($msg)*))
    };
}

macro_rules! bug_assert {
    ($test:expr, $($msg:tt)*) => {
        assert!($test, "winit iOS bug, file an issue: {}", format!($($msg)*))
    };
}

enum UserCallbackTransitionResult<'a> {
    Success {
        event_handler: Box<dyn EventHandler>,
        active_control_flow: ControlFlow,
        processing_redraws: bool,
    },
    ReentrancyPrevented {
        queued_events: &'a mut Vec<EventWrapper>,
    },
}

impl Event<'static, Never> {
    fn is_redraw(&self) -> bool {
        if let Event::RedrawRequested(_) = self {
            true
        } else {
            false
        }
    }
}

// this is the state machine for the app lifecycle
#[derive(Debug)]
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
enum AppStateImpl {
    NotLaunched {
        queued_windows: Vec<id>,
        queued_events: Vec<EventWrapper>,
        queued_gpu_redraws: HashSet<id>,
    },
    Launching {
        queued_windows: Vec<id>,
        queued_events: Vec<EventWrapper>,
        queued_event_handler: Box<dyn EventHandler>,
        queued_gpu_redraws: HashSet<id>,
    },
    ProcessingEvents {
        event_handler: Box<dyn EventHandler>,
        queued_gpu_redraws: HashSet<id>,
        active_control_flow: ControlFlow,
    },
    // special state to deal with reentrancy and prevent mutable aliasing.
    InUserCallback {
        queued_events: Vec<EventWrapper>,
        queued_gpu_redraws: HashSet<id>,
    },
    ProcessingRedraws {
        event_handler: Box<dyn EventHandler>,
        active_control_flow: ControlFlow,
    },
    Waiting {
        waiting_event_handler: Box<dyn EventHandler>,
        start: Instant,
    },
    PollFinished {
        waiting_event_handler: Box<dyn EventHandler>,
    },
    Terminated,
}

struct AppState {
    // This should never be `None`, except for briefly during a state transition.
    app_state: Option<AppStateImpl>,
    control_flow: ControlFlow,
    waker: EventLoopWaker,
}

impl Drop for AppState {
    fn drop(&mut self) {
        match self.state_mut() {
            &mut AppStateImpl::NotLaunched {
                ref mut queued_windows,
                ..
            }
            | &mut AppStateImpl::Launching {
                ref mut queued_windows,
                ..
            } => {
                for &mut window in queued_windows {
                    unsafe {
                        let () = msg_send![window, release];
                    }
                }
            }
            _ => {}
        }
    }
}

impl AppState {
    // requires main thread
    unsafe fn get_mut() -> RefMut<'static, AppState> {
        // basically everything in UIKit requires the main thread, so it's pointless to use the
        // std::sync APIs.
        // must be mut because plain `static` requires `Sync`
        static mut APP_STATE: RefCell<Option<AppState>> = RefCell::new(None);

        if cfg!(debug_assertions) {
            assert_main_thread!(
                "bug in winit: `AppState::get_mut()` can only be called on the main thread"
            );
        }
        let mut guard = APP_STATE.borrow_mut();
        if guard.is_none() {
            #[inline(never)]
            #[cold]
            unsafe fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
                let waker = EventLoopWaker::new(CFRunLoopGetMain());
                **guard = Some(AppState {
                    app_state: Some(AppStateImpl::NotLaunched {
                        queued_windows: Vec::new(),
                        queued_events: Vec::new(),
                        queued_gpu_redraws: HashSet::new(),
                    }),
                    control_flow: ControlFlow::default(),
                    waker,
                });
            }
            init_guard(&mut guard)
        }
        RefMut::map(guard, |state| state.as_mut().unwrap())
    }

    fn state(&self) -> &AppStateImpl {
        match &self.app_state {
            Some(ref state) => state,
            None => bug!("`AppState` previously failed a state transition"),
        }
    }

    fn state_mut(&mut self) -> &mut AppStateImpl {
        match &mut self.app_state {
            Some(ref mut state) => state,
            None => bug!("`AppState` previously failed a state transition"),
        }
    }

    fn take_state(&mut self) -> AppStateImpl {
        match self.app_state.take() {
            Some(state) => state,
            None => bug!("`AppState` previously failed a state transition"),
        }
    }

    fn set_state(&mut self, new_state: AppStateImpl) {
        bug_assert!(
            self.app_state.is_none(),
            "attempted to set an `AppState` without calling `take_state` first {:?}",
            self.app_state
        );
        self.app_state = Some(new_state)
    }

    fn replace_state(&mut self, new_state: AppStateImpl) -> AppStateImpl {
        match &mut self.app_state {
            Some(ref mut state) => mem::replace(state, new_state),
            None => bug!("`AppState` previously failed a state transition"),
        }
    }

    fn has_launched(&self) -> bool {
        match self.state() {
            &AppStateImpl::NotLaunched { .. } | &AppStateImpl::Launching { .. } => false,
            _ => true,
        }
    }

    fn will_launch_transition(&mut self, queued_event_handler: Box<dyn EventHandler>) {
        let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() {
            AppStateImpl::NotLaunched {
                queued_windows,
                queued_events,
                queued_gpu_redraws,
            } => (queued_windows, queued_events, queued_gpu_redraws),
            s => bug!("unexpected state {:?}", s),
        };
        self.set_state(AppStateImpl::Launching {
            queued_windows,
            queued_events,
            queued_event_handler,
            queued_gpu_redraws,
        });
    }

    fn did_finish_launching_transition(&mut self) -> (Vec<id>, Vec<EventWrapper>) {
        let (windows, events, event_handler, queued_gpu_redraws) = match self.take_state() {
            AppStateImpl::Launching {
                queued_windows,
                queued_events,
                queued_event_handler,
                queued_gpu_redraws,
            } => (
                queued_windows,
                queued_events,
                queued_event_handler,
                queued_gpu_redraws,
            ),
            s => bug!("unexpected state {:?}", s),
        };
        self.set_state(AppStateImpl::ProcessingEvents {
            event_handler,
            active_control_flow: ControlFlow::Poll,
            queued_gpu_redraws,
        });
        (windows, events)
    }

    fn wakeup_transition(&mut self) -> Option<EventWrapper> {
        // before `AppState::did_finish_launching` is called, pretend there is no running
        // event loop.
        if !self.has_launched() {
            return None;
        }

        let (event_handler, event) = match (self.control_flow, self.take_state()) {
            (
                ControlFlow::Poll,
                AppStateImpl::PollFinished {
                    waiting_event_handler,
                },
            ) => (
                waiting_event_handler,
                EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)),
            ),
            (
                ControlFlow::Wait,
                AppStateImpl::Waiting {
                    waiting_event_handler,
                    start,
                },
            ) => (
                waiting_event_handler,
                EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
                    start,
                    requested_resume: None,
                })),
            ),
            (
                ControlFlow::WaitUntil(requested_resume),
                AppStateImpl::Waiting {
                    waiting_event_handler,
                    start,
                },
            ) => {
                let event = if Instant::now() >= requested_resume {
                    EventWrapper::StaticEvent(Event::NewEvents(StartCause::ResumeTimeReached {
                        start,
                        requested_resume,
                    }))
                } else {
                    EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
                        start,
                        requested_resume: Some(requested_resume),
                    }))
                };
                (waiting_event_handler, event)
            }
            (ControlFlow::Exit, _) => bug!("unexpected `ControlFlow` `Exit`"),
            s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
        };

        self.set_state(AppStateImpl::ProcessingEvents {
            event_handler,
            queued_gpu_redraws: Default::default(),
            active_control_flow: self.control_flow,
        });
        Some(event)
    }

    fn try_user_callback_transition(&mut self) -> UserCallbackTransitionResult<'_> {
        // If we're not able to process an event due to recursion or `Init` not having been sent out
        // yet, then queue the events up.
        match self.state_mut() {
            &mut AppStateImpl::Launching {
                ref mut queued_events,
                ..
            }
            | &mut AppStateImpl::NotLaunched {
                ref mut queued_events,
                ..
            }
            | &mut AppStateImpl::InUserCallback {
                ref mut queued_events,
                ..
            } => {
                // A lifetime cast: early returns are not currently handled well with NLL, but
                // polonius handles them well. This transmute is a safe workaround.
                return unsafe {
                    mem::transmute::<
                        UserCallbackTransitionResult<'_>,
                        UserCallbackTransitionResult<'_>,
                    >(UserCallbackTransitionResult::ReentrancyPrevented {
                        queued_events,
                    })
                };
            }

            &mut AppStateImpl::ProcessingEvents { .. }
            | &mut AppStateImpl::ProcessingRedraws { .. } => {}

            s @ &mut AppStateImpl::PollFinished { .. }
            | s @ &mut AppStateImpl::Waiting { .. }
            | s @ &mut AppStateImpl::Terminated => {
                bug!("unexpected attempted to process an event {:?}", s)
            }
        }

        let (event_handler, queued_gpu_redraws, active_control_flow, processing_redraws) =
            match self.take_state() {
                AppStateImpl::Launching { .. }
                | AppStateImpl::NotLaunched { .. }
                | AppStateImpl::InUserCallback { .. } => unreachable!(),
                AppStateImpl::ProcessingEvents {
                    event_handler,
                    queued_gpu_redraws,
                    active_control_flow,
                } => (
                    event_handler,
                    queued_gpu_redraws,
                    active_control_flow,
                    false,
                ),
                AppStateImpl::ProcessingRedraws {
                    event_handler,
                    active_control_flow,
                } => (event_handler, Default::default(), active_control_flow, true),
                AppStateImpl::PollFinished { .. }
                | AppStateImpl::Waiting { .. }
                | AppStateImpl::Terminated => unreachable!(),
            };
        self.set_state(AppStateImpl::InUserCallback {
            queued_events: Vec::new(),
            queued_gpu_redraws,
        });
        UserCallbackTransitionResult::Success {
            event_handler,
            active_control_flow,
            processing_redraws,
        }
    }

    fn main_events_cleared_transition(&mut self) -> HashSet<id> {
        let (event_handler, queued_gpu_redraws, active_control_flow) = match self.take_state() {
            AppStateImpl::ProcessingEvents {
                event_handler,
                queued_gpu_redraws,
                active_control_flow,
            } => (event_handler, queued_gpu_redraws, active_control_flow),
            s => bug!("unexpected state {:?}", s),
        };
        self.set_state(AppStateImpl::ProcessingRedraws {
            event_handler,
            active_control_flow,
        });
        queued_gpu_redraws
    }

    fn events_cleared_transition(&mut self) {
        if !self.has_launched() {
            return;
        }
        let (waiting_event_handler, old) = match self.take_state() {
            AppStateImpl::ProcessingRedraws {
                event_handler,
                active_control_flow,
            } => (event_handler, active_control_flow),
            s => bug!("unexpected state {:?}", s),
        };

        let new = self.control_flow;
        match (old, new) {
            (ControlFlow::Poll, ControlFlow::Poll) => self.set_state(AppStateImpl::PollFinished {
                waiting_event_handler,
            }),
            (ControlFlow::Wait, ControlFlow::Wait) => {
                let start = Instant::now();
                self.set_state(AppStateImpl::Waiting {
                    waiting_event_handler,
                    start,
                });
            }
            (ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant))
                if old_instant == new_instant =>
            {
                let start = Instant::now();
                self.set_state(AppStateImpl::Waiting {
                    waiting_event_handler,
                    start,
                });
            }
            (_, ControlFlow::Wait) => {
                let start = Instant::now();
                self.set_state(AppStateImpl::Waiting {
                    waiting_event_handler,
                    start,
                });
                self.waker.stop()
            }
            (_, ControlFlow::WaitUntil(new_instant)) => {
                let start = Instant::now();
                self.set_state(AppStateImpl::Waiting {
                    waiting_event_handler,
                    start,
                });
                self.waker.start_at(new_instant)
            }
            (_, ControlFlow::Poll) => {
                self.set_state(AppStateImpl::PollFinished {
                    waiting_event_handler,
                });
                self.waker.start()
            }
            (_, ControlFlow::Exit) => {
                // https://developer.apple.com/library/archive/qa/qa1561/_index.html
                // it is not possible to quit an iOS app gracefully and programatically
                warn!("`ControlFlow::Exit` ignored on iOS");
                self.control_flow = old
            }
        }
    }

    fn terminated_transition(&mut self) -> Box<dyn EventHandler> {
        match self.replace_state(AppStateImpl::Terminated) {
            AppStateImpl::ProcessingEvents { event_handler, .. } => event_handler,
            s => bug!(
                "`LoopDestroyed` happened while not processing events {:?}",
                s
            ),
        }
    }
}

// requires main thread and window is a UIWindow
// retains window
pub unsafe fn set_key_window(window: id) {
    bug_assert!(
        {
            let is_window: BOOL = msg_send![window, isKindOfClass: class!(UIWindow)];
            is_window == YES
        },
        "set_key_window called with an incorrect type"
    );
    let mut this = AppState::get_mut();
    match this.state_mut() {
        &mut AppStateImpl::NotLaunched {
            ref mut queued_windows,
            ..
        } => return queued_windows.push(msg_send![window, retain]),
        &mut AppStateImpl::ProcessingEvents { .. }
        | &mut AppStateImpl::InUserCallback { .. }
        | &mut AppStateImpl::ProcessingRedraws { .. } => {}
        s @ &mut AppStateImpl::Launching { .. }
        | s @ &mut AppStateImpl::Waiting { .. }
        | s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s),
        &mut AppStateImpl::Terminated => {
            panic!("Attempt to create a `Window` after the app has terminated")
        }
    }
    drop(this);
    msg_send![window, makeKeyAndVisible]
}

// requires main thread and window is a UIWindow
// retains window
pub unsafe fn queue_gl_or_metal_redraw(window: id) {
    bug_assert!(
        {
            let is_window: BOOL = msg_send![window, isKindOfClass: class!(UIWindow)];
            is_window == YES
        },
        "set_key_window called with an incorrect type"
    );
    let mut this = AppState::get_mut();
    match this.state_mut() {
        &mut AppStateImpl::NotLaunched {
            ref mut queued_gpu_redraws,
            ..
        }
        | &mut AppStateImpl::Launching {
            ref mut queued_gpu_redraws,
            ..
        }
        | &mut AppStateImpl::ProcessingEvents {
            ref mut queued_gpu_redraws,
            ..
        }
        | &mut AppStateImpl::InUserCallback {
            ref mut queued_gpu_redraws,
            ..
        } => drop(queued_gpu_redraws.insert(window)),
        s @ &mut AppStateImpl::ProcessingRedraws { .. }
        | s @ &mut AppStateImpl::Waiting { .. }
        | s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s),
        &mut AppStateImpl::Terminated => {
            panic!("Attempt to create a `Window` after the app has terminated")
        }
    }
    drop(this);
}

// requires main thread
pub unsafe fn will_launch(queued_event_handler: Box<dyn EventHandler>) {
    AppState::get_mut().will_launch_transition(queued_event_handler)
}

// requires main thread
pub unsafe fn did_finish_launching() {
    let mut this = AppState::get_mut();
    let windows = match this.state_mut() {
        AppStateImpl::Launching { queued_windows, .. } => mem::replace(queued_windows, Vec::new()),
        s => bug!("unexpected state {:?}", s),
    };

    // start waking up the event loop now!
    bug_assert!(
        this.control_flow == ControlFlow::Poll,
        "unexpectedly not setup to `Poll` on launch!"
    );
    this.waker.start();

    // have to drop RefMut because the window setup code below can trigger new events
    drop(this);

    for window in windows {
        let count: NSUInteger = msg_send![window, retainCount];
        // make sure the window is still referenced
        if count > 1 {
            // Do a little screen dance here to account for windows being created before
            // `UIApplicationMain` is called. This fixes visual issues such as being
            // offcenter and sized incorrectly. Additionally, to fix orientation issues, we
            // gotta reset the `rootViewController`.
            //
            // relevant iOS log:
            // ```
            // [ApplicationLifecycle] Windows were created before application initialzation
            // completed. This may result in incorrect visual appearance.
            // ```
            let screen: id = msg_send![window, screen];
            let _: id = msg_send![screen, retain];
            let () = msg_send![window, setScreen:0 as id];
            let () = msg_send![window, setScreen: screen];
            let () = msg_send![screen, release];
            let controller: id = msg_send![window, rootViewController];
            let () = msg_send![window, setRootViewController:ptr::null::<()>()];
            let () = msg_send![window, setRootViewController: controller];
            let () = msg_send![window, makeKeyAndVisible];
        }
        let () = msg_send![window, release];
    }

    let (windows, events) = AppState::get_mut().did_finish_launching_transition();

    let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents(
        StartCause::Init,
    )))
    .chain(events);
    handle_nonuser_events(events);

    // the above window dance hack, could possibly trigger new windows to be created.
    // we can just set those windows up normally, as they were created after didFinishLaunching
    for window in windows {
        let count: NSUInteger = msg_send![window, retainCount];
        // make sure the window is still referenced
        if count > 1 {
            let () = msg_send![window, makeKeyAndVisible];
        }
        let () = msg_send![window, release];
    }
}

// requires main thread
// AppState::did_finish_launching handles the special transition `Init`
pub unsafe fn handle_wakeup_transition() {
    let mut this = AppState::get_mut();
    let wakeup_event = match this.wakeup_transition() {
        None => return,
        Some(wakeup_event) => wakeup_event,
    };
    drop(this);

    handle_nonuser_event(wakeup_event)
}

// requires main thread
pub unsafe fn handle_nonuser_event(event: EventWrapper) {
    handle_nonuser_events(std::iter::once(event))
}

// requires main thread
pub unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events: I) {
    let mut this = AppState::get_mut();
    let (mut event_handler, active_control_flow, processing_redraws) =
        match this.try_user_callback_transition() {
            UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
                queued_events.extend(events);
                return;
            }
            UserCallbackTransitionResult::Success {
                event_handler,
                active_control_flow,
                processing_redraws,
            } => (event_handler, active_control_flow, processing_redraws),
        };
    let mut control_flow = this.control_flow;
    drop(this);

    for wrapper in events {
        match wrapper {
            EventWrapper::StaticEvent(event) => {
                if !processing_redraws && event.is_redraw() {
                    log::info!("processing `RedrawRequested` during the main event loop");
                } else if processing_redraws && !event.is_redraw() {
                    log::warn!(
                        "processing non `RedrawRequested` event after the main event loop: {:#?}",
                        event
                    );
                }
                event_handler.handle_nonuser_event(event, &mut control_flow)
            }
            EventWrapper::EventProxy(proxy) => {
                handle_event_proxy(&mut event_handler, control_flow, proxy)
            }
        }
    }

    loop {
        let mut this = AppState::get_mut();
        let queued_events = match this.state_mut() {
            &mut AppStateImpl::InUserCallback {
                ref mut queued_events,
                queued_gpu_redraws: _,
            } => mem::replace(queued_events, Vec::new()),
            s => bug!("unexpected state {:?}", s),
        };
        if queued_events.is_empty() {
            let queued_gpu_redraws = match this.take_state() {
                AppStateImpl::InUserCallback {
                    queued_events: _,
                    queued_gpu_redraws,
                } => queued_gpu_redraws,
                _ => unreachable!(),
            };
            this.app_state = Some(if processing_redraws {
                bug_assert!(
                    queued_gpu_redraws.is_empty(),
                    "redraw queued while processing redraws"
                );
                AppStateImpl::ProcessingRedraws {
                    event_handler,
                    active_control_flow,
                }
            } else {
                AppStateImpl::ProcessingEvents {
                    event_handler,
                    queued_gpu_redraws,
                    active_control_flow,
                }
            });
            this.control_flow = control_flow;
            break;
        }
        drop(this);

        for wrapper in queued_events {
            match wrapper {
                EventWrapper::StaticEvent(event) => {
                    if !processing_redraws && event.is_redraw() {
                        log::info!("processing `RedrawRequested` during the main event loop");
                    } else if processing_redraws && !event.is_redraw() {
                        log::warn!(
                            "processing non-`RedrawRequested` event after the main event loop: {:#?}",
                            event
                        );
                    }
                    event_handler.handle_nonuser_event(event, &mut control_flow)
                }
                EventWrapper::EventProxy(proxy) => {
                    handle_event_proxy(&mut event_handler, control_flow, proxy)
                }
            }
        }
    }
}

// requires main thread
unsafe fn handle_user_events() {
    let mut this = AppState::get_mut();
    let mut control_flow = this.control_flow;
    let (mut event_handler, active_control_flow, processing_redraws) =
        match this.try_user_callback_transition() {
            UserCallbackTransitionResult::ReentrancyPrevented { .. } => {
                bug!("unexpected attempted to process an event")
            }
            UserCallbackTransitionResult::Success {
                event_handler,
                active_control_flow,
                processing_redraws,
            } => (event_handler, active_control_flow, processing_redraws),
        };
    if processing_redraws {
        bug!("user events attempted to be sent out while `ProcessingRedraws`");
    }
    drop(this);

    event_handler.handle_user_events(&mut control_flow);

    loop {
        let mut this = AppState::get_mut();
        let queued_events = match this.state_mut() {
            &mut AppStateImpl::InUserCallback {
                ref mut queued_events,
                queued_gpu_redraws: _,
            } => mem::replace(queued_events, Vec::new()),
            s => bug!("unexpected state {:?}", s),
        };
        if queued_events.is_empty() {
            let queued_gpu_redraws = match this.take_state() {
                AppStateImpl::InUserCallback {
                    queued_events: _,
                    queued_gpu_redraws,
                } => queued_gpu_redraws,
                _ => unreachable!(),
            };
            this.app_state = Some(AppStateImpl::ProcessingEvents {
                event_handler,
                queued_gpu_redraws,
                active_control_flow,
            });
            this.control_flow = control_flow;
            break;
        }
        drop(this);

        for wrapper in queued_events {
            match wrapper {
                EventWrapper::StaticEvent(event) => {
                    event_handler.handle_nonuser_event(event, &mut control_flow)
                }
                EventWrapper::EventProxy(proxy) => {
                    handle_event_proxy(&mut event_handler, control_flow, proxy)
                }
            }
        }
        event_handler.handle_user_events(&mut control_flow);
    }
}

// requires main thread
pub unsafe fn handle_main_events_cleared() {
    let mut this = AppState::get_mut();
    if !this.has_launched() {
        return;
    }
    match this.state_mut() {
        &mut AppStateImpl::ProcessingEvents { .. } => {}
        _ => bug!("`ProcessingRedraws` happened unexpectedly"),
    };
    drop(this);

    // User events are always sent out at the end of the "MainEventLoop"
    handle_user_events();
    handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared));

    let mut this = AppState::get_mut();
    let mut redraw_events: Vec<EventWrapper> = this
        .main_events_cleared_transition()
        .into_iter()
        .map(|window| {
            EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.into())))
        })
        .collect();

    if !redraw_events.is_empty() {
        redraw_events.push(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
    }
    drop(this);

    handle_nonuser_events(redraw_events);
}

// requires main thread
pub unsafe fn handle_events_cleared() {
    AppState::get_mut().events_cleared_transition();
}

// requires main thread
pub unsafe fn terminated() {
    let mut this = AppState::get_mut();
    let mut event_handler = this.terminated_transition();
    let mut control_flow = this.control_flow;
    drop(this);

    event_handler.handle_nonuser_event(Event::LoopDestroyed, &mut control_flow)
}

fn handle_event_proxy(
    event_handler: &mut Box<dyn EventHandler>,
    control_flow: ControlFlow,
    proxy: EventProxy,
) {
    match proxy {
        EventProxy::DpiChangedProxy {
            suggested_size,
            scale_factor,
            window_id,
        } => handle_hidpi_proxy(
            event_handler,
            control_flow,
            suggested_size,
            scale_factor,
            window_id,
        ),
    }
}

fn handle_hidpi_proxy(
    event_handler: &mut Box<dyn EventHandler>,
    mut control_flow: ControlFlow,
    suggested_size: LogicalSize<f64>,
    scale_factor: f64,
    window_id: id,
) {
    let mut size = suggested_size.to_physical(scale_factor);
    let new_inner_size = &mut size;
    let event = Event::WindowEvent {
        window_id: RootWindowId(window_id.into()),
        event: WindowEvent::ScaleFactorChanged {
            scale_factor,
            new_inner_size,
        },
    };
    event_handler.handle_nonuser_event(event, &mut control_flow);
    let (view, screen_frame) = get_view_and_screen_frame(window_id);
    let physical_size = *new_inner_size;
    let logical_size = physical_size.to_logical(scale_factor);
    let size = CGSize::new(logical_size);
    let new_frame: CGRect = CGRect::new(screen_frame.origin, size);
    unsafe {
        let () = msg_send![view, setFrame: new_frame];
    }
}

fn get_view_and_screen_frame(window_id: id) -> (id, CGRect) {
    unsafe {
        let view_controller: id = msg_send![window_id, rootViewController];
        let view: id = msg_send![view_controller, view];
        let bounds: CGRect = msg_send![window_id, bounds];
        let screen: id = msg_send![window_id, screen];
        let screen_space: id = msg_send![screen, coordinateSpace];
        let screen_frame: CGRect =
            msg_send![window_id, convertRect:bounds toCoordinateSpace:screen_space];
        (view, screen_frame)
    }
}

struct EventLoopWaker {
    timer: CFRunLoopTimerRef,
}

impl Drop for EventLoopWaker {
    fn drop(&mut self) {
        unsafe {
            CFRunLoopTimerInvalidate(self.timer);
            CFRelease(self.timer as _);
        }
    }
}

impl EventLoopWaker {
    fn new(rl: CFRunLoopRef) -> EventLoopWaker {
        extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
        unsafe {
            // Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
            // It is initially setup with a first fire time really far into the
            // future, but that gets changed to fire immediately in did_finish_launching
            let timer = CFRunLoopTimerCreate(
                ptr::null_mut(),
                std::f64::MAX,
                0.000_000_1,
                0,
                0,
                wakeup_main_loop,
                ptr::null_mut(),
            );
            CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes);

            EventLoopWaker { timer }
        }
    }

    fn stop(&mut self) {
        unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) }
    }

    fn start(&mut self) {
        unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) }
    }

    fn start_at(&mut self, instant: Instant) {
        let now = Instant::now();
        if now >= instant {
            self.start();
        } else {
            unsafe {
                let current = CFAbsoluteTimeGetCurrent();
                let duration = instant - now;
                let fsecs =
                    duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64;
                CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs)
            }
        }
    }
}

macro_rules! os_capabilities {
    (
        $(
            $(#[$attr:meta])*
            $error_name:ident: $objc_call:literal,
            $name:ident: $major:literal-$minor:literal
        ),*
        $(,)*
    ) => {
        #[derive(Clone, Debug)]
        pub struct OSCapabilities {
            $(
                pub $name: bool,
            )*

            os_version: NSOperatingSystemVersion,
        }

        impl From<NSOperatingSystemVersion> for OSCapabilities {
            fn from(os_version: NSOperatingSystemVersion) -> OSCapabilities {
                $(let $name = os_version.meets_requirements($major, $minor);)*
                OSCapabilities { $($name,)* os_version, }
            }
        }

        impl OSCapabilities {$(
            $(#[$attr])*
            pub fn $error_name(&self, extra_msg: &str) {
                log::warn!(
                    concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"),
                    $major, $minor, self.os_version.major, self.os_version.minor, self.os_version.patch,
                    extra_msg
                )
            }
        )*}
    };
}

os_capabilities! {
    /// https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc
    #[allow(unused)] // error message unused
    safe_area_err_msg: "-[UIView safeAreaInsets]",
    safe_area: 11-0,
    /// https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc
    home_indicator_hidden_err_msg: "-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]",
    home_indicator_hidden: 11-0,
    /// https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc
    defer_system_gestures_err_msg: "-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystem]",
    defer_system_gestures: 11-0,
    /// https://developer.apple.com/documentation/uikit/uiscreen/2806814-maximumframespersecond?language=objc
    maximum_frames_per_second_err_msg: "-[UIScreen maximumFramesPerSecond]",
    maximum_frames_per_second: 10-3,
    /// https://developer.apple.com/documentation/uikit/uitouch/1618110-force?language=objc
    #[allow(unused)] // error message unused
    force_touch_err_msg: "-[UITouch force]",
    force_touch: 9-0,
}

impl NSOperatingSystemVersion {
    fn meets_requirements(&self, required_major: NSInteger, required_minor: NSInteger) -> bool {
        (self.major, self.minor) >= (required_major, required_minor)
    }
}

pub fn os_capabilities() -> OSCapabilities {
    lazy_static! {
        static ref OS_CAPABILITIES: OSCapabilities = {
            let version: NSOperatingSystemVersion = unsafe {
                let process_info: id = msg_send![class!(NSProcessInfo), processInfo];
                let atleast_ios_8: BOOL = msg_send![
                    process_info,
                    respondsToSelector: sel!(operatingSystemVersion)
                ];
                // winit requires atleast iOS 8 because no one has put the time into supporting earlier os versions.
                // Older iOS versions are increasingly difficult to test. For example, Xcode 11 does not support
                // debugging on devices with an iOS version of less than 8. Another example, in order to use an iOS
                // simulator older than iOS 8, you must download an older version of Xcode (<9), and at least Xcode 7
                // has been tested to not even run on macOS 10.15 - Xcode 8 might?
                //
                // The minimum required iOS version is likely to grow in the future.
                assert!(
                    atleast_ios_8 == YES,
                    "`winit` requires iOS version 8 or greater"
                );
                msg_send![process_info, operatingSystemVersion]
            };
            version.into()
        };
    }
    OS_CAPABILITIES.clone()
}
