/*
 * Copyright 2024 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "include/core/SkCanvas.h"
#include "include/core/SkRRect.h"
#include "include/core/SkRect.h"
#include "include/core/SkStream.h"
#include "include/core/SkSurface.h"
#include "include/gpu/ganesh/GrDirectContext.h"
#include "include/gpu/ganesh/SkSurfaceGanesh.h"
#include "include/gpu/ganesh/gl/GrGLDirectContext.h"
#include "include/gpu/ganesh/gl/GrGLInterface.h"
#include "include/encode/SkWebpEncoder.h"

#if defined(__linux__)
#include "include/gpu/ganesh/gl/glx/GrGLMakeGLXInterface.h"

#include <X11/Xlib.h>
#include <GL/glx.h>
#include <GL/gl.h>
#endif

#if defined(__APPLE__) && TARGET_OS_MAC == 1
#include "include/gpu/ganesh/gl/mac/GrGLMakeMacInterface.h"

#include "gl_context_helper.h"
#endif

#if defined(_MSC_VER)
#include <windows.h>
#include "include/gpu/ganesh/gl/win/GrGLMakeWinInterface.h"
#endif

#include <cstdio>

#if defined(__linux__)

// Set up an X Display that can be rendered to GL. This will not be visible while
// the program runs. It is cribbed from how Skia's tooling sets itself up (e.g. viewer).
bool initialize_gl_linux() {
    Display* display = XOpenDisplay(nullptr);
    if (!display) {
        printf("Could not open an X display\n");
        return false;
    }
    static int constexpr kChooseFBConfigAtt[] = {
        GLX_RENDER_TYPE, GLX_RGBA_BIT,
        GLX_DOUBLEBUFFER, True,
        GLX_STENCIL_SIZE, 8,
        None
    };
    int n;
    GLXFBConfig* fbConfig = glXChooseFBConfig(display, DefaultScreen(display), kChooseFBConfigAtt, &n);
    XVisualInfo* visualInfo;
    if (n > 0) {
        visualInfo = glXGetVisualFromFBConfig(display, *fbConfig);
    } else {
        // For some reason glXChooseVisual takes a non-const pointer to the attributes.
        int chooseVisualAtt[] = {
            GLX_RGBA,
            GLX_DOUBLEBUFFER,
            GLX_STENCIL_SIZE, 8,
            None
        };
        visualInfo = glXChooseVisual(display, DefaultScreen(display), chooseVisualAtt);
    }
    if (!visualInfo) {
        printf("Could not get X visualInfo\n");
        return false;
    }
    GLXContext glContext = glXCreateContext(display, visualInfo, nullptr, GL_TRUE);
    if (!glContext) {
        printf("Could not make GL X context\n");
        return false;
    }
    Colormap colorMap = XCreateColormap(display,
                                        RootWindow(display, visualInfo->screen),
                                        visualInfo->visual,
                                        AllocNone);
    XSetWindowAttributes swa;
    swa.colormap = colorMap;
    swa.event_mask = 0;
    Window window = XCreateWindow(display,
                            RootWindow(display, visualInfo->screen),
                            0, 0, // x, y
                            1280, 960, // width, height
                            0, // border width
                            visualInfo->depth,
                            InputOutput,
                            visualInfo->visual,
                            CWEventMask | CWColormap,
                            &swa);

    if (!glXMakeCurrent(display, window, glContext)) {
        printf("Could not set GL X context to be the created one\n");
        return false;
    }
    return true;
}
#endif  // defined(__linux__)

#if defined(_MSC_VER)

// This was mostly taken from
// https://skia.googlesource.com/skia/+/f725a5ba8a29ec7c271805624d54755ce34968a1/tools/gpu/gl/win/CreatePlatformGLTestContext_win.cpp#57
bool initialize_gl_win() {
    HINSTANCE hInstance = (HINSTANCE)GetModuleHandle(nullptr);
    WNDCLASS wc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hbrBackground = nullptr;
    wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wc.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
    wc.hInstance = hInstance;
    wc.lpfnWndProc = (WNDPROC) DefWindowProc;
    wc.lpszClassName = TEXT("external_client");
    wc.lpszMenuName = nullptr;
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;

    ATOM cls = RegisterClass(&wc);
    if (!cls) {
        printf("Could not register window class.\n");
        return false;
    }

    HWND window;
    if (!(window = CreateWindow(TEXT("external_client"),
                                 TEXT("my-window"),
                                 CS_OWNDC,
                                 0, 0, 1, 1,
                                 nullptr, nullptr,
                                 hInstance, nullptr))) {
        printf("Could not create window.\n");
        return false;
    }

    HDC deviceContext;
    if (!(deviceContext = GetDC(window))) {
        printf("Could not get device context.\n");
        return false;
    }

    // Taken from https://www.khronos.org/opengl/wiki/Creating_an_OpenGL_Context_(WGL)
    PIXELFORMATDESCRIPTOR pfd = {
        sizeof(PIXELFORMATDESCRIPTOR),
        1,
        PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,    // Flags
        PFD_TYPE_RGBA,        // The kind of framebuffer. RGBA or palette.
        32,                   // Colordepth of the framebuffer.
        0, 0, 0, 0, 0, 0,
        0,
        0,
        0,
        0, 0, 0, 0,
        24,                   // Number of bits for the depthbuffer
        8,                    // Number of bits for the stencilbuffer
        0,                    // Number of Aux buffers in the framebuffer.
        PFD_MAIN_PLANE,
        0,
        0, 0, 0
    };
    int pixelFormat = ChoosePixelFormat(deviceContext, &pfd);
    SetPixelFormat(deviceContext, pixelFormat, &pfd);

    HGLRC glrc;
    if (!(glrc = wglCreateContext(deviceContext))) {
        printf("Could not create rendering context.\n");
        return false;
    }

    if (!(wglMakeCurrent(deviceContext, glrc))) {
        printf("Could not set the context.\n");
        return false;
    }
    return true;
}
#endif  // defined(_MSC_VER)

int main(int argc, char** argv) {
    if (argc != 2) {
        printf("Usage: %s <name.webp>\n", argv[0]);
        return 1;
    }

    SkFILEWStream output(argv[1]);
    if (!output.isValid()) {
        printf("Cannot open output file %s\n", argv[1]);
        return 1;
    }

    GrContextOptions opts;
    opts.fSuppressPrints = true;
#if defined(__linux__)
    if (!initialize_gl_linux()) {
        return 1;
    }
    sk_sp<const GrGLInterface> iface = GrGLInterfaces::MakeGLX();
#elif defined(__APPLE__) && TARGET_OS_MAC == 1
    if (!initialize_gl_mac()) {
        return 1;
    }
    sk_sp<const GrGLInterface> iface = GrGLInterfaces::MakeMac();
#elif defined(_MSC_VER)
    if (!initialize_gl_win()) {
        return 1;
    }
    sk_sp<const GrGLInterface> iface = GrGLInterfaces::MakeWin();
#endif
    if (!iface) {
        printf("Could not make GL interface\n");
        return 1;
    }

    sk_sp<GrDirectContext> ctx = GrDirectContexts::MakeGL(iface, opts);
    if (!ctx) {
        printf("Could not make GrDirectContext\n");
        return 1;
    }
    printf("Context made, now to make the surface\n");

    SkImageInfo imageInfo =
            SkImageInfo::Make(200, 400, kRGBA_8888_SkColorType, kPremul_SkAlphaType);

    sk_sp<SkSurface> surface =
            SkSurfaces::RenderTarget(ctx.get(), skgpu::Budgeted::kYes, imageInfo);
    if (!surface) {
        printf("Could not make surface from GL DirectContext\n");
        return 1;
    }

    SkCanvas* canvas = surface->getCanvas();
    canvas->clear(SK_ColorRED);
    SkRRect rrect = SkRRect::MakeRectXY(SkRect::MakeLTRB(10, 20, 50, 70), 10, 10);

    SkPaint paint;
    paint.setColor(SK_ColorBLUE);
    paint.setAntiAlias(true);

    canvas->drawRRect(rrect, paint);

    ctx->flush();

    printf("Drew to surface, now doing readback\n");
    sk_sp<SkImage> img = surface->makeImageSnapshot();
    sk_sp<SkData> webp = SkWebpEncoder::Encode(ctx.get(), img.get(), {});
    if (!webp) {
        printf("Readback of pixels (or encoding) failed\n");
        return 1;
    }
    output.write(webp->data(), webp->size());
    output.fsync();

    return 0;
}