//! Tools for configuring graphics cards and monitors.

use {
    crate::{
        _private::WireMode,
        Direction, PciId, Workspace,
        video::connector_type::{
            CON_9PIN_DIN, CON_COMPONENT, CON_COMPOSITE, CON_DISPLAY_PORT, CON_DPI, CON_DSI,
            CON_DVIA, CON_DVID, CON_DVII, CON_EDP, CON_EMBEDDED_WINDOW, CON_HDMIA, CON_HDMIB,
            CON_LVDS, CON_SPI, CON_SVIDEO, CON_TV, CON_UNKNOWN, CON_USB, CON_VGA, CON_VIRTUAL,
            CON_WRITEBACK, ConnectorType,
        },
    },
    serde::{Deserialize, Serialize},
    std::{str::FromStr, time::Duration},
};

/// The mode of a connector.
///
/// Currently a mode consists of three properties:
///
/// - width in pixels
/// - height in pixels
/// - refresh rate in mhz.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct Mode {
    pub(crate) width: i32,
    pub(crate) height: i32,
    pub(crate) refresh_millihz: u32,
}

impl Mode {
    /// Returns the width of the mode.
    pub fn width(&self) -> i32 {
        self.width
    }

    /// Returns the height of the mode.
    pub fn height(&self) -> i32 {
        self.height
    }

    /// Returns the refresh rate of the mode in mhz.
    ///
    /// For a 60hz monitor, this function would return 60_000.
    pub fn refresh_rate(&self) -> u32 {
        self.refresh_millihz
    }

    pub(crate) fn zeroed() -> Self {
        Self {
            width: 0,
            height: 0,
            refresh_millihz: 0,
        }
    }
}

/// A connector that is potentially connected to an output device.
///
/// A connector is the part that sticks out of your graphics card. A graphics card usually
/// has many connectors but one few of them are actually connected to a monitor.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct Connector(pub u64);

impl Connector {
    /// Returns whether this connector existed at the time `get_connector` was called.
    ///
    /// This only implies existence at the time `get_connector` was called. Even if this
    /// function returns true, the connector might since have disappeared.
    pub fn exists(self) -> bool {
        self.0 != 0
    }

    /// Returns whether the connector is connected to an output device.
    pub fn connected(self) -> bool {
        if !self.exists() {
            return false;
        }
        get!(false).connector_connected(self)
    }

    /// Returns the scale of the currently connected monitor.
    pub fn scale(self) -> f64 {
        if !self.exists() {
            return 1.0;
        }
        get!(1.0).connector_get_scale(self)
    }

    /// Sets the scale to use for the currently connected monitor.
    pub fn set_scale(self, scale: f64) {
        if !self.exists() {
            return;
        }
        log::info!("setting scale to {}", scale);
        get!().connector_set_scale(self, scale);
    }

    /// Returns the connector type.
    pub fn ty(self) -> ConnectorType {
        if !self.exists() {
            return CON_UNKNOWN;
        }
        get!(CON_UNKNOWN).connector_type(self)
    }

    /// Returns the current mode of the connector.
    pub fn mode(self) -> Mode {
        if !self.exists() {
            return Mode::zeroed();
        }
        get!(Mode::zeroed()).connector_mode(self)
    }

    /// Tries to set the mode of the connector.
    ///
    /// If the refresh rate is not specified, tries to use the first mode with the given
    /// width and height.
    ///
    /// The default mode is the first mode advertised by the connector. This is usually
    /// the native mode.
    pub fn set_mode(self, width: i32, height: i32, refresh_millihz: Option<u32>) {
        if !self.exists() {
            log::warn!("set_mode called on a connector that does not exist");
            return;
        }
        let refresh_millihz = match refresh_millihz {
            Some(r) => r,
            _ => match self
                .modes()
                .iter()
                .find(|m| m.width == width && m.height == height)
            {
                Some(m) => m.refresh_millihz,
                _ => {
                    log::warn!("Could not find any mode with width {width} and height {height}");
                    return;
                }
            },
        };
        get!().connector_set_mode(
            self,
            WireMode {
                width,
                height,
                refresh_millihz,
            },
        )
    }

