/*
    This file is part of kdepim.

    Copyright (c) 2004 Cornelius Schumacher <schumacher@kde.org>
    Copyright (c) 2010 David Faure <dfaure@kdab.com>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include <QtCore/QFile>
#include <QtCore/QStringList>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#    include <QtCore/QTextCodec>
#endif
#include <QtCore/QTextStream>
#include <QtCore/QFileInfo>
#include <QDebug>

#include "printer.h"

using namespace KODE;

class Printer::Private
{
public:
    Private(Printer *parent) : mParent(parent) {}

    void addLabel(Code &code, const QString &label);
    QString classHeader(const Class &classObject, bool publicMembers, bool nestedClass = false);
    QString classImplementation(const Class &classObject, bool nestedClass = false);
    void addFunctionHeaders(Code &code, const Function::List &functions, const QString &className,
                            int access);
    QString formatType(const QString &type) const;

    Printer *mParent;
    Style mStyle;
    bool mCreationWarning = false;
    bool mLabelsDefineIndent = true;
    bool mIndentLabels = true;
    QString mGenerator = QStringLiteral("libkode");
    QString mOutputDirectory;
    QString mSourceFile;
    QStringList mStatementsAfterIncludes;

    /**
     * @brief printCodeIntoFile
     * Writes the string passed through the code parameter to the file referenced
     * by the file parameter in the case if it differs from the content of the file pointed by the
     * file parameter.
     * @param code reference to a Code object which contents needs to be printed
     * @param file the target file in unopened state with filename set
     */
    void printCodeIntoFile(const Code &code, QFile *file);
};

void Printer::Private::addLabel(Code &code, const QString &label)
{
    if (!mIndentLabels)
        code.unindent();
    code += label;
    if (!mIndentLabels)
        code.indent();
}

QString Printer::Private::formatType(const QString &type) const
{
    QString s = type;
    if (s.endsWith('*') || s.endsWith('&')) {
        if (s.at(s.length() - 2) != ' ') {
            // Turn "Foo*" into "Foo *" for readability
            s.insert(s.length() - 1, ' ');
        }
    } else {
        s += ' ';
    }
    return s;
}

