/*
 * Copyright 2023 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include <EGL/egl.h>
#include <GLES/gl.h>
#include "include/gpu/ganesh/gl/GrGLInterface.h"
#include "tools/window/DisplayParams.h"
#include "tools/window/GLWindowContext.h"
#include "tools/window/android/WindowContextFactory_android.h"

#define EGL_PROTECTED_CONTENT_EXT 0x32C0

using skwindow::DisplayParams;

namespace {
class GLWindowContext_android : public skwindow::internal::GLWindowContext {
public:
    GLWindowContext_android(ANativeWindow*, std::unique_ptr<const DisplayParams>);

    ~GLWindowContext_android() override;

    sk_sp<const GrGLInterface> onInitializeContext() override;
    void onDestroyContext() override;

private:
    void onSwapBuffers() override;

    EGLDisplay fDisplay;
    EGLContext fEGLContext;
    EGLSurface fSurfaceAndroid;

    // For setDisplayParams and resize which call onInitializeContext with null platformData
    ANativeWindow* fNativeWindow = nullptr;
};

GLWindowContext_android::GLWindowContext_android(ANativeWindow* window,
                                                 std::unique_ptr<const DisplayParams> params)
        : GLWindowContext(std::move(params))
        , fDisplay(EGL_NO_DISPLAY)
        , fEGLContext(EGL_NO_CONTEXT)
        , fSurfaceAndroid(EGL_NO_SURFACE)
        , fNativeWindow(window) {
    // any config code here (particularly for msaa)?

    this->initializeContext();
}

GLWindowContext_android::~GLWindowContext_android() {
    this->destroyContext();
}

sk_sp<const GrGLInterface> GLWindowContext_android::onInitializeContext() {
    fWidth = ANativeWindow_getWidth(fNativeWindow);
    fHeight = ANativeWindow_getHeight(fNativeWindow);

    fDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);

    EGLint majorVersion;
    EGLint minorVersion;
    eglInitialize(fDisplay, &majorVersion, &minorVersion);

    const char* extensions = eglQueryString(fDisplay, EGL_EXTENSIONS);

    if (fDisplayParams->createProtectedNativeBackend() &&
        !strstr(extensions, "EGL_EXT_protected_content")) {
        SkDebugf("Protected Context requested but no protected support\n");
        fDisplayParams = skwindow::DisplayParamsBuilder(fDisplayParams.get())
                                 .createProtectedNativeBackend(false)
                                 .detach();
    }

    SkAssertResult(eglBindAPI(EGL_OPENGL_ES_API));

    EGLint numConfigs = 0;
    EGLint eglSampleCnt =
            fDisplayParams->msaaSampleCount() > 1 ? fDisplayParams->msaaSampleCount() > 1 : 0;
    const EGLint configAttribs[] = {
        EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
        EGL_RED_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_BLUE_SIZE, 8,
        EGL_ALPHA_SIZE, 8,
        EGL_STENCIL_SIZE, 8,
        EGL_SAMPLE_BUFFERS, eglSampleCnt ? 1 : 0,
        EGL_SAMPLES, eglSampleCnt,
        EGL_NONE
    };

    EGLConfig surfaceConfig;
    SkAssertResult(eglChooseConfig(fDisplay, configAttribs, &surfaceConfig, 1, &numConfigs));
    SkASSERT(numConfigs > 0);

    std::vector<EGLint> kEGLContextAttribsForOpenGLES = {
            EGL_CONTEXT_CLIENT_VERSION, 2,
    };

    if (fDisplayParams->createProtectedNativeBackend()) {
        kEGLContextAttribsForOpenGLES.push_back(EGL_PROTECTED_CONTENT_EXT);
        kEGLContextAttribsForOpenGLES.push_back(EGL_TRUE);
    }

    kEGLContextAttribsForOpenGLES.push_back(EGL_NONE);

    fEGLContext = eglCreateContext(
            fDisplay, surfaceConfig, nullptr, kEGLContextAttribsForOpenGLES.data());
    SkASSERT(EGL_NO_CONTEXT != fEGLContext);

//    SkDebugf("EGL: %d.%d", majorVersion, minorVersion);
//    SkDebugf("Vendor: %s", eglQueryString(fDisplay, EGL_VENDOR));
//    SkDebugf("Extensions: %s", eglQueryString(fDisplay, EGL_EXTENSIONS));

    const EGLint surfaceAttribs[] = {
            fDisplayParams->createProtectedNativeBackend() ? EGL_PROTECTED_CONTENT_EXT : EGL_NONE,
            fDisplayParams->createProtectedNativeBackend() ? EGL_TRUE : EGL_NONE,
            EGL_NONE};

    fSurfaceAndroid = eglCreateWindowSurface(fDisplay, surfaceConfig, fNativeWindow,
                                             surfaceAttribs);
    SkASSERT(EGL_NO_SURFACE != fSurfaceAndroid);

    SkAssertResult(eglMakeCurrent(fDisplay, fSurfaceAndroid, fSurfaceAndroid, fEGLContext));
    // GLWindowContext::initializeContext will call GrGLMakeNativeInterface so we
    // won't call it here.

    glClearStencil(0);
    glClearColor(0, 0, 0, 0);
    glStencilMask(0xffffffff);
    glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

    eglGetConfigAttrib(fDisplay, surfaceConfig, EGL_STENCIL_SIZE, &fStencilBits);
    eglGetConfigAttrib(fDisplay, surfaceConfig, EGL_SAMPLES, &fSampleCount);
    fSampleCount = std::max(fSampleCount, 1);

    eglSwapInterval(fDisplay, fDisplayParams->disableVsync() ? 0 : 1);

    return GrGLMakeNativeInterface();
}

void GLWindowContext_android::onDestroyContext() {
    if (!fDisplay || !fEGLContext || !fSurfaceAndroid) {
        return;
    }
    eglMakeCurrent(fDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    SkAssertResult(eglDestroySurface(fDisplay, fSurfaceAndroid));
    SkAssertResult(eglDestroyContext(fDisplay, fEGLContext));
    fEGLContext = EGL_NO_CONTEXT;
    fSurfaceAndroid = EGL_NO_SURFACE;
}

void GLWindowContext_android::onSwapBuffers() {
    if (fDisplay && fEGLContext && fSurfaceAndroid) {
        eglSwapBuffers(fDisplay, fSurfaceAndroid);
    }
}

}  // anonymous namespace

namespace skwindow {

std::unique_ptr<WindowContext> MakeGLForAndroid(ANativeWindow* window,
                                                std::unique_ptr<const DisplayParams> params) {
    std::unique_ptr<WindowContext> ctx(new GLWindowContext_android(window, std::move(params)));
    if (!ctx->isValid()) {
        return nullptr;
    }
    return ctx;
}

}  // namespace skwindow
