/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial Usage
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QtCore/qbytearray.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qdatetime.h>
#include <QtCore/qdebug.h>
#include <QtCore/qfile.h>
#include <QtCore/qstring.h>
#include <QtCore/qstringlist.h>
#include <QtCore/qtextstream.h>
#include <QtCore/qset.h>

#include <QtDBus/QtDBus>
#include "qdbusmetaobject_p.h"
#include "qdbusintrospection_p.h"

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef Q_WS_WIN
#include <process.h>
#endif

#define PROGRAMNAME     "qdbusxml2cpp"
#define PROGRAMVERSION  "0.7"
#define PROGRAMCOPYRIGHT "Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)."

#define ANNOTATION_NO_WAIT      "org.freedesktop.DBus.Method.NoReply"

static QString adaptorPackage;
static QString proxyPackage;
static QString parentClassName;
static QString proxyFile;
static QString adaptorFile;
static QString inputFile;
static bool skipNamespaces;
static bool verbose;
static bool includeMocs;
static QString commandLine;
static QStringList includes;
static QStringList wantedInterfaces;

static const char help[] =
    "Usage: " PROGRAMNAME " [options...] [xml-or-xml-file] [interfaces...]\n"
    "Produces the C++ code to implement the interfaces defined in the input file.\n"
    "\n"
    "Options:\n"
    "  -a <filename>    Write the adaptor code to <filename>\n"
    "  -A <package>     Use <package> as the package for the generated adaptor\n"
    "  -h               Show this information\n"
    "  -i <filename>    Add #include to the output\n"
    "  -l <classname>   When generating an adaptor, use <classname> as the parent class\n"
    "  -m               Generate #include \"filename.moc\" statements in the .cpp files\n"
    "  -N               Don't use namespaces\n"
    "  -p <filename>    Write the proxy code to <filename>\n"
    "  -P <package>     Use <package> as the package for the generated proxy\n"
    "  -v               Be verbose.\n"
    "  -V               Show the program version and quit.\n"
    "\n"
    "If the file name given to the options -a and -p does not end in .cpp or .h, the\n"
    "program will automatically append the suffixes and produce both files.\n"
    "You can also use a colon (:) to separate the header name from the source file\n"
    "name, as in '-a filename_p.h:filename.cpp'.\n"
    "\n"
    "If you pass a dash (-) as the argument to either -p or -a, the output is written\n"
    "to the standard output\n";

static const char forwardDeclarations[] =
    "class QByteArray;\n"
    "template<class T> class QList;\n"
    "template<class Key, class Value> class QMap;\n"
    "class QString;\n"
    "class QStringList;\n"
    "class QVariant;\n";

static void showHelp()
{
    printf("%s", help);
    exit(0);
}

static void showVersion()
{
    printf("%s version %s\n", PROGRAMNAME, PROGRAMVERSION);
    printf("D-Bus binding tool for Qt\n");
    exit(0);
}

static QString nextArg(QStringList &args, int i, char opt)
{
    QString arg = args.value(i);
    if (arg.isEmpty()) {
        printf("-%c needs at least one argument\n", opt);
        exit(1);
    }
    return args.takeAt(i);
}

static void parseCmdLine(QStringList args)
{
    args.takeFirst();

    commandLine = QLatin1String(PROGRAMNAME " ");
    commandLine += args.join(QLatin1String(" "));

    int i = 0;
    while (i < args.count()) {

        if (!args.at(i).startsWith(QLatin1Char('-'))) {
            ++i;
            continue;
        }
        QString arg = args.takeAt(i);

        char c = '\0';
        if (arg.length() == 2)
            c = arg.at(1).toLatin1();
        else if (arg == QLatin1String("--help"))
            c = 'h';
        else if (arg == QLatin1String("--adaptorPackage"))
            c = 'A';
        else if (arg == QLatin1String("--proxyPackage"))
            c = 'P';


        switch (c) {
        case 'a':
            adaptorFile = nextArg(args, i, 'a');
            break;

        case 'A':
            adaptorPackage = nextArg(args, i, 'A');
            break;

        case 'v':
            verbose = true;
            break;

        case 'i':
            includes << nextArg(args, i, 'i');
            break;

        case 'l':
            parentClassName = nextArg(args, i, 'l');
            break;

        case 'm':
            includeMocs = true;
            break;

        case 'N':
            skipNamespaces = true;
            break;

        case '?':
        case 'h':
            showHelp();
            break;

        case 'V':
            showVersion();
            break;

        case 'p':
            proxyFile = nextArg(args, i, 'p');
            break;

        case 'P':
            proxyPackage = nextArg(args, i, 'P');
            break;

        default:
            printf("unknown option: '%s'\n", qPrintable(arg));
            exit(1);
        }
    }

    if (!args.isEmpty())
        inputFile = args.takeFirst();

    wantedInterfaces << args;
}

