#include "../test_harness.h"

#if defined(_WIN32)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <GL/gl.h>
#endif

#include <array>
#include <cstdint>
#include <iostream>

namespace {

#if defined(_WIN32)

class HiddenWglContext {
public:
    HiddenWglContext()
    {
        instance_ = GetModuleHandleW(nullptr);

        WNDCLASSW window_class {};
        window_class.style = CS_OWNDC;
        window_class.lpfnWndProc = DefWindowProcW;
        window_class.hInstance = instance_;
        window_class.lpszClassName = class_name_;

        registered_ = RegisterClassW(&window_class) != 0 || GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
        if (!registered_) {
            skip_reason_ = "RegisterClassW failed";
            return;
        }

        window_ = CreateWindowExW(
            WS_EX_TOOLWINDOW,
            class_name_,
            L"PanoPainter GPU readback test",
            WS_POPUP,
            0,
            0,
            16,
            16,
            nullptr,
            nullptr,
            instance_,
            nullptr);
        if (window_ == nullptr) {
            skip_reason_ = "CreateWindowExW failed";
            return;
        }
        ShowWindow(window_, SW_SHOWNA);
        UpdateWindow(window_);

        device_context_ = GetDC(window_);
        if (device_context_ == nullptr) {
            skip_reason_ = "GetDC failed";
            return;
        }

        PIXELFORMATDESCRIPTOR pixel_format {};
        pixel_format.nSize = sizeof(pixel_format);
        pixel_format.nVersion = 1;
        pixel_format.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
        pixel_format.iPixelType = PFD_TYPE_RGBA;
        pixel_format.cColorBits = 24;
        pixel_format.cAlphaBits = 8;
        pixel_format.cDepthBits = 24;
        pixel_format.iLayerType = PFD_MAIN_PLANE;

        const int format = ChoosePixelFormat(device_context_, &pixel_format);
        if (format == 0 || SetPixelFormat(device_context_, format, &pixel_format) == FALSE) {
            skip_reason_ = "OpenGL pixel format setup failed";
            return;
        }

        render_context_ = wglCreateContext(device_context_);
        if (render_context_ == nullptr) {
            skip_reason_ = "wglCreateContext failed";
            return;
        }

        if (wglMakeCurrent(device_context_, render_context_) == FALSE) {
            skip_reason_ = "wglMakeCurrent failed";
            return;
        }

        ready_ = true;
    }

    HiddenWglContext(const HiddenWglContext&) = delete;
    HiddenWglContext& operator=(const HiddenWglContext&) = delete;

    ~HiddenWglContext()
    {
        if (render_context_ != nullptr) {
            wglMakeCurrent(nullptr, nullptr);
            wglDeleteContext(render_context_);
        }
        if (window_ != nullptr && device_context_ != nullptr) {
            ReleaseDC(window_, device_context_);
        }
        if (window_ != nullptr) {
            DestroyWindow(window_);
        }
        if (registered_) {
            UnregisterClassW(class_name_, instance_);
        }
    }

    [[nodiscard]] bool ready() const noexcept
    {
        return ready_;
    }

    [[nodiscard]] const char* skip_reason() const noexcept
    {
        return skip_reason_;
    }

private:
    static constexpr const wchar_t* class_name_ = L"PanoPainterGpuReadbackTestWindow";

    HINSTANCE instance_ = nullptr;
    HWND window_ = nullptr;
    HDC device_context_ = nullptr;
    HGLRC render_context_ = nullptr;
    const char* skip_reason_ = "OpenGL context unavailable";
    bool registered_ = false;
    bool ready_ = false;
};

#endif

void opengl_clear_readback_matches_fixture(pp::tests::Harness& h)
{
#if defined(_WIN32)
    HiddenWglContext context;
    if (!context.ready()) {
        std::cout << "[skip] desktop GPU OpenGL readback unavailable: " << context.skip_reason() << "\n";
        return;
    }

    glViewport(0, 0, 1, 1);
    glDrawBuffer(GL_BACK);
    glReadBuffer(GL_BACK);
    glClearColor(1.0F, 0.0F, 0.0F, 1.0F);
    glClear(GL_COLOR_BUFFER_BIT);
    glFinish();

    std::array<std::uint8_t, 4> pixel {};
    glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data());

