/*
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301  USA
 *
 */

#include "irisnetplugin.h"

#include <QtCore>
#include <QtNetwork>
#include "qdnssd.h"

// for ntohl
#ifdef Q_OS_WIN
# 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;
    foreach(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);
    }
};

}

//----------------------------------------------------------------------------
// 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();
        }
        foreach(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;
        }

        foreach(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;
        foreach(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;
        foreach(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;
        foreach(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"
