//
// Copyright 2019 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// entry_points_wgl.cpp: Implements the exported WGL functions.

#include "entry_points_wgl.h"

#include "common/debug.h"
#include "common/event_tracer.h"
#include "common/utilities.h"
#include "libANGLE/Context.h"
#include "libANGLE/Display.h"
#include "libANGLE/EGLSync.h"
#include "libANGLE/Surface.h"
#include "libANGLE/Texture.h"
#include "libANGLE/Thread.h"
#include "libANGLE/entry_points_utils.h"
#include "libANGLE/queryutils.h"
#include "libANGLE/validationEGL.h"
#include "libGLESv2/global_state.h"
#include "libGLESv2/proc_table_wgl.h"

using namespace wgl;
using namespace egl;

namespace
{

bool CompareProc(const ProcEntry &a, const char *b)
{
    return strcmp(a.first, b) < 0;
}

void ClipConfigs(const std::vector<const Config *> &filteredConfigs,
                 EGLConfig *output_configs,
                 EGLint config_size,
                 EGLint *num_config)
{
    EGLint result_size = static_cast<EGLint>(filteredConfigs.size());
    if (output_configs)
    {
        result_size = std::max(std::min(result_size, config_size), 0);
        for (EGLint i = 0; i < result_size; i++)
        {
            output_configs[i] = const_cast<Config *>(filteredConfigs[i]);
        }
    }
    *num_config = result_size;
}
}  // anonymous namespace

#define WGL_EVENT(EP, FMT, ...) EVENT(nullptr, WGL##EP, FMT, __VA_ARGS__)

