//
// Copyright 2018 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.
//
// WGLWindow:
//   Implements initializing a WGL rendering context.
//

#include "util/windows/WGLWindow.h"

#include "common/string_utils.h"
#include "common/system_utils.h"
#include "util/OSWindow.h"

#include <iostream>

namespace
{
constexpr int kColorBits   = 24;
constexpr int kAlphaBits   = 8;
constexpr int kDepthBits   = 24;
constexpr int kStencilBits = 8;

PIXELFORMATDESCRIPTOR GetDefaultPixelFormatDescriptor()
{
    PIXELFORMATDESCRIPTOR pixelFormatDescriptor = {};
    pixelFormatDescriptor.nSize                 = sizeof(pixelFormatDescriptor);
    pixelFormatDescriptor.nVersion              = 1;
    pixelFormatDescriptor.dwFlags =
        PFD_DRAW_TO_WINDOW | PFD_GENERIC_ACCELERATED | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
    pixelFormatDescriptor.iPixelType   = PFD_TYPE_RGBA;
    pixelFormatDescriptor.cColorBits   = kColorBits;
    pixelFormatDescriptor.cAlphaBits   = kAlphaBits;
    pixelFormatDescriptor.cDepthBits   = kDepthBits;
    pixelFormatDescriptor.cStencilBits = kStencilBits;
    pixelFormatDescriptor.iLayerType   = PFD_MAIN_PLANE;

    return pixelFormatDescriptor;
}

PFNWGLGETPROCADDRESSPROC gCurrentWGLGetProcAddress = nullptr;
HMODULE gCurrentModule                             = nullptr;

angle::GenericProc WINAPI GetProcAddressWithFallback(const char *name)
{
    angle::GenericProc proc = reinterpret_cast<angle::GenericProc>(gCurrentWGLGetProcAddress(name));
    if (proc)
    {
        return proc;
    }

    return reinterpret_cast<angle::GenericProc>(GetProcAddress(gCurrentModule, name));
}

bool HasExtension(const std::vector<std::string> &extensions, const char *ext)
{
    return std::find(extensions.begin(), extensions.end(), ext) != extensions.end();
}

void DumpLastWindowsError()
{
    std::cerr << "Last Windows error code: 0x" << std::hex << GetLastError() << std::endl;
}

// Based on GetDefaultPixelFormatAttributes from wgl_utils.cpp
std::vector<int> GetPixelFormatAttributes(const ConfigParameters &configParams)
{
    std::vector<int> attribs;
    attribs.push_back(WGL_DRAW_TO_WINDOW_ARB);
    attribs.push_back(TRUE);

    attribs.push_back(WGL_ACCELERATION_ARB);
    attribs.push_back(WGL_FULL_ACCELERATION_ARB);

    attribs.push_back(WGL_SUPPORT_OPENGL_ARB);
    attribs.push_back(TRUE);

    attribs.push_back(WGL_DOUBLE_BUFFER_ARB);
    attribs.push_back(TRUE);

    attribs.push_back(WGL_PIXEL_TYPE_ARB);
    attribs.push_back(WGL_TYPE_RGBA_ARB);

    attribs.push_back(WGL_COLOR_BITS_ARB);
    attribs.push_back(kColorBits);

    attribs.push_back(WGL_ALPHA_BITS_ARB);
    attribs.push_back(kAlphaBits);

    attribs.push_back(WGL_DEPTH_BITS_ARB);
    attribs.push_back(kDepthBits);

    attribs.push_back(WGL_STENCIL_BITS_ARB);
    attribs.push_back(kStencilBits);

    attribs.push_back(WGL_SWAP_METHOD_ARB);
    attribs.push_back(WGL_SWAP_UNDEFINED_ARB);

    attribs.push_back(WGL_COLORSPACE_EXT);
    if (configParams.colorSpace == EGL_COLORSPACE_sRGB)
    {
        attribs.push_back(WGL_COLORSPACE_SRGB_EXT);
    }
    else
    {
        attribs.push_back(WGL_COLORSPACE_LINEAR_EXT);
    }

    attribs.push_back(0);

    return attribs;
}

}  // namespace

WGLWindow::WGLWindow(int glesMajorVersion, int glesMinorVersion)
    : GLWindowBase(glesMajorVersion, glesMinorVersion),
      mDeviceContext(nullptr),
      mWGLContext(nullptr),
      mWindow(nullptr)
{}

WGLWindow::~WGLWindow() {}