QString Printer::Private::classHeader(const Class &classObject, bool publicMembers,
                                      bool nestedClass)
{
    Code code;

    int numNamespaces = 0;
    if (!classObject.nameSpace().isEmpty()) {
        const QStringList nsList = classObject.nameSpace().split("::");
        Q_FOREACH (const QString &ns, nsList) {
            code += "namespace " + ns + " {";
            code.indent();
            ++numNamespaces;
        }
    }

    if (nestedClass)
        code.indent();

    if (!classObject.docs().isEmpty()) {
        code += "/**";
        code.indent();
        code.addFormattedText(classObject.docs());
        code.unindent();
        code += " */";
    }

    QString txt = "class ";
    if (!classObject.exportDeclaration().isEmpty()) {
        txt += classObject.exportDeclaration().toUpper() + "_EXPORT ";
    }
    txt += classObject.name();

    Class::List baseClasses = classObject.baseClasses();
    if (!baseClasses.isEmpty()) {
        txt += " : ";
        Class::List::ConstIterator it;
        for (it = baseClasses.constBegin(); it != baseClasses.constEnd(); ++it) {
            Class bc = *it;

            if (it != baseClasses.constBegin())
                txt += ", ";

            txt += "public ";
            if (!bc.nameSpace().isEmpty())
                txt += bc.nameSpace() + "::";

            txt += bc.name();
        }
    }
    code += txt;

    if (nestedClass) {
        code.indent();
        code += '{';
    } else {
        code += '{';
        // We always want to indent here; so that Q_OBJECT and enums etc. are indented.
        // However with mIndentLabels=false, we'll unindent before printing out "public:".
        code.indent();
    }

    if (classObject.isQObject()) {
        code += "Q_OBJECT";
        code.newLine();
    } else {
        if (classObject.isQGadget()) {
            code += "Q_GADGET";
            code.newLine();
        }
    }

    Q_FOREACH (const QString &declMacro, classObject.declarationMacros()) {
        code += declMacro;
        code.newLine();
    }

    Class::List nestedClasses = classObject.nestedClasses();
    // Generate nestedclasses
    if (!classObject.nestedClasses().isEmpty()) {
        addLabel(code, "public:");

        Class::List::ConstIterator it, itEnd = nestedClasses.constEnd();
        for (it = nestedClasses.constBegin(); it != itEnd; ++it) {
            code += classHeader((*it), false, true);
        }

        code.newLine();
    }

    Typedef::List typedefs = classObject.typedefs();
    if (typedefs.count() > 0) {
        addLabel(code, "public:");
        if (mLabelsDefineIndent)
            code.indent();

        Typedef::List::ConstIterator it;
        for (it = typedefs.constBegin(); it != typedefs.constEnd(); ++it)
            code += (*it).declaration();

        if (mLabelsDefineIndent)
            code.unindent();
        code.newLine();
    }

    Enum::List enums = classObject.enums();
    if (enums.count() > 0) {
        addLabel(code, "public:");
        if (mLabelsDefineIndent)
            code.indent();

        Enum::List::ConstIterator it;
        for (it = enums.constBegin(); it != enums.constEnd(); ++it)
            (*it).printDeclaration(code);

        if (mLabelsDefineIndent)
            code.unindent();
        code.newLine();
    }

    Function::List functions = classObject.functions();

    addFunctionHeaders(code, functions, classObject.name(), Function::Public);

    if (classObject.canBeCopied() && classObject.useDPointer()
        && !classObject.memberVariables().isEmpty()) {
        Function cc(classObject.name());
        cc.addArgument("const " + classObject.name() + '&');
        Function op("operator=", classObject.name() + '&');
        op.addArgument("const " + classObject.name() + '&');
        Function::List list;
        list << cc << op;
        addFunctionHeaders(code, list, classObject.name(), Function::Public);
    }

    addFunctionHeaders(code, functions, classObject.name(), Function::Public | Function::Slot);
    addFunctionHeaders(code, functions, classObject.name(), Function::Signal);
    addFunctionHeaders(code, functions, classObject.name(), Function::Protected);
    addFunctionHeaders(code, functions, classObject.name(), Function::Protected | Function::Slot);
    addFunctionHeaders(code, functions, classObject.name(), Function::Private);
    addFunctionHeaders(code, functions, classObject.name(), Function::Private | Function::Slot);

    if (!classObject.memberVariables().isEmpty()) {
        Function::List::ConstIterator it;
        // Do we have any private function?
        bool hasPrivateFunc = false;
        bool hasPrivateSlot = false;
        for (it = functions.constBegin(); it != functions.constEnd(); ++it) {
            if ((*it).access() == Function::Private) {
                hasPrivateFunc = true;
            } else if ((*it).access() == (Function::Private | Function::Slot)) {
                hasPrivateSlot = true;
            }
        }

        if (publicMembers)
            addLabel(code, "public:");
        else if (!hasPrivateFunc || hasPrivateSlot)
            addLabel(code, "private:");

        if (mLabelsDefineIndent)
            code.indent();

        if (classObject.useDPointer() && !classObject.memberVariables().isEmpty()) {
            code += "class PrivateDPtr;";
            if (classObject.useSharedData())
                code += "QSharedDataPointer<PrivateDPtr> " + classObject.dPointerName() + ";";
            else
                code += "PrivateDPtr *" + classObject.dPointerName() + ";";
        } else {
            MemberVariable::List variables = classObject.memberVariables();
            MemberVariable::List::ConstIterator it2;
            for (it2 = variables.constBegin(); it2 != variables.constEnd(); ++it2) {
                MemberVariable v = *it2;

                QString decl;
                if (v.isStatic())
                    decl += "static ";

                decl += formatType(v.type());

                decl += v.name() + ';';

                code += decl;
            }
        }
        if (mLabelsDefineIndent)
            code.unindent();
    }

    code.unindent();
    code += "};";

    for (int i = 0; i < numNamespaces; ++i) {
        code.unindent();
        code += "} // namespace end";
    }

    return code.text();
}

