/***************************************************************************
**
** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB)
** Contact: http://www.qt.io/licensing/
**
** This file is part of the utilities of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "codegenerator.h"

#include <QDebug>
#include <QFile>
#include <QSettings>
#include <QTextStream>

static const QString extensionRegistryFileName = QStringLiteral("qopengl-extension-registry.ini");
static const QString extensionIdGroupName = QStringLiteral("ExtensionIds");

CodeGenerator::CodeGenerator()
    : m_parser(0)
{
}

void CodeGenerator::generateCoreClasses(const QString &baseFileName) const
{
    // Output header and implementation files for the backend and base class
    writeCoreHelperClasses(baseFileName + QStringLiteral(".h"), Declaration);
    writeCoreHelperClasses(baseFileName + QStringLiteral(".cpp"), Definition);

    // Output the per-version and profile public classes
    writeCoreClasses(baseFileName);

    // We also need to generate a factory class that can be used by
    // QOpenGLContext to actually create version function objects
    writeCoreFactoryHeader(baseFileName + QStringLiteral("factory_p.h"));
    writeCoreFactoryImplementation(baseFileName + QStringLiteral("factory.cpp"));
}

void CodeGenerator::generateExtensionClasses(const QString &baseFileName) const
{
    writeExtensionHeader(baseFileName + QStringLiteral(".h"));
    writeExtensionImplementation(baseFileName + QStringLiteral(".cpp"));
}

bool CodeGenerator::isLegacyVersion(Version v) const
{
    return (v.major < 3 || (v.major == 3 && v.minor == 0));
}

bool CodeGenerator::versionHasProfiles(Version v) const
{
    VersionProfile vp;
    vp.version = v;
    return vp.hasProfiles();
}

void CodeGenerator::writeCoreHelperClasses(const QString &fileName, ClassComponent component) const
{
    if (!m_parser)
        return;

    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
        return;
    QTextStream stream(&file);

    // Write the preamble
    writePreamble(fileName, stream);

    // Iterate over each OpenGL version. For each version output a private class for
    // core functions and a private class for deprecated functions.
    const QString privateRootClass = QStringLiteral("QOpenGLVersionFunctionsBackend");
    Q_FOREACH (const VersionProfile &versionProfile, m_parser->versionProfiles()) {
        switch (component) {
        case Declaration:
            writeBackendClassDeclaration(stream, versionProfile, privateRootClass);
            break;

        case Definition:
            writeBackendClassImplementation(stream, versionProfile, privateRootClass);
            break;
        }
    }

    // Write the postamble
    writePostamble(fileName, stream);
}

void CodeGenerator::writeCoreClasses(const QString &baseFileName) const
{
    // Iterate over each OpenGL version. For each version output a public class (for legacy
    // versions or two public classes (for modern versions with profiles). Each public class
    // is given pointers to private classes containing the actual entry points. For example,
    // the class for OpenGL 1.1 will have pointers to the private classes for 1.0 core, 1.1
    // core, 1.0 deprecated and 1.1 deprecated. Whereas the class for OpenGL 3.2 Core profile
    // will have pointers to the private classes for 1.0 core, 1.1 core, ..., 3.2 core but
    // not to any of the deprecated private classes
    QList<ClassComponent> components = (QList<ClassComponent>() << Declaration << Definition);
    Q_FOREACH (const ClassComponent &component, components) {
        const QString rootClass = QStringLiteral("QAbstractOpenGLFunctions");
        Q_FOREACH (const Version &classVersion, m_parser->versions()) {
            VersionProfile v;
            v.version = classVersion;
            v.profile = VersionProfile::CompatibilityProfile;

            if (isLegacyVersion(classVersion)) {
                switch (component) {
                case Declaration:
                    writePublicClassDeclaration(baseFileName, v, rootClass);
                    break;

                case Definition:
                    writePublicClassImplementation(baseFileName, v, rootClass);
                    break;
                }
            } else {
                switch (component) {
                case Declaration:
                    writePublicClassDeclaration(baseFileName, v, rootClass);
                    v.profile = VersionProfile::CoreProfile;
                    writePublicClassDeclaration(baseFileName, v, rootClass);
                    break;

                case Definition:
                    writePublicClassImplementation(baseFileName, v, rootClass);
                    v.profile = VersionProfile::CoreProfile;
                    writePublicClassImplementation(baseFileName, v, rootClass);
                    break;
                }
            }
        }
    }
}

void CodeGenerator::writeCoreFactoryHeader(const QString &fileName) const
{
    if (!m_parser)
        return;

    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
        return;
    QTextStream stream(&file);

    // Write the preamble
    writePreamble(fileName, stream);

    // Write the postamble
    writePostamble(fileName, stream);
}

void CodeGenerator::writeCoreFactoryImplementation(const QString &fileName) const
{
    if (!m_parser)
        return;

    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
        return;
    QTextStream stream(&file);

    // Write the preamble
    writePreamble(fileName, stream);

    // Get the set of version functions classes we need to create
    QList<Version> versions = m_parser->versions();
    qSort(versions.begin(), versions.end(), qGreater<Version>());

    // Outout the #include statements
    stream << QStringLiteral("#if !defined(QT_OPENGL_ES_2)") << endl;
    Q_FOREACH (const Version &classVersion, versions) {
        if (!versionHasProfiles(classVersion)) {
            stream << QString(QStringLiteral("#include \"qopenglfunctions_%1_%2.h\""))
                      .arg(classVersion.major)
                      .arg(classVersion.minor) << endl;
        } else {
            const QList<VersionProfile::OpenGLProfile> profiles = (QList<VersionProfile::OpenGLProfile>()
                << VersionProfile::CoreProfile << VersionProfile::CompatibilityProfile);

            Q_FOREACH (const VersionProfile::OpenGLProfile profile, profiles) {
                const QString profileSuffix = profile == VersionProfile::CoreProfile
                                            ? QStringLiteral("core")
                                            : QStringLiteral("compatibility");
                stream << QString(QStringLiteral("#include \"qopenglfunctions_%1_%2_%3.h\""))
                          .arg(classVersion.major)
                          .arg(classVersion.minor)
                          .arg(profileSuffix) << endl;
            }
        }
    }
    stream << QStringLiteral("#else") << endl;
    stream << QStringLiteral("#include \"qopenglfunctions_es2.h\"") << endl;
    stream << QStringLiteral("#endif") << endl;

    stream << endl;

    stream << QStringLiteral("QT_BEGIN_NAMESPACE") << endl << endl;
    stream << QStringLiteral("QAbstractOpenGLFunctions *QOpenGLVersionFunctionsFactory::create(const QOpenGLVersionProfile &versionProfile)") << endl;
    stream << QStringLiteral("{") << endl;
    stream << QStringLiteral("#if !defined(QT_OPENGL_ES_2)") << endl;
    stream << QStringLiteral("    const int major = versionProfile.version().first;") << endl;
    stream << QStringLiteral("    const int minor = versionProfile.version().second;") << endl << endl;

    // Iterate over classes with profiles
    stream << QStringLiteral("    if (versionProfile.hasProfiles()) {") << endl;
    stream << QStringLiteral("        switch (versionProfile.profile()) {") << endl;
    const QList<VersionProfile::OpenGLProfile> profiles = (QList<VersionProfile::OpenGLProfile>()
        << VersionProfile::CoreProfile << VersionProfile::CompatibilityProfile);
    Q_FOREACH (const VersionProfile::OpenGLProfile profile, profiles) {
        const QString caseLabel = profile == VersionProfile::CoreProfile
                                ? QStringLiteral("QSurfaceFormat::CoreProfile")
                                : QStringLiteral("QSurfaceFormat::CompatibilityProfile");
        stream << QString(QStringLiteral("        case %1:")).arg(caseLabel) << endl;

        int i = 0;
        Q_FOREACH (const Version &classVersion, versions) {
            if (!versionHasProfiles(classVersion))
                continue;

            const QString ifString = (i++ == 0) ? QStringLiteral("if") : QStringLiteral("else if");
            stream << QString(QStringLiteral("            %1 (major == %2 && minor == %3)"))
                      .arg(ifString)
                      .arg(classVersion.major)
                      .arg(classVersion.minor) << endl;

            VersionProfile v;
            v.version = classVersion;
            v.profile = profile;
            stream << QString(QStringLiteral("                return new %1;"))
                      .arg(generateClassName(v)) << endl;
        }

        stream << QStringLiteral("            break;") << endl << endl;
    }

    stream << QStringLiteral("        case QSurfaceFormat::NoProfile:") << endl;
    stream << QStringLiteral("        default:") << endl;
    stream << QStringLiteral("            break;") << endl;
    stream << QStringLiteral("        };") << endl;
    stream << QStringLiteral("    } else {") << endl;

    // Iterate over the legacy classes (no profiles)
    int i = 0;
    Q_FOREACH (const Version &classVersion, versions) {
        if (versionHasProfiles(classVersion))
            continue;

        const QString ifString = (i++ == 0) ? QStringLiteral("if") : QStringLiteral("else if");
        stream << QString(QStringLiteral("        %1 (major == %2 && minor == %3)"))
                  .arg(ifString)
                  .arg(classVersion.major)
                  .arg(classVersion.minor) << endl;

        VersionProfile v;
        v.version = classVersion;
        stream << QString(QStringLiteral("            return new %1;"))
                  .arg(generateClassName(v)) << endl;
    }

    stream << QStringLiteral("    }") << endl;
    stream << QStringLiteral("    return 0;") << endl;

    stream << QStringLiteral("#else") << endl;
    stream << QStringLiteral("    Q_UNUSED(versionProfile);") << endl;
    stream << QStringLiteral("    return new QOpenGLFunctions_ES2;") << endl;
    stream << QStringLiteral("#endif") << endl;
    stream << QStringLiteral("}") << endl;

    // Write the postamble
    writePostamble(fileName, stream);
}

/**
  \returns all functions to be included in the class defined by \a classVersionProfile
 */