// Internally initializes GL resources.
GLWindowResult WGLWindow::initializeGLWithResult(OSWindow *osWindow,
                                                 angle::Library *glWindowingLibrary,
                                                 angle::GLESDriverType driverType,
                                                 const EGLPlatformParameters &platformParams,
                                                 const ConfigParameters &configParams)
{
    if (driverType != angle::GLESDriverType::SystemWGL)
    {
        std::cerr << "WGLWindow requires angle::GLESDriverType::SystemWGL.\n";
        return GLWindowResult::Error;
    }

    glWindowingLibrary->getAs("wglGetProcAddress", &gCurrentWGLGetProcAddress);

    if (!gCurrentWGLGetProcAddress)
    {
        std::cerr << "Error loading wglGetProcAddress." << std::endl;
        return GLWindowResult::Error;
    }

    gCurrentModule = reinterpret_cast<HMODULE>(glWindowingLibrary->getNative());
    angle::LoadWGL(GetProcAddressWithFallback);

    mWindow                                           = osWindow->getNativeWindow();
    mDeviceContext                                    = GetDC(mWindow);
    const PIXELFORMATDESCRIPTOR pixelFormatDescriptor = GetDefaultPixelFormatDescriptor();

    int pixelFormat = 0;

    if (!_wglChoosePixelFormatARB)
    {
        std::cout << "Driver does not expose wglChoosePixelFormatARB." << std::endl;
    }
    else
    {
        std::vector<int> pixelFormatAttribs = GetPixelFormatAttributes(configParams);

        UINT matchingFormats = 0;
        _wglChoosePixelFormatARB(mDeviceContext, &pixelFormatAttribs[0], nullptr, 1u, &pixelFormat,
                                 &matchingFormats);
    }

    if (pixelFormat == 0 && configParams.colorSpace != EGL_COLORSPACE_LINEAR)
    {
        std::cerr << "Could not find a compatible pixel format for a non-linear color space."
                  << std::endl;
        return GLWindowResult::NoColorspaceSupport;
    }

    if (pixelFormat == 0)
    {
        pixelFormat = ChoosePixelFormat(mDeviceContext, &pixelFormatDescriptor);
    }

    if (pixelFormat == 0)
    {
        std::cerr << "Could not find a compatible pixel format." << std::endl;
        DumpLastWindowsError();
        return GLWindowResult::Error;
    }

    // According to the Windows docs, it is an error to set a pixel format twice.
    int currentPixelFormat = GetPixelFormat(mDeviceContext);
    if (currentPixelFormat != pixelFormat)
    {
        if (SetPixelFormat(mDeviceContext, pixelFormat, &pixelFormatDescriptor) != TRUE)
        {
            std::cerr << "Failed to set the pixel format." << std::endl;
            DumpLastWindowsError();
            return GLWindowResult::Error;
        }
    }

    mWGLContext = createContext(configParams, nullptr);
    if (mWGLContext == nullptr)
    {
        return GLWindowResult::Error;
    }

    if (!makeCurrent())
    {
        return GLWindowResult::Error;
    }

    mPlatform     = platformParams;
    mConfigParams = configParams;

    angle::LoadGLES(GetProcAddressWithFallback);
    return GLWindowResult::NoError;
}

bool WGLWindow::initializeGL(OSWindow *osWindow,
                             angle::Library *glWindowingLibrary,
                             angle::GLESDriverType driverType,
                             const EGLPlatformParameters &platformParams,
                             const ConfigParameters &configParams)
{
    return initializeGLWithResult(osWindow, glWindowingLibrary, driverType, platformParams,
                                  configParams) == GLWindowResult::NoError;
}

