// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

#include "widget.h"
#include "renderwindow.h"
#include <QVBoxLayout>
#include <QComboBox>
#include <QGroupBox>
#include <QRadioButton>
#include <QCheckBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QList>
#include <QByteArray>
#include <QPushButton>
#include <QTextEdit>
#include <QSplitter>
#include <QGuiApplication>
#include <QSurfaceFormat>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QDebug>
#include <QTextStream>

struct Version {
    const char *str;
    int major;
    int minor;
};

static struct Version versions[] = {
    { "1.0", 1, 0 },
    { "1.1", 1, 1 },
    { "1.2", 1, 2 },
    { "1.3", 1, 3 },
    { "1.4", 1, 4 },
    { "1.5", 1, 5 },
    { "2.0", 2, 0 },
    { "2.1", 2, 1 },
    { "3.0", 3, 0 },
    { "3.1", 3, 1 },
    { "3.2", 3, 2 },
    { "3.3", 3, 3 },
    { "4.0", 4, 0 },
    { "4.1", 4, 1 },
    { "4.2", 4, 2 },
    { "4.3", 4, 3 },
    { "4.4", 4, 4 },
    { "4.5", 4, 5 }
};

struct Profile {
    const char *str;
    QSurfaceFormat::OpenGLContextProfile profile;
};

static struct Profile profiles[] = {
    { "none", QSurfaceFormat::NoProfile },
    { "core", QSurfaceFormat::CoreProfile },
    { "compatibility", QSurfaceFormat::CompatibilityProfile }
};

struct Option {
    const char *str;
    QSurfaceFormat::FormatOption option;
};

static struct Option options[] = {
    { "deprecated functions (not forward compatible)", QSurfaceFormat::DeprecatedFunctions },
    { "debug context", QSurfaceFormat::DebugContext },
    { "stereo buffers", QSurfaceFormat::StereoBuffers },
    // This is not a QSurfaceFormat option but is helpful to determine if the driver
    // allows compiling old-style shaders with core profile.
    { "force version 110 shaders", QSurfaceFormat::FormatOption(0) }
};

struct Renderable {
    const char *str;
    QSurfaceFormat::RenderableType renderable;
};

static struct Renderable renderables[] = {
    { "default", QSurfaceFormat::DefaultRenderableType },
#ifndef Q_OS_ANDROID
    { "OpenGL", QSurfaceFormat::OpenGL },
#endif
    { "OpenGL ES", QSurfaceFormat::OpenGLES }
};

void Widget::addVersions(QLayout *layout)
{
    QHBoxLayout *hbox = new QHBoxLayout;
    hbox->setSpacing(20);
    QLabel *label = new QLabel(tr("Context &version: "));
    hbox->addWidget(label);
    m_version = new QComboBox;
    m_version->setMinimumWidth(60);
    label->setBuddy(m_version);
    hbox->addWidget(m_version);
    for (size_t i = 0; i < sizeof(versions) / sizeof(Version); ++i) {
        m_version->addItem(QString::fromLatin1(versions[i].str));
        if (versions[i].major == 2 && versions[i].minor == 0)
            m_version->setCurrentIndex(m_version->count() - 1);
    }

    QPushButton *btn = new QPushButton(tr("Create context"));
    connect(btn, &QPushButton::clicked, this, &Widget::start);
    btn->setMinimumSize(120, 40);
    hbox->addWidget(btn);

    layout->addItem(hbox);
}

void Widget::addProfiles(QLayout *layout)
{
    QGroupBox *groupBox = new QGroupBox(tr("Profile"));
    QVBoxLayout *vbox = new QVBoxLayout;
    for (size_t i = 0; i < sizeof(profiles) / sizeof(Profile); ++i)
        vbox->addWidget(new QRadioButton(QString::fromLatin1(profiles[i].str)));
    static_cast<QRadioButton *>(vbox->itemAt(0)->widget())->setChecked(true);
    groupBox->setLayout(vbox);
    layout->addWidget(groupBox);
    m_profiles = vbox;
}

void Widget::addOptions(QLayout *layout)
{
    QGroupBox *groupBox = new QGroupBox(tr("Options"));
    QVBoxLayout *vbox = new QVBoxLayout;
    for (size_t i = 0; i < sizeof(options) / sizeof(Option); ++i)
        vbox->addWidget(new QCheckBox(QString::fromLatin1(options[i].str)));
    groupBox->setLayout(vbox);
    layout->addWidget(groupBox);
    m_options = vbox;
}