FunctionCollection CodeGenerator::functionCollection( const VersionProfile& classVersionProfile ) const
{
    const Version classVersion = classVersionProfile.version;
    FunctionCollection functionSet;
    QList<Version> versions = m_parser->versions();

    // Populate these based upon the class version and profile
    Version minVersion;
    minVersion.major = 1;
    minVersion.minor = 0;
    Version maxVersion = classVersion;
    QList<VersionProfile::OpenGLProfile> profiles;
    profiles << VersionProfile::CoreProfile; // Always need core functions

    if (isLegacyVersion(classVersion)
        || (classVersionProfile.hasProfiles()
         && classVersionProfile.profile == VersionProfile::CompatibilityProfile)) {
        // For versions < 3.1 and Compatibility profile we include both core and deprecated functions
        profiles << VersionProfile::CompatibilityProfile;
    }

    Q_FOREACH (const Version &v, versions) {
        // Only include functions from versions in the range
        if (v < minVersion)
            continue;
        if (v > maxVersion)
            break;

        Q_FOREACH (VersionProfile::OpenGLProfile profile, profiles) {
            // Combine version and profile for this subset of functions
            VersionProfile version;
            version.version = v;
            version.profile = profile;

            // Fetch the functions and add to collection for this class
            QList<Function> functions = m_parser->functionsForVersion(version);
            functionSet.insert(version, functions);
        }
    }

    return functionSet;
}

