//! The [`EventLoop`] struct and assorted supporting types, including
//! [`ControlFlow`].
//!
//! If you want to send custom events to the event loop, use
//! [`EventLoop::create_proxy`] to acquire an [`EventLoopProxy`] and call its
//! [`wake_up`][EventLoopProxy::wake_up] method. Then during handling the wake up
//! you can poll your event sources.
//!
//! See the root-level documentation for information on how to create and use an event loop to
//! handle events.
use std::marker::PhantomData;
#[cfg(any(x11_platform, wayland_platform))]
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};

use rwh_06::{DisplayHandle, HandleError, HasDisplayHandle};
pub use winit_core::event_loop::*;

use crate::application::ApplicationHandler;
use crate::cursor::{CustomCursor, CustomCursorSource};
use crate::error::{EventLoopError, RequestError};
use crate::platform_impl;

/// 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 [`EventLoop::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-dependent 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.
///
/// [`Window`]: crate::window::Window
#[derive(Debug)]
pub struct EventLoop {
    pub(crate) event_loop: platform_impl::EventLoop,
    pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
}

/// Object that allows building the event loop.
///
/// This is used to make specifying options that affect the whole application
/// easier. But note that constructing multiple event loops is not supported.
///
/// This can be created using [`EventLoop::builder`].
#[derive(Default, Debug, PartialEq, Eq, Hash)]
pub struct EventLoopBuilder {
    pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
}

impl EventLoopBuilder {
    /// 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.
    /// `EventLoopBuilderExt::with_any_thread` functions are exposed in the relevant
    /// [`platform`] module if the target platform supports creating an event
    /// loop on any thread.
    ///
    /// ## Platform-specific
    ///
    /// - **Wayland/X11:** to prevent running under `Wayland` or `X11` unset `WAYLAND_DISPLAY` or
    ///   `DISPLAY` respectively when building the event loop.
    /// - **Android:** must be configured with an `AndroidApp` from `android_main()` by calling
    ///   [`.with_android_app(app)`] before calling `.build()`, otherwise it'll panic.
    ///
    /// [`platform`]: crate::platform
    #[cfg_attr(
        android_platform,
        doc = "[`.with_android_app(app)`]: \
               crate::platform::android::EventLoopBuilderExtAndroid::with_android_app"
    )]
    #[cfg_attr(
        not(android_platform),
        doc = "[`.with_android_app(app)`]: #only-available-on-android"
    )]
    #[inline]
    pub fn build(&mut self) -> Result<EventLoop, EventLoopError> {
        let _entered = tracing::debug_span!("winit::EventLoopBuilder::build").entered();

        // Certain platforms accept a mutable reference in their API.
        #[allow(clippy::unnecessary_mut_passed)]
        Ok(EventLoop {
            event_loop: platform_impl::EventLoop::new(&mut self.platform_specific)?,
            _marker: PhantomData,
        })
    }
}

impl EventLoop {
    /// Create the event loop.
    ///
    /// This is an alias of `EventLoop::builder().build()`.
    #[inline]
    pub fn new() -> Result<EventLoop, EventLoopError> {
        Self::builder().build()
    }

    /// Start building a new event loop.
    ///
    /// This returns an [`EventLoopBuilder`], to allow configuring the event loop before creation.
    ///
    /// To get the actual event loop, call [`build`][EventLoopBuilder::build] on that.
    #[inline]
    pub fn builder() -> EventLoopBuilder {
        EventLoopBuilder { platform_specific: Default::default() }
    }