    /// Returns the available modes of the connector.
    pub fn modes(self) -> Vec<Mode> {
        if !self.exists() {
            return Vec::new();
        }
        get!(Vec::new()).connector_modes(self)
    }

    /// Returns whether this connector supports arbitrary modes.
    pub fn supports_arbitrary_modes(self) -> bool {
        if !self.exists() {
            return false;
        }
        get!(false).connector_supports_arbitrary_modes(self)
    }

    /// Returns the logical width of the connector.
    ///
    /// The returned value will be different from `mode().width()` if the scale is not 1.
    pub fn width(self) -> i32 {
        get!().connector_size(self).0
    }

    /// Returns the logical height of the connector.
    ///
    /// The returned value will be different from `mode().height()` if the scale is not 1.
    pub fn height(self) -> i32 {
        get!().connector_size(self).1
    }

    /// Returns the refresh rate in mhz of the current mode of the connector.
    ///
    /// This is a shortcut for `mode().refresh_rate()`.
    pub fn refresh_rate(self) -> u32 {
        self.mode().refresh_millihz
    }

    /// Retrieves the position of the output in the global compositor space.
    pub fn position(self) -> (i32, i32) {
        if !self.connected() {
            return (0, 0);
        }
        get!().connector_get_position(self)
    }

    /// Sets the position of the connector in the global compositor space.
    ///
    /// `x` and `y` must be non-negative and must not exceed a currently unspecified limit.
    /// Any reasonable values for `x` and `y` should work.
    ///
    /// This function allows the connector to overlap with other connectors, however, such
    /// configurations are not supported and might result in unexpected behavior.
    pub fn set_position(self, x: i32, y: i32) {
        if !self.exists() {
            log::warn!("set_position called on a connector that does not exist");
            return;
        }
        get!().connector_set_position(self, x, y);
    }

    /// Enables or disables the connector.
    ///
    /// By default, all connectors are enabled.
    pub fn set_enabled(self, enabled: bool) {
        if !self.exists() {
            log::warn!("set_enabled called on a connector that does not exist");
            return;
        }
        get!().connector_set_enabled(self, enabled);
    }

    /// Sets the transformation to apply to the content of this connector.
    pub fn set_transform(self, transform: Transform) {
        if !self.exists() {
            log::warn!("set_transform called on a connector that does not exist");
            return;
        }
        get!().connector_set_transform(self, transform);
    }

    pub fn name(self) -> String {
        if !self.exists() {
            return String::new();
        }
        get!(String::new()).connector_get_name(self)
    }

    pub fn model(self) -> String {
        if !self.exists() {
            return String::new();
        }
        get!(String::new()).connector_get_model(self)
    }

    pub fn manufacturer(self) -> String {
        if !self.exists() {
            return String::new();
        }
        get!(String::new()).connector_get_manufacturer(self)
    }

    pub fn serial_number(self) -> String {
        if !self.exists() {
            return String::new();
        }
        get!(String::new()).connector_get_serial_number(self)
    }

    /// Sets the VRR mode.
    pub fn set_vrr_mode(self, mode: VrrMode) {
        get!().set_vrr_mode(Some(self), mode)
    }

    /// Sets the VRR cursor refresh rate.
    ///
    /// Limits the rate at which cursors are updated on screen when VRR is active.
    ///
    /// Setting this to infinity disables the limiter.
    pub fn set_vrr_cursor_hz(self, hz: f64) {
        get!().set_vrr_cursor_hz(Some(self), hz)
    }

    /// Sets the tearing mode.
    pub fn set_tearing_mode(self, mode: TearingMode) {
        get!().set_tearing_mode(Some(self), mode)
    }

    /// Sets the format to use for framebuffers.
    pub fn set_format(self, format: Format) {
        get!().connector_set_format(self, format);
    }

