// SPDX-License-Identifier: AGPL-3.0-or-later

use crate::backend::{BackendError, KeyedMutexBackend, TextureFormat};

#[derive(Clone)]
pub struct D3D11SharedHandle {
    pub raw_handle: u64,
    pub slot_index: u32,
    pub width: u32,
    pub height: u32,
}

#[cfg(target_os = "windows")]
pub struct D3D11KeyedMutexBackend {
    device: Option<windows::Win32::Graphics::Direct3D11::ID3D11Device>,
    #[allow(dead_code, reason = "held for RAII: device context lifetime")]
    context: Option<windows::Win32::Graphics::Direct3D11::ID3D11DeviceContext>,
    slots: Vec<D3D11SlotState>,
    width: u32,
    height: u32,
    format: TextureFormat,
}

#[cfg(target_os = "windows")]
struct D3D11SlotState {
    #[allow(
        dead_code,
        reason = "held for RAII: texture lifetime tied to keyed mutex"
    )]
    texture: windows::Win32::Graphics::Direct3D11::ID3D11Texture2D,
    keyed_mutex: windows::Win32::Graphics::Dxgi::IDXGIKeyedMutex,
    #[allow(dead_code, reason = "exposed via D3D11SharedHandle to clients")]
    shared_handle: u64,
    slot_index: u32,
    expected_key: u64,
    acquired: bool,
    completed: bool,
}

#[cfg(target_os = "windows")]
impl D3D11KeyedMutexBackend {
    pub fn new() -> Result<Self, BackendError> {
        let (device, context) = unsafe { create_d3d11_device_windows()? };
        let backend = Self {
            device: Some(device),
            context: Some(context),
            slots: Vec::with_capacity(<Self as KeyedMutexBackend>::NUM_SLOTS),
            width: 0,
            height: 0,
            format: TextureFormat::Nv12,
        };
        assert!(backend.slots.is_empty(), "fresh backend has no slots");
        assert!(backend.device.is_some(), "device created");
        Ok(backend)
    }

    fn find_slot(&mut self, slot: &D3D11SharedHandle) -> Option<usize> {
        assert!((slot.slot_index as usize) < <Self as KeyedMutexBackend>::NUM_SLOTS);
        self.slots
            .iter()
            .position(|s| s.slot_index == slot.slot_index)
    }

    pub fn texture_for_slot(
        &self,
        slot_index: u32,
    ) -> Option<windows::Win32::Graphics::Direct3D11::ID3D11Texture2D> {
        assert!(
            slot_index < (<Self as KeyedMutexBackend>::NUM_SLOTS as u32),
            "slot_index in range"
        );
        assert!(!self.slots.is_empty(), "slots have been created");
        for state in self.slots.iter() {
            if state.slot_index == slot_index {
                return Some(state.texture.clone());
            }
        }
        None
    }

    pub fn device(&self) -> Option<windows::Win32::Graphics::Direct3D11::ID3D11Device> {
        let dev = self.device.clone();
        assert!(dev.is_some(), "device exists");
        assert!(
            !self.slots.is_empty() || self.width == 0,
            "post-init invariant"
        );
        dev
    }

    pub fn context(&self) -> Option<windows::Win32::Graphics::Direct3D11::ID3D11DeviceContext> {
        let ctx = self.context.clone();
        assert!(ctx.is_some(), "context exists");
        assert!(
            self.width > 0 || self.slots.is_empty(),
            "post-init invariant"
        );
        ctx
    }
}

#[cfg(target_os = "windows")]
unsafe fn create_d3d11_device_windows() -> Result<
    (
        windows::Win32::Graphics::Direct3D11::ID3D11Device,
        windows::Win32::Graphics::Direct3D11::ID3D11DeviceContext,
    ),
    BackendError,