static QDBusIntrospection::Interfaces readInput()
{
    QFile input(inputFile);
    if (inputFile.isEmpty() || inputFile == QLatin1String("-"))
        input.open(stdin, QIODevice::ReadOnly);
    else
        input.open(QIODevice::ReadOnly);

    QByteArray data = input.readAll();

    // check if the input is already XML
    data = data.trimmed();
    if (data.startsWith("<!DOCTYPE ") || data.startsWith("<?xml") ||
        data.startsWith("<node") || data.startsWith("<interface"))
        // already XML
        return QDBusIntrospection::parseInterfaces(QString::fromUtf8(data));

    fprintf(stderr, "Cannot process input: '%s'. Stop.\n", qPrintable(inputFile));
    exit(1);
}

static void cleanInterfaces(QDBusIntrospection::Interfaces &interfaces)
{
    if (!wantedInterfaces.isEmpty()) {
        QDBusIntrospection::Interfaces::Iterator it = interfaces.begin();
        while (it != interfaces.end())
            if (!wantedInterfaces.contains(it.key()))
                it = interfaces.erase(it);
            else
                ++it;
    }
}

// produce a header name from the file name
static QString header(const QString &name)
{
    QStringList parts = name.split(QLatin1Char(':'));
    QString retval = parts.first();

    if (retval.isEmpty() || retval == QLatin1String("-"))
        return retval;

    if (!retval.endsWith(QLatin1String(".h")) && !retval.endsWith(QLatin1String(".cpp")) &&
        !retval.endsWith(QLatin1String(".cc")))
        retval.append(QLatin1String(".h"));

    return retval;
}

// produce a cpp name from the file name
static QString cpp(const QString &name)
{
    QStringList parts = name.split(QLatin1Char(':'));
    QString retval = parts.last();

    if (retval.isEmpty() || retval == QLatin1String("-"))
        return retval;

    if (!retval.endsWith(QLatin1String(".h")) && !retval.endsWith(QLatin1String(".cpp")) &&
        !retval.endsWith(QLatin1String(".cc")))
        retval.append(QLatin1String(".cpp"));

    return retval;
}

// produce a moc name from the file name
static QString moc(const QString &name)
{
    QString retval = header(name);
    if (retval.isEmpty())
        return retval;

    retval.truncate(retval.length() - 1); // drop the h in .h
    retval += QLatin1String("moc");
    return retval;
}

// produce a perl module name from the file name
static QString pm(const QString &name)
{
    QStringList parts = name.split(QLatin1Char(':'));
    QString retval = parts.first();

    if (retval.isEmpty() || retval == QLatin1String("-"))
        return retval;

    if (!retval.endsWith(QLatin1String(".pm")))
        retval.append(QLatin1String(".pm"));

    return retval;
}

static QTextStream &writeHeader(QTextStream &ts, bool changesWillBeLost)
{
    ts << "#" << endl
       << "# This file was generated by " PROGRAMNAME " version " PROGRAMVERSION << endl
       << "# Command line was: " << commandLine << endl
       << "#" << endl
       << "# " PROGRAMNAME " is " PROGRAMCOPYRIGHT << endl
       << "#" << endl
       << "# This is an auto-generated file." << endl;

    if (changesWillBeLost)
        ts << "# Do not edit! All changes made to it will be lost." << endl;
    else
        ts << "# This file may have been hand-edited. Look for HAND-EDIT comments" << endl
           << "# before re-generating it." << endl;

    ts << "#" << endl
       << endl;

    return ts;
}

