/*
 * Copyright (C) 2009,2010  Barracuda Networks, Inc.
 *
 * 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 WITHANY 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 "ice176.h"

#include <QSet>
#include <QTimer>
#include <QUdpSocket>
#include <QtCrypto>
#include "stuntransaction.h"
#include "stunbinding.h"
#include "stunmessage.h"
#include "udpportreserver.h"
#include "icelocaltransport.h"
#include "iceturntransport.h"
#include "icecomponent.h"

namespace XMPP {

enum
{
    Direct,
    Relayed
};

static QChar randomPrintableChar()
{
    // 0-25 = a-z
    // 26-51 = A-Z
    // 52-61 = 0-9

    uchar c = QCA::Random::randomChar() % 62;
    if(c <= 25)
        return 'a' + c;
    else if(c <= 51)
        return 'A' + (c - 26);
    else
        return '0' + (c - 52);
}

static QString randomCredential(int len)
{
    QString out;
    for(int n = 0; n < len; ++n)
        out += randomPrintableChar();
    return out;
}

static qint64 calc_pair_priority(int a, int b)
{
    qint64 priority = ((qint64)1 << 32) * qMin(a, b);
    priority += (qint64)2 * qMax(a, b);
    if(a > b)
        ++priority;
    return priority;
}

// see if candidates are considered the same for pruning purposes
static bool compare_candidates(const IceComponent::CandidateInfo &a, const IceComponent::CandidateInfo &b)
{
    if(a.addr == b.addr && a.componentId == b.componentId)
        return true;
    else
        return false;
}

// scope values: 0 = local, 1 = link-local, 2 = private, 3 = public
// FIXME: dry (this is in psi avcall also)
static int getAddressScope(const QHostAddress &a)
{
    if(a.protocol() == QAbstractSocket::IPv6Protocol)
    {
        if(a == QHostAddress(QHostAddress::LocalHostIPv6))
            return 0;
        else if(XMPP::Ice176::isIPv6LinkLocalAddress(a))
            return 1;
    }
    else if(a.protocol() == QAbstractSocket::IPv4Protocol)
    {
        quint32 v4 = a.toIPv4Address();
        quint8 a0 = v4 >> 24;
        quint8 a1 = (v4 >> 16) & 0xff;
        if(a0 == 127)
            return 0;
        else if(a0 == 169 && a1 == 254)
            return 1;
        else if(a0 == 10)
            return 2;
        else if(a0 == 172 && a1 >= 16 && a1 <= 31)
            return 2;
        else if(a0 == 192 && a1 == 168)
            return 2;
    }

    return 3;
}

class Ice176::Private : public QObject
{
    Q_OBJECT

public:
    enum State
    {
        Stopped,
        Starting,
        Started,
        Stopping
    };

    enum CandidatePairState
    {
        PWaiting,
        PInProgress,
        PSucceeded,
        PFailed,
        PFrozen
    };

    enum CheckListState
    {
        LRunning,
        LCompleted,
        LFailed
    };

    class CandidatePair
    {
    public:
        IceComponent::CandidateInfo local, remote;
        bool isDefault;
        bool isValid;
        bool isNominated;
        CandidatePairState state;

        qint64 priority;
        QString foundation;

        StunBinding *binding;

        // FIXME: this is wrong i think, it should be in LocalTransport
        //   or such, to multiplex ids
        StunTransactionPool *pool;

        CandidatePair() :
            binding(0),
            pool(0)
        {
        }
    };

    class CheckList
    {
    public:
        QList<CandidatePair> pairs;
        CheckListState state;
    };

    class Component
    {
    public:
        int id;
        IceComponent *ic;
        bool localFinished;
        bool stopped;
        bool lowOverhead;

        Component() :
            localFinished(false),
            stopped(false),
            lowOverhead(false)
        {
        }
    };

    Ice176 *q;
    Ice176::Mode mode;
    State state;
    TurnClient::Proxy proxy;
    UdpPortReserver *portReserver;
    int componentCount;
    QList<Ice176::LocalAddress> localAddrs;
    QList<Ice176::ExternalAddress> extAddrs;
    QHostAddress stunBindAddr;
    int stunBindPort;
    QHostAddress stunRelayUdpAddr;
    int stunRelayUdpPort;
    QString stunRelayUdpUser;
    QCA::SecureArray stunRelayUdpPass;
    QHostAddress stunRelayTcpAddr;
    int stunRelayTcpPort;
    QString stunRelayTcpUser;
    QCA::SecureArray stunRelayTcpPass;
    QString localUser, localPass;
    QString peerUser, peerPass;
    QList<Component> components;
    QList<IceComponent::Candidate> localCandidates;
    QSet<IceTransport*> iceTransports;
    CheckList checkList;
    QList< QList<QByteArray> > in;
    bool useLocal;
    bool useStunBind;
    bool useStunRelayUdp;
    bool useStunRelayTcp;
    bool useTrickle;
    QTimer *collectTimer;

    Private(Ice176 *_q) :
        QObject(_q),
        q(_q),
        state(Stopped),
        portReserver(0),
        componentCount(0),
        useLocal(true),
        useStunBind(true),
        useStunRelayUdp(true),
        useStunRelayTcp(true),
        useTrickle(false),
        collectTimer(0)
    {
    }

    ~Private()
    {
        if(collectTimer)
        {
            collectTimer->disconnect(this);
            collectTimer->deleteLater();
        }

        foreach(const Component &c, components)
            delete c.ic;

        // no need to delete pools and bindings since pools already deleted here
        // by QObject destructor as children of this(Ice176::Private) object.
        // Bindings deleted too as children of pool
        // should be reviewed by Justin =)
        /*for(int n = 0; n < checkList.pairs.count(); ++n)
        {
            StunBinding *binding = checkList.pairs[n].binding;
            StunTransactionPool *pool = checkList.pairs[n].pool;

            delete binding;

            if(pool)
            {
                pool->disconnect(this);
                pool->setParent(0);
                pool->deleteLater();
            }
        }*/
    }

    void reset()
    {
        // TODO
    }

    int findLocalAddress(const QHostAddress &addr)
    {
        for(int n = 0; n < localAddrs.count(); ++n)
        {
            if(localAddrs[n].addr == addr)
                return n;
        }

        return -1;
    }

    void updateLocalAddresses(const QList<LocalAddress> &addrs)
    {
        // for now, ignore address changes during operation
        if(state != Stopped)
            return;

        localAddrs.clear();
        foreach(const LocalAddress &la, addrs)
        {
            int at = findLocalAddress(la.addr);
            if(at == -1)
                localAddrs += la;
        }
    }

    void updateExternalAddresses(const QList<ExternalAddress> &addrs)
    {
        // for now, ignore address changes during operation
        if(state != Stopped)
            return;

        extAddrs.clear();
        foreach(const ExternalAddress &ea, addrs)
        {
            int at = findLocalAddress(ea.base.addr);
            if(at != -1)
                extAddrs += ea;
        }
    }

    void start()
    {
        Q_ASSERT(state == Stopped);

        state = Starting;

        localUser = randomCredential(4);
        localPass = randomCredential(22);

        QList<QUdpSocket*> socketList;
        if(portReserver)
            socketList = portReserver->borrowSockets(componentCount, this);

        for(int n = 0; n < componentCount; ++n)
        {
            Component c;
            c.id = n + 1;
            c.ic = new IceComponent(c.id, this);
            c.ic->setDebugLevel(IceComponent::DL_Info);
            connect(c.ic, SIGNAL(candidateAdded(XMPP::IceComponent::Candidate)), SLOT(ic_candidateAdded(XMPP::IceComponent::Candidate)));
            connect(c.ic, SIGNAL(candidateRemoved(XMPP::IceComponent::Candidate)), SLOT(ic_candidateRemoved(XMPP::IceComponent::Candidate)));
            connect(c.ic, SIGNAL(localFinished()), SLOT(ic_localFinished()));
            connect(c.ic, SIGNAL(stopped()), SLOT(ic_stopped()));
            connect(c.ic, SIGNAL(debugLine(QString)), SLOT(ic_debugLine(QString)));

            c.ic->setClientSoftwareNameAndVersion("Iris");
            c.ic->setProxy(proxy);
            if(portReserver)
                c.ic->setPortReserver(portReserver);
            c.ic->setLocalAddresses(localAddrs);
            c.ic->setExternalAddresses(extAddrs);
            if(!stunBindAddr.isNull())
                c.ic->setStunBindService(stunBindAddr, stunBindPort);
            if(!stunRelayUdpAddr.isNull())
                c.ic->setStunRelayUdpService(stunRelayUdpAddr, stunRelayUdpPort, stunRelayUdpUser, stunRelayUdpPass);
            if(!stunRelayTcpAddr.isNull())
                c.ic->setStunRelayTcpService(stunRelayTcpAddr, stunRelayTcpPort, stunRelayTcpUser, stunRelayTcpPass);

            c.ic->setUseLocal(useLocal);
            c.ic->setUseStunBind(useStunBind);
            c.ic->setUseStunRelayUdp(useStunRelayUdp);
            c.ic->setUseStunRelayTcp(useStunRelayTcp);

            // create an inbound queue for this component
            in += QList<QByteArray>();

            components += c;

            c.ic->update(&socketList);
        }

        // socketList should always empty here, but might not be if
        //   the app provided a different address list to
        //   UdpPortReserver and Ice176.  and that would really be
        //   a dumb thing to do but I'm not going to Q_ASSERT it
        if(!socketList.isEmpty())
            portReserver->returnSockets(socketList);
    }

    void stop()
    {
        Q_ASSERT(state == Starting || state == Started);

        state = Stopping;

        if(!components.isEmpty())
        {
            for(int n = 0; n < components.count(); ++n)
                components[n].ic->stop();
        }
        else
        {
            // TODO: hmm, is it possible to have no components?
            QMetaObject::invokeMethod(this, "postStop", Qt::QueuedConnection);
        }
    }

    void addRemoteCandidates(const QList<Candidate> &list)
    {
        QList<IceComponent::CandidateInfo> remoteCandidates;
        foreach(const Candidate &c, list)
        {
            IceComponent::CandidateInfo ci;
            ci.addr.addr = c.ip;
            ci.addr.addr.setScopeId(QString());
            ci.addr.port = c.port;
            ci.type = (IceComponent::CandidateType)string_to_candidateType(c.type); // TODO: handle error
            ci.componentId = c.component;
            ci.priority = c.priority;
            ci.foundation = c.foundation;
            if(!c.rel_addr.isNull())
            {
                ci.base.addr = c.rel_addr;
                ci.base.addr.setScopeId(QString());
                ci.base.port = c.rel_port;
            }
            ci.network = c.network;
            ci.id = c.id;
            remoteCandidates += ci;
        }

        printf("adding %d remote candidates\n", remoteCandidates.count());

        QList<CandidatePair> pairs;
        foreach(const IceComponent::Candidate &cc, localCandidates)
        {
            const IceComponent::CandidateInfo &lc = cc.info;

            foreach(const IceComponent::CandidateInfo &rc, remoteCandidates)
            {
                if(lc.componentId != rc.componentId)
                    continue;

                // don't pair ipv4 with ipv6.  FIXME: is this right?
                if(lc.addr.addr.protocol() != rc.addr.addr.protocol())
                    continue;

                // don't relay to localhost.  turnserver
                //   doesn't like it.  i don't know if this
                //   should qualify as a HACK or not.
                //   trying to relay to localhost is pretty
                //   stupid anyway
                if(lc.type == IceComponent::RelayedType && getAddressScope(rc.addr.addr) == 0)
                    continue;

                CandidatePair pair;
                pair.state = PFrozen; // FIXME: setting state here may be wrong
                pair.local = lc;
                pair.remote = rc;
                if(pair.local.addr.addr.protocol() == QAbstractSocket::IPv6Protocol && isIPv6LinkLocalAddress(pair.local.addr.addr))
                    pair.remote.addr.addr.setScopeId(pair.local.addr.addr.scopeId());
                pair.isDefault = false;
                pair.isValid = false;
                pair.isNominated = false;
                if(mode == Ice176::Initiator)
                    pair.priority = calc_pair_priority(lc.priority, rc.priority);
                else
                    pair.priority = calc_pair_priority(rc.priority, lc.priority);
                pairs += pair;
            }
        }

        printf("%d pairs\n", pairs.count());

        // combine pairs with existing, and sort
        pairs = checkList.pairs + pairs;
        checkList.pairs.clear();
        foreach(const CandidatePair &pair, pairs)
        {
            int at;
            for(at = 0; at < checkList.pairs.count(); ++at)
            {
                if(compare_pair(pair, checkList.pairs[at]) < 0)
                    break;
            }

            checkList.pairs.insert(at, pair);
        }

        // pruning

        for(int n = 0; n < checkList.pairs.count(); ++n)
        {
            CandidatePair &pair = checkList.pairs[n];
            if(pair.local.type == IceComponent::ServerReflexiveType)
                pair.local.addr = pair.local.base;
        }

        for(int n = 0; n < checkList.pairs.count(); ++n)
        {
            CandidatePair &pair = checkList.pairs[n];
            printf("%d, %s:%d -> %s:%d\n", pair.local.componentId, qPrintable(pair.local.addr.addr.toString()), pair.local.addr.port, qPrintable(pair.remote.addr.addr.toString()), pair.remote.addr.port);

            bool found = false;
            for(int i = n - 1; i >= 0; --i)
            {
                if(compare_candidates(pair.local, checkList.pairs[i].local) && compare_candidates(pair.remote, checkList.pairs[i].remote))
                {
                    found = true;
                    break;
                }
            }

            if(found )
            {
                checkList.pairs.removeAt(n);
                --n; // adjust position
            }
        }

        // max pairs is 100 * number of components
        int max_pairs = 100 * components.count();
        while(checkList.pairs.count() > max_pairs)
            checkList.pairs.removeLast();

        printf("%d after pruning\n", checkList.pairs.count());

        // set state
        for(int n = 0; n < checkList.pairs.count(); ++n)
        {
            CandidatePair &pair = checkList.pairs[n];

            // only initialize the new pairs
            if(pair.state != PFrozen)
                continue;

            pair.foundation = pair.local.foundation + pair.remote.foundation;

            // FIXME: for now we just do checks to everything immediately
            pair.state = PInProgress;

            int at = findLocalCandidate(pair.local.addr.addr, pair.local.addr.port);
            Q_ASSERT(at != -1);

            IceComponent::Candidate &lc = localCandidates[at];

            Component &c = components[findComponent(lc.info.componentId)];

            pair.pool = new StunTransactionPool(StunTransaction::Udp, this);
            connect(pair.pool, SIGNAL(outgoingMessage(QByteArray,QHostAddress,int)), SLOT(pool_outgoingMessage(QByteArray,QHostAddress,int)));
            //pair.pool->setUsername(peerUser + ':' + localUser);
            //pair.pool->setPassword(peerPass.toUtf8());

            pair.binding = new StunBinding(pair.pool);
            connect(pair.binding, SIGNAL(success()), SLOT(binding_success()));

            int prflx_priority = c.ic->peerReflexivePriority(lc.iceTransport, lc.path);
            pair.binding->setPriority(prflx_priority);

            if(mode == Ice176::Initiator)
            {
                pair.binding->setIceControlling(0);
                pair.binding->setUseCandidate(true);
            }
            else
                pair.binding->setIceControlled(0);

            pair.binding->setShortTermUsername(peerUser + ':' + localUser);
            pair.binding->setShortTermPassword(peerPass);

            pair.binding->start();
        }
    }

    void write(int componentIndex, const QByteArray &datagram)
    {
        int at = -1;
        for(int n = 0; n < checkList.pairs.count(); ++n)
        {
            if(checkList.pairs[n].local.componentId - 1 == componentIndex && checkList.pairs[n].isValid)
            {
                at = n;
                break;
            }
        }
        if(at == -1)
            return;

        CandidatePair &pair = checkList.pairs[at];

        at = findLocalCandidate(pair.local.addr.addr, pair.local.addr.port);
        if(at == -1) // FIXME: assert?
            return;

        IceComponent::Candidate &lc = localCandidates[at];

        IceTransport *sock = lc.iceTransport;
        int path = lc.path;

        sock->writeDatagram(path, datagram, pair.remote.addr.addr, pair.remote.addr.port);

        // DOR-SR?
        QMetaObject::invokeMethod(q, "datagramsWritten", Qt::QueuedConnection, Q_ARG(int, componentIndex), Q_ARG(int, 1));
    }

    void flagComponentAsLowOverhead(int componentIndex)
    {
        // FIXME: ok to assume in order?
        Component &c = components[componentIndex];
        c.lowOverhead = true;

        // FIXME: actually do something
    }