extern "C" {

// WGL 1.0
int GL_APIENTRY wglChoosePixelFormat(HDC hDc, const PIXELFORMATDESCRIPTOR *pPfd)
{
    UNIMPLEMENTED();
    return 1;
}

int GL_APIENTRY wglDescribePixelFormat(HDC hdc, int ipfd, UINT cjpfd, PIXELFORMATDESCRIPTOR *ppfd)
{
    UNIMPLEMENTED();
    if (ppfd)
    {
        ppfd->dwFlags      = ppfd->dwFlags | PFD_DRAW_TO_WINDOW;
        ppfd->dwFlags      = ppfd->dwFlags | PFD_SUPPORT_OPENGL;
        ppfd->dwFlags      = ppfd->dwFlags | PFD_GENERIC_ACCELERATED;
        ppfd->dwFlags      = ppfd->dwFlags | PFD_DOUBLEBUFFER;
        ppfd->iPixelType   = PFD_TYPE_RGBA;
        ppfd->cColorBits   = 24;
        ppfd->cRedBits     = 8;
        ppfd->cGreenBits   = 8;
        ppfd->cBlueBits    = 8;
        ppfd->cAlphaBits   = 8;
        ppfd->cDepthBits   = 24;
        ppfd->cStencilBits = 8;
        ppfd->nVersion     = 1;
    }
    return 1;
}

UINT GL_APIENTRY wglGetEnhMetaFilePixelFormat(HENHMETAFILE hemf,
                                              UINT cbBuffer,
                                              PIXELFORMATDESCRIPTOR *ppfd)
{
    UNIMPLEMENTED();
    return 1u;
}

int GL_APIENTRY wglGetPixelFormat(HDC hdc)
{
    UNIMPLEMENTED();
    return 1;
}

BOOL GL_APIENTRY wglSetPixelFormat(HDC hdc, int ipfd, const PIXELFORMATDESCRIPTOR *ppfd)
{
    UNIMPLEMENTED();
    return TRUE;
}

BOOL GL_APIENTRY wglSwapBuffers(HDC hdc)
{
    Thread *thread        = egl::GetCurrentThread();
    egl::Display *display = egl::Display::GetExistingDisplayFromNativeDisplay(hdc);

    ANGLE_EGL_TRY_RETURN(thread, display->getWGLSurface()->swap(thread->getContext()),
                         "wglSwapBuffers", display->getWGLSurface(), FALSE);
    return TRUE;
}

BOOL GL_APIENTRY wglCopyContext(HGLRC hglrcSrc, HGLRC hglrcDst, UINT mask)
{
    UNIMPLEMENTED();
    return TRUE;
}

HGLRC GL_APIENTRY wglCreateContext(HDC hDc)
{
    Thread *thread = egl::GetCurrentThread();

    std::vector<EGLAttrib> displayAttributes;
    displayAttributes.push_back(EGL_PLATFORM_ANGLE_TYPE_ANGLE);
    GLenum platformType = EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE;
    displayAttributes.push_back(platformType);
    displayAttributes.push_back(EGL_NONE);

    const auto &attribMapDisplay = AttributeMap::CreateFromAttribArray(displayAttributes.data());
    attribMapDisplay.initializeWithoutValidation();

    egl::Display *display =
        egl::Display::GetDisplayFromNativeDisplay(EGL_PLATFORM_ANGLE_ANGLE, hDc, attribMapDisplay);

    ANGLE_EGL_TRY_RETURN(thread, display->initialize(), "wglCreateContext", display, nullptr);

    thread->setAPI(EGL_OPENGL_API);

    // Default config
    const EGLint configAttributes[] = {EGL_NONE};

    // Choose config
    EGLint configCount;
    EGLConfig config;
    AttributeMap attribMapConfig = AttributeMap::CreateFromIntArray(configAttributes);
    attribMapConfig.initializeWithoutValidation();
    ClipConfigs(display->chooseConfig(attribMapConfig), &config, 1, &configCount);

    Config *configuration = static_cast<Config *>(config);

    // Initialize surface
    std::vector<EGLint> surfaceAttributes;
    surfaceAttributes.push_back(EGL_NONE);
    surfaceAttributes.push_back(EGL_NONE);
    AttributeMap surfAttributes = AttributeMap::CreateFromIntArray(&surfaceAttributes[0]);
    surfAttributes.initializeWithoutValidation();

    // Create first window surface
    egl::Surface *surface = nullptr;
    ANGLE_EGL_TRY_RETURN(
        thread,
        display->createWindowSurface(configuration, WindowFromDC(hDc), surfAttributes, &surface),
        "wglCreateContext", display, nullptr);

    // Initialize context
    EGLint contextAttibutes[] = {EGL_CONTEXT_CLIENT_VERSION, 4, EGL_CONTEXT_MINOR_VERSION, 6,
                                 EGL_NONE};

    gl::Context *sharedGLContext = static_cast<gl::Context *>(nullptr);
    AttributeMap ctxAttributes   = AttributeMap::CreateFromIntArray(contextAttibutes);
    ctxAttributes.initializeWithoutValidation();

    gl::Context *context = nullptr;

    ANGLE_EGL_TRY_RETURN(thread,
                         display->createContext(configuration, sharedGLContext, EGL_OPENGL_API,
                                                ctxAttributes, &context),
                         "wglCreateContext", display, nullptr);

    return reinterpret_cast<HGLRC>(context);
}

HGLRC GL_APIENTRY wglCreateLayerContext(HDC hDc, int level)
{
    UNIMPLEMENTED();
    return nullptr;
}

BOOL GL_APIENTRY wglDeleteContext(HGLRC oldContext)
{
    UNIMPLEMENTED();
    return FALSE;
}

BOOL GL_APIENTRY wglDescribeLayerPlane(HDC hDc,
                                       int pixelFormat,
                                       int layerPlane,
                                       UINT nBytes,
                                       LAYERPLANEDESCRIPTOR *plpd)
{
    UNIMPLEMENTED();
    return FALSE;
}

HGLRC GL_APIENTRY wglGetCurrentContext()
{
    UNIMPLEMENTED();
    return nullptr;
}

HDC GL_APIENTRY wglGetCurrentDC()
{
    UNIMPLEMENTED();
    return nullptr;
}

int GL_APIENTRY
wglGetLayerPaletteEntries(HDC hdc, int iLayerPlane, int iStart, int cEntries, COLORREF *pcr)
{
    UNIMPLEMENTED();
    return 0;
}

PROC GL_APIENTRY wglGetProcAddress(LPCSTR lpszProc)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    WGL_EVENT(GetProcAddress, "const char *procname = \"%s\"", lpszProc);
    egl::Thread *thread = egl::GetCurrentThread();

    const ProcEntry *entry =
        std::lower_bound(&g_procTable[0], &g_procTable[g_numProcs], lpszProc, CompareProc);

    thread->setSuccess();

    if (entry == &g_procTable[g_numProcs] || strcmp(entry->first, lpszProc) != 0)
    {
        return nullptr;
    }

    return entry->second;
}