HGLRC WGLWindow::createContext(const ConfigParameters &configParams, HGLRC shareContext)
{
    HGLRC context = _wglCreateContext(mDeviceContext);
    if (!context)
    {
        std::cerr << "Failed to create a WGL context." << std::endl;
        return context;
    }

    if (!makeCurrent(context))
    {
        std::cerr << "Failed to make WGL context current." << std::endl;
        return context;
    }

    // Reload entry points to capture extensions.
    angle::LoadWGL(GetProcAddressWithFallback);

    if (!_wglGetExtensionsStringARB)
    {
        std::cerr << "Driver does not expose wglGetExtensionsStringARB." << std::endl;
        return context;
    }

    const char *extensionsString = _wglGetExtensionsStringARB(mDeviceContext);

    std::vector<std::string> extensions;
    angle::SplitStringAlongWhitespace(extensionsString, &extensions);

    if (!HasExtension(extensions, "WGL_EXT_create_context_es2_profile"))
    {
        std::cerr << "Driver does not expose WGL_EXT_create_context_es2_profile." << std::endl;
        return context;
    }

    if (mConfigParams.webGLCompatibility.valid() || mConfigParams.robustResourceInit.valid())
    {
        std::cerr << "WGLWindow does not support the requested feature set." << std::endl;
        return context;
    }

    // Tear down the context and create another with ES2 compatibility.
    _wglDeleteContext(context);

    // This could be extended to cover ES1 compatiblity.
    int kCreateAttribs[] = {WGL_CONTEXT_MAJOR_VERSION_ARB,
                            mClientMajorVersion,
                            WGL_CONTEXT_MINOR_VERSION_ARB,
                            mClientMinorVersion,
                            WGL_CONTEXT_PROFILE_MASK_ARB,
                            WGL_CONTEXT_ES2_PROFILE_BIT_EXT,
                            0,
                            0};

    context = _wglCreateContextAttribsARB(mDeviceContext, shareContext, kCreateAttribs);
    if (!context)
    {
        std::cerr << "Failed to create an ES2 compatible WGL context." << std::endl;
        return context;
    }

    return context;
}

void WGLWindow::destroyGL()
{
    if (mWGLContext)
    {
        _wglDeleteContext(mWGLContext);
        mWGLContext = nullptr;
    }

    if (mDeviceContext)
    {
        ReleaseDC(mWindow, mDeviceContext);
        mDeviceContext = nullptr;
    }
}

bool WGLWindow::isGLInitialized() const
{
    return mWGLContext != nullptr;
}

GLWindowContext WGLWindow::getCurrentContextGeneric()
{
    return reinterpret_cast<GLWindowContext>(mWGLContext);
}

GLWindowContext WGLWindow::createContextGeneric(GLWindowContext share)
{
    HGLRC shareContext = reinterpret_cast<HGLRC>(share);
    HGLRC newContext   = createContext(mConfigParams, shareContext);

    // createContext() calls makeCurrent(newContext), so we need to restore the current context.
    if (!makeCurrent())
    {
        return nullptr;
    }

    return reinterpret_cast<GLWindowContext>(newContext);
}

bool WGLWindow::makeCurrent()
{
    return makeCurrent(mWGLContext);
}

bool WGLWindow::makeCurrentGeneric(GLWindowContext context)
{
    HGLRC wglContext = reinterpret_cast<HGLRC>(context);
    return makeCurrent(wglContext);
}

bool WGLWindow::makeCurrent(HGLRC context)
{
    if (_wglMakeCurrent(mDeviceContext, context) == FALSE)
    {
        std::cerr << "Error during wglMakeCurrent.\n";
        return false;
    }

    return true;
}

WGLWindow::Image WGLWindow::createImage(GLWindowContext context,
                                        Enum target,
                                        ClientBuffer buffer,
                                        const Attrib *attrib_list)
{
    std::cerr << "WGLWindow::createImage not implemented.\n";
    return nullptr;
}

WGLWindow::Image WGLWindow::createImageKHR(GLWindowContext context,
                                           Enum target,
                                           ClientBuffer buffer,
                                           const AttribKHR *attrib_list)
{
    std::cerr << "WGLWindow::createImageKHR not implemented.\n";
    return nullptr;
}

EGLBoolean WGLWindow::destroyImage(Image image)
{
    std::cerr << "WGLWindow::destroyImage not implemented.\n";
    return EGL_FALSE;
}

EGLBoolean WGLWindow::destroyImageKHR(Image image)
{
    std::cerr << "WGLWindow::destroyImageKHR not implemented.\n";
    return EGL_FALSE;
}

bool WGLWindow::setSwapInterval(EGLint swapInterval)
{
    if (!_wglSwapIntervalEXT || _wglSwapIntervalEXT(swapInterval) == FALSE)
    {
        std::cerr << "Error during wglSwapIntervalEXT.\n";
        return false;
    }
    return true;
}

void WGLWindow::swap()
{
    if (SwapBuffers(mDeviceContext) == FALSE)
    {
        std::cerr << "Error during SwapBuffers.\n";
    }
}

bool WGLWindow::hasError() const
{
    return GetLastError() != S_OK;
}

angle::GenericProc WGLWindow::getProcAddress(const char *name)
{
    return GetProcAddressWithFallback(name);
}

// static
WGLWindow *WGLWindow::New(int glesMajorVersion, int glesMinorVersion)
{
    return new WGLWindow(glesMajorVersion, glesMinorVersion);
}

// static
void WGLWindow::Delete(WGLWindow **window)
{
    delete *window;
    *window = nullptr;
}
