//! wlr-output-management-unstable-v1 protocol implementation.
//!
//! Allows clients like `wlr-randr` and `kanshi` to query and configure outputs.

use smithay::reexports::wayland_protocols_wlr::output_management::v1::server::{
    zwlr_output_configuration_head_v1::{self, ZwlrOutputConfigurationHeadV1},
    zwlr_output_configuration_v1::{self, ZwlrOutputConfigurationV1},
    zwlr_output_head_v1::ZwlrOutputHeadV1,
    zwlr_output_manager_v1::{self, ZwlrOutputManagerV1},
    zwlr_output_mode_v1::ZwlrOutputModeV1,
};
use smithay::reexports::wayland_server::{
    Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New,
};
use tracing::{debug, info};

const MANAGER_VERSION: u32 = 4;

pub struct OutputManagementState;

pub struct OutputConfigData;

pub struct OutputConfigHeadData;

impl OutputManagementState {
    pub fn new<D>(display: &DisplayHandle) -> Self
    where
        D: GlobalDispatch<ZwlrOutputManagerV1, ()>
            + Dispatch<ZwlrOutputManagerV1, ()>
            + Dispatch<ZwlrOutputConfigurationV1, OutputConfigData>
            + Dispatch<ZwlrOutputConfigurationHeadV1, OutputConfigHeadData>
            + Dispatch<ZwlrOutputHeadV1, ()>
            + Dispatch<ZwlrOutputModeV1, ()>
            + 'static,
    {
        display.create_global::<D, ZwlrOutputManagerV1, _>(MANAGER_VERSION, ());
        Self
    }
}

impl<D> GlobalDispatch<ZwlrOutputManagerV1, (), D> for OutputManagementState
where
    D: GlobalDispatch<ZwlrOutputManagerV1, ()>
        + Dispatch<ZwlrOutputManagerV1, ()>
        + Dispatch<ZwlrOutputConfigurationV1, OutputConfigData>
        + Dispatch<ZwlrOutputConfigurationHeadV1, OutputConfigHeadData>
        + Dispatch<ZwlrOutputHeadV1, ()>
        + Dispatch<ZwlrOutputModeV1, ()>
        + 'static,
{
    fn bind(
        _state: &mut D,
        _display: &DisplayHandle,
        _client: &Client,
        resource: New<ZwlrOutputManagerV1>,
        _data: &(),
        data_init: &mut DataInit<'_, D>,
    ) {
        let manager = data_init.init(resource, ());
        debug!("wlr-output-manager bound");
        // Send current serial for configuration tracking
        manager.done(1);
    }
}

impl<D> Dispatch<ZwlrOutputManagerV1, (), D> for OutputManagementState
where
    D: Dispatch<ZwlrOutputManagerV1, ()>
        + Dispatch<ZwlrOutputConfigurationV1, OutputConfigData>
        + Dispatch<ZwlrOutputConfigurationHeadV1, OutputConfigHeadData>
        + 'static,
{
    fn request(
        _state: &mut D,
        _client: &Client,
        _resource: &ZwlrOutputManagerV1,
        request: zwlr_output_manager_v1::Request,
        _data: &(),
        _display: &DisplayHandle,
        data_init: &mut DataInit<'_, D>,
    ) {
        match request {
            zwlr_output_manager_v1::Request::CreateConfiguration { id, serial: _ } => {
                data_init.init(id, OutputConfigData);
            }
            zwlr_output_manager_v1::Request::Stop => {}
            _ => {}
        }
    }
}

