/*
 * s5b.h - direct connection protocol via tcp
 * Copyright (C) 2003  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
 *
 */

#ifndef XMPP_S5B_H
#define XMPP_S5B_H

#include <QObject>
#include <QList>
#include <QHostAddress>

#include "bytestream.h"
#include "xmpp_bytestream.h"
#include "xmpp/jid/jid.h"
#include "xmpp_task.h"
#include "xmpp_stanza.h"

class SocksClient;
class SocksUDP;

namespace XMPP
{
    class StreamHost;
    class Client;
    class S5BConnection;
    class S5BManager;
    class S5BServer;
    struct S5BRequest;
    typedef QList<StreamHost> StreamHostList;
    typedef QList<S5BConnection*> S5BConnectionList;

    class S5BDatagram
    {
    public:
        S5BDatagram();
        S5BDatagram(int source, int dest, const QByteArray &data);

        int sourcePort() const;
        int destPort() const;
        QByteArray data() const;

    private:
        int _source = 0;
        int _dest = 0;
        QByteArray _buf;
    };

    class S5BConnection : public BSConnection
    {
        Q_OBJECT
    public:
        enum Mode { Stream, Datagram };

        ~S5BConnection();

        Jid proxy() const;
        void setProxy(const Jid &proxy);

        void connectToJid(const Jid &peer, const QString &sid, Mode m = Stream);
        void connectToJid(const Jid &peer, const QString &sid) {
            connectToJid(peer, sid, Stream);
        }

        void accept();
        void close();

        Jid peer() const;
        QString sid() const;
        BytestreamManager* manager() const;
        bool isRemote() const;
        Mode mode() const;
        int state() const;

        qint64 bytesAvailable() const;
        qint64 bytesToWrite() const;

        void writeDatagram(const S5BDatagram &);
        S5BDatagram readDatagram();
        int datagramsAvailable() const;

    protected:
        qint64 writeData(const char *data, qint64 maxSize);
        qint64 readData(char * data, qint64 maxSize);

    signals:
        void proxyQuery();                             // querying proxy for streamhost information
        void proxyResult(bool b);                      // query success / fail
        void requesting();                             // sent actual S5B request (requester only)
        void accepted();                               // target accepted (requester only
        void tryingHosts(const StreamHostList &hosts); // currently connecting to these hosts
        void proxyConnect();                           // connecting to proxy
        void waitingForActivation();                   // waiting for activation (target only)
        void connected();                              // connection active
        void datagramReady();

    private slots:
        void doPending();

        void sc_connectionClosed();
        void sc_delayedCloseFinished();
        void sc_readyRead();
        void sc_bytesWritten(qint64);
        void sc_error(int);

        void su_packetReady(const QByteArray &buf);

    private:
        class Private;
        Private *d;

        void resetConnection(bool clear=false);
        void handleUDP(const QByteArray &buf);
        void sendUDP(const QByteArray &buf);

        friend class S5BManager;
        void man_waitForAccept(const S5BRequest &r);
        void man_clientReady(SocksClient *, SocksUDP *);
        void man_udpReady(const QByteArray &buf);
        void man_failed(int);
        S5BConnection(S5BManager *, QObject *parent=0);
    };

    class S5BManager : public BytestreamManager
    {
        Q_OBJECT
    public:
        S5BManager(Client *);
        ~S5BManager();

        static const char* ns();
        Client *client() const;
        S5BServer *server() const;
        void setServer(S5BServer *s);

        bool isAcceptableSID(const Jid &peer, const QString &sid) const;

        BSConnection *createConnection();
        S5BConnection *takeIncoming();

        class Item;
        class Entry;

    protected:
        const char* sidPrefix() const;

    private slots:
        void ps_incoming(const S5BRequest &req);
        void ps_incomingUDPSuccess(const Jid &from, const QString &dstaddr);
        void ps_incomingActivate(const Jid &from, const QString &sid, const Jid &streamHost);
        void item_accepted();
        void item_tryingHosts(const StreamHostList &list);
        void item_proxyConnect();
        void item_waitingForActivation();
        void item_connected();
        void item_error(int);
        void query_finished();

    private:
        class Private;
        Private *d;

        S5BConnection *findIncoming(const Jid &from, const QString &sid) const;
        Entry *findEntry(S5BConnection *) const;
        Entry *findEntry(Item *) const;
        Entry *findEntryByHash(const QString &key) const;
        Entry *findEntryBySID(const Jid &peer, const QString &sid) const;
        Entry *findServerEntryByHash(const QString &key) const;