    /// Run the event loop with the given application on the calling thread.
    ///
    /// For details see [`EventLoopProvider`].
    ///
    /// ## Returns
    ///
    /// The semantics of this function can be a bit confusing, because the way different platforms
    /// control their event loop varies significantly.
    ///
    /// On most platforms (Android, macOS, Orbital, X11, Wayland, Windows), this blocks the caller,
    /// runs the event loop internally, and then returns once [`ActiveEventLoop::exit`] is called.
    /// See [`run_app_on_demand`] for more detailed semantics.
    ///
    /// On iOS, this will register the application handler, and then call [`UIApplicationMain`]
    /// (which is the only way to run the system event loop), which never returns to the caller
    /// (the process instead exits after the handler has been dropped). See also
    /// [`run_app_never_return`].
    ///
    /// On the web, this works by registering the application handler, and then immediately
    /// returning to the caller. This is necessary because WebAssembly (and JavaScript) is always
    /// executed in the context of the browser's own (internal) event loop, and thus we need to
    /// return to avoid blocking that and allow events to later be delivered asynchronously. See
    /// also [`register_app`].
    ///
    /// If you call this function inside `fn main`, you usually do not need to think about these
    /// details.
    ///
    /// [`UIApplicationMain`]: https://developer.apple.com/documentation/uikit/uiapplicationmain(_:_:_:_:)-1yub7?language=objc
    /// [`run_app_on_demand`]: crate::event_loop::run_on_demand::EventLoopExtRunOnDemand::run_app_on_demand
    /// [`run_app_never_return`]: crate::event_loop::never_return::EventLoopExtNeverReturn::run_app_never_return
    /// [`register_app`]: crate::event_loop::register::EventLoopExtRegister::register_app
    /// [`EventLoopProvider`]: winit_core::event_loop::EventLoopProvider
    ///
    /// ## Static
    ///
    /// To alleviate the issues noted above, this function requires that you pass in a `'static`
    /// handler, to ensure that any state your application uses will be alive as long as the
    /// application is running.
    ///
    /// To be clear, you should avoid doing e.g. `event_loop.run_app(&mut app)?`, and prefer
    /// `event_loop.run_app(app)?` instead.
    ///
    /// If this requirement is prohibitive for you, consider using [`run_app_on_demand`] instead
    /// (though note that this is not available on iOS and web).
    #[inline]
    pub fn run_app<A: ApplicationHandler + 'static>(self, app: A) -> Result<(), EventLoopError> {
        self.event_loop.run_app(app)
    }

    /// Creates an [`EventLoopProxy`] that can be used to dispatch user events
    /// to the main event loop, possibly from another thread.
    pub fn create_proxy(&self) -> EventLoopProxy {
        self.event_loop.window_target().create_proxy()
    }

    /// Gets a persistent reference to the underlying platform display.
    ///
    /// See the [`OwnedDisplayHandle`] type for more information.
    pub fn owned_display_handle(&self) -> OwnedDisplayHandle {
        self.event_loop.window_target().owned_display_handle()
    }

    /// Change if or when [`DeviceEvent`]s are captured.
    ///
    /// See [`ActiveEventLoop::listen_device_events`] for details.
    ///
    /// [`DeviceEvent`]: crate::event::DeviceEvent
    pub fn listen_device_events(&self, allowed: DeviceEvents) {
        let _entered = tracing::debug_span!(
            "winit::EventLoop::listen_device_events",
            allowed = ?allowed
        )
        .entered();
        self.event_loop.window_target().listen_device_events(allowed)
    }

    /// Sets the [`ControlFlow`].
    pub fn set_control_flow(&self, control_flow: ControlFlow) {
        self.event_loop.window_target().set_control_flow(control_flow);
    }

    /// Create custom cursor.
    ///
    /// ## Platform-specific
    ///
    /// **iOS / Android / Orbital:** Unsupported.
    pub fn create_custom_cursor(
        &self,
        custom_cursor: CustomCursorSource,
    ) -> Result<CustomCursor, RequestError> {
        self.event_loop.window_target().create_custom_cursor(custom_cursor)
    }
}