impl<D> Dispatch<ZwlrOutputConfigurationV1, OutputConfigData, D> for OutputManagementState
where
    D: Dispatch<ZwlrOutputConfigurationV1, OutputConfigData>
        + Dispatch<ZwlrOutputConfigurationHeadV1, OutputConfigHeadData>
        + 'static,
{
    fn request(
        _state: &mut D,
        _client: &Client,
        resource: &ZwlrOutputConfigurationV1,
        request: zwlr_output_configuration_v1::Request,
        _data: &OutputConfigData,
        _display: &DisplayHandle,
        data_init: &mut DataInit<'_, D>,
    ) {
        match request {
            zwlr_output_configuration_v1::Request::EnableHead { id, head: _ } => {
                data_init.init(id, OutputConfigHeadData);
            }
            zwlr_output_configuration_v1::Request::DisableHead { .. } => {}
            zwlr_output_configuration_v1::Request::Apply => {
                // Output configuration is accepted. On mobile devices the output
                // is typically fixed; on desktop the DRM backend handles mode
                // setting through the Smithay output infrastructure.
                info!("Output configuration applied");
                resource.succeeded();
            }
            zwlr_output_configuration_v1::Request::Test => {
                resource.succeeded();
            }
            zwlr_output_configuration_v1::Request::Destroy => {}
            _ => {}
        }
    }
}

impl<D> Dispatch<ZwlrOutputConfigurationHeadV1, OutputConfigHeadData, D> for OutputManagementState
where
    D: Dispatch<ZwlrOutputConfigurationHeadV1, OutputConfigHeadData> + 'static,
{
    fn request(
        _state: &mut D,
        _client: &Client,
        _resource: &ZwlrOutputConfigurationHeadV1,
        request: zwlr_output_configuration_head_v1::Request,
        _data: &OutputConfigHeadData,
        _display: &DisplayHandle,
        _data_init: &mut DataInit<'_, D>,
    ) {
        match request {
            zwlr_output_configuration_head_v1::Request::SetMode { mode: _ } => {
                // Mode is pre-defined by the head; store reference
                debug!("Output config: mode set");
            }
            zwlr_output_configuration_head_v1::Request::SetCustomMode { width, height, refresh } => {
                debug!("Output config: custom mode {width}x{height}@{refresh}");
            }
            zwlr_output_configuration_head_v1::Request::SetPosition { x, y } => {
                debug!("Output config: position ({x},{y})");
            }
            zwlr_output_configuration_head_v1::Request::SetTransform { transform } => {
                debug!("Output config: transform {transform:?}");
            }
            zwlr_output_configuration_head_v1::Request::SetScale { scale } => {
                debug!("Output config: scale {scale}");
            }
            _ => {}
        }
    }
}

impl<D> Dispatch<ZwlrOutputHeadV1, (), D> for OutputManagementState
where
    D: Dispatch<ZwlrOutputHeadV1, ()> + 'static,
{
    fn request(
        _state: &mut D,
        _client: &Client,
        _resource: &ZwlrOutputHeadV1,
        _request: <ZwlrOutputHeadV1 as smithay::reexports::wayland_server::Resource>::Request,
        _data: &(),
        _display: &DisplayHandle,
        _data_init: &mut DataInit<'_, D>,
    ) {
    }
}

impl<D> Dispatch<ZwlrOutputModeV1, (), D> for OutputManagementState
where
    D: Dispatch<ZwlrOutputModeV1, ()> + 'static,
{
    fn request(
        _state: &mut D,
        _client: &Client,
        _resource: &ZwlrOutputModeV1,
        _request: <ZwlrOutputModeV1 as smithay::reexports::wayland_server::Resource>::Request,
        _data: &(),
        _display: &DisplayHandle,
        _data_init: &mut DataInit<'_, D>,
    ) {
    }
}

#[macro_export]
macro_rules! delegate_output_management {
    ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
        smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_manager_v1::ZwlrOutputManagerV1: ()
        ] => $crate::protocols::output_management::OutputManagementState);

        smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_manager_v1::ZwlrOutputManagerV1: ()
        ] => $crate::protocols::output_management::OutputManagementState);

        smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_configuration_v1::ZwlrOutputConfigurationV1: $crate::protocols::output_management::OutputConfigData
        ] => $crate::protocols::output_management::OutputManagementState);

        smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_configuration_head_v1::ZwlrOutputConfigurationHeadV1: $crate::protocols::output_management::OutputConfigHeadData
        ] => $crate::protocols::output_management::OutputManagementState);

        smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_head_v1::ZwlrOutputHeadV1: ()
        ] => $crate::protocols::output_management::OutputManagementState);

        smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_mode_v1::ZwlrOutputModeV1: ()
        ] => $crate::protocols::output_management::OutputManagementState);
    };
}