private:
    int findComponent(const IceComponent *ic) const
    {
        for(int n = 0; n < components.count(); ++n)
        {
            if(components[n].ic == ic)
                return n;
        }

        return -1;
    }

    int findComponent(int id) const
    {
        for(int n = 0; n < components.count(); ++n)
        {
            if(components[n].id == id)
                return n;
        }

        return -1;
    }

    int findLocalCandidate(const IceTransport *iceTransport, int path) const
    {
        for(int n = 0; n < localCandidates.count(); ++n)
        {
            const IceComponent::Candidate &cc = localCandidates[n];
            if(cc.iceTransport == iceTransport && cc.path == path)
                return n;
        }

        return -1;
    }

    int findLocalCandidate(const QHostAddress &fromAddr, int fromPort)
    {
        for(int n = 0; n < localCandidates.count(); ++n)
        {
            const IceComponent::Candidate &cc = localCandidates[n];
            if(cc.info.addr.addr == fromAddr && cc.info.addr.port == fromPort)
                return n;
        }

        return -1;
    }

    static QString candidateType_to_string(IceComponent::CandidateType type)
    {
        QString out;
        switch(type)
        {
            case IceComponent::HostType: out = "host"; break;
            case IceComponent::PeerReflexiveType: out = "prflx"; break;
            case IceComponent::ServerReflexiveType: out = "srflx"; break;
            case IceComponent::RelayedType: out = "relay"; break;
            default: Q_ASSERT(0);
        }
        return out;
    }

    static int string_to_candidateType(const QString &in)
    {
        if(in == "host")
            return IceComponent::HostType;
        else if(in == "prflx")
            return IceComponent::PeerReflexiveType;
        else if(in == "srflx")
            return IceComponent::ServerReflexiveType;
        else if(in == "relay")
            return IceComponent::RelayedType;
        else
            return -1;
    }

    static int compare_pair(const CandidatePair &a, const CandidatePair &b)
    {
        // prefer remote srflx, for leap
        if(a.remote.type == IceComponent::ServerReflexiveType && b.remote.type != IceComponent::ServerReflexiveType && b.remote.addr.addr.protocol() != QAbstractSocket::IPv6Protocol)
            return -1;
        else if(b.remote.type == IceComponent::ServerReflexiveType && a.remote.type != IceComponent::ServerReflexiveType && a.remote.addr.addr.protocol() != QAbstractSocket::IPv6Protocol)
            return 1;

        if(a.priority > b.priority)
            return -1;
        else if(b.priority > a.priority)
            return 1;

        return 0;
    }

