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

#include "OpenGLESDispatch/EGLDispatch.h"

#include "aemu/base/system/System.h"
#include "aemu/base/misc/StringUtils.h"
#include "host-common/feature_control.h"
#include "host-common/opengl/misc.h"

#include <algorithm>

namespace gfxstream {
namespace gl {

// Config + context attributes to query the underlying OpenGL if it is
// a OpenGL ES backend. Only try for OpenGL ES 3, and assume OpenGL ES 2
// exists (if it doesn't, this is the least of our problems).
static const EGLint gles3ConfigAttribs[] =
    { EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
      EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT | EGL_OPENGL_ES3_BIT, EGL_NONE };

static const EGLint pbufAttribs[] =
    { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE };

static const EGLint gles31Attribs[] =
   { EGL_CONTEXT_MAJOR_VERSION_KHR, 3,
     EGL_CONTEXT_MINOR_VERSION_KHR, 1, EGL_NONE };

static const EGLint gles30Attribs[] =
   { EGL_CONTEXT_MAJOR_VERSION_KHR, 3,
     EGL_CONTEXT_MINOR_VERSION_KHR, 0, EGL_NONE };

static bool sTryContextCreation(EGLDisplay dpy, GLESDispatchMaxVersion ver) {
    EGLConfig config;
    EGLSurface surface;

    const EGLint* contextAttribs = nullptr;

    // Assume ES2 capable.
    if (ver == GLES_DISPATCH_MAX_VERSION_2) return true;

    switch (ver) {
    case GLES_DISPATCH_MAX_VERSION_3_0:
        contextAttribs = gles30Attribs;
        break;
    case GLES_DISPATCH_MAX_VERSION_3_1:
        contextAttribs = gles31Attribs;
        break;
    default:
        break;
    }

    if (!contextAttribs) return false;

    int numConfigs;
    if (!s_egl.eglChooseConfig(
            dpy, gles3ConfigAttribs, &config, 1, &numConfigs) ||
        numConfigs == 0) {
        return false;
    }

    surface = s_egl.eglCreatePbufferSurface(dpy, config, pbufAttribs);
    if (surface == EGL_NO_SURFACE) {
        return false;
    }

    EGLContext ctx = s_egl.eglCreateContext(dpy, config, EGL_NO_CONTEXT,
                                            contextAttribs);

    if (ctx == EGL_NO_CONTEXT) {
        s_egl.eglDestroySurface(dpy, surface);
        return false;
    } else {
        s_egl.eglDestroyContext(dpy, ctx);
        s_egl.eglDestroySurface(dpy, surface);
        return true;
    }
}

GLESDispatchMaxVersion calcMaxVersionFromDispatch(const gfxstream::host::FeatureSet& features,
                                                  EGLDisplay dpy) {
    // TODO: 3.1 is the highest
    // b/360208429: CTS conformance for OpenGL ES 3.1
    GLESDispatchMaxVersion maxVersion =
       GLES_DISPATCH_MAX_VERSION_3_1;

    if (emugl::getRenderer() == SELECTED_RENDERER_HOST
        || emugl::getRenderer() == SELECTED_RENDERER_SWIFTSHADER_INDIRECT
        || emugl::getRenderer() == SELECTED_RENDERER_ANGLE_INDIRECT
        || emugl::getRenderer() == SELECTED_RENDERER_ANGLE9_INDIRECT) {
        if (s_egl.eglGetMaxGLESVersion) {
            maxVersion =
                (GLESDispatchMaxVersion)s_egl.eglGetMaxGLESVersion(dpy);
        }
    } else {
        if (!sTryContextCreation(dpy, GLES_DISPATCH_MAX_VERSION_3_1)) {
            maxVersion = GLES_DISPATCH_MAX_VERSION_3_0;
            if (!sTryContextCreation(dpy, GLES_DISPATCH_MAX_VERSION_3_0)) {
                maxVersion = GLES_DISPATCH_MAX_VERSION_2;
            }
        }
    }

    int maj = 2; int min = 0;
    switch (maxVersion) {
        case GLES_DISPATCH_MAX_VERSION_2:
            maj = 2; min = 0; break;
        case GLES_DISPATCH_MAX_VERSION_3_0:
            maj = 3; min = 0; break;
        case GLES_DISPATCH_MAX_VERSION_3_1:
            maj = 3; min = 1; break;
        case GLES_DISPATCH_MAX_VERSION_3_2:
            maj = 3; min = 2; break;
        default:
            break;
    }

    emugl::setGlesVersion(maj, min);
    return maxVersion;
}

// For determining whether or not to use core profile OpenGL.
// (Note: This does not affect the detection of possible core profile configs,
// just whether to use them)
bool shouldEnableCoreProfile() {
    int dispatchMaj, dispatchMin;

    emugl::getGlesVersion(&dispatchMaj, &dispatchMin);
    return emugl::getRenderer() == SELECTED_RENDERER_HOST &&
           dispatchMaj > 2;
}

void sAddExtensionIfSupported(GLESDispatchMaxVersion currVersion,
                              const std::string& from,
                              GLESDispatchMaxVersion extVersion,
                              const std::string& ext,
                              std::string& to) {
    // If we chose a GLES version less than or equal to
    // the |extVersion| the extension |ext| is tagged with,
    // filter it according to the whitelist.
    if (emugl::hasExtension(from.c_str(), ext.c_str()) &&
        currVersion > extVersion) {
        to += ext;
        to += " ";
    }
}

static bool sWhitelistedExtensionsGLES2(const std::string& hostExt) {

#define WHITELIST(ext) \
    if (hostExt == #ext) return true; \

WHITELIST(GL_OES_compressed_ETC1_RGB8_texture)
WHITELIST(GL_OES_depth24)
WHITELIST(GL_OES_depth32)
WHITELIST(GL_OES_depth_texture)
WHITELIST(GL_OES_depth_texture_cube_map)
WHITELIST(GL_OES_EGL_image)
WHITELIST(GL_OES_EGL_image_external)
WHITELIST(GL_OES_EGL_sync)
WHITELIST(GL_OES_element_index_uint)
WHITELIST(GL_OES_framebuffer_object)
WHITELIST(GL_OES_packed_depth_stencil)
WHITELIST(GL_OES_rgb8_rgba8)
WHITELIST(GL_OES_standard_derivatives)
WHITELIST(GL_OES_texture_float)
WHITELIST(GL_OES_texture_float_linear)
WHITELIST(GL_OES_texture_half_float)
WHITELIST(GL_OES_texture_half_float_linear)
WHITELIST(GL_OES_texture_npot)
WHITELIST(GL_OES_texture_3D)
WHITELIST(GL_OVR_multiview2)
WHITELIST(GL_EXT_multiview_texture_multisample)
WHITELIST(GL_EXT_blend_minmax)
WHITELIST(GL_EXT_color_buffer_half_float)
WHITELIST(GL_EXT_draw_buffers)
WHITELIST(GL_EXT_instanced_arrays)
WHITELIST(GL_EXT_occlusion_query_boolean)
WHITELIST(GL_EXT_read_format_bgra)
WHITELIST(GL_EXT_texture_compression_rgtc)
WHITELIST(GL_EXT_texture_filter_anisotropic)
WHITELIST(GL_EXT_texture_format_BGRA8888)
WHITELIST(GL_EXT_texture_rg)
WHITELIST(GL_ANGLE_framebuffer_blit)
WHITELIST(GL_ANGLE_framebuffer_multisample)
WHITELIST(GL_ANGLE_instanced_arrays)
WHITELIST(GL_CHROMIUM_texture_filtering_hint)
WHITELIST(GL_NV_fence)
WHITELIST(GL_NV_framebuffer_blit)
WHITELIST(GL_NV_read_depth)

#if defined(__linux__)
WHITELIST(GL_EXT_texture_compression_bptc)
WHITELIST(GL_EXT_texture_compression_s3tc)
#endif

#undef WHITELIST

    return false;
}

std::string filterExtensionsBasedOnMaxVersion(const gfxstream::host::FeatureSet& features,
                                              GLESDispatchMaxVersion ver,
                                              const std::string& exts) {
    // We need to advertise ES 2 extensions if:
    // a. the dispatch version on the host is ES 2
    // b. the guest image is not updated for ES 3+
    // (GLESDynamicVersion is disabled)
    if (ver > GLES_DISPATCH_MAX_VERSION_2 && features.GlesDynamicVersion.enabled) {
        return exts;
    }

    std::string filteredExtensions;
    filteredExtensions.reserve(4096);
    auto add = [&filteredExtensions](const std::string& hostExt) {
        if (!hostExt.empty() &&
            sWhitelistedExtensionsGLES2(hostExt)) {
            filteredExtensions += hostExt;
            filteredExtensions += " ";
        }
    };

    android::base::split<std::string>(exts, " ", add);

    return filteredExtensions;
}

}  // namespace gl
}  // namespace gfxstream
