#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>
#include <strsafe.h>
#include "hwscapture.h"

#ifndef DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((HANDLE)-4)
#endif

static HWS_PORT g_videoInput = nullptr;
static HWS_PORT g_videoRender = nullptr;
static HWND g_mainWnd = nullptr;
static HWND g_renderWnd = nullptr;

static const wchar_t* kMainWndClass   = L"HwsCaptureExamplePreviewWnd";
static const wchar_t* kRenderWndClass = L"HwsCaptureExampleRenderHostWnd";

static void EnableBestProcessDpiAwareness()
{
    // Important for D3D child-window rendering.
    // If the process is DPI-unaware, Windows may bitmap-scale the whole window
    // after resize/maximize, which makes the preview look soft even though the
    // SDK renderer itself is correct.
    HMODULE user32 = GetModuleHandleW(L"user32.dll");
    if (!user32) return;

    typedef BOOL(WINAPI* SetProcessDpiAwarenessContextFn)(HANDLE);
    SetProcessDpiAwarenessContextFn setCtx =
        reinterpret_cast<SetProcessDpiAwarenessContextFn>(
            GetProcAddress(user32, "SetProcessDpiAwarenessContext"));
    if (setCtx && setCtx(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
        return;
    }

    typedef BOOL(WINAPI* SetProcessDPIAwareFn)();
    SetProcessDPIAwareFn setAware =
        reinterpret_cast<SetProcessDPIAwareFn>(GetProcAddress(user32, "SetProcessDPIAware"));
    if (setAware) {
        setAware();
    }
}

static void ShowError(HWND hwnd, const wchar_t* text)
{
    MessageBoxW(hwnd, text, L"hwscapture example", MB_ICONERROR | MB_OK);
}

static void StopPreview()
{
    if (g_videoInput) {
        HWS_Stop(g_videoInput);
    }
    if (g_videoInput && g_videoRender) {
        HWS_Disconnect(g_videoInput, g_videoRender);
    }
    if (g_videoRender) {
        HWS_DestroyPort(g_videoRender);
        g_videoRender = nullptr;
    }
    if (g_videoInput) {
        HWS_DestroyPort(g_videoInput);
        g_videoInput = nullptr;
    }
}

static void Layout(HWND hwnd)
{
    RECT rc = {};
    GetClientRect(hwnd, &rc);
    int w = rc.right - rc.left;
    int h = rc.bottom - rc.top;
    if (w < 1) w = 1;
    if (h < 1) h = 1;

    if (g_renderWnd) {
        // Keep the native render HWND exactly equal to the main client area.
        // Do not draw through STATIC/GDI scaling. The SDK owns all D3D rendering.
        SetWindowPos(g_renderWnd,
                     nullptr,
                     0,
                     0,
                     w,
                     h,
                     SWP_NOZORDER | SWP_NOACTIVATE);
    }
}

static bool StartDefaultPreview(HWND hwnd)
{
    if (HWS_Init() != HWS_OK) {
        ShowError(hwnd, L"HWS_Init failed.");
        return false;
    }

    const int deviceCount = HWS_GetVideoDeviceCount();
    if (deviceCount <= 0) {
        ShowError(hwnd, L"No video capture device found.");
        return false;
    }

    Layout(hwnd);

    g_videoInput = HWS_CreateVideoInput();
    g_videoRender = HWS_CreateVideoRender(g_renderWnd);
    if (!g_videoInput || !g_videoRender) {
        ShowError(hwnd, L"Create video input/render port failed.");
        return false;
    }

    HWS_VIDEO_INPUT_CONFIG cfg = {};
    cfg.deviceIndex = 0;
    cfg.width = 1280;
    cfg.height = 720;
    cfg.fps = 30;
    cfg.pixelFormat = HWS_PIXFMT_YUY2;
    cfg.colorSpace = HWS_COLOR_SPACE_AUTO;
    cfg.colorRange = HWS_COLOR_RANGE_AUTO;
    cfg.bufferCount = 4;

    HWS_VIDEO_RENDER_CONFIG rcfg = {};
    rcfg.width = cfg.width;
    rcfg.height = cfg.height;
    rcfg.pixelFormat = cfg.pixelFormat;

    if (HWS_SetVideoInputConfig(g_videoInput, &cfg) != HWS_OK) {
        ShowError(hwnd, L"HWS_SetVideoInputConfig failed.");
        return false;
    }

    if (HWS_SetVideoRenderConfig(g_videoRender, &rcfg) != HWS_OK) {
        ShowError(hwnd, L"HWS_SetVideoRenderConfig failed.");
        return false;
    }

#if defined(HWS_SDK_API_VERSION) && (HWS_SDK_API_VERSION >= 50203)
    // Match the verified native hwscapture_test behavior:
    // the SDK automatically uses the sharp path near 1:1 and a stable path when
    // downscaling. This avoids the maximize-softness problem.
    HWS_SetVideoRenderInterpolationMode(g_videoRender, HWS_RENDER_INTERPOLATION_AUTO);
#endif

    if (HWS_Connect(g_videoInput, g_videoRender) != HWS_OK) {
        ShowError(hwnd, L"HWS_Connect failed.");
        return false;
    }

    if (HWS_Start(g_videoInput) != HWS_OK) {
        ShowError(hwnd, L"HWS_Start failed.");
        return false;
    }

    return true;
}

static LRESULT CALLBACK RenderHostProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
    switch (msg) {
    case WM_ERASEBKGND:
        return 1;
    case WM_PAINT:
    {
        PAINTSTRUCT ps = {};
        BeginPaint(hwnd, &ps);
        EndPaint(hwnd, &ps);
        return 0;
    }
    }
    return DefWindowProcW(hwnd, msg, wp, lp);
}