> {
    use windows::Win32::Foundation::HMODULE;
    use windows::Win32::Graphics::Direct3D::{D3D_DRIVER_TYPE_HARDWARE, D3D_FEATURE_LEVEL_11_0};
    use windows::Win32::Graphics::Direct3D11::{
        D3D11_CREATE_DEVICE_BGRA_SUPPORT, D3D11_SDK_VERSION, D3D11CreateDevice, ID3D11Device,
        ID3D11DeviceContext,
    };
    let mut device: Option<ID3D11Device> = None;
    let mut context: Option<ID3D11DeviceContext> = None;
    let feature_levels = [D3D_FEATURE_LEVEL_11_0];
    unsafe {
        D3D11CreateDevice(
            None,
            D3D_DRIVER_TYPE_HARDWARE,
            HMODULE::default(),
            D3D11_CREATE_DEVICE_BGRA_SUPPORT,
            Some(&feature_levels),
            D3D11_SDK_VERSION,
            Some(&mut device),
            None,
            Some(&mut context),
        )
    }
    .map_err(|_| BackendError::PlatformUnsupported {
        reason: "D3D11CreateDevice failed",
    })?;
    let device = device.ok_or(BackendError::PlatformUnsupported {
        reason: "D3D11 device null",
    })?;
    let context = context.ok_or(BackendError::PlatformUnsupported {
        reason: "D3D11 context null",
    })?;
    Ok((device, context))
}

#[cfg(target_os = "windows")]
unsafe fn create_keyed_mutex_texture_windows(
    device: &windows::Win32::Graphics::Direct3D11::ID3D11Device,
    width: u32,
    height: u32,
    slot_index: u32,
) -> Result<
    (
        windows::Win32::Graphics::Direct3D11::ID3D11Texture2D,
        windows::Win32::Graphics::Dxgi::IDXGIKeyedMutex,
        u64,
    ),
    BackendError,
> {
    use windows::Win32::Graphics::Direct3D11::{
        D3D11_BIND_RENDER_TARGET, D3D11_BIND_SHADER_RESOURCE,
        D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX, D3D11_RESOURCE_MISC_SHARED_NTHANDLE,
        D3D11_TEXTURE2D_DESC, D3D11_USAGE_DEFAULT, ID3D11Texture2D,
    };
    use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_SAMPLE_DESC};
    use windows::Win32::Graphics::Dxgi::{IDXGIKeyedMutex, IDXGIResource1};
    use windows::core::Interface;
    assert!(width > 0);
    assert!(height > 0);
    assert!(slot_index < 8);
    let desc = D3D11_TEXTURE2D_DESC {
        Width: width,
        Height: height,
        MipLevels: 1,
        ArraySize: 1,
        Format: DXGI_FORMAT_B8G8R8A8_UNORM,
        SampleDesc: DXGI_SAMPLE_DESC {
            Count: 1,
            Quality: 0,
        },
        Usage: D3D11_USAGE_DEFAULT,
        BindFlags: (D3D11_BIND_RENDER_TARGET.0 | D3D11_BIND_SHADER_RESOURCE.0) as u32,
        CPUAccessFlags: 0,
        MiscFlags: (D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX.0 | D3D11_RESOURCE_MISC_SHARED_NTHANDLE.0)
            as u32,
    };
    let mut texture: Option<ID3D11Texture2D> = None;
    unsafe { device.CreateTexture2D(&desc, None, Some(&mut texture)) }.map_err(|_| {
        BackendError::PlatformUnsupported {
            reason: "CreateTexture2D failed",
        }
    })?;
    let texture = texture.ok_or(BackendError::PlatformUnsupported {
        reason: "texture null",
    })?;
    let keyed_mutex: IDXGIKeyedMutex =
        texture
            .cast()
            .map_err(|_| BackendError::PlatformUnsupported {
                reason: "IDXGIKeyedMutex cast failed",
            })?;
    let resource1: IDXGIResource1 =
        texture
            .cast()
            .map_err(|_| BackendError::PlatformUnsupported {
                reason: "IDXGIResource1 cast failed",
            })?;
    let access_rw: u32 = windows::Win32::Graphics::Dxgi::DXGI_SHARED_RESOURCE_READ.0
        | windows::Win32::Graphics::Dxgi::DXGI_SHARED_RESOURCE_WRITE.0;
    let shared =
        unsafe { resource1.CreateSharedHandle(None, access_rw, windows::core::PCWSTR::null()) }
            .map_err(|_| BackendError::PlatformUnsupported {
                reason: "CreateSharedHandle failed",
            })?;
    Ok((texture, keyed_mutex, shared.0 as u64))
}