    /// Sets the color space and EOTF of the connector.
    ///
    /// By default, the default values are used which usually means sRGB color space with
    /// gamma22 EOTF.
    ///
    /// If the output supports it, HDR10 can be enabled by setting the color space to
    /// BT.2020 and the EOTF to PQ.
    ///
    /// Note that some displays might ignore incompatible settings.
    pub fn set_colors(self, color_space: ColorSpace, eotf: Eotf) {
        get!().connector_set_colors(self, color_space, eotf);
    }

    /// Sets the space in which blending is performed for this output.
    ///
    /// The default is [`BlendSpace::SRGB`]
    pub fn set_blend_space(self, blend_space: BlendSpace) {
        get!().connector_set_blend_space(self, blend_space);
    }

    /// Sets the brightness of the output.
    ///
    /// By default or when `brightness` is `None`, the brightness depends on the
    /// EOTF:
    ///
    /// - [`Eotf::DEFAULT`]: The maximum brightness of the output.
    /// - [`Eotf::PQ`]: 203 cd/m^2.
    ///
    /// This should only be used with the PQ transfer function. If the default transfer
    /// function is used, you should instead calibrate the hardware directly.
    ///
    /// When used with the default transfer function, the default brightness is anchored
    /// at 80 cd/m^2. That is, setting this to 40 cd/m^2 makes everything appear half as
    /// bright as normal and creates 50% HDR headroom.
    ///
    /// This has no effect unless the vulkan renderer is used.
    pub fn set_brightness(self, brightness: Option<f64>) {
        get!().connector_set_brightness(self, brightness);
    }

    /// Get the currently visible/active workspace.
    ///
    /// If this connector is not connected, or is there no active workspace, returns a
    /// workspace whose `exists()` returns false.
    pub fn active_workspace(self) -> Workspace {
        get!(Workspace(0)).get_connector_active_workspace(self)
    }

    /// Get all workspaces on this connector.
    ///
    /// If this connector is not connected, returns an empty list.
    pub fn workspaces(self) -> Vec<Workspace> {
        get!().get_connector_workspaces(self)
    }

    /// Find the closest connector in the given direction.
    ///
    /// Uses center-to-center distance calculation and prefers outputs better aligned
    /// with the movement axis.
    ///
    /// If no connector exists in the given direction, returns a connector whose
    /// `exists()` returns false.
    pub fn connector_in_direction(self, direction: Direction) -> Connector {
        get!(Connector(0)).get_connector_in_direction(self, direction)
    }

    /// Configures whether the display primaries are used.
    ///
    /// By default, Jay pretends that the display uses sRGB primaries. This is also how
    /// most other systems behave. In reality, most displays use a much larger gamut. For
    /// example, they advertise that they support 95% of the DCI-P3 gamut. If the display
    /// is interpreting colors in their native gamut, then colors will appear more
    /// saturated than their specification.
    ///
    /// If this is set to `true`, Jay assumes that the display uses the primaries
    /// advertised in its EDID. This might produce more accurate colors while also
    /// allowing color-managed applications to use the full gamut of the display.
    ///
    /// This setting has no effect when the display is explicitly operating in a wide
    /// color space.
    ///
    /// The default is `false`.
    pub fn set_use_native_gamut(self, use_native_gamut: bool) {
        get!().connector_set_use_native_gamut(self, use_native_gamut);
    }
}

/// Returns all available DRM devices.
pub fn drm_devices() -> Vec<DrmDevice> {
    get!().drm_devices()
}

/// Sets the callback to be called when a new DRM device appears.
pub fn on_new_drm_device<F: FnMut(DrmDevice) + 'static>(f: F) {
    get!().on_new_drm_device(f)
}

/// Sets the callback to be called when a DRM device is removed.
pub fn on_drm_device_removed<F: FnMut(DrmDevice) + 'static>(f: F) {
    get!().on_del_drm_device(f)
}

/// Sets the callback to be called when a new connector appears.
pub fn on_new_connector<F: FnMut(Connector) + 'static>(f: F) {
    get!().on_new_connector(f)
}

