//
// Copyright (c) 2015 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.
//

// SurfaceWGL.cpp: WGL implementation of egl::Surface

#include "libANGLE/renderer/gl/wgl/PbufferSurfaceWGL.h"

#include "common/debug.h"
#include "libANGLE/renderer/gl/RendererGL.h"
#include "libANGLE/renderer/gl/wgl/FunctionsWGL.h"
#include "libANGLE/renderer/gl/wgl/wgl_utils.h"

namespace rx
{

PbufferSurfaceWGL::PbufferSurfaceWGL(const egl::SurfaceState &state,
                                     RendererGL *renderer,
                                     EGLint width,
                                     EGLint height,
                                     EGLenum textureFormat,
                                     EGLenum textureTarget,
                                     bool largest,
                                     int pixelFormat,
                                     HDC deviceContext,
                                     const FunctionsWGL *functions)
    : SurfaceWGL(state, renderer),
      mWidth(width),
      mHeight(height),
      mLargest(largest),
      mTextureFormat(textureFormat),
      mTextureTarget(textureTarget),
      mPixelFormat(pixelFormat),
      mParentDeviceContext(deviceContext),
      mPbuffer(nullptr),
      mPbufferDeviceContext(nullptr),
      mFunctionsWGL(functions)
{
}

PbufferSurfaceWGL::~PbufferSurfaceWGL()
{
    mFunctionsWGL->releasePbufferDCARB(mPbuffer, mPbufferDeviceContext);
    mPbufferDeviceContext = nullptr;

    mFunctionsWGL->destroyPbufferARB(mPbuffer);
    mPbuffer = nullptr;
}

static int GetWGLTextureType(EGLenum eglTextureType)
{
    switch (eglTextureType)
    {
      case EGL_NO_TEXTURE:    return WGL_NO_TEXTURE_ARB;
      case EGL_TEXTURE_RGB:   return WGL_TEXTURE_RGB_ARB;
      case EGL_TEXTURE_RGBA:  return WGL_TEXTURE_RGBA_ARB;
      default: UNREACHABLE(); return 0;
    }
}

static int GetWGLTextureTarget(EGLenum eglTextureTarget)
{
    switch (eglTextureTarget)
    {
      case EGL_NO_TEXTURE:    return WGL_NO_TEXTURE_ARB;
      case EGL_TEXTURE_2D:    return WGL_TEXTURE_2D_ARB;
      default: UNREACHABLE(); return 0;
    }
}

egl::Error PbufferSurfaceWGL::initialize(const egl::Display *display)
{
    const int pbufferCreationAttributes[] =
    {
        WGL_PBUFFER_LARGEST_ARB, mLargest ? 1 : 0,
        WGL_TEXTURE_FORMAT_ARB, GetWGLTextureType(mTextureFormat),
        WGL_TEXTURE_TARGET_ARB, GetWGLTextureTarget(mTextureTarget),
        0, 0,
    };

    mPbuffer = mFunctionsWGL->createPbufferARB(mParentDeviceContext, mPixelFormat, mWidth, mHeight,
                                               pbufferCreationAttributes);
    if (mPbuffer == nullptr)
    {
        DWORD error = GetLastError();
        return egl::EglBadAlloc() << "Failed to create a native WGL pbuffer, "
                                  << gl::FmtErr(HRESULT_CODE(error));
    }

    // The returned pbuffer may not be as large as requested, update the size members.
    if (mFunctionsWGL->queryPbufferARB(mPbuffer, WGL_PBUFFER_WIDTH_ARB, &mWidth) != TRUE ||
        mFunctionsWGL->queryPbufferARB(mPbuffer, WGL_PBUFFER_HEIGHT_ARB, &mHeight) != TRUE)
    {
        DWORD error = GetLastError();
        return egl::EglBadAlloc() << "Failed to query the WGL pbuffer's dimensions, "
                                  << gl::FmtErr(HRESULT_CODE(error));
    }

    mPbufferDeviceContext = mFunctionsWGL->getPbufferDCARB(mPbuffer);
    if (mPbufferDeviceContext == nullptr)
    {
        mFunctionsWGL->destroyPbufferARB(mPbuffer);
        mPbuffer = nullptr;

        DWORD error = GetLastError();
        return egl::EglBadAlloc() << "Failed to get the WGL pbuffer handle, "
                                  << gl::FmtErr(HRESULT_CODE(error));
    }

    return egl::NoError();
}

egl::Error PbufferSurfaceWGL::makeCurrent()
{
    return egl::NoError();
}

egl::Error PbufferSurfaceWGL::swap(const gl::Context *context)
{
    return egl::NoError();
}

egl::Error PbufferSurfaceWGL::postSubBuffer(const gl::Context *context,
                                            EGLint x,
                                            EGLint y,
                                            EGLint width,
                                            EGLint height)
{
    return egl::NoError();
}

egl::Error PbufferSurfaceWGL::querySurfacePointerANGLE(EGLint attribute, void **value)
{
    *value = nullptr;
    return egl::NoError();
}

static int GetWGLBufferBindTarget(EGLint buffer)
{
    switch (buffer)
    {
      case EGL_BACK_BUFFER:   return WGL_BACK_LEFT_ARB;
      default: UNREACHABLE(); return 0;
    }
}

egl::Error PbufferSurfaceWGL::bindTexImage(gl::Texture *texture, EGLint buffer)
{
    if (!mFunctionsWGL->bindTexImageARB(mPbuffer, GetWGLBufferBindTarget(buffer)))
    {
        DWORD error = GetLastError();
        return egl::EglBadSurface()
               << "Failed to bind native wgl pbuffer, " << gl::FmtErr(HRESULT_CODE(error));
    }

    return egl::NoError();
}

egl::Error PbufferSurfaceWGL::releaseTexImage(EGLint buffer)
{
    if (!mFunctionsWGL->releaseTexImageARB(mPbuffer, GetWGLBufferBindTarget(buffer)))
    {
        DWORD error = GetLastError();
        return egl::EglBadSurface()
               << "Failed to unbind native wgl pbuffer, " << gl::FmtErr(HRESULT_CODE(error));
    }

    return egl::NoError();
}

void PbufferSurfaceWGL::setSwapInterval(EGLint interval)
{
}

EGLint PbufferSurfaceWGL::getWidth() const
{
    return mWidth;
}

EGLint PbufferSurfaceWGL::getHeight() const
{
    return mHeight;
}

EGLint PbufferSurfaceWGL::isPostSubBufferSupported() const
{
    return EGL_FALSE;
}

EGLint PbufferSurfaceWGL::getSwapBehavior() const
{
    return EGL_BUFFER_PRESERVED;
}

HDC PbufferSurfaceWGL::getDC() const
{
    return mPbufferDeviceContext;
}
}