#[cfg(target_os = "windows")]
const ACQUIRE_SYNC_WAIT_TIMEOUT: i32 = 0x102;
#[cfg(target_os = "windows")]
const ACQUIRE_SYNC_WAIT_ABANDONED: i32 = 0x80;

#[cfg(not(target_os = "windows"))]
pub struct D3D11KeyedMutexBackend;

#[cfg(not(target_os = "windows"))]
impl D3D11KeyedMutexBackend {
    pub fn new() -> Result<Self, BackendError> {
        Err(BackendError::PlatformUnsupported {
            reason: "D3D11 keyed-mutex backend is only available on Windows",
        })
    }
}

impl KeyedMutexBackend for D3D11KeyedMutexBackend {
    type SlotHandle = D3D11SharedHandle;
    const NUM_SLOTS: usize = 8;

    #[cfg(target_os = "windows")]
    fn create_slots(
        &mut self,
        width: u32,
        height: u32,
        format: TextureFormat,
    ) -> Result<Vec<D3D11SharedHandle>, BackendError> {
        assert!(self.slots.is_empty(), "slots created once");
        if width == 0 || height == 0 {
            return Err(BackendError::DimensionsOutOfRange { width, height });
        }
        if !matches!(format, TextureFormat::Nv12) {
            return Err(BackendError::UnsupportedFormat { format });
        }
        let device = self
            .device
            .as_ref()
            .ok_or(BackendError::PlatformUnsupported {
                reason: "device dropped",
            })?;
        let mut out: Vec<D3D11SharedHandle> = Vec::with_capacity(Self::NUM_SLOTS);
        for idx in 0..Self::NUM_SLOTS {
            let idx_u32 = idx as u32;
            let (texture, keyed_mutex, shared_handle) =
                unsafe { create_keyed_mutex_texture_windows(device, width, height, idx_u32)? };
            self.slots.push(D3D11SlotState {
                texture,
                keyed_mutex,
                shared_handle,
                slot_index: idx_u32,
                expected_key: 0,
                acquired: false,
                completed: false,
            });
            out.push(D3D11SharedHandle {
                raw_handle: shared_handle,
                slot_index: idx_u32,
                width,
                height,
            });
        }
        self.width = width;
        self.height = height;
        self.format = format;
        assert_eq!(out.len(), Self::NUM_SLOTS);
        assert_eq!(self.slots.len(), Self::NUM_SLOTS);
        Ok(out)
    }

    #[cfg(not(target_os = "windows"))]
    fn create_slots(
        &mut self,
        _width: u32,
        _height: u32,
        _format: TextureFormat,
    ) -> Result<Vec<D3D11SharedHandle>, BackendError> {
        Err(BackendError::PlatformUnsupported {
            reason: "real D3D11 create_slots requires Windows",
        })
    }

    #[cfg(target_os = "windows")]
    fn acquire_write(&mut self, slot: &D3D11SharedHandle, key: u64) -> Result<(), BackendError> {
        use windows::core::Interface;
        let position = self
            .find_slot(slot)
            .ok_or(BackendError::PlatformUnsupported {
                reason: "slot not found",
            })?;
        let state = &mut self.slots[position];
        if state.expected_key != key {
            return Err(BackendError::KeyMismatch {
                expected: state.expected_key,
                observed: key,
            });
        }
        if state.acquired {
            return Err(BackendError::AcquireWhileWriting {
                slot_index: state.slot_index,
            });
        }
        let hr = unsafe {
            (Interface::vtable(&state.keyed_mutex).AcquireSync)(
                Interface::as_raw(&state.keyed_mutex),
                key,
                0,
            )
        };
        if hr.0 == ACQUIRE_SYNC_WAIT_TIMEOUT || hr.0 == ACQUIRE_SYNC_WAIT_ABANDONED {
            return Err(BackendError::WouldBlock {
                slot_index: state.slot_index,
            });
        }
        if hr.is_err() {
            return Err(BackendError::KeyMismatch {
                expected: key,
                observed: u64::MAX,
            });
        }
        state.acquired = true;
        state.completed = false;
        assert!(state.acquired);
        Ok(())
    }