enum ClassType { Proxy, Adaptor };
static QString classNameForInterface(const QString &interface, ClassType classType)
{

    QStringList parts = interface.split(QLatin1Char('.'));

    QString retval;
    if (classType == Proxy) {
        if (!proxyPackage.isEmpty())
            return proxyPackage;
        foreach (QString part, parts) {
            part[0] = part[0].toUpper();
            retval += part;
        }
    }
    else {
        if (!adaptorPackage.isEmpty())
            return adaptorPackage;
        retval = parts.last();
        retval[0] = retval[0].toUpper();
    }

    if (classType == Proxy)
        retval += QLatin1String("Interface");
    else
        retval += QLatin1String("Adaptor");

    return retval;
}

static QByteArray qtTypeName(const QString &signature, const QDBusIntrospection::Annotations &annotations, int paramId = -1, const char *direction = "Out")
{
    int type = QDBusMetaType::signatureToType(signature.toLatin1());
    if (type == QVariant::Invalid) {
        QString annotationName = QString::fromLatin1("com.trolltech.QtDBus.QtTypeName");
        if (paramId >= 0)
            annotationName += QString::fromLatin1(".%1%2").arg(QLatin1String(direction)).arg(paramId);
        QString qttype = annotations.value(annotationName);
        if (!qttype.isEmpty())
            return qttype.toLatin1();

        fprintf(stderr, "Got unknown type `%s'\n", qPrintable(signature));
        fprintf(stderr, "You should add <annotation name=\"%s\" value=\"<type>\"/> to the XML description\n",
                qPrintable(annotationName));
        exit(1);
    }

    return QVariant::typeToName(QVariant::Type(type));
}

static QString nonConstRefArg(const QByteArray &arg)
{
    return QLatin1String(arg + " &");
}

static QString templateArg(const QByteArray &arg)
{
    if (!arg.endsWith('>'))
        return QLatin1String(arg);

    return QLatin1String(arg + ' ');
}

static QString constRefArg(const QByteArray &arg)
{
    if (!arg.startsWith('Q'))
        return QLatin1String(arg + ' ');
    else
        return QString( QLatin1String("const %1 &") ).arg( QLatin1String(arg) );
}

static QStringList makeArgNames(const QDBusIntrospection::Arguments &inputArgs,
                                const QDBusIntrospection::Arguments &outputArgs =
                                QDBusIntrospection::Arguments())
{
    QStringList retval;
    for (int i = 0; i < inputArgs.count(); ++i) {
        const QDBusIntrospection::Argument &arg = inputArgs.at(i);
        QString name = arg.name;
        if (name.isEmpty())
            name = QString( QLatin1String("in%1") ).arg(i);
        while (retval.contains(name))
            name += QLatin1String("_");
        retval << name;
    }
    for (int i = 0; i < outputArgs.count(); ++i) {
        const QDBusIntrospection::Argument &arg = outputArgs.at(i);
        QString name = arg.name;
        if (name.isEmpty())
            name = QString( QLatin1String("out%1") ).arg(i);
        while (retval.contains(name))
            name += QLatin1String("_");
        retval << name;
    }
    return retval;
}

static void writeArgTypesList(QTextStream &ts, const QStringList &/*argNames*/,
                         const QDBusIntrospection::Annotations &annotations,
                         const QDBusIntrospection::Arguments &inputArgs,
                         const QDBusIntrospection::Arguments &outputArgs = QDBusIntrospection::Arguments())
{
    // input args:
    bool first = true;
    int argPos = 0;
    for (int i = 0; i < inputArgs.count(); ++i) {
        const QDBusIntrospection::Argument &arg = inputArgs.at(i);
        QString type = constRefArg(qtTypeName(arg.type, annotations, i, "In"));

        if (!first)
            ts << ", ";
        ts << "'" << type << "'";
        first = false;
    }

    argPos++;

    // output args
    // yes, starting from 1
    for (int i = 1; i < outputArgs.count(); ++i) {
        const QDBusIntrospection::Argument &arg = outputArgs.at(i);
        QString name = arg.name;

        if (!first)
            ts << ", ";
        ts << "'" << nonConstRefArg(qtTypeName(arg.type, annotations, i, "Out")) << "'";
        first = false;
    }
}