QString Printer::Private::classImplementation(const Class &classObject, bool nestedClass)
{
    Code code;

    bool needNewLine = false;

    QString functionClassName = classObject.name();
    if (nestedClass)
        functionClassName.prepend(classObject.parentClassName() + QLatin1String("::"));
    else if (!classObject.nameSpace().isEmpty())
        functionClassName.prepend(classObject.nameSpace() + QLatin1String("::"));

    // Generate private class
    if (classObject.useDPointer() && !classObject.memberVariables().isEmpty()) {
        Class privateClass(functionClassName + "::PrivateDPtr");
        if (classObject.useSharedData()) {
            privateClass.addBaseClass(Class("QSharedData"));
        }
        MemberVariable::List vars = classObject.memberVariables();
        MemberVariable::List::ConstIterator it;
        Function ctor("PrivateDPtr");
        bool hasInitializers = false;
        for (it = vars.constBegin(); it != vars.constEnd(); ++it) {
            const MemberVariable v = *it;
            privateClass.addMemberVariable(v);
            if (!v.initializer().isEmpty()) {
                ctor.addInitializer(v.name() + '(' + v.initializer() + ')');
                hasInitializers = true;
            }
        }
        if (hasInitializers)
            privateClass.addFunction(ctor);
        code += classHeader(privateClass, true /*publicMembers*/);
        if (hasInitializers)
            code += classImplementation(privateClass);
    }

    // Generate static vars
    MemberVariable::List vars = classObject.memberVariables();
    MemberVariable::List::ConstIterator itV;
    for (itV = vars.constBegin(); itV != vars.constEnd(); ++itV) {
        const MemberVariable v = *itV;
        if (!v.isStatic())
            continue;

        // ## I thought the static int foo = 42; syntax was not portable?
        code += v.type() + functionClassName + "::" + v.name() + " = " + v.initializer() + ';';
        needNewLine = true;
    }

    if (needNewLine)
        code.newLine();

    Function::List functions = classObject.functions();
    Function::List::ConstIterator it;
    for (it = functions.constBegin(); it != functions.constEnd(); ++it) {
        Function f = *it;

        // Omit signals
        if (f.access() == Function::Signal)
            continue;
        // Omit pure virtuals without a body
        if (f.virtualMode() == Function::PureVirtual && f.body().isEmpty())
            continue;

        code += mParent->functionSignature(f, functionClassName, true);

        QStringList inits = f.initializers();
        if (classObject.useDPointer() && !classObject.memberVariables().isEmpty()
            && f.name() == classObject.name()) {
            inits.append(classObject.dPointerName() + "(new PrivateDPtr)");
        }
        if (!classObject.useDPointer() && f.name() == classObject.name()
            && f.arguments().isEmpty()) {
            // Default constructor: add initializers for variables
            for (itV = vars.constBegin(); itV != vars.constEnd(); ++itV) {
                const MemberVariable v = *itV;
                if (!v.initializer().isEmpty()) {
                    inits.append(v.name() + '(' + v.initializer() + ')');
                }
            }
        }

        if (!inits.isEmpty()) {
            code.indent();
            code += ": " + inits.join(", ");
            code.unindent();
        }

        code += '{';
        code.addBlock(f.body(), Code::defaultIndentation());

        if (classObject.useDPointer() && !classObject.useSharedData()
            && !classObject.memberVariables().isEmpty() && f.name() == '~' + classObject.name()) {
            // Delete d pointer
            code.newLine();
            code.indent();
            code += "delete " + classObject.dPointerName() + ";";
            code += classObject.dPointerName() + " = nullptr;";
            code.unindent();
        }
        code += '}';
        code.newLine();
    }

    if (classObject.useDPointer() && classObject.canBeCopied()
        && !classObject.memberVariables().isEmpty()) {

        // print copy constructor
        Function cc(classObject.name());
        cc.addArgument("const " + functionClassName + "& other");

        Code body;
        if (!classObject.useSharedData()) {
            body += classObject.dPointerName() + " = new PrivateDPtr;";
            body += "*" + classObject.dPointerName() + " = *other." + classObject.dPointerName()
                    + ";";
        }
        cc.setBody(body);

        code += mParent->functionSignature(cc, functionClassName, true);

        // call copy constructor of base classes
        QStringList list;
        Class::List baseClasses = classObject.baseClasses();
        for (int i = 0; i < baseClasses.count(); ++i) {
            list.append(baseClasses[i].name() + "( other )");
        }
        if (classObject.useSharedData()) {
            list.append(classObject.dPointerName() + "( other." + classObject.dPointerName()
                        + " )");
        }
        if (!list.isEmpty()) {
            code.indent();
            code += ": " + list.join(", ");
            code.unindent();
        }

        code += '{';
        code.addBlock(cc.body(), Code::defaultIndentation());
        code += '}';
        code.newLine();

        // print assignment operator
        Function op("operator=", functionClassName + "& ");
        op.addArgument("const " + functionClassName + "& other");

        body.clear();
        body += "if ( this == &other )";
        body.indent();
        body += "return *this;";
        body.unindent();
        body.newLine();
        if (classObject.useSharedData())
            body += classObject.dPointerName() + " = other." + classObject.dPointerName() + ";";
        else
            body += "*" + classObject.dPointerName() + " = *other." + classObject.dPointerName()
                    + ";";
        for (int i = 0; i < baseClasses.count(); ++i) {
            body += QLatin1String("* static_cast<") + baseClasses[i].name()
                    + QLatin1String(" *>(this) = other;");
        }

        body.newLine();
        body += "return *this;";
        op.setBody(body);

        code += mParent->functionSignature(op, functionClassName, true);
        code += '{';
        code.addBlock(op.body(), Code::defaultIndentation());
        code += '}';
        code.newLine();
    }

    // Generate nested class functions
    const auto nestedClasses = classObject.nestedClasses();
    if (!nestedClasses.isEmpty()) {
        for (const Class &nested : nestedClasses) {
            code += classImplementation(nested, true);
        }
    }

    return code.text();
}