    #[cfg(not(target_os = "windows"))]
    fn acquire_write(&mut self, _slot: &D3D11SharedHandle, _key: u64) -> Result<(), BackendError> {
        Err(BackendError::PlatformUnsupported {
            reason: "real D3D11 acquire_write requires Windows",
        })
    }

    #[cfg(target_os = "windows")]
    fn release_write(
        &mut self,
        slot: &D3D11SharedHandle,
        next_key: u64,
    ) -> Result<(), BackendError> {
        let position = self
            .find_slot(slot)
            .ok_or(BackendError::PlatformUnsupported {
                reason: "slot not found",
            })?;
        let state = &mut self.slots[position];
        if !state.acquired {
            return Err(BackendError::ReleaseWithoutAcquire {
                slot_index: state.slot_index,
            });
        }
        unsafe { state.keyed_mutex.ReleaseSync(next_key) }.map_err(|_| {
            BackendError::KeyMismatch {
                expected: next_key,
                observed: u64::MAX,
            }
        })?;
        state.acquired = false;
        state.expected_key = next_key;
        state.completed = true;
        assert!(!state.acquired);
        assert!(state.completed);
        Ok(())
    }

    #[cfg(not(target_os = "windows"))]
    fn release_write(
        &mut self,
        _slot: &D3D11SharedHandle,
        _next_key: u64,
    ) -> Result<(), BackendError> {
        Err(BackendError::PlatformUnsupported {
            reason: "real D3D11 release_write requires Windows",
        })
    }

    #[cfg(target_os = "windows")]
    fn poll_complete(&mut self, slot: &D3D11SharedHandle) -> bool {
        match self.find_slot(slot) {
            Some(position) => self.slots[position].completed,
            None => false,
        }
    }

    #[cfg(not(target_os = "windows"))]
    fn poll_complete(&mut self, _slot: &D3D11SharedHandle) -> bool {
        false
    }

    #[cfg(target_os = "windows")]
    fn mark_consumed(&mut self, slot: &D3D11SharedHandle) {
        if let Some(position) = self.find_slot(slot) {
            self.slots[position].completed = false;
        }
    }

    #[cfg(not(target_os = "windows"))]
    fn mark_consumed(&mut self, _slot: &D3D11SharedHandle) {}
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[cfg(not(target_os = "windows"))]
    fn instance_creation_fails_on_non_windows() {
        let err = D3D11KeyedMutexBackend::new().err();
        assert!(matches!(
            err,
            Some(BackendError::PlatformUnsupported { .. })
        ));
    }

    #[test]
    fn handle_clones_preserve_index() {
        let h = D3D11SharedHandle {
            raw_handle: 0xdead,
            slot_index: 3,
            width: 1920,
            height: 1080,
        };
        let h2 = h.clone();
        assert_eq!(h2.slot_index, 3);
        assert_eq!(h2.raw_handle, 0xdead);
    }

    #[test]
    #[cfg(target_os = "windows")]
    fn windows_real_keyed_mutex_eight_slots_round_trip() {
        let backend_result = D3D11KeyedMutexBackend::new();
        let mut backend = match backend_result {
            Ok(b) => b,
            Err(BackendError::PlatformUnsupported { .. }) => return,
            Err(other) => unreachable!("unexpected backend init err: {other:?}"),
        };
        let slots_result = backend.create_slots(64, 64, TextureFormat::Nv12);
        let slots = match slots_result {
            Ok(s) => s,
            Err(BackendError::PlatformUnsupported { .. }) => return,
            Err(other) => unreachable!("unexpected create_slots err: {other:?}"),
        };
        assert_eq!(slots.len(), 8);
        for (idx, slot) in slots.iter().enumerate() {
            assert_eq!(slot.slot_index, idx as u32);
            assert_ne!(slot.raw_handle, 0);
            assert_eq!(slot.width, 64);
        }
        let slot = slots[0].clone();
        backend.acquire_write(&slot, 0).expect("acquire key=0");
        backend.release_write(&slot, 1).expect("release key=1");
        assert!(backend.poll_complete(&slot));
        backend.mark_consumed(&slot);
        assert!(!backend.poll_complete(&slot));
        backend.acquire_write(&slot, 1).expect("acquire key=1");
        backend.release_write(&slot, 2).expect("release key=2");
        assert!(backend.poll_complete(&slot));
    }
}