static void writeArgList(QTextStream &ts, const QStringList &argNames,
                         const QDBusIntrospection::Annotations &/*annotations*/,
                         const QDBusIntrospection::Arguments &inputArgs,
                         const QDBusIntrospection::Arguments &outputArgs = QDBusIntrospection::Arguments())
{
    // input args:
    bool first = true;
    int argPos = 0;
    for (int i = 0; i < inputArgs.count(); ++i) {

        if (!first)
            ts << ", ";
        ts << "$" << argNames.at(argPos++);
        first = false;
    }

    argPos++;

    // output args
    // yes, starting from 1
    for (int i = 1; i < outputArgs.count(); ++i) {

        if (!first)
            ts << ", ";
        ts << "$" << argNames.at(argPos++);
        first = false;
    }
}

static QString propertyGetter(const QDBusIntrospection::Property &property)
{
    QString getter = property.annotations.value(QLatin1String("com.trolltech.QtDBus.propertyGetter"));
    if (getter.isEmpty()) {
        getter =  property.name;
        getter[0] = getter[0].toLower();
    }
    return getter;
}

static QString propertySetter(const QDBusIntrospection::Property &property)
{
    QString setter = property.annotations.value(QLatin1String("com.trolltech.QtDBus.propertySetter"));
    if (setter.isEmpty()) {
        setter = QLatin1String("set") + property.name;
        setter[3] = setter[3].toUpper();
    }
    return setter;
}

static QString stringify(const QString &data)
{
    QString retval;
    int i;
    for (i = 0; i < data.length(); ++i) {
        retval += QLatin1Char('\"');
        for ( ; i < data.length() && data[i] != QLatin1Char('\n'); ++i)
            if (data[i] == QLatin1Char('\"'))
                retval += QLatin1String("\\\"");
            else
                retval += data[i];
        retval += QLatin1String("\\n\" .\n");
    }
    return retval;
}

static void openFile(const QString &fileName, QFile &file)
{
    if (fileName.isEmpty())
        return;

    bool isOk = false;
    if (fileName == QLatin1String("-")) {
        isOk = file.open(stdout, QIODevice::WriteOnly | QIODevice::Text);
    } else {
        file.setFileName(fileName);
        isOk = file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text);
    }

    if (!isOk)
        fprintf(stderr, "Unable to open '%s': %s\n", qPrintable(fileName),
                qPrintable(file.errorString()));
}