void CodeGenerator::writePreamble(const QString &baseFileName, QTextStream &stream, const QString replacement) const
{
    const QString fileName = baseFileName + QStringLiteral(".header");
    if (!QFile::exists(fileName))
        return;

    QFile file(fileName);
    if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QTextStream preambleStream(&file);
        QString preamble = preambleStream.readAll();
        if (!replacement.isEmpty())
            preamble.replace(QStringLiteral("__VERSION__"), replacement, Qt::CaseSensitive);
        stream << preamble;
    }
}

void CodeGenerator::writePostamble(const QString &baseFileName, QTextStream &stream) const
{
    const QString fileName = baseFileName + QStringLiteral(".footer");
    if (!QFile::exists(fileName))
        return;

    QFile file(fileName);
    if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QTextStream postambleStream(&file);
        QString postamble = postambleStream.readAll();
        stream << postamble;
    }
}

QString CodeGenerator::passByType(const Argument &arg) const
{
    QString passBy;
    switch (arg.mode) {
    case Argument::Reference:
    case Argument::Array:
        passBy = QStringLiteral("*");
        break;

    default:
    case Argument::Value:
        passBy = QString();
    }
    return passBy;
}

QString CodeGenerator::safeArgumentName(const QString& arg) const
{
    if (arg == QLatin1String("near")) // MS Windows defines near and far
        return QStringLiteral("nearVal");
    else if (arg == QLatin1String("far"))
        return QStringLiteral("farVal");
    else if (arg == QLatin1String("d"))
        return QStringLiteral("dd"); // Don't shadow d pointer
    else
        return arg;
}

QString CodeGenerator::generateClassName(const VersionProfile &classVersion, ClassVisibility visibility) const
{
    QString className;
    switch ( visibility ) {
    case Public: {
        // Class name and base class
        QString profileSuffix;
        if (classVersion.hasProfiles())
            profileSuffix = (classVersion.profile == VersionProfile::CoreProfile ? QStringLiteral("_Core") : QStringLiteral("_Compatibility"));

        className = QString(QStringLiteral("QOpenGLFunctions_%1_%2%3"))
                    .arg(classVersion.version.major)
                    .arg(classVersion.version.minor)
                    .arg(profileSuffix);
        break;
    }
    case Private: {
        QString statusSuffix = (classVersion.profile == VersionProfile::CoreProfile ? QStringLiteral("_Core") : QStringLiteral("_Deprecated"));

        className = QString(QStringLiteral("QOpenGLFunctions_%1_%2%3Private"))
                    .arg(classVersion.version.major)
                    .arg(classVersion.version.minor)
                    .arg(statusSuffix);
        break;
        }
    }

    return className;
}

void CodeGenerator::writeBackendClassDeclaration(QTextStream &stream,
                                                 const VersionProfile &versionProfile,
                                                 const QString &baseClass) const
{
    const QString className = backendClassName(versionProfile);
    stream << QString(QStringLiteral("class %1 : public %2"))
              .arg(className)
              .arg(baseClass)
           << endl;
    stream << QStringLiteral("{") << endl;
    stream << QStringLiteral("public:") << endl;
    stream << QString( QStringLiteral("    %1(QOpenGLContext *context);") ).arg(className) << endl << endl;

    // Output function used for generating key used in QOpenGLContextPrivate
    stream << QStringLiteral("    static QOpenGLVersionStatus versionStatus();") << endl << endl;

    // Get the functions needed for this class
    FunctionList functions = m_parser->functionsForVersion(versionProfile);
    FunctionCollection functionSet;
    functionSet.insert(versionProfile, functions);

    // Declare the functions
    writeClassFunctionDeclarations(stream, functionSet, Private);

    stream << QStringLiteral("};") << endl;
    stream << endl;
}

void CodeGenerator::writeBackendClassImplementation(QTextStream &stream,
                                                    const VersionProfile &versionProfile,
                                                    const QString &baseClass) const
{
    const QString className = backendClassName(versionProfile);
    stream << QString(QStringLiteral("%1::%1(QOpenGLContext *context)")).arg(className) << endl;
    stream << QString(QStringLiteral("    : %1(context)")).arg(baseClass) << endl
           << QStringLiteral("{") << endl;

    // Resolve the entry points for this set of functions
    // Get the functions needed for this class
    FunctionList functions = m_parser->functionsForVersion(versionProfile);
    FunctionCollection functionSet;
    functionSet.insert(versionProfile, functions);
    writeEntryPointResolutionCode(stream, functionSet);

    stream << QStringLiteral("}") << endl << endl;

    stream << QString(QStringLiteral("QOpenGLVersionStatus %1::versionStatus()")).arg(className) << endl;
    stream << QStringLiteral("{") << endl;
    const QString status = versionProfile.profile == VersionProfile::CoreProfile
                         ? QStringLiteral("QOpenGLVersionStatus::CoreStatus")
                         : QStringLiteral("QOpenGLVersionStatus::DeprecatedStatus");
    stream << QString(QStringLiteral("    return QOpenGLVersionStatus(%1, %2, %3);"))
              .arg(versionProfile.version.major)
              .arg(versionProfile.version.minor)
              .arg(status) << endl;
    stream << QStringLiteral("}") << endl << endl;
}