void Printer::Private::addFunctionHeaders(Code &code, const Function::List &functions,
                                          const QString &className, int access)
{
    bool needNewLine = false;
    bool hasAccess = false;

    Function::List::ConstIterator it;
    for (it = functions.constBegin(); it != functions.constEnd(); ++it) {
        Function f = *it;
        if (f.access() == access) {
            if (!hasAccess) {
                addLabel(code, f.accessAsString() + ':');
                hasAccess = true;
            }
            if (mLabelsDefineIndent)
                code.indent();
            if (!(*it).docs().isEmpty()) {
                code += "/**";
                code.indent();
                code.addFormattedText((*it).docs());
                code.unindent();
                code += " */";
            }
            code += mParent->functionSignature(*it, className, false) + ';';
            if (mLabelsDefineIndent)
                code.unindent();
            needNewLine = true;
        }
    }

    if (needNewLine)
        code.newLine();
}

Printer::Printer() : d(new Private(this)) {}

Printer::Printer(const Printer &other) : d(new Private(this))
{
    *d = *other.d;
    d->mParent = this;
}

Printer::Printer(const Style &style) : d(new Private(this))
{
    d->mStyle = style;
}

Printer::~Printer()
{
    delete d;
}

Printer &Printer::operator=(const Printer &other)
{
    if (this == &other)
        return *this;

    *d = *other.d;
    d->mParent = this;

    return *this;
}

void Printer::setCreationWarning(bool v)
{
    d->mCreationWarning = v;
}

void Printer::setGenerator(const QString &generator)
{
    d->mGenerator = generator;
}

void Printer::setOutputDirectory(const QString &outputDirectory)
{
    d->mOutputDirectory = outputDirectory;
}

void Printer::setSourceFile(const QString &sourceFile)
{
    d->mSourceFile = sourceFile;
}

void Printer::setLabelsDefineIndent(bool b)
{
    d->mLabelsDefineIndent = b;
}

void Printer::setIndentLabels(bool b)
{
    d->mIndentLabels = b;
}