    constexpr std::array<std::uint8_t, 4> expected {
        255,
        0,
        0,
        255,
    };
    if (pixel != expected) {
        std::cout << "readback rgba: "
                  << static_cast<int>(pixel[0]) << ", "
                  << static_cast<int>(pixel[1]) << ", "
                  << static_cast<int>(pixel[2]) << ", "
                  << static_cast<int>(pixel[3]) << "\n";
    }
    PP_EXPECT(h, pixel == expected);
#else
    std::cout << "[skip] desktop GPU OpenGL readback unavailable: no platform context helper\n";
#endif
}

void opengl_preview_readback_matches_fixture(pp::tests::Harness& h)
{
#if defined(_WIN32)
    HiddenWglContext context;
    if (!context.ready()) {
        std::cout << "[skip] desktop GPU preview OpenGL readback unavailable: " << context.skip_reason() << "\n";
        return;
    }

    glViewport(0, 0, 1, 1);
    glDrawBuffer(GL_BACK);
    glReadBuffer(GL_BACK);
    glClearColor(0.0F, 1.0F, 0.0F, 1.0F);
    glClear(GL_COLOR_BUFFER_BIT);
    glFinish();

    std::array<std::uint8_t, 4> pixel {};
    glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data());

    constexpr std::array<std::uint8_t, 4> expected {
        0,
        255,
        0,
        255,
    };
    if (pixel != expected) {
        std::cout << "preview readback rgba: "
                  << static_cast<int>(pixel[0]) << ", "
                  << static_cast<int>(pixel[1]) << ", "
                  << static_cast<int>(pixel[2]) << ", "
                  << static_cast<int>(pixel[3]) << "\n";
    }
    PP_EXPECT(h, pixel == expected);
#else
    std::cout << "[skip] desktop GPU preview OpenGL readback unavailable: no platform context helper\n";
#endif
}

void opengl_compositor_readback_matches_fixture(pp::tests::Harness& h)
{
#if defined(_WIN32)
    HiddenWglContext context;
    if (!context.ready()) {
        std::cout << "[skip] desktop GPU compositor OpenGL readback unavailable: " << context.skip_reason() << "\n";
        return;
    }

    glViewport(0, 0, 1, 1);
    glDrawBuffer(GL_BACK);
    glReadBuffer(GL_BACK);
    glClearColor(0.0F, 0.0F, 1.0F, 1.0F);
    glClear(GL_COLOR_BUFFER_BIT);
    glFinish();

    std::array<std::uint8_t, 4> pixel {};
    glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data());

    constexpr std::array<std::uint8_t, 4> expected {
        0,
        0,
        255,
        255,
    };
    if (pixel != expected) {
        std::cout << "compositor readback rgba: "
                  << static_cast<int>(pixel[0]) << ", "
                  << static_cast<int>(pixel[1]) << ", "
                  << static_cast<int>(pixel[2]) << ", "
                  << static_cast<int>(pixel[3]) << "\n";
    }
    PP_EXPECT(h, pixel == expected);
#else
    std::cout << "[skip] desktop GPU compositor OpenGL readback unavailable: no platform context helper\n";
#endif
}

}

int main()
{
    pp::tests::Harness harness;
    harness.run("opengl_clear_readback_matches_fixture", opengl_clear_readback_matches_fixture);
    harness.run("opengl_preview_readback_matches_fixture", opengl_preview_readback_matches_fixture);
    harness.run("opengl_compositor_readback_matches_fixture", opengl_compositor_readback_matches_fixture);
    return harness.finish();
}
