/*
 * 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_CLIENTSTREAM_H
#define XMPP_CLIENTSTREAM_H

#include <QtCrypto>

#include "xmpp_stream.h"

class QByteArray;
class QString;
class QDomDocument;
class QDomElement;
class QObject;
class ByteStream;
class QHostAddress;

namespace XMPP
{
    class TLSHandler;
    class Connector;
    class StreamFeatures;

    class ClientStream : public Stream
    {
        Q_OBJECT
    public:
        enum Error {
            ErrConnection = ErrCustom,  // Connection error, ask Connector-subclass what's up
            ErrNeg,                     // Negotiation error, see condition
            ErrTLS,                     // TLS error, see condition
            ErrAuth,                    // Auth error, see condition
            ErrSecurityLayer,           // broken SASL security layer
            ErrSmResume,                // SM resume error
            ErrBind                     // Resource binding error
        };
        enum Warning {
            WarnOldVersion,             // server uses older XMPP/Jabber "0.9" protocol
            WarnNoTLS,                  // there is no chance for TLS at this point
            WarnSMReconnection          // SM started a quiet stream reconnection
        };
        enum NegCond {
            HostGone,                   // host no longer hosted
            HostUnknown,                // unknown host
            RemoteConnectionFailed,     // unable to connect to a required remote resource
            SeeOtherHost,               // a 'redirect', see errorText() for other host
            UnsupportedVersion          // unsupported XMPP version
        };
        enum TLSCond {
            TLSStart,                   // server rejected STARTTLS
            TLSFail                     // TLS failed, ask TLSHandler-subclass what's up
        };
        enum SecurityLayer {
            LayerTLS,
            LayerSASL
        };
        enum AuthCond {
            GenericAuthError,           // all-purpose "can't login" error (includes: IncorrectEncoding, )

            // standard xmpp auth errors (not all. some of the converted to GenericAuthError):
            Aborted,                    // server confirms auth abort
            AccountDisabled,            // account temporrily disabled
            CredentialsExpired,         // credential expired
            EncryptionRequired,         // can't use mech without TLS
            InvalidAuthzid,             // bad input JID
            InvalidMech,                // bad mechanism
            MalformedRequest,           // malformded request
            MechTooWeak,                // can't use mech with this authzid
            NotAuthorized,              // bad user, bad password, bad creditials
            TemporaryAuthFailure,       // please try again later!

            // non-xmpp
            NoMech,                     // No appropriate auth mech available
            BadServ,                    // Server failed mutual auth
        };
        enum BindCond {
            BindNotAllowed,             // not allowed to bind a resource
            BindConflict                // resource in-use
        };
        enum AllowPlainType {
            NoAllowPlain,
            AllowPlain,
            AllowPlainOverTLS
        };

        ClientStream(Connector *conn, TLSHandler *tlsHandler=0, QObject *parent=0);
        ClientStream(const QString &host, const QString &defRealm, ByteStream *bs, QCA::TLS *tls=0, QObject *parent=0); // server
        ~ClientStream();

        Jid jid() const;
        void connectToServer(const Jid &jid, bool auth=true);
        void accept(); // server
        bool isActive() const;
        bool isAuthenticated() const;

        // login params
        void setUsername(const QString &s);
        void setPassword(const QString &s);
        void setRealm(const QString &s);
        void setAuthzid(const QString &s);
        void continueAfterParams();
        void abortAuth();
        void setSaslMechanismProvider(const QString &m, const QString &p);
        QString saslMechanismProvider(const QString &m) const;
        QCA::Provider::Context *currentSASLContext() const;

        void setSCRAMStoredSaltedHash(const QString &s);
        const QString getSCRAMStoredSaltedHash();

        // SASL information
        QString saslMechanism() const;
        int saslSSF() const;

        // binding
        void setResourceBinding(bool);

        // Language
        void setLang(const QString&);

        // security options (old protocol only uses the first !)
        void setAllowPlain(AllowPlainType);
        void setRequireMutualAuth(bool);
        void setSSFRange(int low, int high);
        void setOldOnly(bool);
        void setSASLMechanism(const QString &s);
        void setLocalAddr(const QHostAddress &addr, quint16 port);

        // Compression
        void setCompress(bool);

        // reimplemented
        QDomDocument & doc() const;
        QString baseNS() const;
        bool old() const;

        void close();
        bool stanzaAvailable() const;
        Stanza read();
        void write(const Stanza &s);
        void clearSendQueue();

        int errorCondition() const;
        QString errorText() const;
        QHash<QString,QString> errorLangText() const;
        QDomElement errorAppSpec() const;

        // extra
        void writeDirect(const QString &s);
        void setNoopTime(int mills);

        // Stream management
        bool isResumed() const;
        void setSMEnabled(bool enable);

        // barracuda extension
        QStringList hosts() const;

        const StreamFeatures &streamFeatures() const;    
        QList<QDomElement> unhandledFeatures() const;

    signals:
        void connected();
        void securityLayerActivated(int);
        void needAuthParams(bool user, bool pass, bool realm);
        void authenticated();
        void warning(int);
        void haveUnhandledFeatures();
        void incomingXml(const QString &s);
        void outgoingXml(const QString &s);
        void stanzasAcked(int);

    public slots:
        void continueAfterWarning();

    private slots:
        void cr_connected();
        void cr_error();

        void bs_connectionClosed();
        void bs_delayedCloseFinished();
        void bs_error(int); // server only

        void ss_readyRead();
        void ss_bytesWritten(qint64);
        void ss_tlsHandshaken();
        void ss_tlsClosed();
        void ss_error(int);

        void sasl_clientFirstStep(bool, const QByteArray&);
        void sasl_nextStep(const QByteArray &stepData);
        void sasl_needParams(const QCA::SASL::Params&);
        void sasl_authCheck(const QString &user, const QString &authzid);
        void sasl_authenticated();
        void sasl_error();

        void sm_timeout();

        void doNoop();
        void doReadyRead();

    private:
        class Private;
        Private *d;

        void reset(bool all=false);
        void processNext();
        int convertedSASLCond() const;
        bool handleNeed();
        void handleError();
        void srvProcessNext();
        void setTimer(int secs);
    };
}

#endif
