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

#include "include/core/SkTypes.h"
#if defined(SK_BUILD_FOR_WIN) && !defined(_M_ARM64) && !defined(WINUWP)

#include "src/utils/win/SkWGL.h"

#include "include/private/base/SkOnce.h"
#include "include/private/base/SkTDArray.h"
#include "src/base/SkTSearch.h"
#include "src/base/SkTSort.h"

bool SkWGLExtensions::hasExtension(HDC dc, const char* ext) const {
    if (nullptr == this->fGetExtensionsString) {
        return false;
    }
    if (!strcmp("WGL_ARB_extensions_string", ext)) {
        return true;
    }
    const char* extensionString = this->getExtensionsString(dc);
    size_t extLength = strlen(ext);

    while (true) {
        size_t n = strcspn(extensionString, " ");
        if (n == extLength && 0 == strncmp(ext, extensionString, n)) {
            return true;
        }
        if (0 == extensionString[n]) {
            return false;
        }
        extensionString += n+1;
    }
}

const char* SkWGLExtensions::getExtensionsString(HDC hdc) const {
    return fGetExtensionsString(hdc);
}

BOOL SkWGLExtensions::choosePixelFormat(HDC hdc,
                                        const int* piAttribIList,
                                        const FLOAT* pfAttribFList,
                                        UINT nMaxFormats,
                                        int* piFormats,
                                        UINT* nNumFormats) const {
    return fChoosePixelFormat(hdc, piAttribIList, pfAttribFList,
                              nMaxFormats, piFormats, nNumFormats);
}

BOOL SkWGLExtensions::getPixelFormatAttribiv(HDC hdc,
                                             int iPixelFormat,
                                             int iLayerPlane,
                                             UINT nAttributes,
                                             const int *piAttributes,
                                             int *piValues) const {
    return fGetPixelFormatAttribiv(hdc, iPixelFormat, iLayerPlane,
                                   nAttributes, piAttributes, piValues);
}

BOOL SkWGLExtensions::getPixelFormatAttribfv(HDC hdc,
                                             int iPixelFormat,
                                             int iLayerPlane,
                                             UINT nAttributes,
                                             const int *piAttributes,
                                             float *pfValues) const {
    return fGetPixelFormatAttribfv(hdc, iPixelFormat, iLayerPlane,
                                   nAttributes, piAttributes, pfValues);
}
HGLRC SkWGLExtensions::createContextAttribs(HDC hDC,
                                            HGLRC hShareContext,
                                            const int *attribList) const {
    return fCreateContextAttribs(hDC, hShareContext, attribList);
}

BOOL SkWGLExtensions::swapInterval(int interval) const {
    return fSwapInterval(interval);
}

HPBUFFER SkWGLExtensions::createPbuffer(HDC hDC,
                                        int iPixelFormat,
                                        int iWidth,
                                        int iHeight,
                                        const int *piAttribList) const {
    return fCreatePbuffer(hDC, iPixelFormat, iWidth, iHeight, piAttribList);
}

HDC SkWGLExtensions::getPbufferDC(HPBUFFER hPbuffer) const {
    return fGetPbufferDC(hPbuffer);
}

int SkWGLExtensions::releasePbufferDC(HPBUFFER hPbuffer, HDC hDC) const {
    return fReleasePbufferDC(hPbuffer, hDC);
}

BOOL SkWGLExtensions::destroyPbuffer(HPBUFFER hPbuffer) const {
    return fDestroyPbuffer(hPbuffer);
}

namespace {

struct PixelFormat {
    int fFormat;
    int fSampleCnt;
    int fChoosePixelFormatRank;
};

bool pf_less(const PixelFormat& a, const PixelFormat& b) {
    if (a.fSampleCnt < b.fSampleCnt) {
        return true;
    } else if (b.fSampleCnt < a.fSampleCnt) {
        return false;
    } else if (a.fChoosePixelFormatRank < b.fChoosePixelFormatRank) {
        return true;
    }
    return false;
}
}

