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_NULL,
                WS_POPUP, 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;
use pool::Pool;

use super::{
    display::DisplayManager,
    winproc::{window_proc, WINPROC_SENDER},
};

pub struct Internal {
    supervisor:Sender<Query>,
    input:Option<Sender<Query>>,
    video:Option<Sender<Query>>,
    app:Option<Sender<Query>>,

    display:DisplayManager,
    windows:Pool<HWND>,
}
impl Internal {
    pub fn thread(supervisor:Sender<Query>, tx:Sender<Query>, rx:Receiver<Query>)
    {
        let mut sv = Internal {
            supervisor,
            input:None,
            video:None,
            app:None,

            display:DisplayManager::new(),
            windows:Pool::new(),
        };

        let (system_sender, system_receiver) = crossbeam::channel::bounded::<InternalQuery>(16);
        let (thread_sender, thread_receiver) = crossbeam::channel::bounded::<Query>(16);

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

        // Enumerate monitors.
        sv.display.enumerate();

        // Prepare window handler thread.
        std::thread::spawn(move || {

            // 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!("error: 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).as_bool() {
                    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 = WS_POPUP | WS_BORDER | WS_VISIBLE; //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::null(),
                                            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.
                                                if system_sender.send(InternalQuery::Create {
                                                    handle:hwnd.into(),
                                                }).is_ok() {

                                                }
                                            }
                                    
                                            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::Notify => {
                        while let Ok(msg) = system_receiver.try_recv() {
                            match msg {

                                InternalQuery::Create {
                                    handle,
                                } => {
                                    sv.windows.add(handle.handle);

                                    if let Some(sc) = &sv.video {
                                        sc.send(Query::new(Some(tx.clone()), QueryData::Video(VideoQuery::CreateContext {
                                            handle:handle.clone(),
                                        }))).ok();
                                    }
                                }

                                InternalQuery::Close {
                                    handle,
                                } => {
                                    for id in sv.windows.list() {
                                        if let Some(wnd) = sv.windows.get(id).cloned() {
                                            if wnd == handle.handle {
                                                sv.windows.remove(id).ok();
                                                break;
                                            }
                                        }
                                    }
                                }

                                InternalQuery::Input {
                                    handle:_,
                                } => {
                                    // Forward input data to system.
                                }

                                InternalQuery::InputDeviceChange {
                                    state:_,
                                    handle:_,
                                } => {
                                    println!("RECV DEVICECHANGE");

                                    // Get new device information.
                                }

                                InternalQuery::DeviceChange => {
                                    println!("RECV DISPLAYCHANGE");

                                    // Get new display information.
                                    sv.display.enumerate();
                                }

                                _ => { }
                            }
                        }
                    }

                    QueryData::Supervisor(query) => match query {
                        SupervisorQuery::Status {
                            system,
                            channel
                        } => match system {
                            System::Input => {
                                sv.input = channel;
                            }

                            System::Video => {
                                sv.video = channel;

                                // Prepare graphics context for each 
                            }

                            System::App => {
                                sv.app = channel;
                            }

                            _ => { }
                        }

                        SupervisorQuery::Stop {
                            system:_,
                        } => {

                        }

                        _ => { }
                    }

                    QueryData::Window(query) => match query {

                        // Forward thread-required queries.
                        WindowQuery::Create { .. } => {
                            if thread_sender.send(Query::new(None, QueryData::Window(query))).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;
                            }
                        }

                        _ => { }
                    }

                    _ => { }
                }
            }
        }
    }
}