BOOL GL_APIENTRY wglMakeCurrent(HDC hDc, HGLRC newContext)
{
    Thread *thread        = egl::GetCurrentThread();
    egl::Display *display = egl::Display::GetExistingDisplayFromNativeDisplay(hDc);
    const gl::Context *context =
        GetContextIfValid(display, reinterpret_cast<gl::Context *>(newContext));

    ScopedSyncCurrentContextFromThread scopedSyncCurrent(thread);

    // If display or context are invalid, make thread's current rendering context not current
    if (!context)
    {
        gl::Context *oldContext = thread->getContext();
        if (oldContext)
        {
            ANGLE_EGL_TRY_RETURN(thread, oldContext->unMakeCurrent(display), "wglMakeCurrent",
                                 GetContextIfValid(display, oldContext), EGL_FALSE);
            thread->setCurrent(nullptr);
        }
        return TRUE;
    }

    egl::Surface *surface        = display->getWGLSurface();
    Surface *previousDraw        = thread->getCurrentDrawSurface();
    Surface *previousRead        = thread->getCurrentReadSurface();
    gl::Context *previousContext = thread->getContext();

    if (previousDraw != surface || previousRead != surface || previousContext != context)
    {
        ANGLE_EGL_TRY_RETURN(thread,
                             display->makeCurrent(thread, previousContext, surface, surface,
                                                  const_cast<gl::Context *>(context)),
                             "wglMakeCurrent", GetContextIfValid(display, context), EGL_FALSE);
    }

    return TRUE;
}

BOOL GL_APIENTRY wglRealizeLayerPalette(HDC hdc, int iLayerPlane, BOOL bRealize)
{
    UNIMPLEMENTED();
    return FALSE;
}

int GL_APIENTRY
wglSetLayerPaletteEntries(HDC hdc, int iLayerPlane, int iStart, int cEntries, const COLORREF *pcr)
{
    UNIMPLEMENTED();
    return 0;
}

BOOL GL_APIENTRY wglShareLists(HGLRC hrcSrvShare, HGLRC hrcSrvSource)
{
    UNIMPLEMENTED();
    return FALSE;
}

BOOL GL_APIENTRY wglSwapLayerBuffers(HDC hdc, UINT fuFlags)
{
    UNIMPLEMENTED();
    return FALSE;
}

BOOL GL_APIENTRY wglUseFontBitmapsA(HDC hDC, DWORD first, DWORD count, DWORD listBase)
{
    UNIMPLEMENTED();
    return FALSE;
}

BOOL GL_APIENTRY wglUseFontBitmapsW(HDC hDC, DWORD first, DWORD count, DWORD listBase)
{
    UNIMPLEMENTED();
    return FALSE;
}

BOOL GL_APIENTRY wglUseFontOutlinesA(HDC hDC,
                                     DWORD first,
                                     DWORD count,
                                     DWORD listBase,
                                     FLOAT deviation,
                                     FLOAT extrusion,
                                     int format,
                                     LPGLYPHMETRICSFLOAT lpgmf)
{
    UNIMPLEMENTED();
    return FALSE;
}

BOOL GL_APIENTRY wglUseFontOutlinesW(HDC hDC,
                                     DWORD first,
                                     DWORD count,
                                     DWORD listBase,
                                     FLOAT deviation,
                                     FLOAT extrusion,
                                     int format,
                                     LPGLYPHMETRICSFLOAT lpgmf)
{
    UNIMPLEMENTED();
    return FALSE;
}

}  // extern "C"
