#ifndef BOO_WIN32COMMON_HPP
#define BOO_WIN32COMMON_HPP

#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS 1 /* STFU MSVC */
#endif
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN 1
#endif
#include <windows.h>
#include <unordered_map>

extern DWORD g_mainThreadId;

#include "boo/IWindow.hpp"

namespace boo {class IWindow;}

#if _WIN32_WINNT_WIN10
#include <dxgi1_4.h>
#include <d3d12.h>
#include <d3d11_1.h>
#include <d3dcompiler.h>
#include <wingdi.h>

struct D3D12Context
{
    ComPtr<IDXGIFactory2> m_dxFactory;
    ComPtr<ID3D12Device> m_dev;
    ComPtr<ID3D12CommandAllocator> m_qalloc[2];
    ComPtr<ID3D12CommandQueue> m_q;
    ComPtr<ID3D12CommandAllocator> m_loadqalloc;
    ComPtr<ID3D12CommandQueue> m_loadq;
    ComPtr<ID3D12Fence> m_loadfence;
    UINT64 m_loadfenceval = 0;
    HANDLE m_loadfencehandle;
    ComPtr<ID3D12GraphicsCommandList> m_loadlist;
    ComPtr<ID3D12RootSignature> m_rs;
    struct Window
    {
        ComPtr<IDXGISwapChain3> m_swapChain;
        UINT m_backBuf = 0;
        bool m_needsResize = false;
        size_t width, height;
    };
    std::unordered_map<const boo::IWindow*, Window> m_windows;
};

#elif _WIN32_WINNT_WIN7
#include <dxgi1_2.h>
#include <d3d11_1.h>
#include <d3dcompiler.h>
#include <wingdi.h>
#else
#error Unsupported Windows target
#endif

struct D3D11Context
{
    ComPtr<IDXGIFactory2> m_dxFactory;
    ComPtr<ID3D11Device1> m_dev;
    ComPtr<ID3D11DeviceContext1> m_devCtx;
    ComPtr<ID3D11SamplerState> m_ss;
    struct Window
    {
        ComPtr<IDXGISwapChain1> m_swapChain;
        bool m_needsResize = false;
        size_t width, height;

        bool m_needsFSTransition = false;
        bool m_fs = false;
        DXGI_MODE_DESC m_fsdesc = {};
    };
    std::unordered_map<const boo::IWindow*, Window> m_windows;
};

struct OGLContext
{
    ComPtr<IDXGIFactory1> m_dxFactory;
    HGLRC m_lastContext = 0;
    struct Window
    {
        HWND m_hwnd;
        HDC m_deviceContext;
        HGLRC m_mainContext;
        HGLRC m_renderContext;
        bool m_needsResize = false;
        size_t width, height;

        bool m_fs = false;
        LONG m_fsStyle;
        LONG m_fsExStyle;
        RECT m_fsRect;
        int m_fsCountDown = 0;
    };
    std::unordered_map<const boo::IWindow*, Window> m_windows;
};

struct Boo3DAppContext
{
    D3D11Context m_ctx11;
#if _WIN32_WINNT_WIN10
    D3D12Context m_ctx12;
#endif
    OGLContext m_ctxOgl;

    void resize(boo::IWindow* window, size_t width, size_t height)
    {
#if _WIN32_WINNT_WIN10
        if (m_ctx12.m_dev)
        {
            D3D12Context::Window& win = m_ctx12.m_windows[window];
            win.width = width;
            win.height = height;
            win.m_needsResize = true;
        }
        else
#endif
        {
            D3D11Context::Window& win = m_ctx11.m_windows[window];
            win.width = width;
            win.height = height;
            win.m_needsResize = true;
        }
    }

    bool isFullscreen(const boo::IWindow* window)
    {
#if _WIN32_WINNT_WIN10
        if (m_ctx12.m_dev)
        {
            D3D12Context::Window& win = m_ctx12.m_windows[window];
            BOOL isFScr;
            win.m_swapChain->GetFullscreenState(&isFScr, nullptr);
            return isFScr;
        }
#endif
        if (m_ctx11.m_dev)
        {
            D3D11Context::Window& win = m_ctx11.m_windows[window];
            BOOL isFScr;
            win.m_swapChain->GetFullscreenState(&isFScr, nullptr);
            return isFScr != 0;
        }
        OGLContext::Window& win = m_ctxOgl.m_windows[window];
        return win.m_fs;
    }