static void writeProxy(const QString &filename, const QDBusIntrospection::Interfaces &interfaces)
{
    // open the file
    QString headerName = pm(filename);
    QString cppName = pm(filename);
    QString perlName = pm(filename);

    QByteArray headerData;
    QTextStream hs(&headerData);

    QByteArray cppData;
    QTextStream cs(&cppData);

    // write the header:
    writeHeader(hs, true);
    if (cppName != headerName)
        writeHeader(cs, false);

    foreach (QString include, includes) {
        hs << "use " << include << ";" << endl;
    }

    hs << endl;

    foreach (const QDBusIntrospection::Interface *interface, interfaces) {
        QString className = classNameForInterface(interface->name, Proxy);

        // comment:
        hs << "#" << endl
           << "# Proxy class for interface " << interface->name << endl
           << "#" << endl;
        cs << "#" << endl
           << "# Implementation of interface class " << className << endl
           << "#" << endl
           << endl;

        // class header:
        hs << "package " << className << ";" << endl;
        hs << "use strict;" << endl;
        hs << "use warnings;" << endl;
        hs << "use QtCore4;" << endl;
        hs << "use QtDBus4;" << endl;
        hs << "use QtCore4::isa qw( Qt::DBusAbstractInterface )" << ";" << endl << endl;

        // the interface name
        cs << "sub staticInterfaceName" << endl
           << "{" << endl
           << "    return \'" << interface->name << "\';" << endl
           << "}" << endl
           << endl;

        // constructors/destructors:
        cs << "sub NEW" << endl
           << "{" << endl
           << "    my ($class, $service, $path, $connection, $parent) = @_;" << endl
           << "    $class->SUPER::NEW($service, $path, staticInterfaceName(), $connection, $parent);" << endl
           << "}" << endl
           << endl;

        // properties:
        foreach (const QDBusIntrospection::Property &property, interface->properties) {
            QByteArray type = qtTypeName(property.type, property.annotations);
            QString templateType = templateArg(type);
            QString constRefType = constRefArg(type);
            QString getter = propertyGetter(property);
            QString setter = propertySetter(property);

            hs << "    Q_PROPERTY(" << type << " " << property.name;

            // getter:
            if (property.access != QDBusIntrospection::Property::Write)
                // it's readble
                hs << " READ " << getter;

            // setter
            if (property.access != QDBusIntrospection::Property::Read)
                // it's writeable
                hs << " WRITE " << setter;

            hs << ")" << endl;

            // getter:
            if (property.access != QDBusIntrospection::Property::Write) {
                hs << "    inline " << type << " " << getter << "() const" << endl
                    << "    { return qvariant_cast< " << type << " >(property(\""
                    << property.name << "\")); }" << endl;
            }

            // setter:
            if (property.access != QDBusIntrospection::Property::Read) {
                hs << "    inline void " << setter << "(" << constRefArg(type) << "value)" << endl
                   << "    { setProperty(\"" << property.name
                   << "\", qVariantFromValue(value)); }" << endl;
            }

            hs << endl;
        }

        // methods:
        if ( interface->methods.size() > 0 ) {
            hs << "use QtCore4::slots # METHODS" << endl;
            foreach (const QDBusIntrospection::Method &method, interface->methods) {
                bool isDeprecated = method.annotations.value(QLatin1String("org.freedesktop.DBus.Deprecated")) == QLatin1String("true");
                bool isNoReply =
                    method.annotations.value(QLatin1String(ANNOTATION_NO_WAIT)) == QLatin1String("true");
                if (isNoReply && !method.outputArgs.isEmpty()) {
                    fprintf(stderr, "warning: method %s in interface %s is marked 'no-reply' but has output arguments.\n",
                            qPrintable(method.name), qPrintable(interface->name));
                    continue;
                }

                hs << "    '";
                if (!isNoReply) {
                    hs << "QDBusPendingReply<";
                    for (int i = 0; i < method.outputArgs.count(); ++i)
                        hs << (i > 0 ? ", " : "")
                           << templateArg(qtTypeName(method.outputArgs.at(i).type, method.annotations, i, "Out"));
                    hs << "> ";
                }

                hs << method.name << "' => [";
                cs << "sub " << method.name << endl
                   << "{" << endl;

                QStringList argNames = makeArgNames(method.inputArgs);


                if ( argNames.size() > 0 )
                   cs << "    my (";

                writeArgTypesList(hs, argNames, method.annotations, method.inputArgs);
                writeArgList(cs, argNames, method.annotations, method.inputArgs);

                hs << "]," << endl;

                if ( argNames.size() > 0 )
                    cs << ") = @_;" << endl;

                cs << "    my $argumentList = [];" << endl;

                if (!method.inputArgs.isEmpty()) {
                    cs << "    push @{$argumentList}";
                    for (int argPos = 0; argPos < method.inputArgs.count(); ++argPos)
                        cs << ", Qt::qVariantFromValue($" << argNames.at(argPos) << ')';
                    cs << ";" << endl;
                }

                if (isNoReply)
                    cs << "    callWithArgumentList(Qt::DBus::NoBlock(), "
                       <<  "'" << method.name << "', $argumentList);" << endl;
                else
                    cs << "    return asyncCallWithArgumentList('"
                       << method.name << "', $argumentList);" << endl;

                // close the function:
                cs << "}" << endl;

                if (method.outputArgs.count() > 1) {
                    // generate the old-form QDBusReply methods with multiple incoming parameters
                    hs << "    inline "
                       << (isDeprecated ? "Q_DECL_DEPRECATED " : "")
                       << "QDBusReply<"
                       << templateArg(qtTypeName(method.outputArgs.first().type, method.annotations, 0, "Out")) << "> ";
                    hs << method.name << "(";

                    QStringList argNames = makeArgNames(method.inputArgs, method.outputArgs);
                    writeArgList(hs, argNames, method.annotations, method.inputArgs, method.outputArgs);

                    hs << ")" << endl
                       << "    {" << endl
                       << "        QList<QVariant> argumentList;" << endl;

                    int argPos = 0;
                    if (!method.inputArgs.isEmpty()) {
                        hs << "        argumentList";
                        for (argPos = 0; argPos < method.inputArgs.count(); ++argPos)
                            hs << " << qVariantFromValue(" << argNames.at(argPos) << ')';
                        hs << ";" << endl;
                    }

                    hs << "        QDBusMessage reply = callWithArgumentList(QDBus::Block, "
                       <<  "QLatin1String(\"" << method.name << "\"), argumentList);" << endl;

                    argPos++;
                    hs << "        if (reply.type() == QDBusMessage::ReplyMessage && reply.arguments().count() == "
                       << method.outputArgs.count() << ") {" << endl;

                    // yes, starting from 1
                    for (int i = 1; i < method.outputArgs.count(); ++i)
                        hs << "            " << argNames.at(argPos++) << " = qdbus_cast<"
                           << templateArg(qtTypeName(method.outputArgs.at(i).type, method.annotations, i, "Out"))
                           << ">(reply.arguments().at(" << i << "));" << endl;
                    hs << "        }" << endl
                       << "        return reply;" << endl
                       << "    }" << endl;
                }

            }
            hs << "    ;" << endl
               << endl;
        }

        if ( interface->signals_.size() > 0 ) {
            hs << "use QtCore4::signals # SIGNALS" << endl;
            foreach (const QDBusIntrospection::Signal &signal, interface->signals_) {
                hs << "    ";
                if (signal.annotations.value(QLatin1String("org.freedesktop.DBus.Deprecated")) ==
                    QLatin1String("true"))
                    hs << "Q_DECL_DEPRECATED ";

                hs << signal.name << "(";

                QStringList argNames = makeArgNames(signal.outputArgs);
                writeArgList(hs, argNames, signal.annotations, signal.outputArgs);

                hs << ");" << endl; // finished for header
            }
            hs << "    ;" << endl
               << endl;
        }

        // close the class:
        cs << "1;" << endl
           << endl;
    }

    QString mocName = moc(filename);
    if (includeMocs && !mocName.isEmpty())
        cs << endl
           << "#include \"" << mocName << "\"" << endl;

    cs.flush();
    hs.flush();

    QFile file;
    openFile(perlName, file);
    file.write(headerData);
    file.write(cppData);
}