/// Sets the callback to be called when a connector becomes connected to an output device.
pub fn on_connector_connected<F: FnMut(Connector) + 'static>(f: F) {
    get!().on_connector_connected(f)
}

/// Sets the callback to be called when a connector is disconnected from an output device.
pub fn on_connector_disconnected<F: FnMut(Connector) + 'static>(f: F) {
    get!().on_connector_disconnected(f)
}

/// Sets the callback to be called when the graphics of the compositor have been initialized.
///
/// This callback is only invoked once during the lifetime of the compositor. This is a good place
/// to auto start graphical applications.
pub fn on_graphics_initialized<F: FnOnce() + 'static>(f: F) {
    get!().on_graphics_initialized(f)
}

pub fn connectors() -> Vec<Connector> {
    get!().connectors(None)
}

/// Returns the connector with the given id.
///
/// The linux kernel identifies connectors by a (type, idx) tuple, e.g., `DP-0`.
/// If the connector does not exist at the time this function is called, a sentinel value is
/// returned. This can be checked by calling `exists()` on the returned connector.
///
/// The `id` argument can either be an explicit tuple, e.g. `(CON_DISPLAY_PORT, 0)`, or a string
/// that can be parsed to such a tuple, e.g. `"DP-0"`.
///
/// The following string prefixes exist:
///
/// - `DP`
/// - `eDP`
/// - `HDMI-A`
/// - `HDMI-B`
/// - `EmbeddedWindow` - this is an implementation detail of the compositor and used if it
///   runs as an embedded application.
/// - `VGA`
/// - `DVI-I`
/// - `DVI-D`
/// - `DVI-A`
/// - `Composite`
/// - `SVIDEO`
/// - `LVDS`
/// - `Component`
/// - `DIN`
/// - `TV`
/// - `Virtual`
/// - `DSI`
/// - `DPI`
/// - `Writeback`
/// - `SPI`
/// - `USB`
pub fn get_connector(id: impl ToConnectorId) -> Connector {
    let (ty, idx) = match id.to_connector_id() {
        Ok(id) => id,
        Err(e) => {
            log::error!("{}", e);
            return Connector(0);
        }
    };
    get!(Connector(0)).get_connector(ty, idx)
}

/// Returns the connector with the given name.
///
/// Unlike [`get_connector`], this function can also be used for connectors whose names
/// don't follow the `<type>-<id>` pattern.
pub fn get_connector_by_name(name: &str) -> Connector {
    get!(Connector(0)).get_connector_by_name(name)
}

/// A type that can be converted to a `(ConnectorType, idx)` tuple.
pub trait ToConnectorId {
    fn to_connector_id(&self) -> Result<(ConnectorType, u32), String>;
}

impl ToConnectorId for (ConnectorType, u32) {
    fn to_connector_id(&self) -> Result<(ConnectorType, u32), String> {
        Ok(*self)
    }
}

impl ToConnectorId for &'_ str {
    fn to_connector_id(&self) -> Result<(ConnectorType, u32), String> {
        let pairs = [
            ("DP-", CON_DISPLAY_PORT),
            ("eDP-", CON_EDP),
            ("HDMI-A-", CON_HDMIA),
            ("HDMI-B-", CON_HDMIB),
            ("EmbeddedWindow-", CON_EMBEDDED_WINDOW),
            ("VGA-", CON_VGA),
            ("DVI-I-", CON_DVII),
            ("DVI-D-", CON_DVID),
            ("DVI-A-", CON_DVIA),
            ("Composite-", CON_COMPOSITE),
            ("SVIDEO-", CON_SVIDEO),
            ("LVDS-", CON_LVDS),
            ("Component-", CON_COMPONENT),
            ("DIN-", CON_9PIN_DIN),
            ("TV-", CON_TV),
            ("Virtual-", CON_VIRTUAL),
            ("DSI-", CON_DSI),
            ("DPI-", CON_DPI),
            ("Writeback-", CON_WRITEBACK),
            ("SPI-", CON_SPI),
            ("USB-", CON_USB),
        ];
        for (prefix, ty) in pairs {
            if let Some(suffix) = self.strip_prefix(prefix)
                && let Ok(idx) = u32::from_str(suffix)
            {
                return Ok((ty, idx));
            }
        }
        Err(format!("`{}` is not a valid connector identifier", self))
    }
}