QString CodeGenerator::coreClassFileName(const VersionProfile &versionProfile,
                                         const QString& fileExtension) const
{
    QString profileSuffix;
    if (versionProfile.hasProfiles())
        profileSuffix = (versionProfile.profile == VersionProfile::CoreProfile ? QStringLiteral("_core") : QStringLiteral("_compatibility"));

    const QString fileName = QString(QStringLiteral("qopenglfunctions_%1_%2%3.%4"))
                             .arg(versionProfile.version.major)
                             .arg(versionProfile.version.minor)
                             .arg(profileSuffix)
                             .arg(fileExtension);
    return fileName;
}

void CodeGenerator::writePublicClassDeclaration(const QString &baseFileName,
                                                const VersionProfile &versionProfile,
                                                const QString &baseClass) const
{
    const QString fileName = coreClassFileName(versionProfile, QStringLiteral("h"));
    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
        return;
    QTextStream stream(&file);

    // Write the preamble
    const QString templateFileName = QString(QStringLiteral("%1__VERSION__.h"))
                                     .arg(baseFileName);
    QString profileSuffix;
    if (versionProfile.hasProfiles())
        profileSuffix = (versionProfile.profile == VersionProfile::CoreProfile ? QStringLiteral("_CORE") : QStringLiteral("_COMPATIBILITY"));

    const QString versionProfileString = QString(QStringLiteral("_%1_%2%3"))
                                         .arg(versionProfile.version.major)
                                         .arg(versionProfile.version.minor)
                                         .arg(profileSuffix);
    writePreamble(templateFileName, stream, versionProfileString);

    // Ctor, dtor, and initialize function;
    const QString className = generateClassName(versionProfile, Public);
    stream << QString(QStringLiteral("class Q_GUI_EXPORT %1 : public %2"))
              .arg(className)
              .arg(baseClass)
           << endl;
    stream << QStringLiteral("{") << endl;
    stream << QStringLiteral("public:") << endl;
    stream << QString(QStringLiteral("    %1();")).arg(className) << endl;
    stream << QString(QStringLiteral("    ~%1();")).arg(className) << endl << endl;
    stream << QStringLiteral("    bool initializeOpenGLFunctions() Q_DECL_OVERRIDE;") << endl << endl;

    // Get the functions needed for this class and declare them
    FunctionCollection functionSet = functionCollection(versionProfile);
    writeClassFunctionDeclarations(stream, functionSet, Public);

    // isCompatible function and backend variables
    stream << QStringLiteral("private:") << endl;
    stream << QStringLiteral("    friend class QOpenGLContext;") << endl << endl;
    stream << QStringLiteral("    static bool isContextCompatible(QOpenGLContext *context);") << endl;
    stream << QStringLiteral("    static QOpenGLVersionProfile versionProfile();") << endl << endl;
    writeBackendVariableDeclarations(stream, backendsForFunctionCollection(functionSet));

    stream << QStringLiteral("};") << endl << endl;

    // Output the inline functions that forward OpenGL calls to the backends' entry points
    writeClassInlineFunctions(stream, className, functionSet);

    // Write the postamble
    writePostamble(templateFileName, stream);
}

