/*
 * Copyright (C) 2021 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 "Egl.h"

#include <GLES/gl.h>

namespace gfxstream {
namespace {

constexpr const char kEglLib[] = "libEGL.so";
constexpr const char kEglLibAlt[] = "libEGL.so.1";

gfxstream::expected<Lib, std::string> LoadEglLib() {
    for (const auto* possible_name : {kEglLib, kEglLibAlt}) {
        auto result = Lib::Load(possible_name);
        if (result.ok()) {
            return std::move(result.value());
        }
    }
    return gfxstream::unexpected("Failed to load libEGL.");
}

}  // namespace

/*static*/
gfxstream::expected<Egl, std::string> Egl::Load() {
    Egl egl;
    egl.mLib = GFXSTREAM_EXPECT(LoadEglLib());

#define LOAD_EGL_FUNCTION_POINTER(return_type, function_name, signature)           \
    egl.function_name = reinterpret_cast<return_type(GL_APIENTRY*) signature>(     \
        egl.mLib.GetSymbol(#function_name));                                       \
    if (egl.function_name == nullptr) {                                            \
        egl.function_name = reinterpret_cast<return_type(GL_APIENTRY*) signature>( \
            egl.eglGetProcAddress(#function_name));                                \
    }

    FOR_EACH_EGL_FUNCTION(LOAD_EGL_FUNCTION_POINTER);

    GFXSTREAM_EXPECT(egl.Init());

    return std::move(egl);
}

gfxstream::expected<Ok, std::string> Egl::Init() {
    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (display == EGL_NO_DISPLAY) {
        return gfxstream::unexpected("Failed to get default display");
    }

    EGLint clientVersionMajor = 0;
    EGLint clientVersionMinor = 0;
    if (eglInitialize(display, &clientVersionMajor, &clientVersionMinor) != EGL_TRUE) {
        return gfxstream::unexpected("Failed to initialize display.");
    }

    const std::string vendorString = eglQueryString(display, EGL_VENDOR);
    if (vendorString.empty()) {
        return gfxstream::unexpected("Failed to query vendor.");
    }

    const std::string extensionsString = eglQueryString(display, EGL_EXTENSIONS);
    if (extensionsString.empty()) {
        return gfxstream::unexpected("Failed to query extensions.");
    }

    if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) {
        return gfxstream::unexpected("Failed to bind GLES API.");
    }

    const EGLint attribs[] = {
        // clang-format off
        EGL_SURFACE_TYPE,     EGL_PBUFFER_BIT,
        EGL_RENDERABLE_TYPE,  EGL_OPENGL_ES3_BIT,
        EGL_RED_SIZE,         8,
        EGL_GREEN_SIZE,       8,
        EGL_BLUE_SIZE,        8,
        EGL_ALPHA_SIZE,       8,
        EGL_NONE,
        // clang-format on
    };

    EGLConfig config;
    EGLint numConfigs = 0;
    if (eglChooseConfig(display, attribs, &config, 1, &numConfigs) != EGL_TRUE) {
        return gfxstream::unexpected("Failed to find matching framebuffer config.");
    }

    const EGLint pbufferAttribs[] = {
        // clang-format off
        EGL_WIDTH,  720,
        EGL_HEIGHT, 720,
        EGL_NONE,
        // clang-format on
    };

    EGLSurface primarySurface = eglCreatePbufferSurface(display, config, pbufferAttribs);
    if (primarySurface == EGL_NO_SURFACE) {
        return gfxstream::unexpected("Failed to create EGL surface.");
    }

    const EGLint contextAttribs[] = {
        // clang-format off
        EGL_CONTEXT_CLIENT_VERSION, 3,
        EGL_NONE
        // clang-format on
    };

    EGLContext primaryContext = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
    if (primaryContext == EGL_NO_CONTEXT) {
      return gfxstream::unexpected("Failed to create EGL context.");
    }

    if (eglMakeCurrent(display, primarySurface, primarySurface, primaryContext) == EGL_FALSE) {
      return gfxstream::unexpected("Failed to make primary EGL context/surface current.");
    }

    return {};
}

}  // namespace gfxstream