impl EventLoopProvider for EventLoop {
    fn run_app<A: ApplicationHandler + 'static>(self, app: A) -> Result<(), EventLoopError> {
        self.run_app(app)
    }

    fn create_proxy(&self) -> EventLoopProxy {
        self.create_proxy()
    }

    fn owned_display_handle(&self) -> OwnedDisplayHandle {
        self.owned_display_handle()
    }

    fn listen_device_events(&self, allowed: DeviceEvents) {
        self.listen_device_events(allowed);
    }

    fn set_control_flow(&self, control_flow: ControlFlow) {
        self.set_control_flow(control_flow);
    }

    fn create_custom_cursor(
        &self,
        custom_cursor: CustomCursorSource,
    ) -> Result<CustomCursor, RequestError> {
        self.create_custom_cursor(custom_cursor)
    }
}

impl HasDisplayHandle for EventLoop {
    fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
        HasDisplayHandle::display_handle(self.event_loop.window_target().rwh_06_handle())
    }
}

#[cfg(any(x11_platform, wayland_platform))]
impl AsFd for EventLoop {
    /// Get the underlying [EventLoop]'s `fd` which you can register
    /// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
    /// loop must be polled with the [`pump_app_events`] API.
    ///
    /// [`calloop`]: https://crates.io/crates/calloop
    /// [`mio`]: https://crates.io/crates/mio
    /// [`pump_app_events`]: crate::event_loop::pump_events::EventLoopExtPumpEvents::pump_app_events
    fn as_fd(&self) -> BorrowedFd<'_> {
        self.event_loop.as_fd()
    }
}

#[cfg(any(x11_platform, wayland_platform))]
impl AsRawFd for EventLoop {
    /// Get the underlying [EventLoop]'s raw `fd` which you can register
    /// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
    /// loop must be polled with the [`pump_app_events`] API.
    ///
    /// [`calloop`]: https://crates.io/crates/calloop
    /// [`mio`]: https://crates.io/crates/mio
    /// [`pump_app_events`]: crate::event_loop::pump_events::EventLoopExtPumpEvents::pump_app_events
    fn as_raw_fd(&self) -> RawFd {
        self.event_loop.as_raw_fd()
    }
}

#[cfg(any(
    windows_platform,
    macos_platform,
    android_platform,
    orbital_platform,
    x11_platform,
    wayland_platform,
    docsrs,
))]
impl winit_core::event_loop::pump_events::EventLoopExtPumpEvents for EventLoop {
    fn pump_app_events<A: ApplicationHandler>(
        &mut self,
        timeout: Option<std::time::Duration>,
        app: A,
    ) -> winit_core::event_loop::pump_events::PumpStatus {
        self.event_loop.pump_app_events(timeout, app)
    }
}

#[allow(unused_imports)]
#[cfg(any(
    windows_platform,
    macos_platform,
    android_platform,
    orbital_platform,
    x11_platform,
    wayland_platform,
    docsrs,
))]
impl winit_core::event_loop::run_on_demand::EventLoopExtRunOnDemand for EventLoop {
    fn run_app_on_demand<A: ApplicationHandler>(&mut self, app: A) -> Result<(), EventLoopError> {
        self.event_loop.run_app_on_demand(app)
    }
}

#[cfg(any(web_platform, docsrs))]
impl winit_core::event_loop::register::EventLoopExtRegister for EventLoop {
    fn register_app<A: ApplicationHandler + 'static>(self, app: A) {
        self.event_loop.register_app(app)
    }
}

#[cfg(android_platform)]
impl winit_android::EventLoopExtAndroid for EventLoop {
    fn android_app(&self) -> &winit_android::activity::AndroidApp {
        &self.event_loop.android_app
    }
}

#[cfg(android_platform)]
impl winit_android::EventLoopBuilderExtAndroid for EventLoopBuilder {
    fn with_android_app(&mut self, app: winit_android::activity::AndroidApp) -> &mut Self {
        self.platform_specific.android_app = Some(app);
        self
    }

    fn handle_volume_keys(&mut self) -> &mut Self {
        self.platform_specific.ignore_volume_keys = false;
        self
    }
}

