/**************************************************************************
 *
 * Copyright 2014 VMware, Inc.
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 **************************************************************************/


#include "glfeatures.hpp"

#include <assert.h>

#include <sstream>

#include "os.hpp"
#include "glproc.hpp"


namespace glfeatures {


bool
Profile::matches(const Profile expected) const
{
    if (api != expected.api) {
        return false;
    }

    if (!versionGreaterOrEqual(expected.major, expected.minor)) {
        return false;
    }

    /*
     * GLX_ARB_create_context/WGL_ARB_create_context specs state that
     *
     *   "If version 3.1 is requested, the context returned may implement
     *   any of the following versions:
     *
     *     * Version 3.1. The GL_ARB_compatibility extension may or may not
     *       be implemented, as determined by the implementation.
     *
     *     * The core profile of version 3.2 or greater."
     */
    if (core != expected.core &&
        (expected.major != 3 || expected.minor != 1)) {
        return false;
    }

    /*
     * Only check forward-compatible flag prior to 3.2 contexts.
     *
     * Note that on MacOSX all 3.2+ context must be forward-compatible.
     */
#ifndef __APPLE__
    if (forwardCompatible > expected.forwardCompatible) {
        return false;
    }
#endif

    return true;
}


std::string
Profile::str(void) const
{
    std::stringstream ss;
    ss << *this;
    return ss.str();
}


std::ostream &
operator << (std::ostream &os, const Profile & profile) {
    os << "OpenGL";
    if (profile.api == API_GLES) {
        os << " ES";
    }
    os << " " << profile.major << "." << profile.minor;
    if (profile.api == API_GL) {
        if (profile.versionGreaterOrEqual(3, 2)) {
            os << " " << (profile.core ? "core" : "compat");
        }
        if (profile.forwardCompatible) {
            os << " forward-compatible";
        }
    }
    return os;
}


static inline bool
isDigit(char c) {
    return c >= '0' && c <= '9';
}


static unsigned
parseNumber(const char * & p)
{
    unsigned n = 0;
    char c = *p;
    while (isDigit(c)) {
        unsigned digit = c - '0';
        n *= 10;
        n += digit;
        ++p;
        c = *p;
    }
    return n;
}


/*
 * Parse API and version numbers from a GL_VERSION string.
 *
 * OpenGL 2.1 specification states that GL_VERSION string is laid out as
 *
 *     <version number><space><vendor-specific information>
 *
 *   Where <version number> is either of the form <major number>.<minor number>
 *   or <major number>.<minor number>.<release number>, where the numbers all
 *   have one or more digits.  The release number and vendor specific
 *   information are optional.
 *
 * OpenGL ES 1.x specification states that GL_VERSION is laid out as
 *
 *   "OpenGL ES-XX 1.x" XX={CM,CL}
 *
 * OpenGL ES 2 and 3 specifications state that GL_VERSION is laid out as
 *
 *   "OpenGL ES N.M vendor-specific information"
 */
static Profile
parseVersion(const char *version)
{
    Profile profile(API_GL, 0, 0, false);

    const char *p = version;

    if (p[0] == 'O' &&
        p[1] == 'p' &&
        p[2] == 'e' &&
        p[3] == 'n' &&
        p[4] == 'G' &&
        p[5] == 'L' &&
        p[6] == ' ' &&
        p[7] == 'E' &&
        p[8] == 'S') {
        p += 9;

        profile.api = API_GLES;

        // skip `-{CM,CL}`
        if (*p == '-') {
            ++p;
            while (*p != ' ') {
                if (*p == '\0') {
                    goto malformed;
                }
                ++p;
            }
        }

        // skip white-space
        while (*p == ' ') {
            if (*p == '\0') {
                goto malformed;
            }
            ++p;
        }
    }

    if (!isDigit(*p)) {
        goto malformed;
    }

    profile.major = parseNumber(p);
    if (*p++ == '.' &&
        isDigit(*p)) {
        profile.minor = parseNumber(p);
    } else {
        goto malformed;
    }

    return profile;

malformed:
    os::log("warning: malformed GL_VERSION (\"%s\")\n", version);
    return profile;
}


/*
 * Get the profile of the current context.
 */
Profile
getCurrentContextProfile(void)
{
    Profile profile(API_GL, 0, 0, false);

    assert(parseVersion("3.0 Mesa 10.3.2") == Profile(API_GL, 3, 0));
    assert(parseVersion("3.3 (Core Profile) Mesa 10.3.2") == Profile(API_GL, 3, 3));
    assert(parseVersion("4.4.0 NVIDIA 331.89") == Profile(API_GL, 4, 4));
    assert(parseVersion("OpenGL ES 3.0 Mesa 10.3.2") == Profile(API_GLES, 3, 0));

    const char *version = (const char *)_glGetString(GL_VERSION);
    if (!version) {
        os::log("apitrace: warning: got null GL_VERSION\n");
        return profile;
    }

    // Parse the version string.
    profile = parseVersion(version);

    if (profile.major >= 3) {
        /*
         * From OpenGL and OpenGL ES version 3 onwards, the GL version may also
         * be queried via GL_MAJOR VERSION and GL_MINOR_VERSION, which should
         * match the major number and minor number in GL_VERSION string, so use
         * it to check we parsed the versions correcly.
         */

        GLint majorVersion = 0;
        _glGetIntegerv(GL_MAJOR_VERSION, &majorVersion);
        GLint minorVersion = 0;
        _glGetIntegerv(GL_MINOR_VERSION, &minorVersion);

        if (majorVersion != profile.major ||
            minorVersion != profile.minor) {
            os::log("apitrace: warning: OpenGL context version mismatch (GL_VERSION=\"%s\", but GL_MAJOR/MINOR_VERSION=%u.%u)\n",
                    version, majorVersion, minorVersion);
        }
    }

    if (profile.api == API_GL) {
        if (profile.versionGreaterOrEqual(3, 0)) {
            GLint contextFlags = 0;
            _glGetIntegerv(GL_CONTEXT_FLAGS, &contextFlags);
            if (contextFlags & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT) {
                profile.forwardCompatible = true;
            }
        }

        if (profile.versionGreaterOrEqual(3, 2)) {
            GLint profileMask = 0;
            _glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &profileMask);
            if (profileMask & GL_CONTEXT_CORE_PROFILE_BIT) {
                profile.core = true;
            }
        }
    }