QString Printer::functionSignature(const Function &function, const QString &className,
                                   bool forImplementation)
{
    QString s;

    if (function.isStatic() && !forImplementation) {
        s += QStringLiteral("static ");
    }

    if (function.virtualMode() != Function::NotVirtual && !forImplementation) {
        s += QStringLiteral("virtual ");
    }

    if (function.isExplicit() && !forImplementation) {
        s += QStringLiteral("explicit ");
    }

    QString ret = function.returnType();
    if (!ret.isEmpty()) {
        s += d->formatType(ret);
    }

    if (forImplementation)
        s += className + "::";

    s += function.name();

    s += '(';
    if (function.hasArguments()) {
        QStringList arguments;
        for (Function::Argument &argument : function.arguments()) {
            if (!forImplementation) {
                arguments.append(argument.headerDeclaration());
            } else {
                arguments.append(argument.bodyDeclaration());
            }
        }
        s += ' ' + arguments.join(", ") + ' ';
    }
    s += ')';

    if (function.isConst())
        s += " const";

    if (function.virtualMode() == Function::Override && !forImplementation) {
        s += " override";
    }

    if (function.virtualMode() == Function::Final && !forImplementation) {
        s += " final";
    }

    if (function.virtualMode() == Function::PureVirtual)
        s += " = 0";

    return s;
}

QString Printer::creationWarning() const
{
    // Create warning about generated file
    QString str = "// This file is generated by " + d->mGenerator;
    if (!d->mSourceFile.isEmpty())
        str += " from " + d->mSourceFile;

    str += ".\n";

    str += "// All changes you do to this file will be lost.";

    return str;
}

QString Printer::licenseHeader(const File &file) const
{
    Code code;

    const QStringList copyrights = file.copyrightStrings();
    if (!file.project().isEmpty() || !copyrights.isEmpty() || !file.license().text().isEmpty()) {
        code += "/*";
        code.setIndent(4);

        if (!file.project().isEmpty()) {
            code += "This file is part of " + file.project() + '.';
            code.newLine();
        }

        if (!copyrights.isEmpty()) {
            code.addBlock(copyrights.join("\n"));
            code.newLine();
        }

        code.addBlock(file.license().text());
        code.setIndent(0);
        code += "*/";
    }

    return code.text();
}

void Printer::setStatementsAfterIncludes(const QStringList &statements)
{
    d->mStatementsAfterIncludes = statements;
}

static QStringList commonLeft(const QStringList &l1, const QStringList &l2)
{
    QStringList r;
    const int l = qMin(l1.size(), l2.size());
    for (int i = 0; i < l; ++i)
        if (l1.at(i) == l2.at(i))
            r.append(l1.at(i));
        else
            return r;
    return r;
}

