use crate::system::*;

use windows::{
    core::PCWSTR,
    Win32::{
        Foundation::{
            GetLastError,
            HINSTANCE,
            HWND,
            LPARAM,
            LRESULT,
            WPARAM,
        },
        Graphics::Gdi::HBRUSH,
        
        UI::{
            Input::{
                GetRawInputData,
                RegisterRawInputDevices,
                HRAWINPUT,
                RAWINPUT,
                RAWINPUTDEVICE,
                RAWINPUTHEADER,
                RID_INPUT,
                RIDEV_DEVNOTIFY,
                RIM_TYPEHID, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE,
            },
            WindowsAndMessaging::{
                PostMessageW,
                CreateWindowExW,
                DefWindowProcW,
                DispatchMessageW,
                GetMessageW,
                PostThreadMessageW,
                RegisterClassExW,
                SetWindowTextW,
                ShowWindow,
                TranslateMessage,
                SetWindowDisplayAffinity,
                HCURSOR,
                HICON,
                MSG,
                WINDOW_EX_STYLE,
                WNDCLASSEXW,
                WDA_MONITOR,
                CS_HREDRAW, CS_VREDRAW, CW_USEDEFAULT,
                SW_SHOW,
                WM_MOVE, WM_SIZE,
                WM_INPUT, WM_DEVICECHANGE, WM_DISPLAYCHANGE,
                WM_NULL,
                WS_OVERLAPPED, WS_POPUPWINDOW,
                WS_BORDER, WS_SIZEBOX, WS_CAPTION,
                WS_SYSMENU, WS_MINIMIZEBOX, WS_MAXIMIZEBOX,
                WS_VISIBLE,
                WS_EX_APPWINDOW, WS_EX_NOREDIRECTIONBITMAP,
            },
        },
        System::Threading::GetCurrentThreadId,
    }
};
use std::sync::RwLock;

mod display; use display::DisplayManager;

static mut WINPROC_SENDER :RwLock<Option<crossbeam::channel::Sender<Query>>> = RwLock::new(None);

enum InternalQuery {
    None,
    Thread(u32),
}

pub struct Internal {
    supervisor:Sender<Query>,
    display:DisplayManager,
}
impl Internal {
    pub fn thread(supervisor:Sender<Query>, tx:Sender<Query>, rx:Receiver<Query>)
    {
        let _sv = Internal {
            supervisor,
            display:DisplayManager::new(),
        };

        if let Ok(channel) = unsafe {WINPROC_SENDER.get_mut()} {
            *channel = Some(tx.clone());
        } else {
            println!("error: failed to set window system sender.");
        }

        let (system_sender, system_receiver) = crossbeam::channel::bounded::<InternalQuery>(16);
        let (thread_sender, thread_receiver) = crossbeam::channel::bounded::<Query>(16);
        std::thread::spawn(move || {
            let system_sender = system_sender;

            // Initialize window settings.
            let class_name = widestring::U16CString::from_str("donten-window-class").unwrap();
            let class_name = PCWSTR(class_name.as_ptr());

            let wnd_class = WNDCLASSEXW {
                cbSize:std::mem::size_of::<WNDCLASSEXW>() as u32,
                style:CS_HREDRAW | CS_VREDRAW,
                lpfnWndProc:Some(window_proc),
                cbClsExtra:0,
                cbWndExtra:0,
                hInstance:HINSTANCE::default(),
                hIcon:HICON::default(),
                hCursor:HCURSOR::default(),
                hbrBackground:HBRUSH::default(),
                lpszMenuName:PCWSTR::null(),
                lpszClassName:class_name,
                hIconSm:HICON::default(),
            };
            unsafe {RegisterClassExW(&wnd_class)};

            // Register input devices.
            let mut rawinput_devices = Vec::new();
            for page in crate::hid::USAGES.pages {
                for usage in page.usages {
                    use crate::hid::UsageType;
                    match usage.usage_type {
                        UsageType::Application => {
                            rawinput_devices.push(RAWINPUTDEVICE {
                                usUsagePage:page.id,
                                usUsage:usage.id,
                                dwFlags:RIDEV_DEVNOTIFY,
                                hwndTarget:HWND::default(),
                            });
                        }
                        _ => { }
                    }
                }
            }
            if unsafe {RegisterRawInputDevices(&rawinput_devices, std::mem::size_of::<RAWINPUTDEVICE>() as u32)}.is_err() {
                println!("Failed to register input devices: {}", unsafe {GetLastError()}.to_hresult().message());
            }

            // Send thread id to system.
            system_sender.send(InternalQuery::Thread(unsafe {GetCurrentThreadId()})).ok();

            // Process window events.
            let mut msg = MSG::default();
            unsafe {
                while GetMessageW(&mut msg, None, 0, 0).into() {
                    TranslateMessage(&msg).ok().ok();
                    DispatchMessageW(&msg);

                    if msg.message == WM_NULL {
                        if let Ok(msg) = thread_receiver.recv() {
                            match msg.data {
                                QueryData::Supervisor(request) => match request {
                                    SupervisorQuery::Stop {
                                        system: _,
                                    } => {
                
                                        // Perform shutdown operations and terminate thread.
                                        return;
                                    }
                
                                    _ => { }
                                }
                
                                QueryData::Window(request) => match request {
                                    WindowQuery::Create {
                                        config,
                                    } => {

                                        let dwexstyle = WS_EX_APPWINDOW;
                                        let mut dwstyle = if config.caption { WS_OVERLAPPED } else { WS_POPUPWINDOW };

                                        if config.sysmenu {
                                            dwstyle |= WS_SYSMENU | WS_MINIMIZEBOX;
                                        }

                                        if config.resize {
                                            dwstyle |= WS_SIZEBOX | WS_MAXIMIZEBOX;
                                        }

                                        if config.visible {
                                            dwstyle |= WS_VISIBLE;
                                        }

                                        match CreateWindowExW(
                                            dwexstyle,
                                            class_name,
                                            PCWSTR(widestring::U16CString::from_str(config.title).unwrap().as_ptr()),
                                            dwstyle,
                                            config.x,
                                            config.y,
                                            config.width,
                                            config.height,
                                            None,
                                            None,
                                            HINSTANCE::default(),
                                            None,
                                        ) {
                                            Ok(hwnd) => {
                                                if config.private {
                                                    SetWindowDisplayAffinity(hwnd, WDA_MONITOR).ok();
                                                }

                                                // Return window information to manager.

                                            }
                                    
                                            Err(e) => {
                                                println!("error: {}", e.to_string());
                                            }
                                        }
                                    }
                
                                    _ => { }
                                }
                
                                _ => { }
                            }
                        }
                    }
                }
            }
        });

        // Wait for handler thread id to be set.
        let mut thread_id = 0u32;
        if let Ok(resp) = system_receiver.recv() {
            if let InternalQuery::Thread(id) = resp { thread_id = id; }

            // Handle messages.
            while let Ok(msg) = rx.recv() {
                match &msg.data {
                    QueryData::Window(query) => match query {

                        // Forward thread-required queries.
                        WindowQuery::Create { .. } => {
                            if thread_sender.send(msg).is_ok() {
                                // Dispatch window message to allow receiving of query.
                                unsafe {PostThreadMessageW(thread_id, WM_NULL, WPARAM(0), LPARAM(0))}.ok();
                            } else {
                                // System is presumed dead.
                                break;
                            }
                        }

                        _ => { }
                    }

                    _ => { }
                }
            }
        }
    }
}