private slots:
    void postStop()
    {
        state = Stopped;
        emit q->stopped();
    }

    void ic_candidateAdded(const XMPP::IceComponent::Candidate &_cc)
    {
        IceComponent::Candidate cc = _cc;
        cc.info.id = randomCredential(10); // FIXME: ensure unique
        cc.info.foundation = "0"; // FIXME
        // TODO
        localCandidates += cc;

        printf("C%d: candidate added: %s;%d\n", cc.info.componentId, qPrintable(cc.info.addr.addr.toString()), cc.info.addr.port);

        if(!iceTransports.contains(cc.iceTransport))
        {
            connect(cc.iceTransport, SIGNAL(readyRead(int)), SLOT(it_readyRead(int)));
            connect(cc.iceTransport, SIGNAL(datagramsWritten(int,int,QHostAddress,int)), SLOT(it_datagramsWritten(int,int,QHostAddress,int)));

            iceTransports += cc.iceTransport;
        }

        if(state == Started && useTrickle)
        {
            QList<Ice176::Candidate> list;

            Ice176::Candidate c;
            c.component = cc.info.componentId;
            c.foundation = cc.info.foundation;
            c.generation = 0; // TODO
            c.id = cc.info.id;
            c.ip = cc.info.addr.addr;
            c.ip.setScopeId(QString());
            c.network = cc.info.network;
            c.port = cc.info.addr.port;
            c.priority = cc.info.priority;
            c.protocol = "udp";
            if(cc.info.type != IceComponent::HostType)
            {
                c.rel_addr = cc.info.base.addr;
                c.rel_addr.setScopeId(QString());
                c.rel_port = cc.info.base.port;
            }
            else
            {
                c.rel_addr = QHostAddress();
                c.rel_port = -1;
            }
            c.rem_addr = QHostAddress();
            c.rem_port = -1;
            c.type = candidateType_to_string(cc.info.type);
            list += c;

            emit q->localCandidatesReady(list);
        }
    }

    void ic_candidateRemoved(const XMPP::IceComponent::Candidate &cc)
    {
        // TODO
        printf("C%d: candidate removed: %s;%d\n", cc.info.componentId, qPrintable(cc.info.addr.addr.toString()), cc.info.addr.port);

        QStringList idList;
        for(int n = 0; n < localCandidates.count(); ++n)
        {
            if(localCandidates[n].id == cc.id && localCandidates[n].info.componentId == cc.info.componentId)
            {
                // FIXME: this is rather ridiculous I think
                idList += localCandidates[n].info.id;

                localCandidates.removeAt(n);
                --n; // adjust position
            }
        }

        bool iceTransportInUse = false;
        foreach(const IceComponent::Candidate &lc, localCandidates)
        {
            if(lc.iceTransport == cc.iceTransport)
            {
                iceTransportInUse = true;
                break;
            }
        }
        if(!iceTransportInUse)
        {
            cc.iceTransport->disconnect(this);
            iceTransports.remove(cc.iceTransport);
        }

        for(int n = 0; n < checkList.pairs.count(); ++n)
        {
            if(idList.contains(checkList.pairs[n].local.id))
            {
                StunBinding *binding = checkList.pairs[n].binding;
                StunTransactionPool *pool = checkList.pairs[n].pool;

                delete binding;

                if(pool)
                {
                    pool->disconnect(this);
                    pool->setParent(0);
                    pool->deleteLater();
                }

                checkList.pairs.removeAt(n);
                --n; // adjust position
            }
        }
    }

    void ic_localFinished()
    {
        IceComponent *ic = static_cast<IceComponent *>(sender());
        int at = findComponent(ic);
        Q_ASSERT(at != -1);

        components[at].localFinished = true;

        bool allFinished = true;
        foreach(const Component &c, components)
        {
            if(!c.localFinished)
            {
                allFinished = false;
                break;
            }
        }

        if(allFinished)
        {
            state = Started;

            emit q->started();

            if(!useTrickle)
            {
                // FIXME: there should be a way to not wait if
                //   we know for sure there is nothing else
                //   possibly coming
                collectTimer = new QTimer(this);
                connect(collectTimer, SIGNAL(timeout()), SLOT(collect_timeout()));
                collectTimer->setSingleShot(true);
                collectTimer->start(4000);
                return;
            }

            // FIXME: DOR-SS
            QList<Ice176::Candidate> list;
            foreach(const IceComponent::Candidate &cc, localCandidates)
            {
                Ice176::Candidate c;
                c.component = cc.info.componentId;
                c.foundation = cc.info.foundation;
                c.generation = 0; // TODO
                c.id = cc.info.id;
                c.ip = cc.info.addr.addr;
                c.ip.setScopeId(QString());
                c.network = cc.info.network;
                c.port = cc.info.addr.port;
                c.priority = cc.info.priority;
                c.protocol = "udp";
                if(cc.info.type != IceComponent::HostType)
                {
                    c.rel_addr = cc.info.base.addr;
                    c.rel_addr.setScopeId(QString());
                    c.rel_port = cc.info.base.port;
                }
                else
                {
                    c.rel_addr = QHostAddress();
                    c.rel_port = -1;
                }
                c.rem_addr = QHostAddress();
                c.rem_port = -1;
                c.type = candidateType_to_string(cc.info.type);
                list += c;
            }
            if(!list.isEmpty())
                emit q->localCandidatesReady(list);
        }
    }

    void ic_stopped()
    {
        IceComponent *ic = static_cast<IceComponent *>(sender());
        int at = findComponent(ic);
        Q_ASSERT(at != -1);

        components[at].stopped = true;

        bool allStopped = true;
        foreach(const Component &c, components)
        {
            if(!c.stopped)
            {
                allStopped = false;
                break;
            }
        }

        if(allStopped)
            postStop();
    }

    void ic_debugLine(const QString &line)
    {
        IceComponent *ic = static_cast<IceComponent *>(sender());
        int at = findComponent(ic);
        Q_ASSERT(at != -1);

        // FIXME: components are always sorted?
        printf("C%d: %s\n", at + 1, qPrintable(line));
    }

    void collect_timeout()
    {
        collectTimer->disconnect(this);
        collectTimer->deleteLater();
        collectTimer = 0;

        QList<Ice176::Candidate> list;
        foreach(const IceComponent::Candidate &cc, localCandidates)
        {
            Ice176::Candidate c;
            c.component = cc.info.componentId;
            c.foundation = cc.info.foundation;
            c.generation = 0; // TODO
            c.id = cc.info.id;
            c.ip = cc.info.addr.addr;
            c.ip.setScopeId(QString());
            c.network = cc.info.network;
            c.port = cc.info.addr.port;
            c.priority = cc.info.priority;
            c.protocol = "udp";
            if(cc.info.type != IceComponent::HostType)
            {
                c.rel_addr = cc.info.base.addr;
                c.rel_addr.setScopeId(QString());
                c.rel_port = cc.info.base.port;
            }
            else
            {
                c.rel_addr = QHostAddress();
                c.rel_port = -1;
            }
            c.rem_addr = QHostAddress();
            c.rem_port = -1;
            c.type = candidateType_to_string(cc.info.type);
            list += c;
        }
        if(!list.isEmpty())
            emit q->localCandidatesReady(list);
    }

    void it_readyRead(int path)
    {
        IceTransport *it = static_cast<IceTransport *>(sender());
        int at = findLocalCandidate(it, path);
        Q_ASSERT(at != -1);

        IceComponent::Candidate &cc = localCandidates[at];

        IceTransport *sock = it;

        while(sock->hasPendingDatagrams(path))
        {
            QHostAddress fromAddr;
            int fromPort;
            QByteArray buf = sock->readDatagram(path, &fromAddr, &fromPort);

            //printf("port %d: received packet (%d bytes)\n", lt->sock->localPort(), buf.size());

            QString requser = localUser + ':' + peerUser;
            QByteArray reqkey = localPass.toUtf8();

            StunMessage::ConvertResult result;
            StunMessage msg = StunMessage::fromBinary(buf, &result, StunMessage::MessageIntegrity | StunMessage::Fingerprint, reqkey);
            if(!msg.isNull() && (msg.mclass() == StunMessage::Request || msg.mclass() == StunMessage::Indication))
            {
                printf("received validated request or indication from %s:%d\n", qPrintable(fromAddr.toString()), fromPort);
                QString user = QString::fromUtf8(msg.attribute(0x0006)); // USERNAME
                if(requser != user)
                {
                    printf("user [%s] is wrong.  it should be [%s].  skipping\n", qPrintable(user), qPrintable(requser));
                    continue;
                }

                if(msg.method() != 0x001)
                {
                    printf("not a binding request.  skipping\n");
                    continue;
                }

                StunMessage response;
                response.setClass(StunMessage::SuccessResponse);
                response.setMethod(0x001);
                response.setId(msg.id());

                quint16 port16 = fromPort;
                quint32 addr4 = fromAddr.toIPv4Address();
                QByteArray val(8, 0);
                quint8 *p = (quint8 *)val.data();
                const quint8 *magic = response.magic();
                p[0] = 0;
                p[1] = 0x01;
                p[2] = (port16 >> 8) & 0xff;
                p[2] ^= magic[0];
                p[3] = port16 & 0xff;
                p[3] ^= magic[1];
                p[4] = (addr4 >> 24) & 0xff;
                p[4] ^= magic[0];
                p[5] = (addr4 >> 16) & 0xff;
                p[5] ^= magic[1];
                p[6] = (addr4 >> 8) & 0xff;
                p[6] ^= magic[2];
                p[7] = addr4 & 0xff;
                p[7] ^= magic[3];

                QList<StunMessage::Attribute> list;
                StunMessage::Attribute attr;
                attr.type = 0x0020;
                attr.value = val;
                list += attr;

                response.setAttributes(list);

                QByteArray packet = response.toBinary(StunMessage::MessageIntegrity | StunMessage::Fingerprint, reqkey);
                sock->writeDatagram(path, packet, fromAddr, fromPort);
            }
            else
            {
                QByteArray reskey = peerPass.toUtf8();
                StunMessage msg = StunMessage::fromBinary(buf, &result, StunMessage::MessageIntegrity | StunMessage::Fingerprint, reskey);
                if(!msg.isNull() && (msg.mclass() == StunMessage::SuccessResponse || msg.mclass() == StunMessage::ErrorResponse))
                {
                    printf("received validated response\n");

                    // FIXME: this is so gross and completely defeats the point of having pools
                    for(int n = 0; n < checkList.pairs.count(); ++n)
                    {
                        CandidatePair &pair = checkList.pairs[n];
                        if(pair.local.addr.addr == cc.info.addr.addr && pair.local.addr.port == cc.info.addr.port)
                            pair.pool->writeIncomingMessage(msg);
                    }
                }
                else
                {
                    //printf("received some non-stun or invalid stun packet\n");

                    // FIXME: i don't know if this is good enough
                    if(StunMessage::isProbablyStun(buf))
                    {
                        printf("unexpected stun packet (loopback?), skipping.\n");
                        continue;
                    }

                    int at = -1;
                    for(int n = 0; n < checkList.pairs.count(); ++n)
                    {
                        CandidatePair &pair = checkList.pairs[n];
                        if(pair.local.addr.addr == cc.info.addr.addr && pair.local.addr.port == cc.info.addr.port)
                        {
                            at = n;
                            break;
                        }
                    }
                    if(at == -1)
                    {
                        printf("the local transport does not seem to be associated with a candidate?!\n");
                        continue;
                    }

                    int componentIndex = checkList.pairs[at].local.componentId - 1;
                    //printf("packet is considered to be application data for component index %d\n", componentIndex);

                    // FIXME: this assumes components are ordered by id in our local arrays
                    in[componentIndex] += buf;
                    emit q->readyRead(componentIndex);
                }
            }
        }
    }

    void it_datagramsWritten(int path, int count, const QHostAddress &addr, int port)
    {
        // TODO
        Q_UNUSED(path);
        Q_UNUSED(count);
        Q_UNUSED(addr);
        Q_UNUSED(port);
    }

    void pool_outgoingMessage(const QByteArray &packet, const QHostAddress &addr, int port)
    {
        Q_UNUSED(addr);
        Q_UNUSED(port);

        StunTransactionPool *pool = static_cast<StunTransactionPool *>(sender());
        int at = -1;
        for(int n = 0; n < checkList.pairs.count(); ++n)
        {
            if(checkList.pairs[n].pool == pool)
            {
                at = n;
                break;
            }
        }
        if(at == -1) // FIXME: assert?
            return;

        CandidatePair &pair = checkList.pairs[at];

        at = findLocalCandidate(pair.local.addr.addr, pair.local.addr.port);
        if(at == -1) // FIXME: assert?
            return;

        IceComponent::Candidate &lc = localCandidates[at];

        IceTransport *sock = lc.iceTransport;
        int path = lc.path;

        printf("connectivity check from %s:%d to %s:%d\n", qPrintable(pair.local.addr.addr.toString()), pair.local.addr.port, qPrintable(pair.remote.addr.addr.toString()), pair.remote.addr.port);
        sock->writeDatagram(path, packet, pair.remote.addr.addr, pair.remote.addr.port);
    }

    void binding_success()
    {
        StunBinding *binding = static_cast<StunBinding *>(sender());
        int at = -1;
        for(int n = 0; n < checkList.pairs.count(); ++n)
        {
            if(checkList.pairs[n].binding == binding)
            {
                at = n;
                break;
            }
        }
        if(at == -1)
            return;

        printf("check success\n");

        CandidatePair &pair = checkList.pairs[at];

        // TODO: if we were cool, we'd do something with the peer
        //   reflexive address received

        // TODO: we're also supposed to do triggered checks.  except
        //   that currently we check everything anyway so this is not
        //   relevant

        // check if there's a candidate already valid
        at = -1;
        for(int n = 0; n < checkList.pairs.count(); ++n)
        {
            if(checkList.pairs[n].local.componentId == pair.local.componentId && checkList.pairs[n].isValid)
            {
                at = n;
                break;
            }
        }

        pair.isValid = true;

        if(at == -1)
        {
            int at = findComponent(pair.local.componentId);
            Component &c = components[at];
            if(c.lowOverhead)
            {
                printf("component is flagged for low overhead.  setting up for %s;%d -> %s;%d\n",
                    qPrintable(pair.local.addr.addr.toString()), pair.local.addr.port, qPrintable(pair.remote.addr.addr.toString()), pair.remote.addr.port);
                at = findLocalCandidate(pair.local.addr.addr, pair.local.addr.port);
                IceComponent::Candidate &cc = localCandidates[at];
                c.ic->flagPathAsLowOverhead(cc.id, pair.remote.addr.addr, pair.remote.addr.port);
            }

            emit q->componentReady(pair.local.componentId - 1);
        }
        else
        {
            printf("component %d already active, not signalling\n", pair.local.componentId);
        }
    }
};