        void entryContinue(Entry *e);
        void queryProxy(Entry *e);
        bool targetShouldOfferProxy(Entry *e);

        friend class S5BConnection;
        void con_connect(S5BConnection *);
        void con_accept(S5BConnection *);
        void con_reject(S5BConnection *);
        void con_unlink(S5BConnection *);
        void con_sendUDP(S5BConnection *, const QByteArray &buf);

        friend class S5BServer;
        bool srv_ownsHash(const QString &key) const;
        void srv_incomingReady(SocksClient *sc, const QString &key);
        void srv_incomingUDP(bool init, const QHostAddress &addr, int port, const QString &key, const QByteArray &data);
        void srv_unlink();

        friend class Item;
        void doSuccess(const Jid &peer, const QString &id, const Jid &streamHost);
        void doError(const Jid &peer, const QString &id, Stanza::Error::ErrorCond, const QString &);
        void doActivate(const Jid &peer, const QString &sid, const Jid &streamHost);
    };

    class S5BConnector : public QObject
    {
        Q_OBJECT
    public:
        S5BConnector(QObject *parent=0);
        ~S5BConnector();

        void resetConnection();
        void start(const Jid &self, const StreamHostList &hosts, const QString &key, bool udp, int timeout);
        SocksClient *takeClient();
        SocksUDP *takeUDP();
        StreamHost streamHostUsed() const;

        class Item;

    signals:
        void result(bool);

    private slots:
        void item_result(bool);
        void t_timeout();

    private:
        class Private;
        Private *d;

        friend class S5BManager;
        void man_udpSuccess(const Jid &streamHost);
    };

    // listens on a port for serving
    class S5BServer : public QObject
    {
        Q_OBJECT
    public:
        S5BServer(QObject *par=0);
        ~S5BServer();

        bool isActive() const;
        bool start(int port);
        void stop();
        int port() const;
        void setHostList(const QStringList &);
        QStringList hostList() const;

        class Item;

    private slots:
        void ss_incomingReady();
        void ss_incomingUDP(const QString &host, int port, const QHostAddress &addr, int sourcePort, const QByteArray &data);
        void item_result(bool);

    private:
        class Private;
        Private *d;

        friend class S5BManager;
        void link(S5BManager *);
        void unlink(S5BManager *);
        void unlinkAll();
        const QList<S5BManager*> & managerList() const;
        void writeUDP(const QHostAddress &addr, int port, const QByteArray &data);
    };

    class JT_S5B : public Task
    {
        Q_OBJECT
    public:
        JT_S5B(Task *);
        ~JT_S5B();

        void request(const Jid &to, const QString &sid, const QString &dstaddr,
                     const StreamHostList &hosts, bool fast, bool udp=false);
        void requestProxyInfo(const Jid &to);
        void requestActivation(const Jid &to, const QString &sid, const Jid &target);

        void onGo();
        void onDisconnect();
        bool take(const QDomElement &);

        Jid streamHostUsed() const;
        StreamHost proxyInfo() const;

    private slots:
        void t_timeout();

    private:
        class Private;
        Private *d;
    };

    struct S5BRequest
    {
        Jid from;
        QString id, sid, dstaddr;
        StreamHostList hosts;
        bool fast;
        bool udp;
    };
    class JT_PushS5B : public Task
    {
        Q_OBJECT
    public:
        JT_PushS5B(Task *);
        ~JT_PushS5B();

        int priority() const;

        void respondSuccess(const Jid &to, const QString &id, const Jid &streamHost);
        void respondError(const Jid &to, const QString &id,
                          Stanza::Error::ErrorCond cond, const QString &str);
        void sendUDPSuccess(const Jid &to, const QString &dstaddr);
        void sendActivate(const Jid &to, const QString &sid, const Jid &streamHost);

        bool take(const QDomElement &);

    signals:
        void incoming(const S5BRequest &req);
        void incomingUDPSuccess(const Jid &from, const QString &dstaddr);
        void incomingActivate(const Jid &from, const QString &sid, const Jid &streamHost);
    };

    class StreamHost
    {
    public:
        StreamHost();

        const Jid & jid() const;
        const QString & host() const;
        int port() const;
        bool isProxy() const;
        void setJid(const Jid &);
        void setHost(const QString &);
        void setPort(int);
        void setIsProxy(bool);

    private:
        Jid j;
        QString v_host;
        int v_port;
        bool proxy;
    };
}

#endif