unsafe extern "system" fn window_proc(
    hwnd:HWND,
    msg:u32,
    wparam:WPARAM,
    lparam:LPARAM,
) -> LRESULT {
    if let Ok(sender) = WINPROC_SENDER.read() {
        if let Some(_sender) = sender.as_ref() {
            match msg {
                WM_MOVE => {
                    
                }

                WM_SIZE => {

                }

                WM_INPUT => {
                    //println!("INPUT");

                    let mut _rawinput_header = RAWINPUTHEADER::default();
                    let header_size = std::mem::size_of::<RAWINPUTHEADER>() as u32;

                    let mut size = 0;
                    let result = unsafe {GetRawInputData(
                        HRAWINPUT(lparam.0 as _),
                        RID_INPUT,
                        None,
                        &mut size,
                        header_size,
                    )};

                    if result == 0 && size > 0 {
                        let mut buffer = vec![0u8; size as usize];

                        if unsafe {GetRawInputData(
                            HRAWINPUT(lparam.0 as _),
                            RID_INPUT,
                            Some(buffer.as_mut_ptr() as *mut _),
                            &mut size,
                            header_size,
                        )} > 0 {
                            let raw_input :&RAWINPUT = &*(buffer.as_ptr() as *const RAWINPUT);

                            const TYPE_MOUSE :u32 = RIM_TYPEMOUSE.0;
                            const TYPE_KEYBOARD :u32 = RIM_TYPEKEYBOARD.0;
                            const TYPE_HID :u32 = RIM_TYPEHID.0;
                            match raw_input.header.dwType {
                                TYPE_MOUSE => {
                                    let _data = unsafe {raw_input.data.mouse};
                                    //sender.send(0).ok();
                                }

                                TYPE_KEYBOARD => {
                                    let _data = unsafe {raw_input.data.keyboard};
                                }

                                TYPE_HID => {
                                    let _data = unsafe {raw_input.data.hid};
                                }

                                _ => { }
                            }

                        } 
                    }

                    return LRESULT(0);
                }

                WM_DEVICECHANGE => {
                    println!("DEVICECHANGE");
                }

                WM_DISPLAYCHANGE => {
                    println!("DISPLAYCHANGE");
                }

                _ => { }
            }
        }
    }
    DefWindowProcW(hwnd, msg, wparam, lparam)
}
