/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40; -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "skia/include/gpu/GrContext.h"
#include "skia/include/gpu/gl/GrGLInterface.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/ThreadLocal.h"
#include "mozilla/DebugOnly.h"

/* SkPostConfig.h includes windows.h, which includes windef.h
 * which redefines min/max. We don't want that. */
#ifdef _WIN32
#undef min
#undef max
#endif

#include "GLContext.h"
#include "SkiaGLGlue.h"

using mozilla::gl::GLContext;
using mozilla::gl::GLFeature;
using mozilla::gl::SkiaGLGlue;
using mozilla::gfx::DrawTarget;

template<typename R, typename... A>
static inline GrGLFunction<R (*)(A...)>
WrapGL(RefPtr<GLContext> aContext, R (GLContext::*aFunc)(A...))
{
  return [aContext, aFunc] (A... args) -> R
  {
    aContext->MakeCurrent();
    return (aContext->*aFunc)(args...);
  };
}

template<typename R, typename... A>
static inline GrGLFunction<R (*)(A...)>
WrapGL(RefPtr<GLContext> aContext, R (*aFunc)(GLContext*, A...))
{
  return [aContext, aFunc] (A... args) -> R
  {
    aContext->MakeCurrent();
    return (*aFunc)(aContext, args...);
  };
}

// Core GL functions required by Ganesh

static const GLubyte*
glGetString_mozilla(GLContext* aContext, GrGLenum aName)
{
    // GLContext only exposes a OpenGL 2.0 style API, so we have to intercept a bunch
    // of checks that Ganesh makes to determine which capabilities are present
    // on the GL implementation and change them to match what GLContext actually exposes.

    if (aName == LOCAL_GL_VERSION) {
        if (aContext->IsGLES()) {
            return reinterpret_cast<const GLubyte*>("OpenGL ES 2.0");
        } else {
            return reinterpret_cast<const GLubyte*>("2.0");
        }
    } else if (aName == LOCAL_GL_EXTENSIONS) {
        // Only expose the bare minimum extensions we want to support to ensure a functional Ganesh
        // as GLContext only exposes certain extensions
        static bool extensionsStringBuilt = false;
        static char extensionsString[1024];

        if (!extensionsStringBuilt) {
            extensionsString[0] = '\0';

            if (aContext->IsGLES()) {
                // OES is only applicable to GLES2
                if (aContext->IsExtensionSupported(GLContext::OES_packed_depth_stencil)) {
                    strcat(extensionsString, "GL_OES_packed_depth_stencil ");
                }

                if (aContext->IsExtensionSupported(GLContext::OES_rgb8_rgba8)) {
                    strcat(extensionsString, "GL_OES_rgb8_rgba8 ");
                }

                if (aContext->IsExtensionSupported(GLContext::OES_texture_npot)) {
                    strcat(extensionsString, "GL_OES_texture_npot ");
                }

                if (aContext->IsExtensionSupported(GLContext::OES_vertex_array_object)) {
                    strcat(extensionsString, "GL_OES_vertex_array_object ");
                }

                if (aContext->IsSupported(GLFeature::standard_derivatives)) {
                    strcat(extensionsString, "GL_OES_standard_derivatives ");
                }
            } else {
                if (aContext->IsSupported(GLFeature::framebuffer_object)) {
                    strcat(extensionsString, "GL_ARB_framebuffer_object ");
                } else if (aContext->IsExtensionSupported(GLContext::EXT_framebuffer_object)) {
                    strcat(extensionsString, "GL_EXT_framebuffer_object ");
                }

                if (aContext->IsSupported(GLFeature::texture_rg)) {
                    strcat(extensionsString, "GL_ARB_texture_rg ");
                }
            }

            if (aContext->IsExtensionSupported(GLContext::EXT_texture_format_BGRA8888)) {
                strcat(extensionsString, "GL_EXT_texture_format_BGRA8888 ");
            }

            if (aContext->IsExtensionSupported(GLContext::EXT_packed_depth_stencil)) {
                strcat(extensionsString, "GL_EXT_packed_depth_stencil ");
            }

            if (aContext->IsExtensionSupported(GLContext::EXT_bgra)) {
                strcat(extensionsString, "GL_EXT_bgra ");
            }

            if (aContext->IsExtensionSupported(GLContext::EXT_read_format_bgra)) {
                strcat(extensionsString, "GL_EXT_read_format_bgra ");
            }

            extensionsStringBuilt = true;
#ifdef DEBUG
            printf_stderr("Exported SkiaGL extensions: %s\n", extensionsString);
#endif
        }

        return reinterpret_cast<const GLubyte*>(extensionsString);

    } else if (aName == LOCAL_GL_SHADING_LANGUAGE_VERSION) {
        if (aContext->IsGLES()) {
            return reinterpret_cast<const GLubyte*>("OpenGL ES GLSL ES 1.0");
        } else {
            return reinterpret_cast<const GLubyte*>("1.10");
        }
    }

    return aContext->fGetString(aName);
}

