use windows::core::PCWSTR;
use windows::Win32::{
    Foundation::{
        BOOL, RECT, LPARAM,
        GetLastError,
    },
    Graphics::Gdi::{
        EnumDisplayMonitors,
        GetMonitorInfoW,
        EnumDisplayDevicesW,
        DISPLAY_DEVICEW,
        MONITORINFO, MONITORINFOEXW,
        HMONITOR, HDC,
    },
    UI::WindowsAndMessaging::MONITORINFOF_PRIMARY,
    Devices::DeviceAndDriverInstallation::{
        SetupDiEnumDeviceInfo,
        SetupDiGetClassDevsW,
        SetupDiGetDeviceRegistryPropertyW,
        SetupDiDestroyDeviceInfoList,
        SetupDiOpenDevRegKey,
        SPDRP_HARDWAREID,
        DIGCF_PRESENT,
        SP_DEVINFO_DATA,
        GUID_DEVCLASS_MONITOR,
        DICS_FLAG_GLOBAL,
        DIREG_DEV,
        DIGCF_DEVICEINTERFACE,
        DIGCF_ALLCLASSES,
    },
    Devices::Display::GUID_DEVINTERFACE_MONITOR,
    System::Registry::{
        REG_SZ,
        HKEY_LOCAL_MACHINE,
        KEY_READ,
        RegQueryValueExW,
    },
};

use pool::Pool;

pub struct Display {
    handle:HMONITOR,
    name:String,
    primary:bool,

    x:i32, y:i32,
    width:i32, height:i32,

    wx:i32, wy:i32,
    wwidth:i32, wheight:i32,
}

pub struct DisplayManager {
    displays:Vec<Display>,
}
impl DisplayManager {
    pub fn new() -> Self
    {
        Self {
            displays:Vec::new(),
        }
    }

    pub fn enumerate(&mut self)
    {
        self.displays.clear();
        unsafe {
            EnumDisplayMonitors(None, None, Some(Self::enumerate_callback), LPARAM(&mut self.displays as *mut Vec<Display> as _)).ok().ok();
        }
    }

    unsafe extern "system" fn enumerate_callback(hmonitor:HMONITOR, _hdc:HDC, _rect:*mut RECT, lparam:LPARAM) -> BOOL
    {
        let displays = lparam.0 as *mut Vec<Display>;

        let mut mi = MONITORINFOEXW {
            monitorInfo:MONITORINFO {
                cbSize: std::mem::size_of::<MONITORINFOEXW>() as u32,
                ..Default::default()
            },
            ..Default::default()
        };

        let mut dv = DISPLAY_DEVICEW {
            cb: std::mem::size_of::<DISPLAY_DEVICEW>() as u32,
            ..Default::default()
        };

        let mut name = String::new();

        if GetMonitorInfoW(hmonitor, &mut mi as *mut MONITORINFOEXW as _).as_bool() {
            if EnumDisplayDevicesW(PCWSTR::from_raw(mi.szDevice.as_ptr()), 0, &mut dv as _, 1).as_bool() {
                let raw_device_id = PCWSTR::from_raw(dv.DeviceID.as_ptr()).to_string().unwrap();
                let cleaned_device_id = raw_device_id
                    .trim_start_matches(r"\\?\")
                    .split("#{")
                    .collect::<Vec<_>>()[0]
                    .replace('#', "\\")
                    .to_string();
                
                match SetupDiGetClassDevsW(
                    None,
                    PCWSTR::from_raw(widestring::U16CString::from_str(cleaned_device_id).unwrap().as_ptr()),
                    None,
                    DIGCF_ALLCLASSES | DIGCF_PRESENT | DIGCF_DEVICEINTERFACE,
                ) {
                    Ok(hdevinfo) => {
                        if !hdevinfo.is_invalid() {
                            let mut devinfo_data = SP_DEVINFO_DATA {
                                cbSize:std::mem::size_of::<SP_DEVINFO_DATA>() as u32,
                                ..Default::default()
                            };

                            let mut device_index = 0;
                            while SetupDiEnumDeviceInfo(hdevinfo, device_index, &mut devinfo_data).is_ok() {
                                const BUFFER_SIZE :usize = 256;
                                let mut buffer_size = BUFFER_SIZE as u32;
                                let mut buffer = [0u8; BUFFER_SIZE];
                                let mut property_type = 0u32;

                                match SetupDiGetDeviceRegistryPropertyW(
                                    hdevinfo,
                                    &devinfo_data,
                                    SPDRP_HARDWAREID,
                                    Some(&mut property_type),
                                    Some(&mut buffer),
                                    Some(&mut buffer_size as _)
                                ) {
                                    Ok(_) => {
                                        match SetupDiOpenDevRegKey(
                                            hdevinfo,
                                            &devinfo_data,
                                            DICS_FLAG_GLOBAL.0,
                                            0,
                                            DIREG_DEV,
                                            KEY_READ.0,
                                        ) {
                                            Ok(hkey) => {
                                                const KEY_BUFFER_SIZE :usize = 1024;
                                                let mut key_buffer = [0u8; KEY_BUFFER_SIZE];
                                                let mut key_buffer_size = KEY_BUFFER_SIZE as u32;

                                                let result = RegQueryValueExW(
                                                    hkey,
                                                    PCWSTR::from_raw(widestring::U16CString::from_str("EDID\0").unwrap().as_ptr() as _),
                                                    None,
                                                    None,
                                                    Some(&mut key_buffer as _),
                                                    Some(&mut key_buffer_size),
                                                );

                                                if result.is_ok() {
                                                    const DESCRIPTOR_START :usize = 54;
                                                    for block in key_buffer[DESCRIPTOR_START..].chunks_exact(18) {
                                                        if block[3] == 0xFC {
                                                            name = String::from_utf8(block[5..18].to_vec()).unwrap_or_default().trim_end_matches(['\0', ' ', '\n', '\r']).to_string();
                                                        }
                                                    }
                                                } else {
                                                    println!("RegQueryValueExW: {}", result.0);
                                                }
                                            }
                                            Err(e) => { println!("regkey: {}", e.message()); }
                                        }
                                    }
                                    Err(e) => { println!("SetupDiOpenDevRegKey: {}", e.message()); }
                                }

                                device_index += 1;
                            }
                        }
                        SetupDiDestroyDeviceInfoList(hdevinfo).ok();
                    }
                    Err(e) => { println!("SetupDiGetClassDevsW: {} ; {}", e.message(), GetLastError().0); }
                }
            }

            (*displays).push(Display {
                handle:hmonitor,
                name,
                primary:(mi.monitorInfo.dwFlags & MONITORINFOF_PRIMARY) != 0,
                
                x:mi.monitorInfo.rcMonitor.left,
                y:mi.monitorInfo.rcMonitor.top,
                width:mi.monitorInfo.rcMonitor.right - mi.monitorInfo.rcMonitor.left,
                height:mi.monitorInfo.rcMonitor.bottom - mi.monitorInfo.rcMonitor.top,

                wx:mi.monitorInfo.rcWork.left,
                wy:mi.monitorInfo.rcWork.top,
                wwidth:mi.monitorInfo.rcWork.right - mi.monitorInfo.rcWork.left,
                wheight:mi.monitorInfo.rcWork.bottom - mi.monitorInfo.rcWork.top,
            });
        }
        
        BOOL(1)
    }
}
