//
// Copyright 2016 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.
//
// DisplayVkWin32.cpp:
//    Implements the class methods for DisplayVkWin32.
//

#include "libANGLE/renderer/vulkan/win32/DisplayVkWin32.h"
#include "common/unsafe_buffers.h"
#include "libANGLE/renderer/vulkan/DisplayVk.h"
#include "libANGLE/renderer/vulkan/vk_renderer.h"

#include <windows.h>

#include "common/vulkan/vk_headers.h"
#include "libANGLE/renderer/vulkan/vk_caps_utils.h"
#include "libANGLE/renderer/vulkan/win32/WindowSurfaceVkWin32.h"

namespace rx
{

DisplayVkWin32::DisplayVkWin32(const egl::DisplayState &state)
    : DisplayVk(state), mWindowClass(NULL), mMockWindow(nullptr)
{}

DisplayVkWin32::~DisplayVkWin32() {}

void DisplayVkWin32::terminate()
{
    if (mMockWindow)
    {
        DestroyWindow(mMockWindow);
        mMockWindow = nullptr;
    }
    if (mWindowClass)
    {
        if (!UnregisterClassA(reinterpret_cast<const char *>(mWindowClass),
                              GetModuleHandle(nullptr)))
        {
            WARN() << "Failed to unregister ANGLE window class: " << gl::FmtHex(mWindowClass);
        }
        mWindowClass = NULL;
    }

    DisplayVk::terminate();
}

bool DisplayVkWin32::isValidNativeWindow(EGLNativeWindowType window) const
{
    return (IsWindow(window) == TRUE);
}

SurfaceImpl *DisplayVkWin32::createWindowSurfaceVk(const egl::SurfaceState &state,
                                                   EGLNativeWindowType window)
{
    return new WindowSurfaceVkWin32(state, window);
}

egl::Error DisplayVkWin32::initialize(egl::Display *display)
{
    ANGLE_TRY(DisplayVk::initialize(display));

    HINSTANCE hinstance = GetModuleHandle(nullptr);

    std::wostringstream stream;
    stream << L"ANGLE DisplayVkWin32 " << gl::FmtHex<egl::Display *, wchar_t>(display)
           << L" Intermediate Window Class";

    const LPWSTR idcArrow  = MAKEINTRESOURCEW(32512);
    std::wstring className = stream.str();
    WNDCLASSW wndClass;
    if (!GetClassInfoW(hinstance, className.c_str(), &wndClass))
    {
        WNDCLASSW intermediateClassDesc     = {};
        intermediateClassDesc.style         = CS_OWNDC;
        intermediateClassDesc.lpfnWndProc   = DefWindowProcW;
        intermediateClassDesc.cbClsExtra    = 0;
        intermediateClassDesc.cbWndExtra    = 0;
        intermediateClassDesc.hInstance     = GetModuleHandle(nullptr);
        intermediateClassDesc.hIcon         = nullptr;
        intermediateClassDesc.hCursor       = LoadCursorW(nullptr, idcArrow);
        intermediateClassDesc.hbrBackground = nullptr;
        intermediateClassDesc.lpszMenuName  = nullptr;
        intermediateClassDesc.lpszClassName = className.c_str();
        mWindowClass                        = RegisterClassW(&intermediateClassDesc);
        if (!mWindowClass)
        {
            std::ostringstream err;
            err << "Failed to register intermediate OpenGL window class \""
                << gl::FmtHex<egl::Display *, char>(display)
                << "\":" << gl::FmtErr(HRESULT_CODE(GetLastError()));
            return egl::Error(EGL_NOT_INITIALIZED, err.str());
        }
    }

    mMockWindow = CreateWindowExW(0, reinterpret_cast<LPCWSTR>(mWindowClass), L"ANGLE Mock Window",
                                  WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                                  CW_USEDEFAULT, nullptr, nullptr, nullptr, nullptr);
    if (!mMockWindow)
    {
        return egl::Error(EGL_NOT_INITIALIZED, "Failed to create mock OpenGL window.");
    }

    VkSurfaceKHR surfaceVk;
    VkWin32SurfaceCreateInfoKHR info = {VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, nullptr, 0,
                                        GetModuleHandle(nullptr), mMockWindow};

    VkInstance instance         = mRenderer->getInstance();
    VkPhysicalDevice physDevice = mRenderer->getPhysicalDevice();

    if (vkCreateWin32SurfaceKHR(instance, &info, nullptr, &surfaceVk) != VK_SUCCESS)
    {
        return egl::Error(EGL_NOT_INITIALIZED, "vkCreateWin32SurfaceKHR failed");
    }
    uint32_t surfaceFormatCount;

    if (vkGetPhysicalDeviceSurfaceFormatsKHR(physDevice, surfaceVk, &surfaceFormatCount, nullptr) !=
        VK_SUCCESS)
    {
        return egl::Error(EGL_NOT_INITIALIZED, "vkGetPhysicalDeviceSurfaceFormatsKHR failed");
    }
    mSurfaceFormats.resize(surfaceFormatCount);
    if (vkGetPhysicalDeviceSurfaceFormatsKHR(physDevice, surfaceVk, &surfaceFormatCount,
                                             mSurfaceFormats.data()) != VK_SUCCESS)
    {
        return egl::Error(EGL_NOT_INITIALIZED, "vkGetPhysicalDeviceSurfaceFormatsKHR (2nd) failed");
    }
    vkDestroySurfaceKHR(instance, surfaceVk, nullptr);

    DestroyWindow(mMockWindow);
    mMockWindow = nullptr;

    return egl::NoError();
}

egl::ConfigSet DisplayVkWin32::generateConfigs()
{
    const std::array<GLenum, 5> kColorFormats = {GL_RGB565, GL_BGRA8_EXT, GL_BGRX8_ANGLEX,
                                                 GL_RGB10_A2_EXT, GL_RGBA16F_EXT};

    std::vector<GLenum> depthStencilFormats(
        egl_vk::kConfigDepthStencilFormats,
        ANGLE_UNSAFE_TODO(egl_vk::kConfigDepthStencilFormats +
                          ArraySize(egl_vk::kConfigDepthStencilFormats)));

    if (getCaps().stencil8)
    {
        depthStencilFormats.push_back(GL_STENCIL_INDEX8);
    }
    return egl_vk::GenerateConfigs(kColorFormats.data(), kColorFormats.size(),
                                   depthStencilFormats.data(), depthStencilFormats.size(), this);
}

void DisplayVkWin32::checkConfigSupport(egl::Config *config)
{
    const vk::Format &formatVk = this->getRenderer()->getFormat(config->renderTargetFormat);

    // If the format list includes just one entry of VK_FORMAT_UNDEFINED,
    // the surface has no preferred format.  Otherwise, at least one
    // supported format will be returned.
    if (mSurfaceFormats.size() == 1u && mSurfaceFormats[0].format == VK_FORMAT_UNDEFINED)
    {
        return;
    }

    for (const VkSurfaceFormatKHR &surfaceFormat : mSurfaceFormats)
    {
        if (surfaceFormat.format == formatVk.getActualRenderableImageVkFormat(this->getRenderer()))
        {
            return;
        }
    }

    // No window support for this config.
    config->surfaceType &= ~EGL_WINDOW_BIT;
}

const char *DisplayVkWin32::getWSIExtension() const
{
    return VK_KHR_WIN32_SURFACE_EXTENSION_NAME;
}

bool IsVulkanWin32DisplayAvailable()
{
    return true;
}

DisplayImpl *CreateVulkanWin32Display(const egl::DisplayState &state)
{
    return new DisplayVkWin32(state);
}
}  // namespace rx