/// Module containing all known connector types.
pub mod connector_type {
    use serde::{Deserialize, Serialize};

    /// The type of a connector.
    #[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
    pub struct ConnectorType(pub u32);

    pub const CON_UNKNOWN: ConnectorType = ConnectorType(0);
    pub const CON_VGA: ConnectorType = ConnectorType(1);
    pub const CON_DVII: ConnectorType = ConnectorType(2);
    pub const CON_DVID: ConnectorType = ConnectorType(3);
    pub const CON_DVIA: ConnectorType = ConnectorType(4);
    pub const CON_COMPOSITE: ConnectorType = ConnectorType(5);
    pub const CON_SVIDEO: ConnectorType = ConnectorType(6);
    pub const CON_LVDS: ConnectorType = ConnectorType(7);
    pub const CON_COMPONENT: ConnectorType = ConnectorType(8);
    pub const CON_9PIN_DIN: ConnectorType = ConnectorType(9);
    pub const CON_DISPLAY_PORT: ConnectorType = ConnectorType(10);
    pub const CON_HDMIA: ConnectorType = ConnectorType(11);
    pub const CON_HDMIB: ConnectorType = ConnectorType(12);
    pub const CON_TV: ConnectorType = ConnectorType(13);
    pub const CON_EDP: ConnectorType = ConnectorType(14);
    pub const CON_VIRTUAL: ConnectorType = ConnectorType(15);
    pub const CON_DSI: ConnectorType = ConnectorType(16);
    pub const CON_DPI: ConnectorType = ConnectorType(17);
    pub const CON_WRITEBACK: ConnectorType = ConnectorType(18);
    pub const CON_SPI: ConnectorType = ConnectorType(19);
    pub const CON_USB: ConnectorType = ConnectorType(20);
    pub const CON_EMBEDDED_WINDOW: ConnectorType = ConnectorType(u32::MAX);
    pub const CON_VIRTUAL_OUTPUT: ConnectorType = ConnectorType(u32::MAX - 1);
}

/// A *Direct Rendering Manager* (DRM) device.
///
/// It's easiest to think of a DRM device as a graphics card.
/// There are also DRM devices that are emulated in software but you are unlikely to encounter
/// those accidentally.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct DrmDevice(pub u64);

impl DrmDevice {
    /// Returns the connectors of this device.
    pub fn connectors(self) -> Vec<Connector> {
        get!().connectors(Some(self))
    }

    /// Returns the devnode of this device.
    ///
    /// E.g. `/dev/dri/card0`.
    pub fn devnode(self) -> String {
        get!().drm_device_devnode(self)
    }

    /// Returns the syspath of this device.
    ///
    /// E.g. `/sys/devices/pci0000:00/0000:00:03.1/0000:07:00.0`.
    pub fn syspath(self) -> String {
        get!().drm_device_syspath(self)
    }

    /// Returns the vendor of this device.
    ///
    /// E.g. `Advanced Micro Devices, Inc. [AMD/ATI]`.
    pub fn vendor(self) -> String {
        get!().drm_device_vendor(self)
    }

    /// Returns the model of this device.
    ///
    /// E.g. `Ellesmere [Radeon RX 470/480/570/570X/580/580X/590] (Radeon RX 570 Armor 8G OC)`.
    pub fn model(self) -> String {
        get!().drm_device_model(self)
    }

    /// Returns the PIC ID of this device.
    ///
    /// E.g. `1002:67DF`.
    pub fn pci_id(self) -> PciId {
        get!().drm_device_pci_id(self)
    }

    /// Makes this device the render device.
    pub fn make_render_device(self) {
        get!().make_render_device(self);
    }