static void writeAdaptor(const QString &filename, const QDBusIntrospection::Interfaces &interfaces)
{
    QString headerName = pm(filename);
    QString cppName = pm(filename);
    QString perlName = pm(filename);

    QByteArray headerData;
    QTextStream hs(&headerData);

    QByteArray cppData;
    QTextStream cs(&cppData);

    // write the headers
    writeHeader(hs, false);

    QString parent = parentClassName;
    if (parentClassName.isEmpty())
        parent = QLatin1String("Qt::Object");

    foreach (const QDBusIntrospection::Interface *interface, interfaces) {
        QString className = classNameForInterface(interface->name, Adaptor);

        // comment:
        hs << "#" << endl
           << "# Adaptor class for interface " << interface->name << endl
           << "#" << endl;
        cs << "#" << endl
           << "# Implementation of adaptor class " << className << endl
           << "#" << endl
           << endl;

        // include our stuff:
        hs << "package " << className << ";" << endl;
        hs << "use strict;" << endl;
        hs << "use warnings;" << endl;
        hs << "use QtCore4;" << endl;
        hs << "use QtDBus4;" << endl;
        hs << "use QtCore4::isa qw( Qt::DBusAbstractAdaptor )" << ";" << endl << endl;

        // class header:
        hs << "use QtCore4::classinfo" << endl
           << "    'D-Bus Interface' => '" << interface->name << "'," << endl
           << "    'D-Bus Introspection' => '' ." << endl
           << stringify(interface->introspection)
           << "    '';" << endl
           << endl;

        foreach (QString include, includes) {
            hs << "use " << include << ";\n" << endl;
        }

        // constructor/destructor
        cs << "sub NEW" << endl
           << "{" << endl
           << "    # constructor" << endl
           << "    my ( $class, $parent, $data ) = @_;" << endl
           << "    $class->SUPER::NEW($parent);" << endl
           << "    if ( defined $data ) {" << endl
           << "        this->{data} = $data;" << endl
           << "    } else {" << endl
           << "        this->{data} = $parent;" << endl
           << "    }" << endl
           << "    this->setAutoRelaySignals(1);" << endl
           << "}" << endl
           << endl;

        /* PerlQt4 does not currently support QProperties
        foreach (const QDBusIntrospection::Property &property, interface->properties) {
            QByteArray type = qtTypeName(property.type, property.annotations);
            QString constRefType = constRefArg(type);
            QString getter = propertyGetter(property);
            QString setter = propertySetter(property);

            hs << "    Q_PROPERTY(" << type << " " << property.name;
            if (property.access != QDBusIntrospection::Property::Write)
                hs << " READ " << getter;
            if (property.access != QDBusIntrospection::Property::Read)
                hs << " WRITE " << setter;
            hs << ")" << endl;

            // getter:
            if (property.access != QDBusIntrospection::Property::Write) {
                hs << "    " << type << " " << getter << "() const;" << endl;
                cs << type << " "
                   << className << "::" << getter << "() const" << endl
                   << "{" << endl
                   << "    // get the value of property " << property.name << endl
                   << "    return qvariant_cast< " << type <<" >(parent()->property(\"" << property.name << "\"));" << endl
                   << "}" << endl
                   << endl;
            }

            // setter
            if (property.access != QDBusIntrospection::Property::Read) {
                hs << "    void " << setter << "(" << constRefType << "value);" << endl;
                cs << "void " << className << "::" << setter << "(" << constRefType << "value)" << endl
                   << "{" << endl
                   << "    // set the value of property " << property.name << endl
                   << "    parent()->setProperty(\"" << property.name << "\", qVariantFromValue(value";
                if (constRefType.contains(QLatin1String("QDBusVariant")))
                    cs << ".variant()";
                cs << "));" << endl
                   << "}" << endl
                   << endl;
            }

            hs << endl;
        }
        */

        if ( interface->methods.size() > 0 ) {
            hs << "use QtCore4::slots # METHODS" << endl
               << "    public => 1," << endl;
            foreach (const QDBusIntrospection::Method &method, interface->methods) {
                bool isNoReply =
                    method.annotations.value(QLatin1String(ANNOTATION_NO_WAIT)) == QLatin1String("true");
                if (isNoReply && !method.outputArgs.isEmpty()) {
                    fprintf(stderr, "warning: method %s in interface %s is marked 'no-reply' but has output arguments.\n",
                            qPrintable(method.name), qPrintable(interface->name));
                    continue;
                }

                hs << "    '";
                //if (method.annotations.value(QLatin1String("org.freedesktop.DBus.Deprecated")) ==
                    //QLatin1String("true"))
                    //hs << "Q_DECL_DEPRECATED ";

                QByteArray returnType;
                cs << "sub ";
                if (!isNoReply && !method.outputArgs.isEmpty()) {
                    returnType = qtTypeName(method.outputArgs.first().type, method.annotations, 0, "Out");
                    hs << returnType << " ";
                }

                QString name = method.name;
                hs << name << "' => [";
                cs << name << endl
                   << "{" << endl;

                QStringList argNames = makeArgNames(method.inputArgs, method.outputArgs);

                if ( argNames.size() > 0 )
                   cs << "    my (";

                writeArgTypesList(hs, argNames, method.annotations, method.inputArgs, method.outputArgs);
                writeArgList(cs, argNames, method.annotations, method.inputArgs, method.outputArgs);

                hs << "]," << endl;

                if ( argNames.size() > 0 )
                    cs << ") = @_;" << endl;

                cs << "    # handle method call " << interface->name << "." << method.name << endl;

                // make the call
                bool usingInvokeMethod = false;
                if (parentClassName.isEmpty() && method.inputArgs.count() <= 10
                    && method.outputArgs.count() <= 1)
                    usingInvokeMethod = true;

                if ( 0 ) {
                /* Can't use invokeMethod() since we can't create QGenericArguments
                if (usingInvokeMethod) {
                    // we are using QMetaObject::invokeMethod
                    if (!returnType.isEmpty())
                        cs << "    " << returnType << " " << argNames.at(method.inputArgs.count())
                           << ";" << endl;

                    static const char invoke[] = "    Qt::MetaObject::invokeMethod(this->parent(), '";
                    cs << invoke << name << "'";

                    if (!method.outputArgs.isEmpty())
                        cs << ", Q_RETURN_ARG("
                           << qtTypeName(method.outputArgs.at(0).type, method.annotations,
                                         0, "Out")
                           << ", "
                           << argNames.at(method.inputArgs.count())
                           << ")";

                    for (int i = 0; i < method.inputArgs.count(); ++i)
                        cs << ", " << "$" << argNames.at(i);

                    cs << ");" << endl;

                    if (!returnType.isEmpty())
                        cs << "    return $" << argNames.at(method.inputArgs.count()) << ";" << endl;
                }
                        */
                } else {
                    //if (parentClassName.isEmpty())
                        //cs << "    //";
                    //else
                        cs << "    ";

                    if (!method.outputArgs.isEmpty())
                        cs << "return ";

                    cs << "this->{data}->";
                    cs << name << "(";

                    int argPos = 0;
                    bool first = true;
                    for (int i = 0; i < method.inputArgs.count(); ++i) {
                        cs << (first ? "" : ", ") << "$" << argNames.at(argPos++);
                        first = false;
                    }
                    ++argPos;           // skip retval, if any
                    for (int i = 1; i < method.outputArgs.count(); ++i) {
                        cs << (first ? "" : ", ") << argNames.at(argPos++);
                        first = false;
                    }

                    cs << ");" << endl;
                }
                cs << "}" << endl
                   << endl;
            }
            hs << "    ;" << endl;
        }

        if ( interface->signals_.size() > 0 ) {
            hs << "use QtCore4::signals # SIGNALS" << endl
               << "    public => 1," << endl;
            foreach (const QDBusIntrospection::Signal &signal, interface->signals_) {
                hs << "    '";
                //if (signal.annotations.value(QLatin1String("org.freedesktop.DBus.Deprecated")) ==
                    //QLatin1String("true"))
                    //hs << "Q_DECL_DEPRECATED ";

                hs << signal.name << "' => [";

                QStringList argNames = makeArgNames(signal.outputArgs);
                writeArgTypesList(hs, argNames, signal.annotations, signal.outputArgs);

                hs << "]," << endl;
            }
            hs << "    ;" << endl;
        }

        hs << endl;
    }

    cs << "1;" << endl;

    cs.flush();
    hs.flush();

    QFile file;
    openFile(perlName, file);
    file.write(headerData);
    file.write(cppData);
}

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

    QDBusIntrospection::Interfaces interfaces = readInput();
    cleanInterfaces(interfaces);

    if (!proxyFile.isEmpty() || adaptorFile.isEmpty())
        writeProxy(proxyFile, interfaces);

    if (!adaptorFile.isEmpty())
        writeAdaptor(adaptorFile, interfaces);

    return 0;
}

/*!
    \page qdbusxml2cpp.html
    \title QtDBus XML compiler (qdbusxml2cpp)
    \keyword qdbusxml2cpp

    The QtDBus XML compiler is a tool that can be used to parse interface descriptions and produce
    static code representing those interfaces, which can then be used to make calls to remote
    objects or implement said interfaces.

    \c qdbusxml2dcpp has two modes of operation, that correspond to the two possible outputs it can
    produce: the interface (proxy) class or the adaptor class. The latter consists of both a C++
    header and a source file, which are meant to be edited and adapted to your needs.

    The \c qdbusxml2dcpp tool is not meant to be run every time you compile your
    application. Instead, it's meant to be used when developing the code or when the interface
    changes.

    The adaptor classes generated by \c qdbusxml2cpp are just a skeleton that must be completed. It
    generates, by default, calls to slots with the same name on the object the adaptor is attached
    to. However, you may modify those slots or the property accessor functions to suit your needs.
*/
