// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.

//! An event provides fine-grained synchronization within a single queue, or from the host to a
//! queue.
//!
//! When an event is signaled from a queue using the [`set_event`] command buffer command,
//! an event acts similar to a [pipeline barrier], but the synchronization scopes are split:
//! the source synchronization scope includes only commands before the `set_event` command,
//! while the destination synchronization scope includes only commands after the
//! [`wait_events`] command. Commands in between the two are not included.
//!
//! An event can also be signaled from the host, by calling the [`set`] method directly on the
//! [`Event`].
//!
//! [`set_event`]: crate::command_buffer::CommandBufferBuilder::set_event
//! [pipeline barrier]: crate::command_buffer::CommandBufferBuilder::pipeline_barrier
//! [`wait_events`]: crate::command_buffer::CommandBufferBuilder::wait_events
//! [`set`]: Event::set

use crate::{
    device::{Device, DeviceOwned},
    macros::impl_id_counter,
    OomError, RequiresOneOf, VulkanError, VulkanObject,
};
use std::{
    error::Error,
    fmt::{Display, Error as FmtError, Formatter},
    mem::MaybeUninit,
    num::NonZeroU64,
    ptr,
    sync::Arc,
};

/// Used to block the GPU execution until an event on the CPU occurs.
///
/// Note that Vulkan implementations may have limits on how long a command buffer will wait for an
/// event to be signaled, in order to avoid interfering with progress of other clients of the GPU.
/// If the event isn't signaled within these limits, results are undefined and may include
/// device loss.
#[derive(Debug)]
pub struct Event {
    handle: ash::vk::Event,
    device: Arc<Device>,
    id: NonZeroU64,
    must_put_in_pool: bool,
}

impl Event {
    /// Creates a new `Event`.
    ///
    /// On [portability subset](crate::instance#portability-subset-devices-and-the-enumerate_portability-flag)
    /// devices, the
    /// [`events`](crate::device::Features::events)
    /// feature must be enabled on the device.
    #[inline]
    pub fn new(device: Arc<Device>, _create_info: EventCreateInfo) -> Result<Event, EventError> {
        // VUID-vkCreateEvent-events-04468
        if device.enabled_extensions().khr_portability_subset && !device.enabled_features().events {
            return Err(EventError::RequirementNotMet {
                required_for: "this device is a portability subset device, and `Event::new` was \
                    called",
                requires_one_of: RequiresOneOf {
                    features: &["events"],
                    ..Default::default()
                },
            });
        }

        let create_info = ash::vk::EventCreateInfo {
            flags: ash::vk::EventCreateFlags::empty(),
            ..Default::default()
        };

        let handle = unsafe {
            let mut output = MaybeUninit::uninit();
            let fns = device.fns();
            (fns.v1_0.create_event)(
                device.handle(),
                &create_info,
                ptr::null(),
                output.as_mut_ptr(),
            )
            .result()
            .map_err(VulkanError::from)?;
            output.assume_init()
        };

        Ok(Event {
            handle,
            device,
            id: Self::next_id(),
            must_put_in_pool: false,
        })
    }

    /// Takes an event from the vulkano-provided event pool.
    /// If the pool is empty, a new event will be allocated.
    /// Upon `drop`, the event is put back into the pool.
    ///
    /// For most applications, using the event pool should be preferred,
    /// in order to avoid creating new events every frame.
    #[inline]
    pub fn from_pool(device: Arc<Device>) -> Result<Event, EventError> {
        let handle = device.event_pool().lock().pop();
        let event = match handle {
            Some(handle) => {
                unsafe {
                    // Make sure the event isn't signaled
                    let fns = device.fns();
                    (fns.v1_0.reset_event)(device.handle(), handle)
                        .result()
                        .map_err(VulkanError::from)?;
                }
                Event {
                    handle,
                    device,
                    id: Self::next_id(),
                    must_put_in_pool: true,
                }
            }
            None => {
                // Pool is empty, alloc new event
                let mut event = Event::new(device, Default::default())?;
                event.must_put_in_pool = true;
                event
            }
        };

        Ok(event)
    }

    /// Creates a new `Event` from a raw object handle.
    ///
    /// # Safety
    ///
    /// - `handle` must be a valid Vulkan object handle created from `device`.
    /// - `create_info` must match the info used to create the object.
    #[inline]
    pub unsafe fn from_handle(
        device: Arc<Device>,
        handle: ash::vk::Event,
        _create_info: EventCreateInfo,
    ) -> Event {
        Event {
            handle,
            device,
            id: Self::next_id(),
            must_put_in_pool: false,
        }
    }