    /// Sets the preferred graphics API for this device.
    ///
    /// If the API cannot be used, the compositor will try other APIs.
    pub fn set_gfx_api(self, gfx_api: GfxApi) {
        get!().set_gfx_api(Some(self), gfx_api);
    }

    /// Enables or disables direct scanout of client surfaces for this device.
    pub fn set_direct_scanout_enabled(self, enabled: bool) {
        get!().set_direct_scanout_enabled(Some(self), enabled);
    }

    /// Sets the flip margin of this device.
    ///
    /// This is duration between the compositor initiating a page flip and the output's
    /// vblank event. This determines the minimum input latency. The default is 1.5 ms.
    ///
    /// Note that if the margin is too small, the compositor will dynamically increase it.
    pub fn set_flip_margin(self, margin: Duration) {
        get!().set_flip_margin(self, margin);
    }
}

/// A graphics API.
#[non_exhaustive]
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum GfxApi {
    OpenGl,
    Vulkan,
}

/// Sets the default graphics API.
///
/// If the API cannot be used, the compositor will try other APIs.
///
/// This setting can be overwritten per-device with [DrmDevice::set_gfx_api].
///
/// This call has no effect on devices that have already been initialized.
pub fn set_gfx_api(gfx_api: GfxApi) {
    get!().set_gfx_api(None, gfx_api);
}

/// Enables or disables direct scanout of client surfaces.
///
/// The default is `true`.
///
/// This setting can be overwritten per-device with [DrmDevice::set_direct_scanout_enabled].
pub fn set_direct_scanout_enabled(enabled: bool) {
    get!().set_direct_scanout_enabled(None, enabled);
}

/// A transformation.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
pub enum Transform {
    /// No transformation.
    #[default]
    None,
    /// Rotate 90 degrees counter-clockwise.
    Rotate90,
    /// Rotate 180 degrees counter-clockwise.
    Rotate180,
    /// Rotate 270 degrees counter-clockwise.
    Rotate270,
    /// Flip around the vertical axis.
    Flip,
    /// Flip around the vertical axis, then rotate 90 degrees counter-clockwise.
    FlipRotate90,
    /// Flip around the vertical axis, then rotate 180 degrees counter-clockwise.
    FlipRotate180,
    /// Flip around the vertical axis, then rotate 270 degrees counter-clockwise.
    FlipRotate270,
}

/// The VRR mode of a connector.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
pub struct VrrMode(pub u32);

impl VrrMode {
    /// VRR is never enabled.
    pub const NEVER: Self = Self(0);
    /// VRR is always enabled.
    pub const ALWAYS: Self = Self(1);
    /// VRR is enabled when one or more applications are displayed fullscreen.
    pub const VARIANT_1: Self = Self(2);
    /// VRR is enabled when a single application is displayed fullscreen.
    pub const VARIANT_2: Self = Self(3);
    /// VRR is enabled when a single game or video is displayed fullscreen.
    pub const VARIANT_3: Self = Self(4);
}

/// Sets the default VRR mode.
///
/// This setting can be overwritten on a per-connector basis with [Connector::set_vrr_mode].
pub fn set_vrr_mode(mode: VrrMode) {
    get!().set_vrr_mode(None, mode)
}

/// Sets the VRR cursor refresh rate.
///
/// Limits the rate at which cursors are updated on screen when VRR is active.
///
/// Setting this to infinity disables the limiter.
///
/// This setting can be overwritten on a per-connector basis with [Connector::set_vrr_cursor_hz].
pub fn set_vrr_cursor_hz(hz: f64) {
    get!().set_vrr_cursor_hz(None, hz)
}

/// The tearing mode of a connector.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
pub struct TearingMode(pub u32);

impl TearingMode {
    /// Tearing is never enabled.
    pub const NEVER: Self = Self(0);
    /// Tearing is always enabled.
    pub const ALWAYS: Self = Self(1);
    /// Tearing is enabled when one or more applications are displayed fullscreen.
    pub const VARIANT_1: Self = Self(2);
    /// Tearing is enabled when a single application is displayed fullscreen.
    pub const VARIANT_2: Self = Self(3);
    /// Tearing is enabled when a single application is displayed fullscreen and the
    /// application has requested tearing.
    ///
    /// This is the default.
    pub const VARIANT_3: Self = Self(4);
}