Ice176::Ice176(QObject *parent) :
    QObject(parent)
{
    d = new Private(this);
}

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

void Ice176::reset()
{
    d->reset();
}

void Ice176::setProxy(const TurnClient::Proxy &proxy)
{
    d->proxy = proxy;
}

void Ice176::setPortReserver(UdpPortReserver *portReserver)
{
    Q_ASSERT(d->state == Private::Stopped);

    d->portReserver = portReserver;
}

void Ice176::setLocalAddresses(const QList<LocalAddress> &addrs)
{
    d->updateLocalAddresses(addrs);
}

void Ice176::setExternalAddresses(const QList<ExternalAddress> &addrs)
{
    d->updateExternalAddresses(addrs);
}

void Ice176::setStunBindService(const QHostAddress &addr, int port)
{
    d->stunBindAddr = addr;
    d->stunBindPort = port;
}

void Ice176::setStunRelayUdpService(const QHostAddress &addr, int port, const QString &user, const QCA::SecureArray &pass)
{
    d->stunRelayUdpAddr = addr;
    d->stunRelayUdpPort = port;
    d->stunRelayUdpUser = user;
    d->stunRelayUdpPass = pass;
}

void Ice176::setStunRelayTcpService(const QHostAddress &addr, int port, const QString &user, const QCA::SecureArray &pass)
{
    d->stunRelayTcpAddr = addr;
    d->stunRelayTcpPort = port;
    d->stunRelayTcpUser = user;
    d->stunRelayTcpPass = pass;
}