    /// Returns true if the event is signaled.
    #[inline]
    pub fn signaled(&self) -> Result<bool, OomError> {
        unsafe {
            let fns = self.device.fns();
            let result = (fns.v1_0.get_event_status)(self.device.handle(), self.handle);
            match result {
                ash::vk::Result::EVENT_SET => Ok(true),
                ash::vk::Result::EVENT_RESET => Ok(false),
                err => Err(VulkanError::from(err).into()),
            }
        }
    }

    /// See the docs of set().
    #[inline]
    pub fn set_raw(&mut self) -> Result<(), OomError> {
        unsafe {
            let fns = self.device.fns();
            (fns.v1_0.set_event)(self.device.handle(), self.handle)
                .result()
                .map_err(VulkanError::from)?;
            Ok(())
        }
    }

    /// Changes the `Event` to the signaled state.
    ///
    /// If a command buffer is waiting on this event, it is then unblocked.
    ///
    /// # Panics
    ///
    /// - Panics if the device or host ran out of memory.
    #[inline]
    pub fn set(&mut self) {
        self.set_raw().unwrap();
    }

    /// See the docs of reset().
    #[inline]
    pub fn reset_raw(&mut self) -> Result<(), OomError> {
        unsafe {
            let fns = self.device.fns();
            (fns.v1_0.reset_event)(self.device.handle(), self.handle)
                .result()
                .map_err(VulkanError::from)?;
            Ok(())
        }
    }

    /// Changes the `Event` to the unsignaled state.
    ///
    /// # Panics
    ///
    /// - Panics if the device or host ran out of memory.
    #[inline]
    pub fn reset(&mut self) {
        self.reset_raw().unwrap();
    }
}

impl Drop for Event {
    #[inline]
    fn drop(&mut self) {
        unsafe {
            if self.must_put_in_pool {
                let raw_event = self.handle;
                self.device.event_pool().lock().push(raw_event);
            } else {
                let fns = self.device.fns();
                (fns.v1_0.destroy_event)(self.device.handle(), self.handle, ptr::null());
            }
        }
    }
}

unsafe impl VulkanObject for Event {
    type Handle = ash::vk::Event;

    #[inline]
    fn handle(&self) -> Self::Handle {
        self.handle
    }
}

unsafe impl DeviceOwned for Event {
    #[inline]
    fn device(&self) -> &Arc<Device> {
        &self.device
    }
}

impl_id_counter!(Event);

/// Parameters to create a new `Event`.
#[derive(Clone, Debug)]
pub struct EventCreateInfo {
    pub _ne: crate::NonExhaustive,
}

impl Default for EventCreateInfo {
    #[inline]
    fn default() -> Self {
        Self {
            _ne: crate::NonExhaustive(()),
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum EventError {
    /// Not enough memory available.
    OomError(OomError),

    RequirementNotMet {
        required_for: &'static str,
        requires_one_of: RequiresOneOf,
    },
}

impl Error for EventError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            Self::OomError(err) => Some(err),
            _ => None,
        }
    }
}

impl Display for EventError {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
        match self {
            Self::OomError(_) => write!(f, "not enough memory available"),
            Self::RequirementNotMet {
                required_for,
                requires_one_of,
            } => write!(
                f,
                "a requirement was not met for: {}; requires one of: {}",
                required_for, requires_one_of,
            ),
        }
    }
}

impl From<VulkanError> for EventError {
    fn from(err: VulkanError) -> Self {
        match err {
            e @ VulkanError::OutOfHostMemory | e @ VulkanError::OutOfDeviceMemory => {
                Self::OomError(e.into())
            }
            _ => panic!("unexpected error: {:?}", err),
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::{sync::event::Event, VulkanObject};

    #[test]
    fn event_create() {
        let (device, _) = gfx_dev_and_queue!();
        let event = Event::new(device, Default::default()).unwrap();
        assert!(!event.signaled().unwrap());
    }

    #[test]
    fn event_set() {
        let (device, _) = gfx_dev_and_queue!();
        let mut event = Event::new(device, Default::default()).unwrap();
        assert!(!event.signaled().unwrap());

        event.set();
        assert!(event.signaled().unwrap());
    }

    #[test]
    fn event_reset() {
        let (device, _) = gfx_dev_and_queue!();

        let mut event = Event::new(device, Default::default()).unwrap();
        event.set();
        assert!(event.signaled().unwrap());

        event.reset();
        assert!(!event.signaled().unwrap());
    }

    #[test]
    fn event_pool() {
        let (device, _) = gfx_dev_and_queue!();

        assert_eq!(device.event_pool().lock().len(), 0);
        let event1_internal_obj = {
            let event = Event::from_pool(device.clone()).unwrap();
            assert_eq!(device.event_pool().lock().len(), 0);
            event.handle()
        };

        assert_eq!(device.event_pool().lock().len(), 1);
        let event2 = Event::from_pool(device.clone()).unwrap();
        assert_eq!(device.event_pool().lock().len(), 0);
        assert_eq!(event2.handle(), event1_internal_obj);
    }
}