void CodeGenerator::writePublicClassImplementation(const QString &baseFileName,
                                                   const VersionProfile &versionProfile,
                                                   const QString& baseClass) const
{
    const QString fileName = coreClassFileName(versionProfile, QStringLiteral("cpp"));
    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
        return;
    QTextStream stream(&file);

    // Write the preamble
    const QString templateFileName = QString(QStringLiteral("%1__VERSION__.cpp"))
                                     .arg(baseFileName);
    QString profileSuffix;
    if (versionProfile.hasProfiles())
        profileSuffix = (versionProfile.profile == VersionProfile::CoreProfile ? QStringLiteral("_core") : QStringLiteral("_compatibility"));

    const QString versionProfileString = QString(QStringLiteral("_%1_%2%3"))
                                         .arg(versionProfile.version.major)
                                         .arg(versionProfile.version.minor)
                                         .arg(profileSuffix);
    writePreamble(templateFileName, stream, versionProfileString);

    const QString className = generateClassName(versionProfile, Public);
    stream << QStringLiteral("/*!") << endl
           << QStringLiteral("    \\class ") << className << endl
           << QStringLiteral("    \\inmodule QtGui") << endl
           << QStringLiteral("    \\since 5.1") << endl
           << QStringLiteral("    \\wrapper") << endl
           << QStringLiteral("    \\brief The ") << className
           << QString(QStringLiteral(" class provides all functions for OpenGL %1.%2 "))
                .arg(versionProfile.version.major)
                .arg(versionProfile.version.minor);

    if (!profileSuffix.isEmpty()) {
        profileSuffix.remove(0, 1);
        profileSuffix.append(QStringLiteral(" profile"));
    } else {
        profileSuffix = "specification";
    }

    stream << profileSuffix << QStringLiteral(".") << endl << endl
           << QStringLiteral("    This class is a wrapper for functions from ")
           << QString(QStringLiteral("OpenGL %1.%2 "))
                .arg(versionProfile.version.major)
                .arg(versionProfile.version.minor)
           << profileSuffix << QStringLiteral(".") << endl
           << QStringLiteral("    See reference pages on \\l {http://www.opengl.org/sdk/docs/}{opengl.org}") << endl
           << QStringLiteral("    for function documentation.") << endl << endl
           << QStringLiteral("    \\sa QAbstractOpenGLFunctions") << endl
           << QStringLiteral("*/") << endl << endl;

    // Get the data we'll need for this class implementation
    FunctionCollection functionSet = functionCollection(versionProfile);
    QList<VersionProfile> backends = backendsForFunctionCollection(functionSet);

    // Output default constructor
    stream << className << QStringLiteral("::") << className << QStringLiteral("()") << endl;
    stream << QStringLiteral(" : ") << baseClass << QStringLiteral("()");
    Q_FOREACH (const VersionProfile &v, backends)
        stream << endl << QString(QStringLiteral(" , %1(0)")).arg(backendVariableName(v));
    stream << endl << QStringLiteral("{") << endl << QStringLiteral("}") << endl << endl;

    // Output the destructor
    stream << className << QStringLiteral("::~") << className << QStringLiteral("()") << endl;
    stream << QStringLiteral("{") << endl;
    Q_FOREACH (const VersionProfile &v, backends) {
        const QString backendVar = backendVariableName(v);
        const QString backendClass = backendClassName(v);
        stream << QString(QStringLiteral("    if (%1 && !%1->refs.deref()) {")).arg(backendVar) << endl;
        stream << QString(QStringLiteral("        QAbstractOpenGLFunctionsPrivate::removeFunctionsBackend(%1->context, %2::versionStatus());"))
                  .arg(backendVar)
                  .arg(backendClass) << endl;
        stream << QString(QStringLiteral("        delete %1;")).arg(backendVar) << endl;
        stream << QStringLiteral("    }") << endl;
    }
    stream << QStringLiteral("}") << endl << endl;

    // Output the initialize function that creates the backend objects
    stream << QString(QStringLiteral("bool %1::initializeOpenGLFunctions()")).arg(className) << endl;
    stream << QStringLiteral("{") << endl;

    stream << QStringLiteral("    if ( isInitialized() )") << endl;
    stream << QStringLiteral("        return true;") << endl << endl;
    stream << QStringLiteral("    QOpenGLContext* context = QOpenGLContext::currentContext();") << endl << endl;
    stream << QStringLiteral("    // If owned by a context object make sure it is current.") << endl;
    stream << QStringLiteral("    // Also check that current context is capable of resolving all needed functions") << endl;
    stream << QStringLiteral("    if (((owningContext() && owningContext() == context) || !owningContext())") << endl;
    stream << QString(QStringLiteral("        && %1::isContextCompatible(context))")).arg(className) << endl;
    stream << QStringLiteral("    {") << endl;
    stream << QStringLiteral("        // Associate with private implementation, creating if necessary") << endl;
    stream << QStringLiteral("        // Function pointers in the backends are resolved at creation time") << endl;
    stream << QStringLiteral("        QOpenGLVersionFunctionsBackend* d = 0;") << endl;

    Q_FOREACH (const VersionProfile &v, backends) {
        const QString backendClass = backendClassName(v);
        const QString backendVar = backendVariableName(v);
        stream << QString(QStringLiteral("        d = QAbstractOpenGLFunctionsPrivate::functionsBackend(context, %1::versionStatus());"))
                  .arg(backendClass) << endl;
        stream << QStringLiteral("        if (!d) {") << endl;
        stream << QString(QStringLiteral("            d = new %1(context);")).arg(backendClass) << endl;
        stream << QString(QStringLiteral("            QAbstractOpenGLFunctionsPrivate::insertFunctionsBackend(context, %1::versionStatus(), d);"))
                  .arg(backendClass) << endl;
        stream << QStringLiteral("        }") << endl;
        stream << QString(QStringLiteral("        %1 = static_cast<%2*>(d);")).arg(backendVar).arg(backendClass) << endl;
        stream << QStringLiteral("        d->refs.ref();") << endl << endl;
    }

    stream << QStringLiteral("        QAbstractOpenGLFunctions::initializeOpenGLFunctions();") << endl;
    stream << QStringLiteral("    }") << endl;

    stream << QStringLiteral("    return isInitialized();") << endl;
    stream << QStringLiteral("}") << endl << endl;

    // Output the context compatibility check function
    stream << QString(QStringLiteral("bool %1::isContextCompatible(QOpenGLContext *context)")).arg(className) << endl;
    stream << QStringLiteral("{") << endl;
    stream << QStringLiteral("    Q_ASSERT(context);") << endl;
    stream << QStringLiteral("    QSurfaceFormat f = context->format();") << endl;
    stream << QStringLiteral("    const QPair<int, int> v = qMakePair(f.majorVersion(), f.minorVersion());") << endl;
    stream << QString(QStringLiteral("    if (v < qMakePair(%1, %2))"))
              .arg(versionProfile.version.major)
              .arg(versionProfile.version.minor) << endl;
    stream << QStringLiteral("        return false;") << endl << endl;

    // If generating a legacy or compatibility profile class we need to ensure that
    // the context does not expose only core functions
    if (versionProfile.profile != VersionProfile::CoreProfile) {
        stream << QStringLiteral("    if (f.profile() == QSurfaceFormat::CoreProfile)") << endl;
        stream << QStringLiteral("        return false;") << endl << endl;
    }

    stream << QStringLiteral("    return true;") << endl;
    stream << QStringLiteral("}") << endl << endl;

    // Output static function used as helper in template versionFunctions() function
    // in QOpenGLContext
    stream << QString(QStringLiteral("QOpenGLVersionProfile %1::versionProfile()")).arg(className) << endl;
    stream << QStringLiteral("{") << endl;
    stream << QStringLiteral("    QOpenGLVersionProfile v;") << endl;
    stream << QString(QStringLiteral("    v.setVersion(%1, %2);"))
              .arg(versionProfile.version.major)
              .arg(versionProfile.version.minor) << endl;
    if (versionProfile.hasProfiles()) {
        const QString profileName = versionProfile.profile == VersionProfile::CoreProfile
                                    ? QStringLiteral("QSurfaceFormat::CoreProfile")
                                    : QStringLiteral("QSurfaceFormat::CompatibilityProfile");
        stream << QString(QStringLiteral("    v.setProfile(%1);")).arg(profileName) << endl;
    }
    stream << QStringLiteral("    return v;") << endl;
    stream << QStringLiteral("}") << endl;

    // Write the postamble
    writePostamble(templateFileName, stream);
}

