/****************************************************************************
**
** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** 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 https://www.qt.io/terms-conditions. For further
** information use the contact form at https://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 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qshadergenerator_p.h"

#include "qshaderlanguage_p.h"
#include <QRegularExpression>

#include <cctype>
#include <qshaderprogram_p.h>

QT_BEGIN_NAMESPACE
namespace Qt3DRender {
Q_LOGGING_CATEGORY(ShaderGenerator, "ShaderGenerator", QtWarningMsg)

namespace
{
    QByteArray toGlsl(QShaderLanguage::StorageQualifier qualifier, const QShaderFormat &format) noexcept
    {
        if (format.version().majorVersion() <= 2 && format.api() != QShaderFormat::RHI) {
            // Note we're assuming fragment shader only here, it'd be different
            // values for vertex shader, will need to be fixed properly at some
            // point but isn't necessary yet (this problem already exists in past
            // commits anyway)
            switch (qualifier) {
            case QShaderLanguage::Const:
                return "const";
            case QShaderLanguage::Input:
                if (format.shaderType() == QShaderFormat::Vertex)
                    return "attribute";
                else
                    return "varying";
            case QShaderLanguage::Output:
                return ""; // Although fragment shaders for <=2 only have fixed outputs
            case QShaderLanguage::Uniform:
                return "uniform";
            case QShaderLanguage::BuiltIn:
                return "//";
            }
        } else {
            switch (qualifier) {
            case QShaderLanguage::Const:
                return "const";
            case QShaderLanguage::Input:
                return "in";
            case QShaderLanguage::Output:
                return "out";
            case QShaderLanguage::Uniform:
                return "uniform";
            case QShaderLanguage::BuiltIn:
                return "//";
            }
        }

        Q_UNREACHABLE();
    }

    QByteArray toGlsl(QShaderLanguage::VariableType type) noexcept
    {
        switch (type) {
        case QShaderLanguage::Bool:
            return "bool";
        case QShaderLanguage::Int:
            return "int";
        case QShaderLanguage::Uint:
            return "uint";
        case QShaderLanguage::Float:
            return "float";
        case QShaderLanguage::Double:
            return "double";
        case QShaderLanguage::Vec2:
            return "vec2";
        case QShaderLanguage::Vec3:
            return "vec3";
        case QShaderLanguage::Vec4:
            return "vec4";
        case QShaderLanguage::DVec2:
            return "dvec2";
        case QShaderLanguage::DVec3:
            return "dvec3";
        case QShaderLanguage::DVec4:
            return "dvec4";
        case QShaderLanguage::BVec2:
            return "bvec2";
        case QShaderLanguage::BVec3:
            return "bvec3";
        case QShaderLanguage::BVec4:
            return "bvec4";
        case QShaderLanguage::IVec2:
            return "ivec2";
        case QShaderLanguage::IVec3:
            return "ivec3";
        case QShaderLanguage::IVec4:
            return "ivec4";
        case QShaderLanguage::UVec2:
            return "uvec2";
        case QShaderLanguage::UVec3:
            return "uvec3";
        case QShaderLanguage::UVec4:
            return "uvec4";
        case QShaderLanguage::Mat2:
            return "mat2";
        case QShaderLanguage::Mat3:
            return "mat3";
        case QShaderLanguage::Mat4:
            return "mat4";
        case QShaderLanguage::Mat2x2:
            return "mat2x2";
        case QShaderLanguage::Mat2x3:
            return "mat2x3";
        case QShaderLanguage::Mat2x4:
            return "mat2x4";
        case QShaderLanguage::Mat3x2:
            return "mat3x2";
        case QShaderLanguage::Mat3x3:
            return "mat3x3";
        case QShaderLanguage::Mat3x4:
            return "mat3x4";
        case QShaderLanguage::Mat4x2:
            return "mat4x2";
        case QShaderLanguage::Mat4x3:
            return "mat4x3";
        case QShaderLanguage::Mat4x4:
            return "mat4x4";
        case QShaderLanguage::DMat2:
            return "dmat2";
        case QShaderLanguage::DMat3:
            return "dmat3";
        case QShaderLanguage::DMat4:
            return "dmat4";
        case QShaderLanguage::DMat2x2:
            return "dmat2x2";
        case QShaderLanguage::DMat2x3:
            return "dmat2x3";
        case QShaderLanguage::DMat2x4:
            return "dmat2x4";
        case QShaderLanguage::DMat3x2:
            return "dmat3x2";
        case QShaderLanguage::DMat3x3:
            return "dmat3x3";
        case QShaderLanguage::DMat3x4:
            return "dmat3x4";
        case QShaderLanguage::DMat4x2:
            return "dmat4x2";
        case QShaderLanguage::DMat4x3:
            return "dmat4x3";
        case QShaderLanguage::DMat4x4:
            return "dmat4x4";
        case QShaderLanguage::Sampler1D:
            return "sampler1D";
        case QShaderLanguage::Sampler2D:
            return "sampler2D";
        case QShaderLanguage::Sampler3D:
            return "sampler3D";
        case QShaderLanguage::SamplerCube:
            return "samplerCube";
        case QShaderLanguage::Sampler2DRect:
            return "sampler2DRect";
        case QShaderLanguage::Sampler2DMs:
            return "sampler2DMS";
        case QShaderLanguage::SamplerBuffer:
            return "samplerBuffer";
        case QShaderLanguage::Sampler1DArray:
            return "sampler1DArray";
        case QShaderLanguage::Sampler2DArray:
            return "sampler2DArray";
        case QShaderLanguage::Sampler2DMsArray:
            return "sampler2DMSArray";
        case QShaderLanguage::SamplerCubeArray:
            return "samplerCubeArray";
        case QShaderLanguage::Sampler1DShadow:
            return "sampler1DShadow";
        case QShaderLanguage::Sampler2DShadow:
            return "sampler2DShadow";
        case QShaderLanguage::Sampler2DRectShadow:
            return "sampler2DRectShadow";
        case QShaderLanguage::Sampler1DArrayShadow:
            return "sampler1DArrayShadow";
        case QShaderLanguage::Sampler2DArrayShadow:
            return "sampler2DArrayShadow";
        case QShaderLanguage::SamplerCubeShadow:
            return "samplerCubeShadow";
        case QShaderLanguage::SamplerCubeArrayShadow:
            return "samplerCubeArrayShadow";
        case QShaderLanguage::ISampler1D:
            return "isampler1D";
        case QShaderLanguage::ISampler2D:
            return "isampler2D";
        case QShaderLanguage::ISampler3D:
            return "isampler3D";
        case QShaderLanguage::ISamplerCube:
            return "isamplerCube";
        case QShaderLanguage::ISampler2DRect:
            return "isampler2DRect";
        case QShaderLanguage::ISampler2DMs:
            return "isampler2DMS";
        case QShaderLanguage::ISamplerBuffer:
            return "isamplerBuffer";
        case QShaderLanguage::ISampler1DArray:
            return "isampler1DArray";
        case QShaderLanguage::ISampler2DArray:
            return "isampler2DArray";
        case QShaderLanguage::ISampler2DMsArray:
            return "isampler2DMSArray";
        case QShaderLanguage::ISamplerCubeArray:
            return "isamplerCubeArray";
        case QShaderLanguage::USampler1D:
            return "usampler1D";
        case QShaderLanguage::USampler2D:
            return "usampler2D";
        case QShaderLanguage::USampler3D:
            return "usampler3D";
        case QShaderLanguage::USamplerCube:
            return "usamplerCube";
        case QShaderLanguage::USampler2DRect:
            return "usampler2DRect";
        case QShaderLanguage::USampler2DMs:
            return "usampler2DMS";
        case QShaderLanguage::USamplerBuffer:
            return "usamplerBuffer";
        case QShaderLanguage::USampler1DArray:
            return "usampler1DArray";
        case QShaderLanguage::USampler2DArray:
            return "usampler2DArray";
        case QShaderLanguage::USampler2DMsArray:
            return "usampler2DMSArray";
        case QShaderLanguage::USamplerCubeArray:
            return "usamplerCubeArray";
        }

        Q_UNREACHABLE();
    }

    QByteArray replaceParameters(const QByteArray &original, const QShaderNode &node,
                                 const QShaderFormat &format) noexcept
    {
        QByteArray result = original;

        const QStringList parameterNames = node.parameterNames();
        for (const QString &parameterName : parameterNames) {
            const QByteArray placeholder = QByteArray(QByteArrayLiteral("$") + parameterName.toUtf8());
            const QVariant parameter = node.parameter(parameterName);
            if (parameter.userType() == qMetaTypeId<QShaderLanguage::StorageQualifier>()) {
                const QShaderLanguage::StorageQualifier qualifier =
                        qvariant_cast<QShaderLanguage::StorageQualifier>(parameter);
                const QByteArray value = toGlsl(qualifier, format);
                result.replace(placeholder, value);
            } else if (parameter.userType() == qMetaTypeId<QShaderLanguage::VariableType>()) {
                const QShaderLanguage::VariableType type =
                        qvariant_cast<QShaderLanguage::VariableType>(parameter);
                const QByteArray value = toGlsl(type);
                result.replace(placeholder, value);
            } else {
                const QByteArray value = parameter.toString().toUtf8();
                result.replace(placeholder, value);
            }
        }

        return result;
    }

    struct ShaderGenerationState
    {
        ShaderGenerationState(const QShaderGenerator &gen,
                              QVector<QShaderGraph::Statement> statements)
            : generator { gen }, statements { statements }
        {

        }

        const QShaderGenerator &generator;
        QVector<QShaderGraph::Statement> statements;
        QByteArrayList code;
    };

    class GLSL45HeaderWriter
    {
    public:
        void writeHeader(ShaderGenerationState &state)
        {
            const auto &format = state.generator.format;
            auto &code = state.code;
            for (const QShaderGraph::Statement &statement : state.statements) {
                const QShaderNode &node = statement.node;
                const QByteArrayList &headerSnippets = node.rule(format).headerSnippets;
                for (const QByteArray &snippet : headerSnippets) {
                    auto replacedSnippet = replaceParameters(snippet, node, format).trimmed();

                    if (replacedSnippet.startsWith(QByteArrayLiteral("add-input"))) {
                        onInOut(code, replacedSnippet);
                    } else if (replacedSnippet.startsWith(QByteArrayLiteral("add-uniform"))) {
                        onNamedUniform(ubo, replacedSnippet);
                    } else if (replacedSnippet.startsWith(QByteArrayLiteral("add-sampler"))) {
                        onNamedSampler(code, replacedSnippet);
                    } else if (replacedSnippet.startsWith(QByteArrayLiteral("#pragma include "))) {
                        onInclude(code, replacedSnippet);
                    } else {
                        code << replacedSnippet;
                    }
                }
            }

            if (!ubo.isEmpty()) {
                code << QByteArrayLiteral("layout(std140, binding = ")
                                + QByteArray::number(currentBinding++)
                                + QByteArrayLiteral(") uniform qt3d_shadergraph_generated_uniforms {");
                code << ubo;
                code << "};";
            }
        }

    private:
        void onInOut(QByteArrayList &code, const QByteArray &snippet) noexcept
        {
            const auto split = snippet.split(' ');
            if (split.size() < 4) {
                qDebug() << "Invalid header snippet: " << snippet;
                return;
            }
            const auto &qualifier = split[1];
            const auto &type = split[2];
            const auto &name = split[3];

            if (qualifier == QByteArrayLiteral("in")) {
                code << (QByteArrayLiteral("layout(location = ")
                         + QByteArray::number(currentInputLocation++) + QByteArrayLiteral(") in ")
                         + type + ' ' + name + QByteArrayLiteral(";"));
            } else if (qualifier == QByteArrayLiteral("out")) {
                code << (QByteArrayLiteral("layout(location = ")
                         + QByteArray::number(currentOutputLocation++) + QByteArrayLiteral(") out ")
                         + type + ' ' + name + QByteArrayLiteral(";"));
            } else if (qualifier == QByteArrayLiteral("uniform")) {
                ubo << (type + ' ' + name + ';');
            }
        }

        void onNamedUniform(QByteArrayList &ubo, const QByteArray &snippet) noexcept
        {
            const auto split = snippet.split(' ');
            if (split.size() < 3) {
                qDebug() << "Invalid header snippet: " << snippet;
                return;
            }

            const auto &type = split[1];
            const auto &name = split[2];

            ubo << (type + ' ' + name + ';');
        }

        void onNamedSampler(QByteArrayList &code, const QByteArray &snippet) noexcept
        {
            const auto split = snippet.split(' ');
            if (split.size() < 3) {
                qDebug() << "Invalid header snippet: " << snippet;
                return;
            }
            const auto binding = QByteArray::number(currentBinding++);
            const auto &type = split[1];
            const auto &name = split[2];

            code << (QByteArrayLiteral("layout(binding = ") + binding + QByteArrayLiteral(") uniform ")
                     + type + ' ' + name + QByteArrayLiteral(";"));
        }

        void onInclude(QByteArrayList &code, const QByteArray &snippet) noexcept
        {
            const auto filepath = QString::fromUtf8(snippet.mid(strlen("#pragma include ")));
            QString deincluded = QString::fromUtf8(QShaderProgramPrivate::deincludify(filepath));

            // This lambda will replace all occurrences of a string (e.g. "binding = auto") by another,
            // with the incremented int passed as argument (e.g. "binding = 1", "binding = 2" ...)
            const auto replaceAndIncrement = [&deincluded](const QRegularExpression &regexp,
                                                           int &variable,
                                                           const QString &replacement) noexcept {
                int matchStart = 0;
                do {
                    matchStart = deincluded.indexOf(regexp, matchStart);
                    if (matchStart != -1) {
                        const auto match = regexp.match(deincluded.midRef(matchStart));
                        const auto length = match.capturedLength(0);

                        deincluded.replace(matchStart, length, replacement.arg(variable++));
                    }
                } while (matchStart != -1);
            };

            // 1. Handle uniforms
            {
                thread_local const QRegularExpression bindings(
                        QStringLiteral("binding\\s?+=\\s?+auto"));

                replaceAndIncrement(bindings, currentBinding, QStringLiteral("binding = %1"));
            }

            // 2. Handle inputs
            {
                thread_local const QRegularExpression inLocations(
                        QStringLiteral("location\\s?+=\\s?+auto\\s?+\\)\\s?+in\\s+"));

                replaceAndIncrement(inLocations, currentInputLocation,
                                    QStringLiteral("location = %1) in "));
            }

            // 3. Handle outputs
            {
                thread_local const QRegularExpression outLocations(
                        QStringLiteral("location\\s?+=\\s?+auto\\s?+\\)\\s?+out\\s+"));

                replaceAndIncrement(outLocations, currentOutputLocation,
                                    QStringLiteral("location = %1) out "));
            }

            code << deincluded.toUtf8();
        }

        int currentInputLocation { 0 };
        int currentOutputLocation { 0 };
        int currentBinding { 2 };
        QByteArrayList ubo;
    };

    struct GLSLHeaderWriter
    {
        void writeHeader(ShaderGenerationState &state)
        {
            const auto &format = state.generator.format;
            auto &code = state.code;
            for (const QShaderGraph::Statement &statement : state.statements) {
                const QShaderNode &node = statement.node;
                const QByteArrayList &headerSnippets = node.rule(format).headerSnippets;
                for (const QByteArray &snippet : headerSnippets) {
                    code << replaceParameters(snippet, node, format);
                }
            }
        }
    };

    QByteArray versionString(const QShaderFormat &format) noexcept
    {
        if (!format.isValid())
            return {};

        switch (format.api()) {
        case QShaderFormat::RHI: {
            return QByteArrayLiteral("#version 450");
        }
        case QShaderFormat::VulkanFlavoredGLSL: {
            const int major = format.version().majorVersion();
            const int minor = format.version().minorVersion();
            return (QByteArrayLiteral("#version ") + QByteArray::number(major * 100 + minor * 10));
        }
        default: {
            const bool isGLES = format.api() == QShaderFormat::OpenGLES;
            const int major = format.version().majorVersion();
            const int minor = format.version().minorVersion();

            const int version = major == 2 && isGLES ? 100
                              : major == 3 && isGLES ? 300
                              : major == 2 ? 100 + 10 * (minor + 1)
                              : major == 3 && minor <= 2 ? 100 + 10 * (minor + 3)
                              : major * 100 + minor * 10;

            const QByteArray profile =
                    isGLES && version > 100 ? QByteArrayLiteral(" es")
                    : version >= 150 && format.api() == QShaderFormat::OpenGLCoreProfile ? QByteArrayLiteral(" core")
                    : version >= 150 && format.api() == QShaderFormat::OpenGLCompatibilityProfile ? QByteArrayLiteral(" compatibility")
                    : QByteArray();

            return (QByteArrayLiteral("#version ") + QByteArray::number(version) + profile);
        }
        }
    }

    QByteArrayList layerDefines(const QStringList &enabledLayers) noexcept
    {
        QByteArrayList defines;
        const QString defineTemplate = QStringLiteral("#define LAYER_%1");

        for (const QString &layer : enabledLayers)
            defines << defineTemplate.arg(layer).toUtf8();

        return defines;
    }
}

QByteArray QShaderGenerator::createShaderCode(const QStringList &enabledLayers) const
{
    const QVector<QShaderNode> nodes = graph.nodes();
    const auto statements = graph.createStatements(enabledLayers);
    ShaderGenerationState state(*this, statements);
    QByteArrayList &code = state.code;

    code << versionString(format);
    code << QByteArray();
    code << layerDefines(enabledLayers);

    if (format.api() == QShaderFormat::VulkanFlavoredGLSL || format.api() == QShaderFormat::RHI) {
        GLSL45HeaderWriter builder;
        builder.writeHeader(state);
    } else {
        GLSLHeaderWriter builder;
        builder.writeHeader(state);
    }

    code << QByteArray();
    code << QByteArrayLiteral("void main()");
    code << QByteArrayLiteral("{");

    const QRegularExpression temporaryVariableToAssignmentRegExp(
            QStringLiteral("([^;]*\\s+(v\\d+))\\s*=\\s*([^;]*);"));
    const QRegularExpression temporaryVariableInAssignmentRegExp(QStringLiteral("\\W*(v\\d+)\\W*"));
    const QRegularExpression statementRegExp(QStringLiteral("\\s*(\\w+)\\s*=\\s*([^;]*);"));

    struct Variable;

    struct Assignment
    {
        QString expression;
        QVector<Variable *> referencedVariables;
    };

    struct Variable
    {
        enum Type { GlobalInput, TemporaryAssignment, Output };

        QString name;
        QString declaration;
        int referenceCount = 0;
        Assignment assignment;
        Type type = TemporaryAssignment;
        bool substituted = false;

        static void substitute(Variable *v)
        {
            if (v->substituted)
                return;

            qCDebug(ShaderGenerator)
                    << "Begin Substituting " << v->name << " = " << v->assignment.expression;
            for (Variable *ref : qAsConst(v->assignment.referencedVariables)) {
                // Recursively substitute
                Variable::substitute(ref);

                // Replace all variables referenced only once in the assignment
                // by their actual expression
                if (ref->referenceCount == 1 || ref->type == Variable::GlobalInput) {
                    const QRegularExpression r(QStringLiteral("(.*\\b)(%1)(\\b.*)").arg(ref->name));
                    if (v->assignment.referencedVariables.size() == 1)
                        v->assignment.expression.replace(
                                r, QStringLiteral("\\1%2\\3").arg(ref->assignment.expression));
                    else
                        v->assignment.expression.replace(
                                r, QStringLiteral("(\\1%2\\3)").arg(ref->assignment.expression));
                }
            }
            qCDebug(ShaderGenerator)
                    << "Done Substituting " << v->name << " = " << v->assignment.expression;
            v->substituted = true;
        }
    };

    struct LineContent
    {
        QByteArray rawContent;
        Variable *var = nullptr;
    };

    // Table to store temporary variables that should be replaced:
    // - If variable references a a global variables
    //   -> we will use the global variable directly
    // - If variable references a function results
    //   -> will be kept only if variable is referenced more than once.
    // This avoids having vec3 v56 = vertexPosition; when we could
    // just use vertexPosition directly.
    // The added benefit is when having arrays, we don't try to create
    // mat4 v38 = skinningPalelette[100] which would be invalid
    QVector<Variable> temporaryVariables;
    // Reserve more than enough space to ensure no reallocation will take place
    temporaryVariables.reserve(nodes.size() * 8);

    QVector<LineContent> lines;

    auto createVariable = [&] () -> Variable * {
        Q_ASSERT(temporaryVariables.capacity() > 0);
        temporaryVariables.resize(temporaryVariables.size() + 1);
        return &temporaryVariables.last();
    };

    auto findVariable = [&] (const QString &name) -> Variable * {
        const auto end = temporaryVariables.end();
        auto it = std::find_if(temporaryVariables.begin(), end,
                               [=] (const Variable &a) { return a.name == name; });
        if (it != end)
            return &(*it);
        return nullptr;
    };

    auto gatherTemporaryVariablesFromAssignment = [&](Variable *v,
                                                      const QString &assignmentContent) {
        QRegularExpressionMatchIterator subMatchIt =
                temporaryVariableInAssignmentRegExp.globalMatch(assignmentContent);
        while (subMatchIt.hasNext()) {
            const QRegularExpressionMatch subMatch = subMatchIt.next();
            const QString variableName = subMatch.captured(1);

            // Variable we care about should already exists -> an expression cannot reference a
            // variable that hasn't been defined
            Variable *u = findVariable(variableName);
            Q_ASSERT(u);

            // Increase reference count for u
            ++u->referenceCount;
            // Insert u as reference for variable v
            v->assignment.referencedVariables.push_back(u);
        }
    };

    for (const QShaderGraph::Statement &statement : statements) {
        const QShaderNode node = statement.node;
        QByteArray line = node.rule(format).substitution;
        const QVector<QShaderNodePort> ports = node.ports();

        struct VariableReplacement
        {
            QByteArray placeholder;
            QByteArray variable;
        };

        QVector<VariableReplacement> variableReplacements;

        // Generate temporary variable names vN
        for (const QShaderNodePort &port : ports) {
            const QString portName = port.name;
            const QShaderNodePort::Direction portDirection = port.direction;
            const bool isInput = port.direction == QShaderNodePort::Input;

            const int portIndex = statement.portIndex(portDirection, portName);

            Q_ASSERT(portIndex >= 0);

            const int variableIndex =
                    isInput ? statement.inputs.at(portIndex) : statement.outputs.at(portIndex);
            if (variableIndex < 0)
                continue;

            VariableReplacement replacement;
            replacement.placeholder = QByteArrayLiteral("$") + portName.toUtf8();
            replacement.variable = QByteArrayLiteral("v") + QByteArray::number(variableIndex);

            variableReplacements.append(std::move(replacement));
        }

        int begin = 0;
        while ((begin = line.indexOf('$', begin)) != -1) {
            int end = begin + 1;
            char endChar = line.at(end);
            const int size = line.size();
            while (end < size && (std::isalnum(uchar(endChar)) || endChar == '_')) {
                ++end;
                endChar = line.at(end);
            }

            const int placeholderLength = end - begin;

            const QByteArray variableName = line.mid(begin, placeholderLength);
            const auto replacementIt =
                    std::find_if(variableReplacements.cbegin(), variableReplacements.cend(),
                                 [&variableName](const VariableReplacement &replacement) {
                                     return variableName == replacement.placeholder;
                                 });

            if (replacementIt != variableReplacements.cend()) {
                line.replace(begin, placeholderLength, replacementIt->variable);
                begin += replacementIt->variable.length();
            } else {
                begin = end;
            }
        }

        // Substitute variable names by generated vN variable names
        const QByteArray substitutionedLine = replaceParameters(line, node, format);

        QRegularExpressionMatchIterator matches;

        switch (node.type()) {
        case QShaderNode::Input:
        case QShaderNode::Output:
            matches = statementRegExp.globalMatch(QString::fromUtf8(substitutionedLine));
            break;
        case QShaderNode::Function:
            matches = temporaryVariableToAssignmentRegExp.globalMatch(
                    QString::fromUtf8(substitutionedLine));
            break;
        case QShaderNode::Invalid:
            break;
        }

        while (matches.hasNext()) {
            QRegularExpressionMatch match = matches.next();

            Variable *v = nullptr;

            switch (node.type()) {
            // Record name of temporary variable that possibly references a global input
            // We will replace the temporary variables by the matching global variables later
            case QShaderNode::Input: {
                const QString localVariable = match.captured(1);
                const QString globalVariable = match.captured(2);

                v = createVariable();
                v->name = localVariable;
                v->type = Variable::GlobalInput;

                Assignment assignment;
                assignment.expression = globalVariable;
                v->assignment = assignment;
                break;
            }

            case QShaderNode::Function: {
                const QString localVariableDeclaration = match.captured(1);
                const QString localVariableName = match.captured(2);
                const QString assignmentContent = match.captured(3);

                // Add new variable -> it cannot exist already
                v = createVariable();
                v->name = localVariableName;
                v->declaration = localVariableDeclaration;
                v->assignment.expression = assignmentContent;

                // Find variables that may be referenced in the assignment
                gatherTemporaryVariablesFromAssignment(v, assignmentContent);
                break;
            }

            case QShaderNode::Output: {
                const QString outputDeclaration = match.captured(1);
                const QString assignmentContent = match.captured(2);

                v = createVariable();
                v->name = outputDeclaration;
                v->declaration = outputDeclaration;
                v->type = Variable::Output;

                Assignment assignment;
                assignment.expression = assignmentContent;
                v->assignment = assignment;

                // Find variables that may be referenced in the assignment
                gatherTemporaryVariablesFromAssignment(v, assignmentContent);
                break;
            }
            case QShaderNode::Invalid:
                break;
            }

            LineContent lineContent;
            lineContent.rawContent = QByteArray(QByteArrayLiteral("    ") + substitutionedLine);
            lineContent.var = v;
            lines << lineContent;
        }
    }

    // Go through all lines
    // Perform substitution of line with temporary variables substitution
    for (LineContent &lineContent : lines) {
        Variable *v = lineContent.var;
        qCDebug(ShaderGenerator) << lineContent.rawContent;
        if (v != nullptr) {
            Variable::substitute(v);

            qCDebug(ShaderGenerator)
                    << "Line " << lineContent.rawContent << "is assigned to temporary" << v->name;

            // Check number of occurrences a temporary variable is referenced
            if (v->referenceCount == 1 || v->type == Variable::GlobalInput) {
                // If it is referenced only once, no point in creating a temporary
                // Clear content for current line
                lineContent.rawContent.clear();
                // We assume expression that were referencing vN will have vN properly substituted
            } else {
                lineContent.rawContent = QStringLiteral("    %1 = %2;")
                                                 .arg(v->declaration)
                                                 .arg(v->assignment.expression)
                                                 .toUtf8();
            }

            qCDebug(ShaderGenerator) << "Updated Line is " << lineContent.rawContent;
        }
    }

    // Go throug all lines and insert content
    for (const LineContent &lineContent : qAsConst(lines)) {
        if (!lineContent.rawContent.isEmpty()) {
            code << lineContent.rawContent;
        }
    }

    code << QByteArrayLiteral("}");
    code << QByteArray();

    return code.join('\n');
}

}
QT_END_NAMESPACE
