/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** 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 General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** 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-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "language.h"

#include <QtCore/qtextstream.h>

namespace language {

static Encoding encoding = Encoding::Utf8;
static Language _language = Language::Cpp;

Language language() { return _language; }

void setLanguage(Language l)
{
    _language = l;
    switch (_language) {
    case Language::Cpp:
        derefPointer = QLatin1String("->");
        nullPtr = QLatin1String("nullptr");
        operatorNew = QLatin1String("new ");
        qtQualifier = QLatin1String("Qt::");
        qualifier = QLatin1String("::");
        self = QLatin1String("");  // for testing: change to "this->";
        eol = QLatin1String(";\n");
        emptyString = QLatin1String("QString()");
        encoding = Encoding::Utf8;
        break;
    case Language::Python:
        derefPointer = QLatin1String(".");
        nullPtr = QLatin1String("None");
        operatorNew = QLatin1String("");
        qtQualifier = QLatin1String("Qt.");
        qualifier = QLatin1String(".");
        self = QLatin1String("self.");
        eol = QLatin1String("\n");
        emptyString = QLatin1String("\"\"");
        encoding = Encoding::Unicode;
        break;
    }
}

QString derefPointer;
QString nullPtr;
QString operatorNew;
QString qtQualifier;
QString qualifier;
QString self;
QString eol;
QString emptyString;

QString cppQualifier = QLatin1String("::");
QString cppTrue = QLatin1String("true");
QString cppFalse = QLatin1String("false");

QTextStream &operator<<(QTextStream &str, const qtConfig &c)
{
    str << "QT_CONFIG(" << c.parameter() << ')';
    return str;
}

QTextStream &operator<<(QTextStream &str, const openQtConfig &c)
{
    str << "#if " << qtConfig(c.parameter())  << '\n';
    return str;
}

QTextStream &operator<<(QTextStream &str, const closeQtConfig &c)
{
    str << "#endif // " << qtConfig(c.parameter()) << '\n';
    return str;
}

struct EnumLookup
{
    int value;
    const char *valueString;
};

template <int N>
const char *lookupEnum(const EnumLookup(&array)[N], int value, int defaultIndex = 0)
{
    for (int i = 0; i < N; ++i) {
        if (value == array[i].value)
            return array[i].valueString;
    }
    const char *defaultValue = array[defaultIndex].valueString;
    qWarning("uic: Warning: Invalid enumeration value %d, defaulting to %s",
             value, defaultValue);
    return defaultValue;
}

QString fixClassName(QString className)
{
    if (language() == Language::Python)
        className.replace(cppQualifier, QLatin1String("_"));
    return className;
}

const char *toolbarArea(int v)
{
    static const EnumLookup toolBarAreas[] =
    {
        {0,   "NoToolBarArea"},
        {0x1, "LeftToolBarArea"},
        {0x2, "RightToolBarArea"},
        {0x4, "TopToolBarArea"},
        {0x8, "BottomToolBarArea"},
        {0xf, "AllToolBarAreas"}
    };
    return lookupEnum(toolBarAreas, v);
}

const char *sizePolicy(int v)
{
    static const EnumLookup sizePolicies[] =
    {
        {0,   "Fixed"},
        {0x1, "Minimum"},
        {0x4, "Maximum"},
        {0x5, "Preferred"},
        {0x3, "MinimumExpanding"},
        {0x7, "Expanding"},
        {0xD, "Ignored"}
    };
    return lookupEnum(sizePolicies, v, 3);
}

const char *dockWidgetArea(int v)
{
    static const EnumLookup dockWidgetAreas[] =
    {
        {0,   "NoDockWidgetArea"},
        {0x1, "LeftDockWidgetArea"},
        {0x2, "RightDockWidgetArea"},
        {0x4, "TopDockWidgetArea"},
        {0x8, "BottomDockWidgetArea"},
        {0xf, "AllDockWidgetAreas"}
    };
    return lookupEnum(dockWidgetAreas, v);
}

const char *paletteColorRole(int v)
{
    static const EnumLookup colorRoles[] =
    {
        {0, "WindowText"},
        {1, "Button"},
        {2, "Light"},
        {3, "Midlight"},
        {4, "Dark"},
        {5, "Mid"},
        {6, "Text"},
        {7, "BrightText"},
        {8, "ButtonText"},
        {9, "Base"},
        {10, "Window"},
        {11, "Shadow"},
        {12, "Highlight"},
        {13, "HighlightedText"},
        {14, "Link"},
        {15, "LinkVisited"},
        {16, "AlternateBase"},
        {17, "NoRole"},
        {18, "ToolTipBase"},
        {19, "ToolTipText"},
        {20, "PlaceholderText"},
    };
    return lookupEnum(colorRoles, v);
}

// Helpers for formatting a character sequences

// Format a special character like '\x0a'
static int formatEscapedNumber(QTextStream &str, ushort value, int base, int width,
                               char prefix = 0)
{
    int length = 1 + width;
    str << '\\';
    if (prefix) {
        str << prefix;
        ++length;
    }
    const auto oldPadChar = str.padChar();
    const auto oldFieldWidth = str.fieldWidth();
    const auto oldFieldAlignment = str.fieldAlignment();
    const auto oldIntegerBase = str.integerBase();
    str.setPadChar(QLatin1Char('0'));
    str.setFieldWidth(width);
    str.setFieldAlignment(QTextStream::AlignRight);
    str.setIntegerBase(base);
    str << value;
    str.setIntegerBase(oldIntegerBase);
    str.setFieldAlignment(oldFieldAlignment);
    str.setFieldWidth(oldFieldWidth);
    str.setPadChar(oldPadChar);
    return length;
}

static int formatSpecialCharacter(QTextStream &str, ushort value)
{
    int length = 0;
    switch (value) {
    case '\\':
        str << "\\\\";
        length += 2;
        break;
    case '\"':
        str << "\\\"";
        length += 2;
        break;
    case '\n':
        str << "\\n\"\n\"";
        length += 5;
        break;
    default:
        break;
    }
    return length;
}

// Format a sequence of characters for C++ with special characters numerically
// escaped (non-raw string literals), wrappped at maxSegmentSize. FormattingTraits
// are used to transform characters into (unsigned) codes, which can be used
// for either normal escapes or Unicode code points as used in Unicode literals.

enum : int { maxSegmentSize = 1024 };

template <Encoding e>
struct FormattingTraits
{
};

template <>
struct FormattingTraits<Encoding::Utf8>
{
    static ushort code(char c) { return uchar(c); }
};

template <>
struct FormattingTraits<Encoding::Unicode>
{
    static ushort code(QChar c) { return c.unicode(); }
};

template <Encoding e, class Iterator>
static void formatStringSequence(QTextStream &str, Iterator it, Iterator end,
                                 const QString &indent,
                                 int escapeIntegerBase, int escapeWidth,
                                 char escapePrefix = 0)
{
    str << '"';
    int length = 0;
    while (it != end) {
        const auto code = FormattingTraits<e>::code(*it);
        if (code >= 0x80) {
            length += formatEscapedNumber(str, code, escapeIntegerBase, escapeWidth, escapePrefix);
        } else if (const int l = formatSpecialCharacter(str, code)) {
            length += l;
        } else if (code != '\r') {
            str << *it;
            ++length;
        }
        ++it;
        if (it != end && length > maxSegmentSize) {
            str << "\"\n" << indent << indent << '"';
            length = 0;
        }
    }
    str << '"';
}

void _formatString(QTextStream &str, const QString &value, const QString &indent,
                   bool qString)
{
    switch (encoding) {
    // Special characters as 3 digit octal escapes (u8"\303\234mlaut")
    case Encoding::Utf8: {
        if (qString && _language == Language::Cpp)
            str << "QString::fromUtf8(";
        const QByteArray utf8 = value.toUtf8();
        formatStringSequence<Encoding::Utf8>(str, utf8.cbegin(), utf8.cend(), indent,
                                             8, 3);
        if (qString && _language == Language::Cpp)
            str << ')';
    }
        break;
    // Special characters as 4 digit hex Unicode points (u8"\u00dcmlaut")
    case Encoding::Unicode:
        str << 'u'; // Python Unicode literal (would be UTF-16 in C++)
        formatStringSequence<Encoding::Unicode>(str, value.cbegin(), value.cend(), indent,
                                                16, 4, 'u');
        break;
    }
}

QTextStream &operator<<(QTextStream &str, const repeat &r)
{
    for (int i = 0; i < r.m_count; ++i)
        str << r.m_char;
    return str;
}

startFunctionDefinition1::startFunctionDefinition1(const char *name, const QString &parameterType,
                                                   const QString &parameterName,
                                                   const QString &indent,
                                                   const char *returnType) :
    m_name(name), m_parameterType(parameterType), m_parameterName(parameterName),
    m_indent(indent), m_return(returnType)
{
}

QTextStream &operator<<(QTextStream &str, const startFunctionDefinition1 &f)
{
    switch (language()) {
    case Language::Cpp:
        str << (f.m_return ? f.m_return : "void") << ' ' << f.m_name << '('
            << f.m_parameterType;
        if (f.m_parameterType.cend()->isLetter())
            str << ' ';
        str << f.m_parameterName << ')' << '\n' << f.m_indent << "{\n";
        break;
    case Language::Python:
        str << "def " << f.m_name << "(self, " << f.m_parameterName << "):\n";
        break;
    }
    return str;
}

endFunctionDefinition::endFunctionDefinition(const char *name) : m_name(name)
{
}

QTextStream &operator<<(QTextStream &str, const endFunctionDefinition &f)
{
    switch (language()) {
    case Language::Cpp:
        str << "} // " << f.m_name << "\n\n";
        break;
    case Language::Python:
        str << "# " << f.m_name << "\n\n";
        break;
    }
    return str;
}

void _formatStackVariable(QTextStream &str, const char *className, QStringView varName,
                          bool withInitParameters)
{
    switch (language()) {
    case Language::Cpp:
        str << className << ' ' << varName;
        if (withInitParameters)
            str << '(';
        break;
    case Language::Python:
        str << varName << " = " << className << '(';
        if (!withInitParameters)
            str << ')';
        break;
    }
}

enum OverloadUse {
    UseOverload,
    UseOverloadWhenNoArguments, // Use overload only when the argument list is empty,
                                // in this case there is no chance of connecting
                                // mismatching T against const T &
    DontUseOverload
};

// Format a member function for a signal slot connection
static void formatMemberFnPtr(QTextStream &str, const SignalSlot &s,
                              OverloadUse useQOverload = DontUseOverload)
{
    const int parenPos = s.signature.indexOf(QLatin1Char('('));
    Q_ASSERT(parenPos >= 0);
    const auto functionName = QStringView{s.signature}.left(parenPos);

    const auto parameters = QStringView{s.signature}.mid(parenPos + 1,
                                               s.signature.size() - parenPos - 2);
    const bool withOverload = useQOverload == UseOverload ||
            (useQOverload == UseOverloadWhenNoArguments && parameters.isEmpty());

    if (withOverload)
        str << "qOverload<" << parameters << ">(";

    str << '&' << s.className << "::" << functionName;

    if (withOverload)
        str << ')';
}

static void formatMemberFnPtrConnection(QTextStream &str,
                                        const SignalSlot &sender,
                                        const SignalSlot &receiver)
{
    str << "QObject::connect(" << sender.name << ", ";
    formatMemberFnPtr(str, sender);
    str << ", " << receiver.name << ", ";
    formatMemberFnPtr(str, receiver, UseOverloadWhenNoArguments);
    str << ')';
}

static void formatStringBasedConnection(QTextStream &str,
                                        const SignalSlot &sender,
                                        const SignalSlot &receiver)
{
    str << "QObject::connect(" << sender.name << ", SIGNAL("<< sender.signature
        << "), " << receiver.name << ", SLOT(" << receiver.signature << "))";
}

void formatConnection(QTextStream &str, const SignalSlot &sender, const SignalSlot &receiver,
                      ConnectionSyntax connectionSyntax)
{
    switch (language()) {
    case Language::Cpp:
        switch (connectionSyntax) {
        case ConnectionSyntax::MemberFunctionPtr:
            formatMemberFnPtrConnection(str, sender, receiver);
            break;
        case ConnectionSyntax::StringBased:
            formatStringBasedConnection(str, sender, receiver);
            break;
        }
        break;
    case Language::Python:
        str << sender.name << '.'
            << QStringView{sender.signature}.left(sender.signature.indexOf(QLatin1Char('(')))
            << ".connect(" << receiver.name << '.'
            << QStringView{receiver.signature}.left(receiver.signature.indexOf(QLatin1Char('(')))
            << ')';
        break;
    }
}

QString boolValue(bool v)
{
    switch (language()) {
    case Language::Cpp:
        return v ? cppTrue : cppFalse;
    case Language::Python:
        return v ? QStringLiteral("True") : QStringLiteral("False");
    }
    Q_UNREACHABLE();
}

static inline QString dot() { return QStringLiteral("."); }

QString enumValue(const QString &value)
{
    if (language() == Language::Cpp || !value.contains(cppQualifier))
        return value;
    QString fixed = value;
    fixed.replace(cppQualifier, dot());
    return fixed;
}

} // namespace language
