/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQuick 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 <QtCore/QByteArray>
#include <QtCore/QString>
#include <QtGui/QSurfaceFormat>

// Duct Tape tokenizer for the purpose of parsing and rewriting
// shader source code

QT_BEGIN_NAMESPACE

namespace QSGShaderRewriter {

struct Tokenizer {

    enum Token {
        Token_Void,
        Token_OpenBrace,
        Token_CloseBrace,
        Token_SemiColon,
        Token_Identifier,
        Token_Macro,
        Token_Unspecified,

        Token_EOF
    };

    static const char *NAMES[];

    void initialize(const char *input);
    Token next();

    const char *stream;
    const char *pos;
    const char *identifier;
};

const char *Tokenizer::NAMES[] = {
    "Void",
    "OpenBrace",
    "CloseBrace",
    "SemiColon",
    "Identifier",
    "Macro",
    "Unspecified",
    "EOF"
};

void Tokenizer::initialize(const char *input)
{
    stream = input;
    pos = input;
    identifier = input;
}

Tokenizer::Token Tokenizer::next()
{
    while (*pos != 0) {
        char c = *pos++;
        switch (c) {
        case '/':

            if (*pos == '/') {
                // '//' comment
                ++pos;
                while (*pos != 0 && *pos != '\n') ++pos;
                if (*pos != 0) ++pos; // skip the newline

            } else if (*pos == '*') {
                // /* */ comment
                ++pos;
                while (*pos != 0 && *pos != '*' && pos[1] != '/') ++pos;
                if (*pos != 0) pos += 2;
            }
            break;

        case '#': {
            while (*pos != 0) {
                if (*pos == '\n') {
                    ++pos;
                    break;
                } else if (*pos == '\\') {
                    ++pos;
                    while (*pos != 0 && (*pos == ' ' || *pos == '\t'))
                        ++pos;
                    if (*pos != 0 && (*pos == '\n' || (*pos == '\r' && pos[1] == '\n')))
                        pos+=2;
                } else {
                    ++pos;
                }
            }
            break;
        }

        case 'v': {
            if (*pos == 'o' && pos[1] == 'i' && pos[2] == 'd') {
                pos += 3;
                return Token_Void;
            }
            Q_FALLTHROUGH();
        }

        case ';': return Token_SemiColon;
        case 0: return Token_EOF;
        case '{': return Token_OpenBrace;
        case '}': return Token_CloseBrace;

        case ' ':
        case '\n':
        case '\r': break;
        default:
            // Identifier...
            if ((c >= 'a' && c <= 'z' ) || (c >= 'A' && c <= 'Z' ) || c == '_') {
                identifier = pos - 1;
                while (*pos != 0 && ((*pos >= 'a' && *pos <= 'z')
                                     || (*pos >= 'A' && *pos <= 'Z')
                                     || *pos == '_'
                                     || (*pos >= '0' && *pos <= '9'))) {
                    ++pos;
                }
                return Token_Identifier;
            } else {
                return Token_Unspecified;
            }
        }
    }

    return Token_EOF;
}

}

using namespace QSGShaderRewriter;

QByteArray qsgShaderRewriter_insertZAttributes(const char *input, QSurfaceFormat::OpenGLContextProfile profile)
{
    Tokenizer tok;
    tok.initialize(input);

    Tokenizer::Token lt = tok.next();
    Tokenizer::Token t = tok.next();

    // First find "void main() { ... "
    const char* voidPos = input;
    while (t != Tokenizer::Token_EOF) {
        if (lt == Tokenizer::Token_Void && t == Tokenizer::Token_Identifier) {
            if (qstrncmp("main", tok.identifier, 4) == 0)
                break;
        }
        voidPos = tok.pos - 4;
        lt = t;
        t = tok.next();
    }

    QByteArray result;
    result.reserve(1024);
    result += QByteArray::fromRawData(input, voidPos - input);
    switch (profile) {
    case QSurfaceFormat::NoProfile:
    case QSurfaceFormat::CompatibilityProfile:
        result += "attribute highp float _qt_order;\n"
                  "uniform highp float _qt_zRange;\n";
        break;

    case QSurfaceFormat::CoreProfile:
        result += "in float _qt_order;\n"
                  "uniform float _qt_zRange;\n";
        break;
    }

    // Find first brace '{'
    while (t != Tokenizer::Token_EOF && t != Tokenizer::Token_OpenBrace) t = tok.next();
    int braceDepth = 1;
    t = tok.next();

    // Find matching brace and insert our code there...
    while (t != Tokenizer::Token_EOF) {
        switch (t) {
        case Tokenizer::Token_CloseBrace:
            braceDepth--;
            if (braceDepth == 0) {
                result += QByteArray::fromRawData(voidPos, tok.pos - 1 - voidPos)
                        + "    gl_Position.z = (gl_Position.z * _qt_zRange + _qt_order) * gl_Position.w;\n"
                        + QByteArray(tok.pos - 1);
                return result;
            }
            break;
        case Tokenizer::Token_OpenBrace:
            ++braceDepth;
            break;
        default:
            break;
        }
        t = tok.next();
    }
    return QByteArray();
}

#ifdef QSGSHADERREWRITER_STANDALONE

const char *selftest =
        "#define highp lowp stuff                           \n"
        "#define multiline \\                               \n"
        "   continue defining multiline                     \n"
        "                                                   \n"
        "attribute highp vec4 qt_Position;                  \n"
        "attribute highp vec2 qt_TexCoord;                  \n"
        "                                                   \n"
        "uniform highp mat4 qt_Matrix;                      \n"
        "                                                   \n"
        "varying lowp vec2 vTexCoord;                       \n"
        "                                                   \n"
        "// commented out main(){}                          \n"
        "/* commented out main() { } again */               \n"
        "/*                                                 \n"
        "   multline comment with main() { }                \n"
        " */                                                \n"
        "                                                   \n"
        "void main() {                                      \n"
        "    gl_Position = qt_Matrix * qt_Position;         \n"
        "    vTexCoord = qt_TexCoord;                       \n"
        "    if (gl_Position < 0) {                         \n"
        "        vTexCoord.y = -vTexCoord.y;                \n"
        "    }                                              \n"
        "}                                                  \n"
        "";

int main(int argc, char **argv)
{
    QCoreApplication app(argc, argv);

    QString fileName;
    QStringList args = app.arguments();

    QByteArray content;

    for (int i=0; i<args.length(); ++i) {
        const QString &a = args.at(i);
        if (a == QLatin1String("--file") && i < args.length() - 1) {
            qDebug() << "Reading file: " << args.at(i);
            QFile file(args.at(++i));
            if (!file.open(QFile::ReadOnly)) {
                qDebug() << "Error: failed to open file," << file.errorString();
                return 1;
            }
            content = file.readAll();
        } else if (a == QLatin1String("--selftest")) {
            qDebug() << "doing a selftest";
            content = QByteArray(selftest);
        } else if (a == QLatin1String("--help") || a == QLatin1String("-h")) {
            qDebug() << "usage:" << endl
                     << "  --file [name]        A vertex shader file to rewrite" << endl;
        }
    }

    QByteArray rewritten = qsgShaderRewriter_insertZAttributes(content);

    qDebug() << "Rewritten to:";
    qDebug() << rewritten.constData();
}
#endif

QT_END_NAMESPACE