void CodeGenerator::writeClassFunctionDeclarations(QTextStream &stream,
                                                   const FunctionCollection &functionSet,
                                                   ClassVisibility visibility) const
{
    Q_FOREACH (const VersionProfile &version, functionSet.keys()) {
        // Add a comment to the header
        stream << QString(QStringLiteral("    // OpenGL %1.%2 %3 functions"))
                  .arg(version.version.major)
                  .arg(version.version.minor)
                  .arg((version.profile == VersionProfile::CoreProfile) ? QStringLiteral("core") : QStringLiteral("deprecated"))
               << endl;

        // Output function declarations
        FunctionList functions = functionSet.value(version);
        Q_FOREACH (const Function &f, functions)
            writeFunctionDeclaration(stream, f, visibility);
        stream << endl;
    } // version and profile
}

void CodeGenerator::writeFunctionDeclaration(QTextStream &stream, const Function &f, ClassVisibility visibility) const
{
    QStringList argList;
    Q_FOREACH (const Argument &arg, f.arguments) {
        QString a = QString(QStringLiteral("%1%2 %3%4"))
                    .arg((arg.direction == Argument::In && arg.mode != Argument::Value) ? QStringLiteral("const ") : QString())
                    .arg(arg.type)
                    .arg(passByType(arg))
                    .arg(safeArgumentName(arg.name));
        argList.append(a);
    }
    QString args = argList.join(QStringLiteral(", "));

    QString signature;
    switch (visibility) {
    case Public:
        signature = QString(QStringLiteral("    %1 gl%2(%3);")).arg(f.returnType).arg(f.name).arg(args);
        break;

    case Private:
    default:
        signature = QString(QStringLiteral("    %1 (QOPENGLF_APIENTRYP %2)(%3);")).arg(f.returnType).arg(f.name).arg(args);
    }
    stream << signature << endl;
}

void CodeGenerator::writeClassInlineFunctions(QTextStream &stream,
                                              const QString &className,
                                              const FunctionCollection &functionSet) const
{
    Q_FOREACH (const VersionProfile &version, functionSet.keys()) {

        // Add a comment to the header
        stream << QString(QStringLiteral("// OpenGL %1.%2 %3 functions"))
                  .arg(version.version.major)
                  .arg(version.version.minor)
                  .arg((version.profile == VersionProfile::CoreProfile) ? QStringLiteral("core") : QStringLiteral("deprecated"))
               << endl;

        // Output function declarations
        const QString backendVar = backendVariableName(version);
        FunctionList functions = functionSet.value(version);
        Q_FOREACH (const Function &f, functions)
            writeInlineFunction(stream, className, backendVar, f);

        stream << endl;

    } // version and profile
}

void CodeGenerator::writeInlineFunction(QTextStream &stream, const QString &className,
                                        const QString &backendVar, const Function &f) const
{
    QStringList argList;
    Q_FOREACH (const Argument &arg, f.arguments) {
        QString a = QString(QStringLiteral("%1%2 %3%4"))
                    .arg((arg.direction == Argument::In && arg.mode != Argument::Value) ? QStringLiteral("const ") : QString())
                    .arg(arg.type)
                    .arg(passByType(arg))
                    .arg(safeArgumentName(arg.name));
        argList.append(a);
    }
    QString args = argList.join(", ");


    QString signature = QString(QStringLiteral("inline %1 %2::gl%3(%4)"))
                        .arg(f.returnType)
                        .arg(className)
                        .arg(f.name)
                        .arg(args);
    stream << signature << endl << QStringLiteral("{") << endl;

    QStringList argumentNames;
    Q_FOREACH (const Argument &arg, f.arguments)
        argumentNames.append(safeArgumentName(arg.name));
    QString argNames = argumentNames.join(", ");

    if (f.returnType == QLatin1String("void"))
        stream << QString(QStringLiteral("    %1->%2(%3);")).arg(backendVar).arg(f.name).arg(argNames) << endl;
    else
        stream << QString(QStringLiteral("    return %1->%2(%3);")).arg(backendVar).arg(f.name).arg(argNames) << endl;
    stream << QStringLiteral("}") << endl << endl;
}

