use crate::windows_native::error::{check_config, WinError, WinResult};
use crate::windows_native::hid::get_interface_guid;
use crate::windows_native::string::{U16Str, U16StringList};
use crate::windows_native::types::{DeviceProperty, PropertyKey};
use std::ptr::{null, null_mut};
use windows_sys::core::GUID;
use windows_sys::Win32::Devices::DeviceAndDriverInstallation::{
    CM_Get_Device_Interface_ListW, CM_Get_Device_Interface_List_SizeW,
    CM_Get_Device_Interface_PropertyW, CM_GET_DEVICE_INTERFACE_LIST_PRESENT, CR_BUFFER_SMALL,
    CR_SUCCESS,
};

pub struct Interface;

impl Interface {
    fn get_property_size<T: DeviceProperty>(
        interface: &U16Str,
        property_key: impl PropertyKey,
    ) -> WinResult<usize> {
        let mut property_type = 0;
        let mut len = 0;
        let cr = unsafe {
            CM_Get_Device_Interface_PropertyW(
                interface.as_ptr(),
                property_key.as_ptr(),
                &mut property_type,
                null_mut(),
                &mut len,
                0,
            )
        };
        check_config(cr, CR_BUFFER_SMALL)?;
        ensure!(
            property_type == T::TYPE,
            Err(WinError::WrongPropertyDataType)
        );
        Ok(len as usize)
    }

    pub fn get_property<T: DeviceProperty>(
        interface: &U16Str,
        property_key: impl PropertyKey,
    ) -> WinResult<T> {
        let size = Self::get_property_size::<T>(interface, property_key)?;
        let mut property = T::create_sized(size);
        let mut property_type = 0;
        let mut len = size as u32;
        let cr = unsafe {
            CM_Get_Device_Interface_PropertyW(
                interface.as_ptr(),
                property_key.as_ptr(),
                &mut property_type,
                property.as_ptr_mut(),
                &mut len,
                0,
            )
        };
        check_config(cr, CR_SUCCESS)?;
        ensure!(size == len as usize, Err(WinError::UnexpectedReturnSize));
        property.validate();
        Ok(property)
    }

    fn get_interface_list_length(interface: GUID) -> WinResult<usize> {
        let mut len = 0;
        let cr = unsafe {
            CM_Get_Device_Interface_List_SizeW(
                &mut len,
                &interface,
                null(),
                CM_GET_DEVICE_INTERFACE_LIST_PRESENT,
            )
        };
        check_config(cr, CR_SUCCESS)?;
        Ok(len as usize)
    }

    pub fn get_interface_list() -> WinResult<U16StringList> {
        let interface_class_guid = get_interface_guid();

        let mut device_interface_list = Vec::new();
        loop {
            device_interface_list.resize(Self::get_interface_list_length(interface_class_guid)?, 0);
            let cr = unsafe {
                CM_Get_Device_Interface_ListW(
                    &interface_class_guid,
                    null(),
                    device_interface_list.as_mut_ptr(),
                    device_interface_list.len() as u32,
                    CM_GET_DEVICE_INTERFACE_LIST_PRESENT,
                )
            };
            if cr == CR_SUCCESS {
                return Ok(U16StringList(device_interface_list));
            }
            check_config(cr, CR_BUFFER_SMALL)?;
        }
    }
}