void Ice176::setUseLocal(bool enabled)
{
    d->useLocal = enabled;
}

void Ice176::setUseStunBind(bool enabled)
{
    d->useStunBind = enabled;
}

void Ice176::setUseStunRelayUdp(bool enabled)
{
    d->useStunRelayUdp = enabled;
}

void Ice176::setUseStunRelayTcp(bool enabled)
{
    d->useStunRelayTcp = enabled;
}

void Ice176::setComponentCount(int count)
{
    Q_ASSERT(d->state == Private::Stopped);

    d->componentCount = count;
}

void Ice176::setLocalCandidateTrickle(bool enabled)
{
    d->useTrickle = enabled;
}

void Ice176::start(Mode mode)
{
    d->mode = mode;
    d->start();
}

void Ice176::stop()
{
    d->stop();
}

QString Ice176::localUfrag() const
{
    return d->localUser;
}

QString Ice176::localPassword() const
{
    return d->localPass;
}

void Ice176::setPeerUfrag(const QString &ufrag)
{
    d->peerUser = ufrag;
}

void Ice176::setPeerPassword(const QString &pass)
{
    d->peerPass = pass;
}

void Ice176::addRemoteCandidates(const QList<Candidate> &list)
{
    d->addRemoteCandidates(list);
}

bool Ice176::hasPendingDatagrams(int componentIndex) const
{
    return !d->in[componentIndex].isEmpty();
}

QByteArray Ice176::readDatagram(int componentIndex)
{
    return d->in[componentIndex].takeFirst();
}

void Ice176::writeDatagram(int componentIndex, const QByteArray &datagram)
{
    d->write(componentIndex, datagram);
}

void Ice176::flagComponentAsLowOverhead(int componentIndex)
{
    d->flagComponentAsLowOverhead(componentIndex);
}

bool Ice176::isIPv6LinkLocalAddress(const QHostAddress &addr)
{
    Q_ASSERT(addr.protocol() == QAbstractSocket::IPv6Protocol);
    Q_IPV6ADDR addr6 = addr.toIPv6Address();
    quint16 hi = addr6[0];
    hi <<= 8;
    hi += addr6[1];
    if((hi & 0xffc0) == 0xfe80)
        return true;
    else
        return false;
}

}

#include "ice176.moc"