void Printer::printHeader(const File &file)
{
    Code out;

    if (d->mCreationWarning)
        out += creationWarning();

    out.addBlock(licenseHeader(file));

    // Create include guard
    QString className = file.filenameHeader();
    QFileInfo headerInfo(className);
    className = headerInfo.fileName(); // remove path, keep only filename
    className.replace('-', "_");

    out.addLine(QStringLiteral("#pragma once"));
    out.newLine();

    // Create includes
    Include::List processedIncludes;
    const Class::List classes = file.classes();
    Q_FOREACH (const Class &cl, classes) {
        Q_ASSERT(!cl.name().isEmpty());
        Include::List includes = cl.headerIncludes();
        if (cl.useSharedData())
            includes.append(Include("QtCore/QSharedData"));
        // qDebug() << "includes=" << includes;
        for (auto include : std::as_const(includes)) {
            if (!processedIncludes.contains(include)) {
                if (include.type == Include::Relative)
                    out += "#include \"" + include.includeFileName + '"';
                else
                    out += "#include <" + include.includeFileName + '>';
                processedIncludes.append(include);
            }
        }
    }

    if (!processedIncludes.isEmpty())
        out.newLine();

    for (const QString &statement : d->mStatementsAfterIncludes) {
        out += statement;
    }

    // Create enums
    Enum::List enums = file.fileEnums();
    Enum::List::ConstIterator enumIt;
    for (enumIt = enums.constBegin(); enumIt != enums.constEnd(); ++enumIt) {
        (*enumIt).printDeclaration(out);
    }

    // Create forward declarations
    QSet<QString> processed;
    Class::List::ConstIterator it;
    for (it = classes.constBegin(); it != classes.constEnd(); ++it) {
        const QStringList decls = (*it).forwardDeclarations();
#if QT_VERSION > QT_VERSION_CHECK(5, 14, 0)
        auto newProcessed = QSet<QString>(decls.begin(), decls.end());
        processed += newProcessed;
#else
        processed += decls.toSet();
#endif
    }
    auto fwdClasses = processed.values();
    fwdClasses.sort();
    fwdClasses += QString(); // for proper closing of the namespace blocks below

    QStringList prevNS;

    Q_FOREACH (const QString &fwd, fwdClasses) {
        // handle namespaces by opening and closing namespace blocks accordingly
        // the sorting will ensure sensible grouping
        const QStringList seg = fwd.split(QLatin1String("::"));
        const QStringList ns = seg.mid(0, seg.size() - 1);
        const QString clas = seg.isEmpty() ? QString() : seg.last();
        const QStringList common = commonLeft(ns, prevNS);
        for (int i = common.size(); i < prevNS.size(); ++i) {
            out.unindent();
            out += "}";
            out.newLine();
        }
        for (int i = common.size(); i < ns.size(); ++i) {
            out += "namespace " + ns.at(i) + " {";
            out.indent();
        }

        if (!clas.isEmpty()) {
            const bool isQtClass =
                    clas.startsWith(QLatin1Char('Q')) && !clas.contains(QLatin1Char('_'));
            if (isQtClass)
                out += QLatin1String("QT_BEGIN_NAMESPACE");
            out += "class " + clas + ';';
            if (isQtClass)
                out += QLatin1String("QT_END_NAMESPACE");
        }
        prevNS = ns;
    }

    if (!processed.isEmpty())
        out.newLine();

    if (!file.nameSpace().isEmpty()) {
        out += "namespace " + file.nameSpace() + " {";
        out.newLine();
    }

    // Create content
    for (it = classes.constBegin(); it != classes.constEnd(); ++it) {
        out.addBlock(d->classHeader(*it, false));
        out.newLine();
    }

    if (!file.nameSpace().isEmpty()) {
        out += '}';
        out.newLine();
    }

    // Print to file
    QString filename = file.filenameHeader();

    if (!d->mOutputDirectory.isEmpty())
        filename.prepend(d->mOutputDirectory + '/');

    //  KSaveFile::simpleBackupFile( filename, QString(), ".backup" );

    QFile header(filename);
    d->printCodeIntoFile(out, &header);
}