void CodeGenerator::writeEntryPointResolutionCode(QTextStream &stream,
                                                  const FunctionCollection &functionSet) const
{
    bool hasModuleHandle = false;
    Q_FOREACH (const VersionProfile &version, functionSet.keys()) {

        // Add a comment to the header
        stream << QString(QStringLiteral("    // OpenGL %1.%2 %3 functions"))
                  .arg(version.version.major)
                  .arg(version.version.minor)
                  .arg((version.profile == VersionProfile::CoreProfile) ? QStringLiteral("core") : QStringLiteral("deprecated"))
               << endl;

        // Output function declarations
        FunctionList functions = functionSet.value(version);

        bool useGetProcAddress = (version.version.major == 1 && (version.version.minor == 0 || version.version.minor == 1));
        if (useGetProcAddress) {
            stream << "#if defined(Q_OS_WIN)" << endl;
            if (!hasModuleHandle) {
                stream << "    HMODULE handle = GetModuleHandleA(\"opengl32.dll\");" << endl;
                hasModuleHandle = true;
            }

            Q_FOREACH (const Function &f, functions)
                writeEntryPointResolutionStatement(stream, f, QString(), useGetProcAddress);

            stream << "#else" << endl;
        }

        Q_FOREACH (const Function &f, functions)
            writeEntryPointResolutionStatement(stream, f);

        if (useGetProcAddress)
            stream << "#endif" << endl;

        stream << endl;

    } // version and profile
}

void CodeGenerator::writeEntryPointResolutionStatement(QTextStream &stream, const Function &f,
                                                       const QString &prefix, bool useGetProcAddress) const
{
    QStringList argList;
    Q_FOREACH (const Argument &arg, f.arguments) {
        QString a = QString("%1%2 %3")
                    .arg((arg.direction == Argument::In && arg.mode != Argument::Value) ? QStringLiteral("const ") : QString())
                    .arg(arg.type)
                    .arg(passByType(arg));
        argList.append(a);
    }
    QString args = argList.join(QStringLiteral(", "));

    QString signature;
    if (!useGetProcAddress) {
        signature = QString(QStringLiteral("    %4%3 = reinterpret_cast<%1 (QOPENGLF_APIENTRYP)(%2)>(context->getProcAddress(\"gl%3\"));"))
                    .arg(f.returnType)
                    .arg(args)
                    .arg(f.name)
                    .arg(prefix);
    } else {
        signature = QString(QStringLiteral("    %4%3 = reinterpret_cast<%1 (QOPENGLF_APIENTRYP)(%2)>(GetProcAddress(handle, \"gl%3\"));"))
                    .arg(f.returnType)
                    .arg(args)
                    .arg(f.name)
                    .arg(prefix);
    }
    stream << signature << endl;
}

QList<VersionProfile> CodeGenerator::backendsForFunctionCollection(const FunctionCollection &functionSet) const
{
    QList<VersionProfile> backends;
    Q_FOREACH (const VersionProfile &versionProfile, functionSet.keys()) {
        if (m_parser->versionProfiles().contains(versionProfile))
            backends.append(versionProfile);
    }
    return backends;
}

QString CodeGenerator::backendClassName(const VersionProfile &v) const
{
    QString statusSuffix = v.profile == VersionProfile::CoreProfile
                         ? QStringLiteral("_Core")
                         : QStringLiteral("_Deprecated");
    const QString className = QString(QStringLiteral("QOpenGLFunctions_%1_%2%3Backend"))
                              .arg(v.version.major)
                              .arg(v.version.minor)
                              .arg(statusSuffix);
    return className;
}

QString CodeGenerator::backendVariableName(const VersionProfile &v) const
{
    const QString status = (v.profile == VersionProfile::CoreProfile)
                         ? QStringLiteral("Core")
                         : QStringLiteral("Deprecated");
    const QString varName = QString(QStringLiteral("d_%1_%2_%3"))
                            .arg(v.version.major)
                            .arg(v.version.minor)
                            .arg(status);
    return varName;
}

void CodeGenerator::writeBackendVariableDeclarations(QTextStream &stream, const QList<VersionProfile> &backends) const
{
    // We need a private class for each version and profile (status: core or deprecated)
    Q_FOREACH (const VersionProfile &v, backends) {
        const QString className = backendClassName(v);
        const QString varName = backendVariableName(v);
        stream << QString(QStringLiteral("    %1* %2;")).arg(className).arg(varName) << endl;
    }
}

void CodeGenerator::writeExtensionHeader(const QString &fileName) const
{
    if (!m_parser)
        return;

    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
        return;
    QTextStream stream(&file);

    // Write the preamble
    writePreamble(fileName, stream);

    // Iterate through the list of extensions and create one class per extension
    QStringList extensions = m_parser->extensions();
    Q_FOREACH (const QString &extension, extensions) {
        writeExtensionClassDeclaration(stream, extension, Private);
        writeExtensionClassDeclaration(stream, extension, Public);
    }

    // Write the postamble
    writePostamble(fileName, stream);
}

void CodeGenerator::writeExtensionImplementation(const QString &fileName) const
{
    if (!m_parser)
        return;

    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
        return;
    QTextStream stream(&file);

    // Write the preamble
    writePreamble(fileName, stream);

    // Iterate through the list of extensions and create one class per extension
    QStringList extensions = m_parser->extensions();
    Q_FOREACH (const QString &extension, extensions)
        writeExtensionClassImplementation(stream, extension);

    // Write the postamble
    writePostamble(fileName, stream);
}