static void RegisterRenderHostClass(HINSTANCE hInst)
{
    WNDCLASSEXW wc = {};
    wc.cbSize = sizeof(wc);
    wc.lpfnWndProc = RenderHostProc;
    wc.hInstance = hInst;
    wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wc.hbrBackground = nullptr;
    wc.lpszClassName = kRenderWndClass;
    RegisterClassExW(&wc);
}

static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
    switch (msg) {
    case WM_CREATE:
    {
        g_mainWnd = hwnd;
        HINSTANCE hInst = reinterpret_cast<HINSTANCE>(GetModuleHandleW(nullptr));
        RegisterRenderHostClass(hInst);

        // Do not use a STATIC control for the D3D render target. A plain child
        // window with CLIP styles matches the verified native test demo and
        // avoids GDI/static-control scaling side effects.
        g_renderWnd = CreateWindowExW(
            0,
            kRenderWndClass,
            nullptr,
            WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
            0,
            0,
            640,
            360,
            hwnd,
            nullptr,
            hInst,
            nullptr);
        if (!g_renderWnd) {
            ShowError(hwnd, L"Create render child window failed.");
            return -1;
        }

        Layout(hwnd);
        StartDefaultPreview(hwnd);
        return 0;
    }

    case WM_SIZE:
        Layout(hwnd);
        return 0;

    case WM_DPICHANGED:
    {
        // For Per-Monitor V2, Windows provides a suggested window rectangle in
        // physical pixels. Applying it prevents a DPI-transition bitmap scale.
        RECT* suggested = reinterpret_cast<RECT*>(lp);
        if (suggested) {
            SetWindowPos(hwnd,
                         nullptr,
                         suggested->left,
                         suggested->top,
                         suggested->right - suggested->left,
                         suggested->bottom - suggested->top,
                         SWP_NOZORDER | SWP_NOACTIVATE);
        }
        Layout(hwnd);
        return 0;
    }

    case WM_ERASEBKGND:
        return 1;

    case WM_CLOSE:
        StopPreview();
        HWS_Shutdown();
        DestroyWindow(hwnd);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProcW(hwnd, msg, wp, lp);
}

int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, PWSTR, int nCmdShow)
{
    EnableBestProcessDpiAwareness();

    WNDCLASSEXW wc = {};
    wc.cbSize = sizeof(wc);
    wc.lpfnWndProc = WndProc;
    wc.style = CS_DBLCLKS;
    wc.hInstance = hInst;
    wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wc.hbrBackground = nullptr;
    wc.lpszClassName = kMainWndClass;
    RegisterClassExW(&wc);

    HWND hwnd = CreateWindowExW(
        0,
        kMainWndClass,
        L"hwscapture example - default preview",
        WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        960,
        540,
        nullptr,
        nullptr,
        hInst,
        nullptr);

    if (!hwnd) {
        return 1;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    MSG msg = {};
    while (GetMessageW(&msg, nullptr, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }

    return static_cast<int>(msg.wParam);
}