void Printer::printImplementation(const File &file, bool createHeaderInclude)
{
    Code out;

    if (d->mCreationWarning)
        out += creationWarning();

    out.addBlock(licenseHeader(file));

    out.newLine();

    // Create includes
    if (createHeaderInclude) {
        out += "#include \"" + file.filenameHeader() + "\"";
        out.newLine();
    }

    QStringList includes = file.includes();
    QStringList::ConstIterator it2;
    for (it2 = includes.constBegin(); it2 != includes.constEnd(); ++it2)
        out += "#include <" + *it2 + '>';

    if (!includes.isEmpty())
        out.newLine();

    // Create class includes
    QStringList processed;
    Class::List classes = file.classes();
    Class::List::ConstIterator it;
    for (it = classes.constBegin(); it != classes.constEnd(); ++it) {
        QStringList includes = (*it).includes();
        QStringList::ConstIterator it2;
        for (it2 = includes.constBegin(); it2 != includes.constEnd(); ++it2) {
            if (!processed.contains(*it2)) {
                out += "#include <" + *it2 + '>';
                processed.append(*it2);
            }
        }
    }

    if (!processed.isEmpty())
        out.newLine();

    if (!file.nameSpace().isEmpty()) {
        out += "namespace " + file.nameSpace() + " {";
        out.newLine();
    }

    // 'extern "C"' declarations
    const QStringList externCDeclarations = file.externCDeclarations();
    if (!externCDeclarations.isEmpty()) {
        out += "extern \"C\" {";
        QStringList::ConstIterator it;
        for (it = externCDeclarations.constBegin(); it != externCDeclarations.constEnd(); ++it) {
            out += *it + ';';
        }
        out += '}';
        out.newLine();
    }

    // File variables
    Variable::List vars = file.fileVariables();
    Variable::List::ConstIterator itV;
    for (itV = vars.constBegin(); itV != vars.constEnd(); ++itV) {
        Variable v = *itV;
        QString str;
        if (v.isStatic())
            str += "static ";
        str += v.type() + ' ' + v.name() + ';';
        out += str;
    }

    if (!vars.isEmpty())
        out.newLine();

    // File code
    if (!file.fileCode().isEmpty()) {
        out += file.fileCode();
        out.newLine();
    }

    // File functions
    Function::List funcs = file.fileFunctions();
    Function::List::ConstIterator itF;
    for (itF = funcs.constBegin(); itF != funcs.constEnd(); ++itF) {
        Function f = *itF;
        out += functionSignature(f);
        out += '{';
        out.addBlock(f.body(), Code::defaultIndentation());
        out += '}';
        out.newLine();
    }

    // Classes
#ifdef KDAB_DELETED
    bool containsQObject = false;
#endif
    for (it = classes.constBegin(); it != classes.constEnd(); ++it) {
#ifdef KDAB_DELETED
        if ((*it).isQObject())
            containsQObject = true;
#endif

        QString str = d->classImplementation(*it);
        if (!str.isEmpty())
            out += d->classImplementation(*it);
    }

    if (!file.nameSpace().isEmpty()) {
        out += "}";
        out.newLine();
    }

    // KDAB: removed; 1) for removing .filename(), and 2) qmake would want moc_foo.cpp anyway
#ifdef KDAB_DELETED
    if (containsQObject) {
        out.newLine();
        // out += "#include \"" + file.filename() + ".moc\"";
    }
#endif

    // Print to file
    QString filename = file.filenameImplementation();

    if (!d->mOutputDirectory.isEmpty())
        filename.prepend(d->mOutputDirectory + '/');

    QFile implementation(filename);
    d->printCodeIntoFile(out, &implementation);
}

void Printer::Private::printCodeIntoFile(const Code &code, QFile *file)
{
    const QString outText = code.text();

    static bool s_compareOutput = qEnvironmentVariableIsSet("LIBKODE_COMPARE_OUTPUT");

    bool identical = false;
    if (s_compareOutput && file->exists()) {
        if (!file->open(QIODevice::ReadOnly)) {
            qWarning("Can't open '%s' for reading.", qPrintable(file->fileName()));
            return;
        }

        QTextStream fileReaderStream(file);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
        fileReaderStream.setCodec(QTextCodec::codecForName("UTF-8"));
#endif

        QTextStream codeStream(outText.toUtf8());
        QString fileLine, outLine;
        identical = true;
        while (fileReaderStream.readLineInto(&fileLine) && codeStream.readLineInto(&outLine)) {
            if (fileLine != outLine) {
                identical = false;
                break;
            }
        }
        if (identical)
            identical = fileReaderStream.atEnd() && codeStream.atEnd();
        file->close();
    }

    if (!identical) {
        if (!file->open(QIODevice::WriteOnly)) {
            qWarning("Can't open '%s' for writing.", qPrintable(file->fileName()));
            return;
        }

        QTextStream fileWriterStream(file);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
        fileWriterStream.setCodec(QTextCodec::codecForName("UTF-8"));
#endif
        fileWriterStream << outText;

        file->close();
    } else {
        qDebug("Skip generating %s because its content did not change",
               qPrintable(file->fileName()));
    }
}

#if 0 // TODO: port to cmake
void Printer::printAutoMakefile( const AutoMakefile &am )
{
  QString filename = "Makefile.am";

  if ( !d->mOutputDirectory.isEmpty() )
    filename.prepend( d->mOutputDirectory + '/' );

//  KSaveFile::simpleBackupFile( filename, QString(), ".backup" );

  QFile file( filename );
  if ( !file.open( QIODevice::WriteOnly ) ) {
    qWarning( "Can't open '%s' for writing.", qPrintable( filename ) );
    return;
  }

  QTextStream ts( &file );

  ts << am.text();
}

#endif
