/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "PlatformWGL.h"

#include <Wingdi.h>

#include "OpenGLDriverFactory.h"

#ifdef _MSC_VER
    // this variable is checked in BlueGL.h (included from "gl_headers.h" right after this), 
    // and prevents duplicate definition of OpenGL apis when building this file. 
    // However, GL_GLEXT_PROTOTYPES need to be defined in BlueGL.h when included from other files.
    #define FILAMENT_PLATFORM_WGL
#endif

#include "gl_headers.h"

#include "Windows.h"
#include <GL/gl.h>
#include "GL/glext.h"
#include "GL/wglext.h"

#include <utils/Log.h>
#include <utils/Panic.h>

namespace {

void reportLastWindowsError() {
    LPSTR lpMessageBuffer = nullptr;
    DWORD dwError = GetLastError();

    if (dwError == 0) {
        return;
    }

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        nullptr,
        dwError,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        lpMessageBuffer,
        0, nullptr
	);

    utils::slog.e << "Windows error code: " << dwError << ". " << lpMessageBuffer
            << utils::io::endl;

    LocalFree(lpMessageBuffer);
}

} // namespace

namespace filament {

using namespace backend;

struct WGLSwapChain {
    HDC hDc = NULL;
    HWND hWnd = NULL;
    bool isHeadless = false;
};

Driver* PlatformWGL::createDriver(void* const sharedGLContext) noexcept {
    int result = 0;
    PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribs = nullptr;
    int pixelFormat = 0;

    mPfd = {
        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
        0,                    // Number of bits for the stencilbuffer
        0,                    // Number of Aux buffers in the framebuffer.
        PFD_MAIN_PLANE,
        0,
        0, 0, 0
    };

    int attribs[] = {
        WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
        WGL_CONTEXT_MINOR_VERSION_ARB, 1,
        WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_PROFILE_MASK_ARB,
        0
    };

    HGLRC tempContext = NULL;

    mHWnd = CreateWindowA("STATIC", "dummy", 0, 0, 0, 1, 1, NULL, NULL, NULL, NULL);
    HDC whdc = mWhdc = GetDC(mHWnd);
    if (whdc == NULL) {
        utils::slog.e << "CreateWindowA() failed" << utils::io::endl;
        goto error;
    }

    pixelFormat = ChoosePixelFormat(whdc, &mPfd);
    SetPixelFormat(whdc, pixelFormat, &mPfd);

    // We need a tmp context to retrieve and call wglCreateContextAttribsARB.
    tempContext = wglCreateContext(whdc);
    if (!wglMakeCurrent(whdc, tempContext)) {
        utils::slog.e << "wglMakeCurrent() failed, whdc=" << whdc << ", tempContext=" <<
                tempContext << utils::io::endl;
        goto error;
    }

    wglCreateContextAttribs =
            (PFNWGLCREATECONTEXTATTRIBSARBPROC) wglGetProcAddress("wglCreateContextAttribsARB");
    mContext = wglCreateContextAttribs(whdc, (HGLRC) sharedGLContext, attribs);
    if (!mContext) {
        utils::slog.e << "wglCreateContextAttribs() failed, whdc=" << whdc << utils::io::endl;
        goto error;
    }

    wglMakeCurrent(NULL, NULL);
    wglDeleteContext(tempContext);
    tempContext = NULL;

    if (!wglMakeCurrent(whdc, mContext)) {
        utils::slog.e << "wglMakeCurrent() failed, whdc=" << whdc << ", mContext=" <<
                mContext << utils::io::endl;
        goto error;
    }

    result = bluegl::bind();
    ASSERT_POSTCONDITION(!result, "Unable to load OpenGL entry points.");
    return OpenGLDriverFactory::create(this, sharedGLContext);

error:
    if (tempContext) {
        wglDeleteContext(tempContext);
    }
    reportLastWindowsError();
    terminate();
    return NULL;
}

void PlatformWGL::terminate() noexcept {
    wglMakeCurrent(NULL, NULL);
    if (mContext) {
        wglDeleteContext(mContext);
        mContext = NULL;
    }
    if (mHWnd && mWhdc) {
        ReleaseDC(mHWnd, mWhdc);
        DestroyWindow(mHWnd);
        mHWnd = NULL;
        mWhdc = NULL;
    } else if (mHWnd) {
        DestroyWindow(mHWnd);
        mHWnd = NULL;
    }
    bluegl::unbind();
}

Platform::SwapChain* PlatformWGL::createSwapChain(void* nativeWindow, uint64_t& flags) noexcept {
    auto* swapChain = new WGLSwapChain();
    swapChain->isHeadless = false;

    // on Windows, the nativeWindow maps to a HWND
    swapChain->hWnd = (HWND) nativeWindow;
    swapChain->hDc = GetDC(swapChain->hWnd);
    if (!ASSERT_POSTCONDITION_NON_FATAL(swapChain->hDc,
            "Unable to create the SwapChain (nativeWindow = %p)", nativeWindow)) {
        reportLastWindowsError();
    }

	// We have to match pixel formats across the HDC and HGLRC (mContext)
    int pixelFormat = ChoosePixelFormat(swapChain->hDc, &mPfd);
    SetPixelFormat(swapChain->hDc, pixelFormat, &mPfd);

    return (Platform::SwapChain*) swapChain;
}

Platform::SwapChain* PlatformWGL::createSwapChain(uint32_t width, uint32_t height, uint64_t& flags) noexcept {
    auto* swapChain = new WGLSwapChain();
    swapChain->isHeadless = true;

    // WS_POPUP was chosen for the window style here after some experimentation.
    // For some reason, using other window styles resulted in corrupted pixel buffers when using
    // readPixels.
    RECT rect = {0, 0, width, height};
    AdjustWindowRect(&rect, WS_POPUP, FALSE);
    width = rect.right - rect.left;
    height = rect.bottom - rect.top;

    swapChain->hWnd = CreateWindowA("STATIC", "headless", WS_POPUP, 0, 0,
            width, height, NULL, NULL, NULL, NULL);
    swapChain->hDc = GetDC(swapChain->hWnd);
    int pixelFormat = ChoosePixelFormat(swapChain->hDc, &mPfd);
    SetPixelFormat(swapChain->hDc, pixelFormat, &mPfd);

    return (Platform::SwapChain*) swapChain;
}

void PlatformWGL::destroySwapChain(Platform::SwapChain* swapChain) noexcept {
    auto* wglSwapChain = (WGLSwapChain*) swapChain;

    HDC dc = wglSwapChain->hDc;
    HWND window = wglSwapChain->hWnd;
    ReleaseDC(window, dc);

    if (wglSwapChain->isHeadless) {
        DestroyWindow(window);
    }

    delete wglSwapChain;

    // make this swapChain not current (by making a dummy one current)
    wglMakeCurrent(mWhdc, mContext);
}

void PlatformWGL::makeCurrent(Platform::SwapChain* drawSwapChain,
                              Platform::SwapChain* readSwapChain) noexcept {
    ASSERT_PRECONDITION_NON_FATAL(drawSwapChain == readSwapChain,
                                  "PlatformWGL does not support distinct draw/read swap chains.");

    auto* wglSwapChain = (WGLSwapChain*) drawSwapChain;
    HDC hdc = wglSwapChain->hDc;
    if (hdc != NULL) {
        BOOL success = wglMakeCurrent(hdc, mContext);
        if (!ASSERT_POSTCONDITION_NON_FATAL(success, "wglMakeCurrent() failed. hdc = %p", hdc)) {
            reportLastWindowsError();
            wglMakeCurrent(0, NULL);
        }
    }
}

void PlatformWGL::commit(Platform::SwapChain* swapChain) noexcept {
    auto* wglSwapChain = (WGLSwapChain*) swapChain;
    HDC hdc = wglSwapChain->hDc;
    if (hdc != NULL) {
        SwapBuffers(hdc);
    }
}

//TODO Implement WGL fences
Platform::Fence* PlatformWGL::createFence() noexcept {
    return nullptr;
}

void PlatformWGL::destroyFence(Fence* fence) noexcept {
}

backend::FenceStatus PlatformWGL::waitFence(Fence* fence, uint64_t timeout) noexcept {
    return backend::FenceStatus::ERROR;
}

} // namespace filament