void Widget::addRenderableTypes(QLayout *layout)
{
    QGroupBox *groupBox = new QGroupBox(tr("Renderable type"));
    QVBoxLayout *vbox = new QVBoxLayout;
    for (size_t i = 0; i < sizeof(renderables) / sizeof(Renderable); ++i)
        vbox->addWidget(new QRadioButton(QString::fromLatin1(renderables[i].str)));
    static_cast<QRadioButton *>(vbox->itemAt(0)->widget())->setChecked(true);
    groupBox->setLayout(vbox);
    layout->addWidget(groupBox);
    m_renderables = vbox;
}

void Widget::addRenderWindow()
{
    m_renderWindowLayout->addWidget(m_renderWindowContainer);
}

static QWidget *widgetWithLayout(QLayout *layout)
{
    QWidget *w = new QWidget;
    w->setLayout(layout);
    return w;
}

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    QVBoxLayout *layout = new QVBoxLayout;
    QSplitter *vsplit = new QSplitter(Qt::Vertical);
    layout->addWidget(vsplit);

    QSplitter *hsplit = new QSplitter;

    QVBoxLayout *settingsLayout = new QVBoxLayout;
    addVersions(settingsLayout);
    addProfiles(settingsLayout);
    addOptions(settingsLayout);
    addRenderableTypes(settingsLayout);
    hsplit->addWidget(widgetWithLayout(settingsLayout));

    QVBoxLayout *outputLayout = new QVBoxLayout;
    m_output = new QTextEdit;
    m_output->setReadOnly(true);
    outputLayout->addWidget(m_output);
    m_extensions = new QTextEdit;
    m_extensions->setReadOnly(true);
    outputLayout->addWidget(m_extensions);
    hsplit->addWidget(widgetWithLayout(outputLayout));

    hsplit->setStretchFactor(0, 4);
    hsplit->setStretchFactor(1, 6);
    vsplit->addWidget(hsplit);

    m_renderWindowLayout = new QVBoxLayout;
    vsplit->addWidget(widgetWithLayout(m_renderWindowLayout));
    vsplit->setStretchFactor(1, 5);

    m_renderWindowContainer = new QWidget;
    addRenderWindow();

    QString description;
    QTextStream str(&description);
    str << "Qt " << QT_VERSION_STR << ' ' << QGuiApplication::platformName();
    const char *openGlVariables[] =
        {"QT_ANGLE_PLATFORM", "QT_OPENGL", "QT_OPENGL_BUGLIST", "QT_OPENGL_DLL"};
    const size_t variableCount = sizeof(openGlVariables) / sizeof(openGlVariables[0]);
    for (size_t v = 0; v < variableCount; ++v) {
        if (qEnvironmentVariableIsSet(openGlVariables[v]))
            str << ' ' << openGlVariables[v] << '=' << qgetenv(openGlVariables[v]);
    }
    if (QCoreApplication::testAttribute(Qt::AA_UseOpenGLES))
        str << " Qt::AA_UseOpenGLES";
    if (QCoreApplication::testAttribute(Qt::AA_UseSoftwareOpenGL))
        str << " Qt::AA_UseSoftwareOpenGL";
    if (QCoreApplication::testAttribute(Qt::AA_UseDesktopOpenGL))
        str << " Qt::AA_UseDesktopOpenGL";
    layout->addWidget(new QLabel(description));

    setLayout(layout);
}

void Widget::start()
{
    QSurfaceFormat fmt;

    int idx = m_version->currentIndex();
    if (idx < 0)
        return;
    fmt.setVersion(versions[idx].major, versions[idx].minor);

    for (size_t i = 0; i < sizeof(profiles) / sizeof(Profile); ++i)
        if (static_cast<QRadioButton *>(m_profiles->itemAt(int(i))->widget())->isChecked()) {
            fmt.setProfile(profiles[i].profile);
            break;
        }

    bool forceGLSL110 = false;
    for (size_t i = 0; i < sizeof(options) / sizeof(Option); ++i)
        if (static_cast<QCheckBox *>(m_options->itemAt(int(i))->widget())->isChecked()) {
            if (options[i].option)
                fmt.setOption(options[i].option);
            else if (i == 3)
                forceGLSL110 = true;
        }

    for (size_t i = 0; i < sizeof(renderables) / sizeof(Renderable); ++i)
        if (static_cast<QRadioButton *>(m_renderables->itemAt(int(i))->widget())->isChecked()) {
            fmt.setRenderableType(renderables[i].renderable);
            break;
        }

    // The example rendering will need a depth buffer.
    fmt.setDepthBufferSize(16);

    m_output->clear();
    m_extensions->clear();
    qDebug() << "Requesting surface format" << fmt;

    m_renderWindowLayout->removeWidget(m_renderWindowContainer);
    delete m_renderWindowContainer;

    RenderWindow *renderWindow = new RenderWindow(fmt);
    if (!renderWindow->context()) {
        m_output->append(tr("Failed to create context"));
        delete renderWindow;
        m_renderWindowContainer = new QWidget;
        addRenderWindow();
        return;
    }
    m_surface = renderWindow;

    renderWindow->setForceGLSL110(forceGLSL110);
    connect(renderWindow, &RenderWindow::ready, this, &Widget::renderWindowReady);
    connect(renderWindow, &RenderWindow::error, this, &Widget::renderWindowError);

    m_renderWindowContainer = QWidget::createWindowContainer(renderWindow);
    addRenderWindow();
}