void CodeGenerator::writeExtensionClassDeclaration(QTextStream &stream, const QString &extension, ClassVisibility visibility) const
{
    const QString className = generateExtensionClassName(extension, visibility);

    QString baseClass = (visibility == Public) ? QStringLiteral("QAbstractOpenGLExtension") : QStringLiteral("QAbstractOpenGLExtensionPrivate");

    stream << QString(QStringLiteral("class %2 : public %3"))
              .arg(className)
              .arg(baseClass)
           << endl << "{" << endl << "public:" << endl;

    if (visibility == Public) {
        // Default constructor
        stream << QStringLiteral("    ") << className << QStringLiteral("();") << endl << endl;

        // Base class virtual function(s)
        QString resolveFunction = QStringLiteral("    bool initializeOpenGLFunctions() Q_DECL_FINAL;");
        stream << resolveFunction << endl << endl;
    }

    // Output the functions provided by this extension
    QList<Function> functions = m_parser->functionsForExtension(extension);
    Q_FOREACH (const Function &f, functions)
        writeFunctionDeclaration(stream, f, visibility);

    if (visibility == Public) {
        // Write out the protected ctor
        stream << endl << QStringLiteral("protected:") << endl;
        stream << QStringLiteral("    Q_DECLARE_PRIVATE(") << className << QStringLiteral(")") << endl;
    }

    // End the class declaration
    stream << QStringLiteral("};") << endl << endl;

    // Output the inline functions for public class
    if (visibility == Public) {
        Q_FOREACH (const Function &f, functions)
            writeExtensionInlineFunction(stream, className, f);
    }
}

void CodeGenerator::writeExtensionInlineFunction(QTextStream &stream, const QString &className, const Function &f) const
{
    QStringList argList;
    Q_FOREACH (const Argument &arg, f.arguments) {
        QString a = QString(QStringLiteral("%1%2 %3%4"))
                    .arg((arg.direction == Argument::In && arg.mode != Argument::Value) ? QStringLiteral("const ") : QString())
                    .arg(arg.type)
                    .arg(passByType(arg))
                    .arg(safeArgumentName(arg.name));
        argList.append(a);
    }
    QString args = argList.join(", ");


    QString signature = QString(QStringLiteral("inline %1 %2::gl%3(%4)"))
                        .arg(f.returnType)
                        .arg(className)
                        .arg(f.name)
                        .arg(args);
    stream << signature << endl << QStringLiteral("{") << endl;

    stream << QString(QStringLiteral("    Q_D(%1);")).arg(className) << endl;

    QStringList argumentNames;
    Q_FOREACH (const Argument &arg, f.arguments)
        argumentNames.append(safeArgumentName(arg.name));
    QString argNames = argumentNames.join(", ");

    if (f.returnType == QStringLiteral("void"))
        stream << QString(QStringLiteral("    d->%1(%2);")).arg(f.name).arg(argNames) << endl;
    else
        stream << QString(QStringLiteral("    return d->%1(%2);")).arg(f.name).arg(argNames) << endl;
    stream << QStringLiteral("}") << endl << endl;
}

void CodeGenerator::writeExtensionClassImplementation(QTextStream &stream, const QString &extension) const
{
    const QString className = generateExtensionClassName(extension);
    const QString privateClassName = generateExtensionClassName(extension, Private);

    // Output default constructor
    stream << className << QStringLiteral("::") << className << QStringLiteral("()") << endl;
    stream << QStringLiteral(" : QAbstractOpenGLExtension(*(new ") << privateClassName << QStringLiteral("))") << endl;
    stream << QStringLiteral("{") << endl << QStringLiteral("}") << endl << endl;


    // Output function to initialize this class
    stream << QStringLiteral("bool ") << className
           << QStringLiteral("::initializeOpenGLFunctions()") << endl
           << QStringLiteral("{") << endl;

    stream << QStringLiteral("    if (isInitialized())") << endl;
    stream << QStringLiteral("        return true;") << endl << endl;

    stream << QStringLiteral("    QOpenGLContext *context = QOpenGLContext::currentContext();") << endl;
    stream << QStringLiteral("    if (!context) {") << endl;
    stream << QStringLiteral("        qWarning(\"A current OpenGL context is required to resolve OpenGL extension functions\");")
           << endl;
    stream << QStringLiteral("        return false;") << endl;
    stream << QStringLiteral("    }") << endl << endl;

    // Output code to resolve entry points for this class
    stream << QStringLiteral("    // Resolve the functions") << endl;
    stream << QStringLiteral("    Q_D(") << className << QStringLiteral(");") << endl;
    stream << endl;

    // Output function declarations
    QList<Function> functions = m_parser->functionsForExtension(extension);
    Q_FOREACH (const Function &f, functions)
        writeEntryPointResolutionStatement(stream, f, QStringLiteral("d->"));

    // Call the base class implementation
    stream << QStringLiteral("    QAbstractOpenGLExtension::initializeOpenGLFunctions();") << endl;

    // Finish off
    stream << QStringLiteral("    return true;") << endl;
    stream << QStringLiteral("}") << endl << endl;
}

QString CodeGenerator::generateExtensionClassName(const QString &extension, ClassVisibility visibility) const
{
    QString visibilitySuffix;
    if (visibility == Private)
        visibilitySuffix = QStringLiteral("Private");

    return QString(QStringLiteral("QOpenGLExtension_%1%2"))
            .arg(extension)
            .arg(visibilitySuffix);
}