/// Sets the default tearing mode.
///
/// This setting can be overwritten on a per-connector basis with [Connector::set_tearing_mode].
pub fn set_tearing_mode(mode: TearingMode) {
    get!().set_tearing_mode(None, mode)
}

/// Creates a virtual output with the given name.
///
/// This is a no-op if a virtual output with that name already exists.
///
/// The created connector can be accessed with [`get_connector_by_name("VO-{name}")`].
///
/// A newly created connector is initially disabled. When a connector is destroyed and
/// later recreated, its previous state is restored.
pub fn create_virtual_output(name: &str) {
    get!().create_virtual_output(name);
}

/// Removes the virtual output with the given name.
///
/// This is a no-op if a virtual output with that name does not exist.
pub fn remove_virtual_output(name: &str) {
    get!().remove_virtual_output(name);
}

/// A graphics format.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Format(pub u32);

impl Format {
    pub const ARGB8888: Self = Self(0);
    pub const XRGB8888: Self = Self(1);
    pub const ABGR8888: Self = Self(2);
    pub const XBGR8888: Self = Self(3);
    pub const R8: Self = Self(4);
    pub const GR88: Self = Self(5);
    pub const RGB888: Self = Self(6);
    pub const BGR888: Self = Self(7);
    pub const RGBA4444: Self = Self(8);
    pub const RGBX4444: Self = Self(9);
    pub const BGRA4444: Self = Self(10);
    pub const BGRX4444: Self = Self(11);
    pub const RGB565: Self = Self(12);
    pub const BGR565: Self = Self(13);
    pub const RGBA5551: Self = Self(14);
    pub const RGBX5551: Self = Self(15);
    pub const BGRA5551: Self = Self(16);
    pub const BGRX5551: Self = Self(17);
    pub const ARGB1555: Self = Self(18);
    pub const XRGB1555: Self = Self(19);
    pub const ARGB2101010: Self = Self(20);
    pub const XRGB2101010: Self = Self(21);
    pub const ABGR2101010: Self = Self(22);
    pub const XBGR2101010: Self = Self(23);
    pub const ABGR16161616: Self = Self(24);
    pub const XBGR16161616: Self = Self(25);
    pub const ABGR16161616F: Self = Self(26);
    pub const XBGR16161616F: Self = Self(27);
    pub const BGR161616: Self = Self(28);
    pub const R16F: Self = Self(29);
    pub const GR1616F: Self = Self(30);
    pub const BGR161616F: Self = Self(31);
    pub const R32F: Self = Self(32);
    pub const GR3232F: Self = Self(33);
    pub const BGR323232F: Self = Self(34);
    pub const ABGR32323232F: Self = Self(35);
}

/// A color space.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct ColorSpace(pub u32);

impl ColorSpace {
    /// The default color space (usually sRGB).
    pub const DEFAULT: Self = Self(0);
    /// The BT.2020 color space.
    pub const BT2020: Self = Self(1);
}

/// An electro-optical transfer function (EOTF).
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Eotf(pub u32);

#[deprecated = "use the Eotf type instead"]
pub type TransferFunction = Eotf;

impl Eotf {
    /// The default EOTF (usually gamma22).
    pub const DEFAULT: Self = Self(0);
    /// The PQ EOTF.
    pub const PQ: Self = Self(1);
}

/// A space in which color blending is performed.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct BlendSpace(pub u32);

impl BlendSpace {
    /// The sRGB blend space with sRGB primaries and gamma22 transfer function. This is
    /// the classic desktop blend space.
    pub const SRGB: Self = Self(0);
    /// The linear blend space performs blending in linear space, which is more physically
    /// correct but leads to much lighter output when blending light and dark colors.
    pub const LINEAR: Self = Self(1);
}