    bool setFullscreen(boo::IWindow* window, bool fs)
    {
#if _WIN32_WINNT_WIN10
        if (m_ctx12.m_dev)
        {
            D3D12Context::Window& win = m_ctx12.m_windows[window];
            BOOL isFScr;
            win.m_swapChain->GetFullscreenState(&isFScr, nullptr);
            if (fs && isFScr)
                return false;
            else if (!fs && !isFScr)
                return false;

            if (fs)
            {
                ComPtr<IDXGIOutput> out;
                win.m_swapChain->GetContainingOutput(&out);
                DXGI_OUTPUT_DESC outDesc;
                out->GetDesc(&outDesc);

                win.m_swapChain->SetFullscreenState(true, nullptr);
                DXGI_MODE_DESC mdesc = {outDesc.DesktopCoordinates.right, outDesc.DesktopCoordinates.bottom};
                win.m_swapChain->ResizeTarget(&mdesc);
            }
            else
                win.m_swapChain->SetFullscreenState(false, nullptr);
            return true;
        }
#endif
        if (m_ctx11.m_dev)
        {
            D3D11Context::Window& win = m_ctx11.m_windows[window];
            BOOL isFScr;
            win.m_swapChain->GetFullscreenState(&isFScr, nullptr);
            if (fs && isFScr)
                return false;
            else if (!fs && !isFScr)
                return false;

            if (fs)
            {
                ComPtr<IDXGIOutput> out;
                win.m_swapChain->GetContainingOutput(&out);
                DXGI_OUTPUT_DESC outDesc;
                out->GetDesc(&outDesc);
                
                win.m_fsdesc.Width = outDesc.DesktopCoordinates.right;
                win.m_fsdesc.Height = outDesc.DesktopCoordinates.bottom;
            }
            win.m_fs = fs;
            win.m_needsFSTransition = true;
            return true;
        }

        OGLContext::Window& win = m_ctxOgl.m_windows[window];
        if (fs && win.m_fs)
            return false;
        else if (!fs && !win.m_fs)
            return false;

        if (fs)
        {
            win.m_fsStyle = GetWindowLong(win.m_hwnd, GWL_STYLE);
            win.m_fsExStyle = GetWindowLong(win.m_hwnd, GWL_EXSTYLE);
            GetWindowRect(win.m_hwnd, &win.m_fsRect);

            SetWindowLong(win.m_hwnd, GWL_STYLE,
                win.m_fsStyle & ~(WS_CAPTION | WS_THICKFRAME));
            SetWindowLong(win.m_hwnd, GWL_EXSTYLE,
                win.m_fsExStyle & ~(WS_EX_DLGMODALFRAME |
                    WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));

            MONITORINFO monitor_info;
            monitor_info.cbSize = sizeof(monitor_info);
            GetMonitorInfo(MonitorFromWindow(win.m_hwnd, MONITOR_DEFAULTTONEAREST),
                &monitor_info);
            SetWindowPos(win.m_hwnd, NULL, monitor_info.rcMonitor.left, monitor_info.rcMonitor.top,
                monitor_info.rcMonitor.right - monitor_info.rcMonitor.left, 
                monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top,
                SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_ASYNCWINDOWPOS);

            win.m_fs = true;
        }
        else
        {
            SetWindowLong(win.m_hwnd, GWL_STYLE, win.m_fsStyle);
            SetWindowLong(win.m_hwnd, GWL_EXSTYLE, win.m_fsExStyle);

            SetWindowPos(win.m_hwnd, NULL, win.m_fsRect.left, win.m_fsRect.top,
                win.m_fsRect.right - win.m_fsRect.left, win.m_fsRect.bottom - win.m_fsRect.top,
                SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_ASYNCWINDOWPOS);

            win.m_fs = false;
        }

        return true;
    }
};

struct HWNDEvent
{
    UINT uMsg;
    WPARAM wParam;
    LPARAM lParam;
    HWNDEvent(UINT m, WPARAM w, LPARAM l)
    : uMsg(m), wParam(w), lParam(l) {}
};

#endif // BOO_WIN32COMMON_HPP