#[cfg(macos_platform)]
impl winit_appkit::EventLoopBuilderExtMacOS for EventLoopBuilder {
    #[inline]
    fn with_activation_policy(
        &mut self,
        activation_policy: winit_appkit::ActivationPolicy,
    ) -> &mut Self {
        self.platform_specific.activation_policy = Some(activation_policy);
        self
    }

    #[inline]
    fn with_default_menu(&mut self, enable: bool) -> &mut Self {
        self.platform_specific.default_menu = enable;
        self
    }

    #[inline]
    fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self {
        self.platform_specific.activate_ignoring_other_apps = ignore;
        self
    }
}

#[cfg(wayland_platform)]
impl winit_wayland::EventLoopExtWayland for EventLoop {
    #[inline]
    fn is_wayland(&self) -> bool {
        self.event_loop.is_wayland()
    }
}

#[cfg(wayland_platform)]
impl winit_wayland::EventLoopBuilderExtWayland for EventLoopBuilder {
    #[inline]
    fn with_wayland(&mut self) -> &mut Self {
        self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::Wayland);
        self
    }

    #[inline]
    fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
        self.platform_specific.any_thread = any_thread;
        self
    }
}

#[cfg(web_platform)]
impl winit_web::EventLoopExtWeb for EventLoop {
    fn set_poll_strategy(&self, strategy: winit_web::PollStrategy) {
        self.event_loop.set_poll_strategy(strategy);
    }

    fn poll_strategy(&self) -> winit_web::PollStrategy {
        self.event_loop.poll_strategy()
    }

    fn set_wait_until_strategy(&self, strategy: winit_web::WaitUntilStrategy) {
        self.event_loop.set_wait_until_strategy(strategy);
    }

    fn wait_until_strategy(&self) -> winit_web::WaitUntilStrategy {
        self.event_loop.wait_until_strategy()
    }

    fn has_multiple_screens(&self) -> Result<bool, winit_core::error::NotSupportedError> {
        self.event_loop.has_multiple_screens()
    }

    fn request_detailed_monitor_permission(&self) -> winit_web::MonitorPermissionFuture {
        self.event_loop.request_detailed_monitor_permission()
    }

    fn has_detailed_monitor_permission(&self) -> winit_web::HasMonitorPermissionFuture {
        self.event_loop.has_detailed_monitor_permission()
    }
}

#[cfg(windows_platform)]
impl winit_win32::EventLoopBuilderExtWindows for EventLoopBuilder {
    #[inline]
    fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
        self.platform_specific.any_thread = any_thread;
        self
    }

    #[inline]
    fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self {
        self.platform_specific.dpi_aware = dpi_aware;
        self
    }

    #[inline]
    fn with_msg_hook<F>(&mut self, callback: F) -> &mut Self
    where
        F: FnMut(*const core::ffi::c_void) -> bool + 'static,
    {
        self.platform_specific.msg_hook = Some(Box::new(callback));
        self
    }
}

#[cfg(x11_platform)]
impl winit_x11::EventLoopExtX11 for EventLoop {
    #[inline]
    fn is_x11(&self) -> bool {
        !self.event_loop.is_wayland()
    }
}

#[cfg(x11_platform)]
impl winit_x11::EventLoopBuilderExtX11 for EventLoopBuilder {
    #[inline]
    fn with_x11(&mut self) -> &mut Self {
        self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::X);
        self
    }

    #[inline]
    fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
        self.platform_specific.any_thread = any_thread;
        self
    }
}

/// ```compile_error
/// use winit::event_loop::run_on_demand::EventLoopExtRunOnDemand;
/// use winit::event_loop::EventLoop;
///
/// let mut event_loop = EventLoop::new().unwrap();
/// event_loop.run_app_on_demand(|_, _| {
///     // Attempt to run the event loop re-entrantly; this must fail.
///     event_loop.run_app_on_demand(|_, _| {});
/// });
/// ```
#[allow(dead_code)]
fn test_run_on_demand_cannot_access_event_loop() {}