    return profile;

}


void
Extensions::getCurrentContextExtensions(const Profile & profile)
{
    assert(strings.empty());
    if (profile.major >= 3) {
        // Use glGetStringi
        GLint num_strings = 0;
        _glGetIntegerv(GL_NUM_EXTENSIONS, &num_strings);
        assert(num_strings);
        for (int i = 0; i < num_strings; ++i) {
            const char *extension = reinterpret_cast<const char *>(_glGetStringi(GL_EXTENSIONS, i));
            assert(extension);
            if (extension) {
                strings.insert(extension);
            }
        }
    } else {
        // Use glGetString
        const char *begin = reinterpret_cast<const char *>(_glGetString(GL_EXTENSIONS));
        assert(begin);
        if (begin) {
            do {
                const char *end = begin;
                char c = *end;
                while (c != '\0' && c != ' ') {
                    ++end;
                    c = *end;
                }
                if (end != begin) {
                    strings.insert(std::string(begin, end));
                }
                if (c == '\0') {
                    break;
                }
                begin = end + 1;
            } while(true);
        }
    }
}


bool
Extensions::has(const char *string) const
{
    return strings.find(string) != strings.end();
}


Features::Features(void)
{
    memset(this, 0, sizeof *this);
}


void
Features::load(const Profile & profile, const Extensions & ext)
{
    ES = profile.es();
    core = profile.core;

    ARB_draw_buffers = !ES;

    // Check extensions we use.
    ARB_sampler_objects = ext.has("GL_ARB_sampler_objects");
    ARB_get_program_binary = ext.has("GL_ARB_get_program_binary");
    KHR_debug = !ES && ext.has("GL_KHR_debug");
    EXT_debug_label = ext.has("GL_EXT_debug_label");
    ARB_direct_state_access = ext.has("GL_ARB_direct_state_access");
    ARB_shader_image_load_store = ext.has("GL_ARB_shader_image_load_store");
    ARB_shader_storage_buffer_object = ext.has("GL_ARB_shader_storage_buffer_object");
    ARB_program_interface_query = ext.has("GL_ARB_program_interface_query");

    NV_read_depth_stencil = ES && ext.has("GL_NV_read_depth_stencil");

    if (profile.desktop()) {
        ARB_color_buffer_float = profile.versionGreaterOrEqual(3, 0) ||
                                 ext.has("GL_ARB_color_buffer_float");

        // GL_EXT_texture3D uses different entrypoints
        texture_3d = profile.versionGreaterOrEqual(1, 2);

        pixel_buffer_object = profile.versionGreaterOrEqual(2, 1) ||
                              ext.has("GL_ARB_pixel_buffer_object") ||
                              ext.has("GL_EXT_pixel_buffer_object");

        read_buffer = 1;

        // GL_EXT_framebuffer_object requires different entry points
        framebuffer_object = profile.versionGreaterOrEqual(3, 0) ||
                             ext.has("GL_ARB_framebuffer_object");

        read_framebuffer_object = framebuffer_object;

        query_buffer_object = profile.versionGreaterOrEqual(4, 4) ||
                              ext.has("GL_ARB_query_buffer_object") ||
                              ext.has("GL_AMD_query_buffer_object");

        primitive_restart = profile.versionGreaterOrEqual(3, 1) ||
                            ext.has("GL_NV_primitive_restart");

        unpack_subimage = 1;
    } else {
        texture_3d = 1;

        pixel_buffer_object = profile.versionGreaterOrEqual(3, 0) ||
                              ext.has("GL_NV_pixel_buffer_object");

        // GL_EXT_multiview_draw_buffers requires different entry points
        // GL_NV_read_buffer requires different entry points
        read_buffer = 0;

        // GL_OES_framebuffer_object requires different entry points
        framebuffer_object = profile.versionGreaterOrEqual(2, 0);

        // GL_ANGLE_framebuffer_blit requires different entry points
        // GL_APPLE_framebuffer_multisample requires different entry points
        // GL_NV_framebuffer_blit requires different entry points
        read_framebuffer_object = 0;

        query_buffer_object = 0;

        primitive_restart = 0;

        unpack_subimage = ext.has("GL_EXT_unpack_subimage");
    }
}


void
Features::load(void)
{
    glfeatures::Profile profile = glfeatures::getCurrentContextProfile();
    glfeatures::Extensions exts;
    exts.getCurrentContextExtensions(profile);
    load(profile, exts);
}


} /* namespace glfeatures */