int SkWGLExtensions::selectFormat(const int formats[],
                                  int formatCount,
                                  HDC dc,
                                  int desiredSampleCount) const {
    SkASSERT(desiredSampleCount >= 1);
    if (formatCount <= 0) {
        return -1;
    }
    PixelFormat desiredFormat = {
        0,
        desiredSampleCount,
        0,
    };
    SkTDArray<PixelFormat> rankedFormats;
    rankedFormats.resize(formatCount);
    for (int i = 0; i < formatCount; ++i) {
        static const int kQueryAttr = SK_WGL_SAMPLES;
        int numSamples;
        this->getPixelFormatAttribiv(dc,
                                     formats[i],
                                     0,
                                     1,
                                     &kQueryAttr,
                                     &numSamples);
        rankedFormats[i].fFormat =  formats[i];
        rankedFormats[i].fSampleCnt = std::max(1, numSamples);
        rankedFormats[i].fChoosePixelFormatRank = i;
    }
    SkTQSort(rankedFormats.begin(), rankedFormats.end(), pf_less);
    int idx = SkTSearch<PixelFormat, pf_less>(rankedFormats.begin(),
                                              rankedFormats.size(),
                                              desiredFormat,
                                              sizeof(PixelFormat));
    if (idx < 0) {
        idx = ~idx;
    }
    // If the caller asked for non-MSAA fail if the closest format has MSAA.
    if (desiredSampleCount == 1 && rankedFormats[idx].fSampleCnt != 1) {
        return -1;
    }
    return rankedFormats[idx].fFormat;
}


namespace {

#if defined(UNICODE)
    #define STR_LIT(X) L## #X
#else
    #define STR_LIT(X) #X
#endif

#define TEMP_CLASS STR_LIT("TempClass")

HWND create_temp_window() {
    HMODULE module = GetModuleHandle(nullptr);
    HWND wnd;
    RECT windowRect;
    windowRect.left = 0;
    windowRect.right = 8;
    windowRect.top = 0;
    windowRect.bottom = 8;

    WNDCLASS wc;

    wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wc.lpfnWndProc = (WNDPROC) DefWindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = module;
    wc.hIcon = LoadIcon(nullptr, IDI_WINLOGO);
    wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wc.hbrBackground = nullptr;
    wc.lpszMenuName = nullptr;
    wc.lpszClassName = TEMP_CLASS;

    if(!RegisterClass(&wc)) {
        return 0;
    }

    DWORD style, exStyle;
    exStyle = WS_EX_CLIENTEDGE;
    style = WS_SYSMENU;

    AdjustWindowRectEx(&windowRect, style, false, exStyle);
    if(!(wnd = CreateWindowEx(exStyle,
                              TEMP_CLASS,
                              STR_LIT("PlaceholderWindow"),
                              WS_CLIPSIBLINGS | WS_CLIPCHILDREN | style,
                              0, 0,
                              windowRect.right-windowRect.left,
                              windowRect.bottom-windowRect.top,
                              nullptr, nullptr,
                              module,
                              nullptr))) {
        UnregisterClass(TEMP_CLASS, module);
        return nullptr;
    }
    ShowWindow(wnd, SW_HIDE);

    return wnd;
}

void destroy_temp_window(HWND wnd) {
    DestroyWindow(wnd);
    HMODULE module = GetModuleHandle(nullptr);
    UnregisterClass(TEMP_CLASS, module);
}
}