static GrGLInterface* CreateGrGLInterfaceFromGLContext(GLContext* context)
{
    GrGLInterface* i = new GrGLInterface();

    context->MakeCurrent();

    // We support both desktop GL and GLES2
    if (context->IsGLES()) {
        i->fStandard = kGLES_GrGLStandard;
    } else {
        i->fStandard = kGL_GrGLStandard;
    }

    GrGLFunction<GrGLGetStringProc> getString = WrapGL(context, &glGetString_mozilla);
    GrGLFunction<GrGLGetIntegervProc> getIntegerv = WrapGL(context, &GLContext::fGetIntegerv);

    GrGLExtensions extensions;
    if (!extensions.init(i->fStandard, getString, nullptr, getIntegerv)) {
        delete i;
        return nullptr;
    }

    i->fExtensions.swap(&extensions);

    // Core GL functions required by Ganesh
    i->fFunctions.fActiveTexture = WrapGL(context, &GLContext::fActiveTexture);
    i->fFunctions.fAttachShader = WrapGL(context, &GLContext::fAttachShader);
    i->fFunctions.fBindAttribLocation = WrapGL(context, &GLContext::fBindAttribLocation);
    i->fFunctions.fBindBuffer = WrapGL(context, &GLContext::fBindBuffer);
    i->fFunctions.fBindFramebuffer = WrapGL(context, &GLContext::fBindFramebuffer);
    i->fFunctions.fBindRenderbuffer = WrapGL(context, &GLContext::fBindRenderbuffer);
    i->fFunctions.fBindTexture = WrapGL(context, &GLContext::fBindTexture);
    i->fFunctions.fBlendFunc = WrapGL(context, &GLContext::fBlendFunc);
    i->fFunctions.fBlendColor = WrapGL(context, &GLContext::fBlendColor);
    i->fFunctions.fBlendEquation = WrapGL(context, &GLContext::fBlendEquation);
    i->fFunctions.fBufferData = WrapGL(context, &GLContext::fBufferData);
    i->fFunctions.fBufferSubData = WrapGL(context, &GLContext::fBufferSubData);
    i->fFunctions.fCheckFramebufferStatus = WrapGL(context, &GLContext::fCheckFramebufferStatus);
    i->fFunctions.fClear = WrapGL(context, &GLContext::fClear);
    i->fFunctions.fClearColor = WrapGL(context, &GLContext::fClearColor);
    i->fFunctions.fClearStencil = WrapGL(context, &GLContext::fClearStencil);
    i->fFunctions.fColorMask = WrapGL(context, &GLContext::fColorMask);
    i->fFunctions.fCompileShader = WrapGL(context, &GLContext::fCompileShader);
    i->fFunctions.fCopyTexSubImage2D = WrapGL(context, &GLContext::fCopyTexSubImage2D);
    i->fFunctions.fCreateProgram = WrapGL(context, &GLContext::fCreateProgram);
    i->fFunctions.fCreateShader = WrapGL(context, &GLContext::fCreateShader);
    i->fFunctions.fCullFace = WrapGL(context, &GLContext::fCullFace);
    i->fFunctions.fDeleteBuffers = WrapGL(context, &GLContext::fDeleteBuffers);
    i->fFunctions.fDeleteFramebuffers = WrapGL(context, &GLContext::fDeleteFramebuffers);
    i->fFunctions.fDeleteProgram = WrapGL(context, &GLContext::fDeleteProgram);
    i->fFunctions.fDeleteRenderbuffers = WrapGL(context, &GLContext::fDeleteRenderbuffers);
    i->fFunctions.fDeleteShader = WrapGL(context, &GLContext::fDeleteShader);
    i->fFunctions.fDeleteTextures = WrapGL(context, &GLContext::fDeleteTextures);
    i->fFunctions.fDepthMask = WrapGL(context, &GLContext::fDepthMask);
    i->fFunctions.fDisable = WrapGL(context, &GLContext::fDisable);
    i->fFunctions.fDisableVertexAttribArray = WrapGL(context, &GLContext::fDisableVertexAttribArray);
    i->fFunctions.fDrawArrays = WrapGL(context, &GLContext::fDrawArrays);
    i->fFunctions.fDrawElements = WrapGL(context, &GLContext::fDrawElements);
    i->fFunctions.fDrawRangeElements = WrapGL(context, &GLContext::fDrawRangeElements);
    i->fFunctions.fEnable = WrapGL(context, &GLContext::fEnable);
    i->fFunctions.fEnableVertexAttribArray = WrapGL(context, &GLContext::fEnableVertexAttribArray);
    i->fFunctions.fFinish = WrapGL(context, &GLContext::fFinish);
    i->fFunctions.fFlush = WrapGL(context, &GLContext::fFlush);
    i->fFunctions.fFramebufferRenderbuffer = WrapGL(context, &GLContext::fFramebufferRenderbuffer);
    i->fFunctions.fFramebufferTexture2D = WrapGL(context, &GLContext::fFramebufferTexture2D);
    i->fFunctions.fFrontFace = WrapGL(context, &GLContext::fFrontFace);
    i->fFunctions.fGenBuffers = WrapGL(context, &GLContext::fGenBuffers);
    i->fFunctions.fGenFramebuffers = WrapGL(context, &GLContext::fGenFramebuffers);
    i->fFunctions.fGenRenderbuffers = WrapGL(context, &GLContext::fGenRenderbuffers);
    i->fFunctions.fGetFramebufferAttachmentParameteriv = WrapGL(context, &GLContext::fGetFramebufferAttachmentParameteriv);
    i->fFunctions.fGenTextures = WrapGL(context, &GLContext::fGenTextures);
    i->fFunctions.fGenerateMipmap = WrapGL(context, &GLContext::fGenerateMipmap);
    i->fFunctions.fGetBufferParameteriv = WrapGL(context, &GLContext::fGetBufferParameteriv);
    i->fFunctions.fGetError = WrapGL(context, &GLContext::fGetError);
    i->fFunctions.fGetIntegerv = getIntegerv;
    i->fFunctions.fGetProgramInfoLog = WrapGL(context, &GLContext::fGetProgramInfoLog);
    i->fFunctions.fGetProgramiv = WrapGL(context, &GLContext::fGetProgramiv);
    i->fFunctions.fGetRenderbufferParameteriv = WrapGL(context, &GLContext::fGetRenderbufferParameteriv);
    i->fFunctions.fGetShaderInfoLog = WrapGL(context, &GLContext::fGetShaderInfoLog);
    i->fFunctions.fGetShaderiv = WrapGL(context, &GLContext::fGetShaderiv);
    i->fFunctions.fGetShaderPrecisionFormat = WrapGL(context, &GLContext::fGetShaderPrecisionFormat);
    i->fFunctions.fGetString = getString;
    i->fFunctions.fGetUniformLocation = WrapGL(context, &GLContext::fGetUniformLocation);
    i->fFunctions.fLineWidth = WrapGL(context, &GLContext::fLineWidth);
    i->fFunctions.fLinkProgram = WrapGL(context, &GLContext::fLinkProgram);
    i->fFunctions.fPixelStorei = WrapGL(context, &GLContext::fPixelStorei);
    i->fFunctions.fReadPixels = WrapGL(context, &GLContext::fReadPixels);
    i->fFunctions.fRenderbufferStorage = WrapGL(context, &GLContext::fRenderbufferStorage);
    i->fFunctions.fScissor = WrapGL(context, &GLContext::fScissor);
    i->fFunctions.fShaderSource = WrapGL(context, &GLContext::fShaderSource);
    i->fFunctions.fStencilFunc = WrapGL(context, &GLContext::fStencilFunc);
    i->fFunctions.fStencilMask = WrapGL(context, &GLContext::fStencilMask);
    i->fFunctions.fStencilOp = WrapGL(context, &GLContext::fStencilOp);
    i->fFunctions.fTexImage2D = WrapGL(context, &GLContext::fTexImage2D);
    i->fFunctions.fTexParameteri = WrapGL(context, &GLContext::fTexParameteri);
    i->fFunctions.fTexParameteriv = WrapGL(context, &GLContext::fTexParameteriv);
    i->fFunctions.fTexSubImage2D = WrapGL(context, &GLContext::fTexSubImage2D);
    i->fFunctions.fUniform1f = WrapGL(context, &GLContext::fUniform1f);
    i->fFunctions.fUniform1i = WrapGL(context, &GLContext::fUniform1i);
    i->fFunctions.fUniform1fv = WrapGL(context, &GLContext::fUniform1fv);
    i->fFunctions.fUniform1iv = WrapGL(context, &GLContext::fUniform1iv);
    i->fFunctions.fUniform2f = WrapGL(context, &GLContext::fUniform2f);
    i->fFunctions.fUniform2i = WrapGL(context, &GLContext::fUniform2i);
    i->fFunctions.fUniform2fv = WrapGL(context, &GLContext::fUniform2fv);
    i->fFunctions.fUniform2iv = WrapGL(context, &GLContext::fUniform2iv);
    i->fFunctions.fUniform3f = WrapGL(context, &GLContext::fUniform3f);
    i->fFunctions.fUniform3i = WrapGL(context, &GLContext::fUniform3i);
    i->fFunctions.fUniform3fv = WrapGL(context, &GLContext::fUniform3fv);
    i->fFunctions.fUniform3iv = WrapGL(context, &GLContext::fUniform3iv);
    i->fFunctions.fUniform4f = WrapGL(context, &GLContext::fUniform4f);
    i->fFunctions.fUniform4i = WrapGL(context, &GLContext::fUniform4i);
    i->fFunctions.fUniform4fv = WrapGL(context, &GLContext::fUniform4fv);
    i->fFunctions.fUniform4iv = WrapGL(context, &GLContext::fUniform4iv);
    i->fFunctions.fUniformMatrix2fv = WrapGL(context, &GLContext::fUniformMatrix2fv);
    i->fFunctions.fUniformMatrix3fv = WrapGL(context, &GLContext::fUniformMatrix3fv);
    i->fFunctions.fUniformMatrix4fv = WrapGL(context, &GLContext::fUniformMatrix4fv);
    i->fFunctions.fUseProgram = WrapGL(context, &GLContext::fUseProgram);
    i->fFunctions.fVertexAttrib1f = WrapGL(context, &GLContext::fVertexAttrib1f);
    i->fFunctions.fVertexAttrib2fv = WrapGL(context, &GLContext::fVertexAttrib2fv);
    i->fFunctions.fVertexAttrib3fv = WrapGL(context, &GLContext::fVertexAttrib3fv);
    i->fFunctions.fVertexAttrib4fv = WrapGL(context, &GLContext::fVertexAttrib4fv);
    i->fFunctions.fVertexAttribPointer = WrapGL(context, &GLContext::fVertexAttribPointer);
    i->fFunctions.fViewport = WrapGL(context, &GLContext::fViewport);

    // Required for either desktop OpenGL 2.0 or OpenGL ES 2.0
    i->fFunctions.fStencilFuncSeparate = WrapGL(context, &GLContext::fStencilFuncSeparate);
    i->fFunctions.fStencilMaskSeparate = WrapGL(context, &GLContext::fStencilMaskSeparate);
    i->fFunctions.fStencilOpSeparate = WrapGL(context, &GLContext::fStencilOpSeparate);

    // GLContext supports glMapBuffer
    i->fFunctions.fMapBuffer = WrapGL(context, &GLContext::fMapBuffer);
    i->fFunctions.fUnmapBuffer = WrapGL(context, &GLContext::fUnmapBuffer);

    // GLContext supports glRenderbufferStorageMultisample/glBlitFramebuffer
    i->fFunctions.fRenderbufferStorageMultisample = WrapGL(context, &GLContext::fRenderbufferStorageMultisample);
    i->fFunctions.fBlitFramebuffer = WrapGL(context, &GLContext::fBlitFramebuffer);

    // GLContext supports glCompressedTexImage2D
    i->fFunctions.fCompressedTexImage2D = WrapGL(context, &GLContext::fCompressedTexImage2D);

    // GL_OES_vertex_array_object
    i->fFunctions.fBindVertexArray = WrapGL(context, &GLContext::fBindVertexArray);
    i->fFunctions.fDeleteVertexArrays = WrapGL(context, &GLContext::fDeleteVertexArrays);
    i->fFunctions.fGenVertexArrays = WrapGL(context, &GLContext::fGenVertexArrays);

    // Desktop GL
    i->fFunctions.fGetTexLevelParameteriv = WrapGL(context, &GLContext::fGetTexLevelParameteriv);
    i->fFunctions.fDrawBuffer = WrapGL(context, &GLContext::fDrawBuffer);
    i->fFunctions.fReadBuffer = WrapGL(context, &GLContext::fReadBuffer);

    // Desktop OpenGL > 1.5
    i->fFunctions.fGenQueries = WrapGL(context, &GLContext::fGenQueries);
    i->fFunctions.fDeleteQueries = WrapGL(context, &GLContext::fDeleteQueries);
    i->fFunctions.fBeginQuery = WrapGL(context, &GLContext::fBeginQuery);
    i->fFunctions.fEndQuery = WrapGL(context, &GLContext::fEndQuery);
    i->fFunctions.fGetQueryiv = WrapGL(context, &GLContext::fGetQueryiv);
    i->fFunctions.fGetQueryObjectiv = WrapGL(context, &GLContext::fGetQueryObjectiv);
    i->fFunctions.fGetQueryObjectuiv = WrapGL(context, &GLContext::fGetQueryObjectuiv);

    // Desktop OpenGL > 2.0
    i->fFunctions.fDrawBuffers = WrapGL(context, &GLContext::fDrawBuffers);

    return i;
}

SkiaGLGlue::SkiaGLGlue(GLContext* context)
    : mGLContext(context)
{
    mGrGLInterface.reset(CreateGrGLInterfaceFromGLContext(mGLContext));
    mGrContext.reset(GrContext::Create(kOpenGL_GrBackend, (GrBackendContext)mGrGLInterface.get()));
}

SkiaGLGlue::~SkiaGLGlue()
{
    /*
     * These members have inter-dependencies, but do not keep each other alive, so
     * destruction order is very important here: mGrContext uses mGrGLInterface, and
     * through it, uses mGLContext
     */
    mGrContext = nullptr;
    if (mGrGLInterface) {
      // Ensure that no references to the GLContext remain, even if the GrContext still lives.
      mGrGLInterface->fFunctions = GrGLInterface::Functions();
      mGrGLInterface = nullptr;
    }
    mGLContext = nullptr;
}