/*
 * Copyright (C) 2007  Justin Karneges
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include "irisnetplugin.h"
#include "qdnssd.h"

#include <QtCore>
#include <QtNetwork>
#ifdef Q_OS_WIN // for ntohl
#include <windows.h>
#else
#include <netinet/in.h>
#endif

static QByteArray nameToDottedString(const QByteArray &in)
{
    QByteArray out;
    int        at = 0;
    while (at < in.size()) {
        int len = in[at++];
        if (len > 0)
            out += in.mid(at, len);
        out += '.';
        at += len;
    }
    return out;
}

static QMap<QString, QByteArray> textsToAttribs(const QList<QByteArray> &texts)
{
    QMap<QString, QByteArray> out;
    for (const QByteArray &a : texts) {
        QString    key;
        QByteArray value;
        int        x = a.indexOf('=');
        if (x != -1) {
            key   = QString::fromLatin1(a.mid(0, x));
            value = a.mid(x + 1);
        } else {
            key = QString::fromLatin1(a);
        }

        out.insert(key, value);
    }
    return out;
}

static QByteArray attribsToTxtRecord(const QMap<QString, QByteArray> &attribs)
{
    QList<QByteArray>                 texts;
    QMapIterator<QString, QByteArray> it(attribs);
    while (it.hasNext()) {
        it.next();
        QByteArray line = it.key().toLatin1() + '=' + it.value();
        texts += line;
    }
    return QDnsSd::createTxtRecord(texts);
}

// returns a list of 3 items, or an empty list on error
static QList<QByteArray> nameToInstanceParts(const QByteArray &name)
{
    // FIXME: improve this parsing...  (what about escaping??)
    int        at    = name.indexOf('.');
    QByteArray sname = name.mid(0, at);
    ++at;
    int next = name.indexOf('.', at);
    ++next;
    next               = name.indexOf('.', next);
    QByteArray stype   = name.mid(at, next - at);
    at                 = next + 1;
    QByteArray sdomain = name.mid(at);

    QList<QByteArray> out;
    out += sname;
    out += stype;
    out += sdomain;
    return out;
}

static XMPP::NameRecord importQDnsSdRecord(const QDnsSd::Record &in)
{
    XMPP::NameRecord out;
    switch (in.rrtype) {
    case 1: // A
    {
        quint32 *p = (quint32 *)in.rdata.data();
        out.setAddress(QHostAddress(ntohl(*p)));
    } break;

    case 28: // AAAA
    {
        out.setAddress(QHostAddress((quint8 *)in.rdata.data()));
    } break;

    case 12: // PTR
    {
        out.setPtr(nameToDottedString(in.rdata));
    } break;

    case 10: // NULL
    {
        out.setNull(in.rdata);
    } break;

    case 16: // TXT
    {
        QList<QByteArray> txtEntries = QDnsSd::parseTxtRecord(in.rdata);
        if (txtEntries.isEmpty())
            return out;
        out.setTxt(txtEntries);
    } break;

    default: // unsupported
    {
        return out;
    }
    }

    out.setOwner(in.name);
    out.setTtl(in.ttl);
    return out;
}

namespace {
class QDnsSdDelegate {
public:
    virtual ~QDnsSdDelegate() { }

    virtual void dns_queryResult(int id, const QDnsSd::QueryResult &result)
    {
        Q_UNUSED(id);
        Q_UNUSED(result);
    }

    virtual void dns_browseResult(int id, const QDnsSd::BrowseResult &result)
    {
        Q_UNUSED(id);
        Q_UNUSED(result);
    }

    virtual void dns_resolveResult(int id, const QDnsSd::ResolveResult &result)
    {
        Q_UNUSED(id);
        Q_UNUSED(result);
    }

    virtual void dns_regResult(int id, const QDnsSd::RegResult &result)
    {
        Q_UNUSED(id);
        Q_UNUSED(result);
    }
};

class IdManager {
private:
    QSet<int> set;
    int       at;

    inline void bump_at()
    {
        if (at == 0x7fffffff)
            at = 0;
        else
            ++at;
    }

public:
    IdManager() : at(0) { }

    int reserveId()
    {
        while (1) {
            if (!set.contains(at)) {
                int id = at;
                set.insert(id);
                bump_at();
                return id;
            }

            bump_at();
        }
    }

    void releaseId(int id) { set.remove(id); }
};

} // namespace

//----------------------------------------------------------------------------
// AppleProvider
//----------------------------------------------------------------------------
class AppleProvider : public XMPP::IrisNetProvider {
    Q_OBJECT
    Q_INTERFACES(XMPP::IrisNetProvider);

public:
    QDnsSd                       dns;
    QHash<int, QDnsSdDelegate *> delegateById;

    AppleProvider() : dns(this)
    {
        connect(&dns, SIGNAL(queryResult(int, QDnsSd::QueryResult)), SLOT(dns_queryResult(int, QDnsSd::QueryResult)));
        connect(&dns, SIGNAL(browseResult(int, QDnsSd::BrowseResult)),
                SLOT(dns_browseResult(int, QDnsSd::BrowseResult)));
        connect(&dns, SIGNAL(resolveResult(int, QDnsSd::ResolveResult)),
                SLOT(dns_resolveResult(int, QDnsSd::ResolveResult)));
        connect(&dns, SIGNAL(regResult(int, QDnsSd::RegResult)), SLOT(dns_regResult(int, QDnsSd::RegResult)));
    }

    virtual XMPP::NameProvider *   createNameProviderInternet();
    virtual XMPP::NameProvider *   createNameProviderLocal();
    virtual XMPP::ServiceProvider *createServiceProvider();

    int query(QDnsSdDelegate *p, const QByteArray &name, int qType)
    {
        int id           = dns.query(name, qType);
        delegateById[id] = p;
        return id;
    }

    int browse(QDnsSdDelegate *p, const QByteArray &serviceType, const QByteArray &domain)
    {
        int id           = dns.browse(serviceType, domain);
        delegateById[id] = p;
        return id;
    }

    int resolve(QDnsSdDelegate *p, const QByteArray &serviceName, const QByteArray &serviceType,
                const QByteArray &domain)
    {
        int id           = dns.resolve(serviceName, serviceType, domain);
        delegateById[id] = p;
        return id;
    }

    int reg(QDnsSdDelegate *p, const QByteArray &serviceName, const QByteArray &serviceType, const QByteArray &domain,
            int port, const QByteArray &txtRecord)
    {
        int id           = dns.reg(serviceName, serviceType, domain, port, txtRecord);
        delegateById[id] = p;
        return id;
    }

    void stop(int id)
    {
        delegateById.remove(id);
        dns.stop(id);
    }

    void stop_all(QDnsSdDelegate *p)
    {
        QList<int>                           ids;
        QHashIterator<int, QDnsSdDelegate *> it(delegateById);
        while (it.hasNext()) {
            it.next();
            if (it.value() == p)
                ids += it.key();
        }
        for (int id : ids)
            stop(id);
    }

private slots:
    void dns_queryResult(int id, const QDnsSd::QueryResult &result) { delegateById[id]->dns_queryResult(id, result); }

    void dns_browseResult(int id, const QDnsSd::BrowseResult &result)
    {
        delegateById[id]->dns_browseResult(id, result);
    }

    void dns_resolveResult(int id, const QDnsSd::ResolveResult &result)
    {
        delegateById[id]->dns_resolveResult(id, result);
    }

    void dns_regResult(int id, const QDnsSd::RegResult &result) { delegateById[id]->dns_regResult(id, result); }
};

//----------------------------------------------------------------------------
// AppleBrowseSession
//----------------------------------------------------------------------------
// only use this class for a single browse.  if you want to browse again,
//   create a new object.
class AppleBrowse : public QObject, public QDnsSdDelegate {
    Q_OBJECT
public:
    AppleProvider *              global;
    int                          browse_id;
    QList<XMPP::ServiceInstance> instances;
    QHash<int, QByteArray>       pendingByQueryId; // waiting for TXT

    AppleBrowse(AppleProvider *_global, QObject *parent = 0) : QObject(parent), global(_global), browse_id(-1)
    {
        connect(this, SIGNAL(unavailable_p(XMPP::ServiceInstance)), SIGNAL(unavailable(XMPP::ServiceInstance)));
    }

    ~AppleBrowse() { global->stop_all(this); }

    void browse(const QString &type, const QString &domain)
    {
        browse_id = global->browse(this, type.toUtf8(), domain.toUtf8());
    }

signals:
    void available(const XMPP::ServiceInstance &instance);
    void unavailable(const XMPP::ServiceInstance &instance);
    void error();

    // emit delayed
    void unavailable_p(const XMPP::ServiceInstance &instance);

protected:
    virtual void dns_browseResult(int id, const QDnsSd::BrowseResult &result)
    {
        Q_UNUSED(id);

        if (!result.success) {
            emit error();
            return;
        }

        for (const QDnsSd::BrowseEntry &e : result.entries) {
            XMPP::ServiceInstance si(e.serviceName, e.serviceType, e.replyDomain, QMap<QString, QByteArray>());

            if (e.added) {
                int query_id               = global->query(this, si.name(), 16); // 16 == TXT
                pendingByQueryId[query_id] = si.name();
            } else // removed
            {
                // emit these queued for SS.  no worry of SR since
                //   the browse operation is not cancellable.
                for (int n = 0; n < instances.count(); ++n) {
                    const XMPP::ServiceInstance &i = instances[n];
                    if (i.name() == si.name()) {
                        emit unavailable_p(i);
                        instances.removeAt(n);
                        --n; // adjust position
                    }
                }
            }
        }
    }

    virtual void dns_queryResult(int id, const QDnsSd::QueryResult &result)
    {
        if (!result.success) {
            // if we get here, then it means we received a browse
            //   entry, but could not fetch its TXT record.  if
            //   that happens, cancel the query and drop the
            //   browse entry.
            global->stop(id);
            pendingByQueryId.remove(id);
            return;
        }

        // qdnssd guarantees at least one answer
        Q_ASSERT(!result.records.isEmpty());

        // only the first entry matters, and it must be an added TXT
        if (!result.records[0].added || result.records[0].rrtype != 16)
            return;

        // we only care about one answer
        QByteArray        name  = pendingByQueryId[id];
        QList<QByteArray> parts = nameToInstanceParts(name);
        if (parts.isEmpty()) {
            // TODO: error
            Q_ASSERT(0);
        }

        global->stop(id);
        pendingByQueryId.remove(id);

        XMPP::NameRecord rec = importQDnsSdRecord(result.records[0]);

        // bad answer?
        if (rec.isNull())
            return;

        QMap<QString, QByteArray> attribs = textsToAttribs(rec.texts());
        // FIXME: conversion/escaping?
        XMPP::ServiceInstance si(QString::fromUtf8(parts[0]), QString::fromUtf8(parts[1]), QString::fromUtf8(parts[2]),
                                 attribs);

        // does qdnssd guarantee we won't receive dups?
        bool found = false;
        for (const XMPP::ServiceInstance &i : instances) {
            if (i.name() == si.name()) {
                found = true;
                break;
            }
        }
        Q_ASSERT(!found);

        instances += si;
        emit available(si);
    }
};

//----------------------------------------------------------------------------
// AppleBrowseLookup
//----------------------------------------------------------------------------
// only use this class for a single lookup.  if you want to lookup again,
//   create a new object.
class AppleBrowseLookup : public QObject, public QDnsSdDelegate {
    Q_OBJECT
public:
    AppleProvider *    global;
    int                resolve_id;
    XMPP::NameResolver nameResolverAaaa;
    XMPP::NameResolver nameResolverA;
    bool               activeAaaa;
    bool               activeA;
    QTimer             waitTimer;
    QByteArray         host;
    QHostAddress       addr4;
    QHostAddress       addr6;
    int                port;

    AppleBrowseLookup(AppleProvider *_global, QObject *parent = 0) :
        QObject(parent), global(_global), resolve_id(-1), nameResolverAaaa(this), nameResolverA(this),
        activeAaaa(false), activeA(false), waitTimer(this)
    {
        connect(&nameResolverAaaa, SIGNAL(resultsReady(QList<XMPP::NameRecord>)),
                SLOT(nameAaaa_resultsReady(QList<XMPP::NameRecord>)));
        connect(&nameResolverAaaa, SIGNAL(error(XMPP::NameResolver::Error)),
                SLOT(nameAaaa_error(XMPP::NameResolver::Error)));

        connect(&nameResolverA, SIGNAL(resultsReady(QList<XMPP::NameRecord>)),
                SLOT(nameA_resultsReady(QList<XMPP::NameRecord>)));
        connect(&nameResolverA, SIGNAL(error(XMPP::NameResolver::Error)), SLOT(nameA_error(XMPP::NameResolver::Error)));

        connect(&waitTimer, SIGNAL(timeout()), SLOT(waitTimer_timeout()));
        waitTimer.setSingleShot(true);
    }

    ~AppleBrowseLookup() { global->stop_all(this); }

    void resolve(const QByteArray &instance, const QByteArray &type, const QByteArray &domain)
    {
        resolve_id = global->resolve(this, instance, type, domain);
    }

signals:
    // emits at least 1 and at most 2
    void finished(const QList<QHostAddress> &addrs, int port);
    void error();

protected:
    void dns_resolveResult(int id, const QDnsSd::ResolveResult &result)
    {
        // there is only one response, so deregister
        global->stop(id);

        if (!result.success) {
            emit error();
            return;
        }

        host = result.hostTarget;
        port = result.port;

        activeAaaa = true;
        activeA    = true;
        nameResolverAaaa.start(host, XMPP::NameRecord::Aaaa);
        nameResolverA.start(host, XMPP::NameRecord::A);
        waitTimer.start(500); // 500ms cut-off time, take what we have and run
    }

private slots:
    void nameAaaa_resultsReady(const QList<XMPP::NameRecord> &results)
    {
        // nameresolver guarantees at least one result, and we only
        //   care about the first
        addr6      = results[0].address();
        activeAaaa = false;
        tryDone();
    }

    void nameAaaa_error(XMPP::NameResolver::Error e)
    {
        Q_UNUSED(e);
        activeAaaa = false;
        tryDone();
    }

    void nameA_resultsReady(const QList<XMPP::NameRecord> &results)
    {
        // nameresolver guarantees at least one result, and we only
        //   care about the first
        addr4   = results[0].address();
        activeA = false;
        tryDone();
    }

    void nameA_error(XMPP::NameResolver::Error e)
    {
        Q_UNUSED(e);
        activeA = false;
        tryDone();
    }

    void waitTimer_timeout() { tryDone(); }

private:
    void tryDone()
    {
        // we're done if both resolves are inactive and we have no
        //   results, or if the wait timer ends and we have at least
        //   one result

        if (!activeAaaa && !activeA && addr6.isNull() && addr4.isNull()) {
            nameResolverAaaa.stop();
            nameResolverA.stop();
            waitTimer.stop();

            emit error();
            return;
        }

        if (!waitTimer.isActive() && (!addr6.isNull() || !addr4.isNull())) {
            nameResolverAaaa.stop();
            nameResolverA.stop();

            QList<QHostAddress> out;
            if (!addr4.isNull())
                out += addr4;
            if (!addr6.isNull())
                out += addr6;
            emit finished(out, port);
        }
    }
};

//----------------------------------------------------------------------------
// AppleNameProvider
//----------------------------------------------------------------------------
class AppleNameProvider : public XMPP::NameProvider, public QDnsSdDelegate {
    Q_OBJECT
public:
    AppleProvider *global;

    AppleNameProvider(AppleProvider *parent) : NameProvider(parent), global(parent) { }

    ~AppleNameProvider() { global->stop_all(this); }

    virtual bool supportsLongLived() const { return true; }

    virtual bool supportsRecordType(int type) const
    {
        // all record types supported
        Q_UNUSED(type);
        return true;
    }

    virtual int resolve_start(const QByteArray &name, int qType, bool longLived)
    {
        Q_UNUSED(longLived); // query is always long lived
        return global->query(this, name, qType);
    }

    virtual void resolve_stop(int id) { global->stop(id); }

protected:
    virtual void dns_queryResult(int id, const QDnsSd::QueryResult &result)
    {
        if (!result.success) {
            emit resolve_error(id, XMPP::NameResolver::ErrorGeneric);
            return;
        }

        QList<XMPP::NameRecord> results;
        for (const QDnsSd::Record &rec : result.records) {
            XMPP::NameRecord nr = importQDnsSdRecord(rec);

            // unsupported type
            if (nr.isNull())
                continue;

            // if removed, ensure ttl is 0
            if (!rec.added)
                nr.setTtl(0);

            results += nr;
        }

        emit resolve_resultsReady(id, results);
    }
};

//----------------------------------------------------------------------------
// AppleServiceProvider
//----------------------------------------------------------------------------
class AppleServiceProvider : public XMPP::ServiceProvider, public QDnsSdDelegate {
    Q_OBJECT
public:
    class Browse {
    public:
        AppleServiceProvider *parent;
        int                   id;
        AppleBrowse *         browse;

        Browse(AppleServiceProvider *_parent) : parent(_parent), id(-1), browse(0) { }

        ~Browse()
        {
            delete browse;
            parent->idManager.releaseId(id);
        }
    };

    class Resolve {
    public:
        AppleServiceProvider *parent;
        int                   id;
        AppleBrowseLookup *   resolve;

        Resolve(AppleServiceProvider *_parent) : parent(_parent), id(-1), resolve(0) { }

        ~Resolve()
        {
            delete resolve;
            parent->idManager.releaseId(id);
        }
    };

    AppleProvider *  global;
    QList<Browse *>  browseList;
    QList<Resolve *> resolveList;
    IdManager        idManager;

    AppleServiceProvider(AppleProvider *parent) : ServiceProvider(parent), global(parent) { }

    ~AppleServiceProvider()
    {
        qDeleteAll(resolveList);
        qDeleteAll(browseList);
        global->stop_all(this);
    }

    int indexOfBrowseByBrowse(AppleBrowse *browse) const
    {
        for (int n = 0; n < browseList.count(); ++n) {
            if (browseList[n]->browse == browse)
                return n;
        }
        return -1;
    }

    int indexOfBrowseById(int id) const
    {
        for (int n = 0; n < browseList.count(); ++n) {
            if (browseList[n]->id == id)
                return n;
        }
        return -1;
    }

    int indexOfResolveByResolve(AppleBrowseLookup *resolve) const
    {
        for (int n = 0; n < resolveList.count(); ++n) {
            if (resolveList[n]->resolve == resolve)
                return n;
        }
        return -1;
    }

    int indexOfResolveById(int id) const
    {
        for (int n = 0; n < resolveList.count(); ++n) {
            if (resolveList[n]->id == id)
                return n;
        }
        return -1;
    }

    virtual int browse_start(const QString &type, const QString &domain)
    {
        Browse *b = new Browse(this);
        b->id     = idManager.reserveId();
        b->browse = new AppleBrowse(global, this);
        connect(b->browse, SIGNAL(available(XMPP::ServiceInstance)), SLOT(browse_available(XMPP::ServiceInstance)));
        connect(b->browse, SIGNAL(unavailable(XMPP::ServiceInstance)), SLOT(browse_unavailable(XMPP::ServiceInstance)));
        connect(b->browse, SIGNAL(error()), SLOT(browse_error()));
        browseList += b;
        b->browse->browse(type, domain);
        return b->id;
    }

    virtual void browse_stop(int id)
    {
        int at = indexOfBrowseById(id);
        if (at == -1)
            return;

        Browse *b = browseList[at];
        browseList.removeAt(at);
        delete b;
    }

    virtual int resolve_start(const QByteArray &name)
    {
        QList<QByteArray> parts = nameToInstanceParts(name);
        if (parts.isEmpty()) {
            // TODO: signal error rather than die
            Q_ASSERT(0);
        }

        Resolve *r = new Resolve(this);
        r->id      = idManager.reserveId();
        r->resolve = new AppleBrowseLookup(global, this);
        connect(r->resolve, SIGNAL(finished(QList<QHostAddress>)), SLOT(resolve_finished(QList<QHostAddress>)));
        connect(r->resolve, SIGNAL(error()), SLOT(resolve_error()));
        resolveList += r;
        r->resolve->resolve(parts[0], parts[1], parts[2]);
        return r->id;
    }

    virtual void resolve_stop(int id)
    {
        int at = indexOfResolveById(id);
        if (at == -1)
            return;

        Resolve *r = resolveList[at];
        resolveList.removeAt(at);
        delete r;
    }

    virtual int publish_start(const QString &instance, const QString &type, int port,
                              const QMap<QString, QByteArray> &attributes)
    {
        QByteArray txtRecord = attribsToTxtRecord(attributes);
        if (txtRecord.isEmpty()) {
            // TODO: signal error rather than die
            Q_ASSERT(0);
        }

        QString domain = "local";

        // FIXME: conversion/escaping is probably wrong?
        return global->reg(this, instance.toUtf8(), type.toUtf8(), domain.toUtf8(), port, txtRecord);
    }

    virtual void publish_update(int id, const QMap<QString, QByteArray> &attributes)
    {
        // TODO: verify 'id' is valid.  if not valid, then assert/return (don't do anything or signal error)

        QByteArray txtRecord = attribsToTxtRecord(attributes);
        if (txtRecord.isEmpty()) {
            // TODO: signal error rather than die
            Q_ASSERT(0);
        }

        if (global->dns.recordUpdateTxt(id, txtRecord, 4500)) {
            // FIXME: SR
            QMetaObject::invokeMethod(this, "publish_published", Qt::QueuedConnection, Q_ARG(int, id));
        } else {
            // TODO: unpublish

            // FIXME: register meta type, SR
            QMetaObject::invokeMethod(
                this, "publish_error", Qt::QueuedConnection, Q_ARG(int, id),
                Q_ARG(XMPP::ServiceLocalPublisher::Error, XMPP::ServiceLocalPublisher::ErrorGeneric));
        }
    }

    virtual void publish_stop(int id) { global->stop(id); }

    virtual int publish_extra_start(int pub_id, const XMPP::NameRecord &name)
    {
        // TODO
        Q_UNUSED(pub_id);
        Q_UNUSED(name);
        return 0;
    }

    virtual void publish_extra_update(int id, const XMPP::NameRecord &name)
    {
        // TODO
        Q_UNUSED(id);
        Q_UNUSED(name);
    }

    virtual void publish_extra_stop(int id)
    {
        // TODO
        Q_UNUSED(id);
    }

    // called by AppleProvider

    void dns_regResult(int id, const QDnsSd::RegResult &result)
    {
        // TODO
        Q_UNUSED(id);
        Q_UNUSED(result);
    }

private slots:
    void browse_available(const XMPP::ServiceInstance &instance)
    {
        int at = indexOfBrowseByBrowse(static_cast<AppleBrowse *>(sender()));
        Q_ASSERT(at != -1);

        emit browse_instanceAvailable(browseList[at]->id, instance);
    }

    void browse_unavailable(const XMPP::ServiceInstance &instance)
    {
        int at = indexOfBrowseByBrowse(static_cast<AppleBrowse *>(sender()));
        Q_ASSERT(at != -1);

        emit browse_instanceUnavailable(browseList[at]->id, instance);
    }

    void browse_error()
    {
        int at = indexOfBrowseByBrowse(static_cast<AppleBrowse *>(sender()));
        Q_ASSERT(at != -1);

        Browse *b = browseList[at];
        browseList.removeAt(at);
        int id = b->id;
        delete b;

        // FIXME: this looks weird, we should probably rename our
        //   local function
        emit ServiceProvider::browse_error(id, XMPP::ServiceBrowser::ErrorGeneric);
    }

    void resolve_finished(const QList<QHostAddress> &addrs, int port)
    {
        int at = indexOfResolveByResolve(static_cast<AppleBrowseLookup *>(sender()));
        Q_ASSERT(at != -1);

        Resolve *r = resolveList[at];
        resolveList.removeAt(at);
        int id = r->id;
        delete r;

        QList<ResolveResult> results;
        for (const QHostAddress &addr : addrs) {
            ResolveResult r;
            r.address = addr;
            r.port    = port;
            results += r;
        }

        emit resolve_resultsReady(id, results);
    }

    void resolve_error()
    {
        int at = indexOfResolveByResolve(static_cast<AppleBrowseLookup *>(sender()));
        Q_ASSERT(at != -1);

        Resolve *r = resolveList[at];
        resolveList.removeAt(at);
        int id = r->id;
        delete r;

        // FIXME: this looks weird, we should probably rename our
        //   local function
        emit ServiceProvider::resolve_error(id, XMPP::ServiceResolver::ErrorGeneric);
    }
};

// AppleProvider
XMPP::NameProvider *AppleProvider::createNameProviderInternet() { return new AppleNameProvider(this); }

XMPP::NameProvider *AppleProvider::createNameProviderLocal() { return new AppleNameProvider(this); }

XMPP::ServiceProvider *AppleProvider::createServiceProvider() { return new AppleServiceProvider(this); }

#ifdef APPLEDNS_STATIC
XMPP::IrisNetProvider *irisnet_createAppleProvider() { return new AppleProvider; }
#else
Q_EXPORT_PLUGIN2(appledns, AppleProvider)
#endif

#include "appledns.moc"