#define GET_PROC(NAME, SUFFIX) f##NAME = \
                     (NAME##Proc) wglGetProcAddress("wgl" #NAME #SUFFIX)


SkWGLExtensions::GetExtensionsStringProc SkWGLExtensions::fGetExtensionsString = nullptr;
SkWGLExtensions::ChoosePixelFormatProc SkWGLExtensions::fChoosePixelFormat = nullptr;
SkWGLExtensions::GetPixelFormatAttribfvProc SkWGLExtensions::fGetPixelFormatAttribfv = nullptr;
SkWGLExtensions::GetPixelFormatAttribivProc SkWGLExtensions::fGetPixelFormatAttribiv = nullptr;
SkWGLExtensions::CreateContextAttribsProc SkWGLExtensions::fCreateContextAttribs = nullptr;
SkWGLExtensions::SwapIntervalProc SkWGLExtensions::fSwapInterval = nullptr;
SkWGLExtensions::CreatePbufferProc SkWGLExtensions::fCreatePbuffer = nullptr;
SkWGLExtensions::GetPbufferDCProc SkWGLExtensions::fGetPbufferDC = nullptr;
SkWGLExtensions::ReleasePbufferDCProc SkWGLExtensions::fReleasePbufferDC = nullptr;
SkWGLExtensions::DestroyPbufferProc SkWGLExtensions::fDestroyPbuffer = nullptr;

SkWGLExtensions::SkWGLExtensions() {
    // We cache these function pointers once, and then reuse them. That's possibly incorrect if
    // there are multiple GPUs, or if we intend to use these for rendering contexts of different
    // pixel formats (where wglGetProcAddress is not guaranteed to return the same pointer).
    static SkOnce once;
    once([] {
        HDC prevDC = wglGetCurrentDC();
        HGLRC prevGLRC = wglGetCurrentContext();

        PIXELFORMATDESCRIPTOR tempPFD;

        ZeroMemory(&tempPFD, sizeof(tempPFD));
        tempPFD.nSize = sizeof(tempPFD);
        tempPFD.nVersion = 1;
        tempPFD.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
        tempPFD.iPixelType = PFD_TYPE_RGBA;
        tempPFD.cColorBits  = 32;
        tempPFD.cDepthBits  = 0;
        tempPFD.cStencilBits = 8;
        tempPFD.iLayerType = PFD_MAIN_PLANE;
        HWND tempWND = create_temp_window();
        if (tempWND) {
            HDC tempDC = GetDC(tempWND);
            int tempFormat = ChoosePixelFormat(tempDC, &tempPFD);
            SetPixelFormat(tempDC, tempFormat, &tempPFD);
            HGLRC tempGLRC = wglCreateContext(tempDC);
            SkASSERT(tempGLRC);
            wglMakeCurrent(tempDC, tempGLRC);

            #if defined(__clang__)
                #pragma clang diagnostic push
                #pragma clang diagnostic ignored "-Wcast-function-type"
            #endif

            GET_PROC(GetExtensionsString, ARB);
            GET_PROC(ChoosePixelFormat, ARB);
            GET_PROC(GetPixelFormatAttribiv, ARB);
            GET_PROC(GetPixelFormatAttribfv, ARB);
            GET_PROC(CreateContextAttribs, ARB);
            GET_PROC(SwapInterval, EXT);
            GET_PROC(CreatePbuffer, ARB);
            GET_PROC(GetPbufferDC, ARB);
            GET_PROC(ReleasePbufferDC, ARB);
            GET_PROC(DestroyPbuffer, ARB);

            #if defined(__clang__)
                #pragma clang diagnostic pop
            #endif

            wglMakeCurrent(tempDC, nullptr);
            wglDeleteContext(tempGLRC);
            destroy_temp_window(tempWND);
        }

        wglMakeCurrent(prevDC, prevGLRC);
    });
}

///////////////////////////////////////////////////////////////////////////////

static void get_pixel_formats_to_try(HDC dc, const SkWGLExtensions& extensions,
                                     bool doubleBuffered, int msaaSampleCount, bool deepColor,
                                     int formatsToTry[2]) {
    auto appendAttr = [](SkTDArray<int>& attrs, int attr, int value) {
        attrs.push_back(attr);
        attrs.push_back(value);
    };

    SkTDArray<int> iAttrs;
    appendAttr(iAttrs, SK_WGL_DRAW_TO_WINDOW, TRUE);
    appendAttr(iAttrs, SK_WGL_DOUBLE_BUFFER, (doubleBuffered ? TRUE : FALSE));
    appendAttr(iAttrs, SK_WGL_ACCELERATION, SK_WGL_FULL_ACCELERATION);
    appendAttr(iAttrs, SK_WGL_SUPPORT_OPENGL, TRUE);
    if (deepColor) {
        appendAttr(iAttrs, SK_WGL_RED_BITS, 10);
        appendAttr(iAttrs, SK_WGL_GREEN_BITS, 10);
        appendAttr(iAttrs, SK_WGL_BLUE_BITS, 10);
        appendAttr(iAttrs, SK_WGL_ALPHA_BITS, 2);
    } else {
        appendAttr(iAttrs, SK_WGL_COLOR_BITS, 24);
        appendAttr(iAttrs, SK_WGL_ALPHA_BITS, 8);
    }
    appendAttr(iAttrs, SK_WGL_STENCIL_BITS, 8);

    float fAttrs[] = {0, 0};

    // Get a MSAA format if requested and possible.
    if (msaaSampleCount > 0 &&
        extensions.hasExtension(dc, "WGL_ARB_multisample")) {
        SkTDArray<int> msaaIAttrs = iAttrs;
        appendAttr(msaaIAttrs, SK_WGL_SAMPLE_BUFFERS, TRUE);
        appendAttr(msaaIAttrs, SK_WGL_SAMPLES, msaaSampleCount);
        appendAttr(msaaIAttrs, 0, 0);
        unsigned int num;
        int formats[64];
        extensions.choosePixelFormat(dc, msaaIAttrs.begin(), fAttrs, 64, formats, &num);
        num = std::min(num, 64U);
        formatsToTry[0] = extensions.selectFormat(formats, num, dc, msaaSampleCount);
    }

    // Get a non-MSAA format
    int* format = -1 == formatsToTry[0] ? &formatsToTry[0] : &formatsToTry[1];
    unsigned int num;
    appendAttr(iAttrs, 0, 0);
    extensions.choosePixelFormat(dc, iAttrs.begin(), fAttrs, 1, format, &num);
}

static HGLRC create_gl_context(HDC dc, const SkWGLExtensions& extensions,
                               SkWGLContextRequest contextType, HGLRC shareContext) {
    HDC prevDC = wglGetCurrentDC();
    HGLRC prevGLRC = wglGetCurrentContext();

    HGLRC glrc = nullptr;
    if (kGLES_SkWGLContextRequest == contextType) {
        if (!extensions.hasExtension(dc, "WGL_EXT_create_context_es2_profile")) {
            wglMakeCurrent(prevDC, prevGLRC);
            return nullptr;
        }
        static const int glesAttribs[] = {
            SK_WGL_CONTEXT_MAJOR_VERSION, 3,
            SK_WGL_CONTEXT_MINOR_VERSION, 0,
            SK_WGL_CONTEXT_PROFILE_MASK,  SK_WGL_CONTEXT_ES2_PROFILE_BIT,
            0,
        };
        glrc = extensions.createContextAttribs(dc, shareContext, glesAttribs);
        if (nullptr == glrc) {
            wglMakeCurrent(prevDC, prevGLRC);
            return nullptr;
        }
    } else {
        if (kGLPreferCoreProfile_SkWGLContextRequest == contextType &&
            extensions.hasExtension(dc, "WGL_ARB_create_context")) {
            static const int kCoreGLVersions[] = {
                4, 3,
                4, 2,
                4, 1,
                4, 0,
                3, 3,
                3, 2,
            };
            int coreProfileAttribs[] = {
                SK_WGL_CONTEXT_MAJOR_VERSION, -1,
                SK_WGL_CONTEXT_MINOR_VERSION, -1,
                SK_WGL_CONTEXT_PROFILE_MASK,  SK_WGL_CONTEXT_CORE_PROFILE_BIT,
                0,
            };
            for (size_t v = 0; v < std::size(kCoreGLVersions) / 2; ++v) {
                coreProfileAttribs[1] = kCoreGLVersions[2 * v];
                coreProfileAttribs[3] = kCoreGLVersions[2 * v + 1];
                glrc = extensions.createContextAttribs(dc, shareContext, coreProfileAttribs);
                if (glrc) {
                    break;
                }
            }
        }
    }

    if (nullptr == glrc) {
        glrc = wglCreateContext(dc);
        if (shareContext) {
            if (!wglShareLists(shareContext, glrc)) {
                wglDeleteContext(glrc);
                return nullptr;
            }
        }
    }
    SkASSERT(glrc);

    wglMakeCurrent(prevDC, prevGLRC);

    return glrc;
}

HGLRC SkCreateWGLContext(HDC dc, int msaaSampleCount, bool deepColor,
                         SkWGLContextRequest contextType, HGLRC shareContext) {
    SkWGLExtensions extensions;
    if (!extensions.hasExtension(dc, "WGL_ARB_pixel_format")) {
        return nullptr;
    }

    BOOL set = FALSE;

    int pixelFormatsToTry[] = { -1, -1 };
    get_pixel_formats_to_try(dc, extensions, true, msaaSampleCount, deepColor, pixelFormatsToTry);
    for (size_t f = 0;
         !set && -1 != pixelFormatsToTry[f] && f < std::size(pixelFormatsToTry);
         ++f) {
        PIXELFORMATDESCRIPTOR pfd;
        DescribePixelFormat(dc, pixelFormatsToTry[f], sizeof(pfd), &pfd);
        set = SetPixelFormat(dc, pixelFormatsToTry[f], &pfd);
    }

    if (!set) {
        return nullptr;
    }

    return create_gl_context(dc, extensions, contextType, shareContext);
}

sk_sp<SkWGLPbufferContext> SkWGLPbufferContext::Create(HDC parentDC,
                                                       SkWGLContextRequest contextType,
                                                       HGLRC shareContext) {
    SkWGLExtensions extensions;
    if (!extensions.hasExtension(parentDC, "WGL_ARB_pixel_format") ||
        !extensions.hasExtension(parentDC, "WGL_ARB_pbuffer")) {
        return nullptr;
    }

    // We cache the pixel formats once, and then reuse them. That's possibly incorrect if
    // there are multiple GPUs, but this function is always called with a freshly made,
    // identically constructed HDC (see WinGLTestContext).
    //
    // We only store two potential pixel formats, one for single buffer, one for double buffer.
    // We never ask for MSAA, so we don't need the second pixel format for each buffering state.
    static int gPixelFormats[2] = { -1, -1 };
    static SkOnce once;
    once([=] {
        {
            // Single buffer
            int pixelFormatsToTry[2] = { -1, -1 };
            get_pixel_formats_to_try(parentDC, extensions, false, 0, false, pixelFormatsToTry);
            gPixelFormats[0] = pixelFormatsToTry[0];
        }
        {
            // Double buffer
            int pixelFormatsToTry[2] = { -1, -1 };
            get_pixel_formats_to_try(parentDC, extensions, true, 0, false, pixelFormatsToTry);
            gPixelFormats[1] = pixelFormatsToTry[0];
        }
    });

    // try for single buffer first
    for (int pixelFormat : gPixelFormats) {
        if (-1 == pixelFormat) {
            continue;
        }
        HPBUFFER pbuf = extensions.createPbuffer(parentDC, pixelFormat, 1, 1, nullptr);
        if (0 != pbuf) {
            HDC dc = extensions.getPbufferDC(pbuf);
            if (dc) {
                HGLRC glrc = create_gl_context(dc, extensions, contextType, shareContext);
                if (glrc) {
                    return sk_sp<SkWGLPbufferContext>(new SkWGLPbufferContext(pbuf, dc, glrc));
                }
                extensions.releasePbufferDC(pbuf, dc);
            }
            extensions.destroyPbuffer(pbuf);
        }
    }
    return nullptr;
}

SkWGLPbufferContext::~SkWGLPbufferContext() {
    SkASSERT(fExtensions.hasExtension(fDC, "WGL_ARB_pbuffer"));
    wglDeleteContext(fGLRC);
    fExtensions.releasePbufferDC(fPbuffer, fDC);
    fExtensions.destroyPbuffer(fPbuffer);
}

SkWGLPbufferContext::SkWGLPbufferContext(HPBUFFER pbuffer, HDC dc, HGLRC glrc)
    : fPbuffer(pbuffer)
    , fDC(dc)
    , fGLRC(glrc) {
}

#endif//defined(SK_BUILD_FOR_WIN)