void Widget::printFormat(const QSurfaceFormat &format)
{
    m_output->append(tr("OpenGL version: %1.%2").arg(format.majorVersion()).arg(format.minorVersion()));

    for (size_t i = 0; i < sizeof(profiles) / sizeof(Profile); ++i)
        if (profiles[i].profile == format.profile()) {
            m_output->append(tr("Profile: %1").arg(QString::fromLatin1(profiles[i].str)));
            break;
        }

    QString opts;
    for (size_t i = 0; i < sizeof(options) / sizeof(Option); ++i)
        if (format.testOption(options[i].option))
            opts += QString::fromLatin1(options[i].str) + QLatin1Char(' ');
    m_output->append(tr("Options: %1").arg(opts));

    for (size_t i = 0; i < sizeof(renderables) / sizeof(Renderable); ++i)
        if (renderables[i].renderable == format.renderableType()) {
            m_output->append(tr("Renderable type: %1").arg(QString::fromLatin1(renderables[i].str)));
            break;
        }

    m_output->append(tr("Depth buffer size: %1").arg(QString::number(format.depthBufferSize())));
    m_output->append(tr("Stencil buffer size: %1").arg(QString::number(format.stencilBufferSize())));
    m_output->append(tr("Samples: %1").arg(QString::number(format.samples())));
    m_output->append(tr("Red buffer size: %1").arg(QString::number(format.redBufferSize())));
    m_output->append(tr("Green buffer size: %1").arg(QString::number(format.greenBufferSize())));
    m_output->append(tr("Blue buffer size: %1").arg(QString::number(format.blueBufferSize())));
    m_output->append(tr("Alpha buffer size: %1").arg(QString::number(format.alphaBufferSize())));
    m_output->append(tr("Swap interval: %1").arg(QString::number(format.swapInterval())));
}

void Widget::renderWindowReady()
{
    QOpenGLContext *context = QOpenGLContext::currentContext();
    Q_ASSERT(context);

    QString vendor, renderer, version, glslVersion;
    const GLubyte *p;
    QOpenGLFunctions *f = context->functions();
    if ((p = f->glGetString(GL_VENDOR)))
        vendor = QString::fromLatin1(reinterpret_cast<const char *>(p));
    if ((p = f->glGetString(GL_RENDERER)))
        renderer = QString::fromLatin1(reinterpret_cast<const char *>(p));
    if ((p = f->glGetString(GL_VERSION)))
        version = QString::fromLatin1(reinterpret_cast<const char *>(p));
    if ((p = f->glGetString(GL_SHADING_LANGUAGE_VERSION)))
        glslVersion = QString::fromLatin1(reinterpret_cast<const char *>(p));

    m_output->append(tr("*** Context information ***"));
    m_output->append(tr("Vendor: %1").arg(vendor));
    m_output->append(tr("Renderer: %1").arg(renderer));
    m_output->append(tr("OpenGL version: %1").arg(version));
    m_output->append(tr("GLSL version: %1").arg(glslVersion));

    m_output->append(tr("\n*** QSurfaceFormat from context ***"));
    printFormat(context->format());

    m_output->append(tr("\n*** QSurfaceFormat from window surface ***"));
    printFormat(m_surface->format());

    m_output->append(tr("\n*** Qt build information ***"));
    const char *gltype[] = { "Desktop", "GLES 2", "GLES 1" };
    m_output->append(tr("Qt OpenGL configuration: %1")
                     .arg(QString::fromLatin1(gltype[QOpenGLContext::openGLModuleType()])));
#if defined(Q_OS_WIN)
    using namespace QNativeInterface;
    m_output->append(tr("Qt OpenGL library handle: %1")
                     .arg(QString::number(qintptr(QWGLContext::openGLModuleHandle()), 16)));
#endif

    QList<QByteArray> extensionList = context->extensions().values();
    std::sort(extensionList.begin(), extensionList.end());
    m_extensions->append(tr("Found %1 extensions:").arg(extensionList.count()));
    for (const QByteArray &ext : std::as_const(extensionList))
        m_extensions->append(QString::fromLatin1(ext));

    m_output->moveCursor(QTextCursor::Start);
    m_extensions->moveCursor(QTextCursor::Start);
}

void Widget::renderWindowError(const QString &msg)
{
    m_output->append(tr("An error has occurred:\n%1").arg(msg));
}