/*
 * Copyright (c) 2007-2017 by Jakob Schröter <js@camaya.net>
 * This file is part of the gloox library. http://camaya.net/gloox
 *
 * This software is distributed under a license. The full license
 * agreement can be found in the file LICENSE in this distribution.
 * This software may not be copied, modified, sold or distributed
 * other than expressed in the named license agreement.
 *
 * This software is distributed without any warranty.
 */

#ifndef CONNECTIONBOSH_H__
#define CONNECTIONBOSH_H__

#include "gloox.h"
#include "connectionbase.h"
#include "logsink.h"
#include "taghandler.h"
#include "parser.h"

#include <string>
#include <list>
#include <ctime>

namespace gloox
{

  /**
   * @brief This is an implementation of a BOSH (HTTP binding) connection.
   *
   * Usage:
   *
   * @code
   * Client *c = new Client( ... );
   * c->setConnectionImpl( new ConnectionBOSH( c,
   *                                new ConnectionTCPClient( c->logInstance(), httpServer, httpPort ),
   *                                c->logInstance(), boshHost, xmpphost, xmppPort ) );
   * @endcode
   *
   * Make sure to pass the BOSH connection manager's host/port to the transport connection
   * (ConnectionTCPClient in this case), and the XMPP server's host and port to the BOSH connection.
   * You must also pass to BOSH the address of the BOSH server you are dealing with, this is used
   * in the HTTP Host header.
   *
   * In the case of using ConnectionBOSH through a HTTP proxy, supply httpServer and httpPort as
   * those of the proxy. In all cases, boshHost should be set to the hostname (not IP address) of
   * the server running the BOSH connection manager.
   *
   * The reason why ConnectionBOSH doesn't manage its own ConnectionTCPClient is that it allows it
   * to be used with other transports (like chained SOCKS5/HTTP proxies, or ConnectionTLS
   * for HTTPS).
   *
   * @note To avoid problems, you should disable TLS in gloox by calling
   * ClientBase::setTls( TLSDisabled ).
   *
   * Sample configurations for different servers can be found in the bosh_example.cpp file included
   * with gloox in the @b src/examples/ directory.
   *
   * @author Matthew Wild <mwild1@gmail.com>
   * @author Jakob Schröter <js@camaya.net>
   * @since 1.0
   */
  class GLOOX_API ConnectionBOSH : public ConnectionBase, ConnectionDataHandler, TagHandler
  {
    public:
      /**
       * Constructs a new ConnectionBOSH object.
       * @param connection A transport connection. It should be configured to connect to
       * the BOSH connection manager's (or a HTTP proxy's) host and port, @b not to the XMPP host.
       * ConnectionBOSH will own the transport connection and delete it in its destructor.
       * @param logInstance The log target. Obtain it from ClientBase::logInstance().
       * @param boshHost The hostname of the BOSH connection manager
       * @param xmppServer A server to connect to. This is the XMPP server's address, @b not the
       * connection manager's.
       * @param xmppPort The port to connect to. This is the XMPP server's port, @b not the connection
       * manager's.
       * @note To properly use this object, you have to set a ConnectionDataHandler using
       * registerConnectionDataHandler(). This is not necessary if this object is
       * part of a 'connection chain', e.g. with ConnectionSOCKS5Proxy.
       */
      ConnectionBOSH( ConnectionBase* connection, const LogSink& logInstance, const std::string& boshHost,
                      const std::string& xmppServer, int xmppPort = 5222 );

      /**
       * Constructs a new ConnectionBOSH object.
       * @param cdh An ConnectionDataHandler-derived object that will handle incoming data.
       * @param connection A transport connection. It should be configured to connect to
       * the connection manager's (or proxy's) host and port, @b not to the XMPP host. ConnectionBOSH
       * will own the transport connection and delete it in its destructor.
       * @param logInstance The log target. Obtain it from ClientBase::logInstance().
       * @param boshHost The hostname of the BOSH connection manager (not any intermediate proxy)
       * @param xmppServer A server to connect to. This is the XMPP server's address, @b not the connection
       * manager's.
       * @param xmppPort The port to connect to. This is the XMPP server's port, @b not the connection
       * manager's.
       */
      ConnectionBOSH( ConnectionDataHandler* cdh, ConnectionBase* connection,
                      const LogSink& logInstance, const std::string& boshHost,
                      const std::string& xmppServer, int xmppPort = 5222 );

      /**
       * Virtual destructor
       */
      virtual ~ConnectionBOSH();

      /**
       * The supported connection modes. Usually auto-detected.
       */
      enum ConnMode
      {
        ModeLegacyHTTP,             /**< HTTP 1.0 connections, closed after receiving a response */
        ModePersistentHTTP,         /**< HTTP 1.1 connections, re-used after receiving a response */
        ModePipelining              /**< HTTP Pipelining (implies HTTP 1.1) a single connection is used */
      };

      /**
       * Sets the XMPP server to proxy to.
       * @param xmppHost The XMPP server hostname (IP address).
       * @param xmppPort The XMPP server port.
       */
      void setServer( const std::string& xmppHost, unsigned short xmppPort = 5222 )
        { m_server = xmppHost; m_port = xmppPort; }

      /**
       * Sets the path on the connection manager to request
       * @param path The path, the default is "/http-bind/", which is the default for
       * many connection managers.
       */
      void setPath( const std::string& path ) { m_path = path; }

      /**
       * Sets the connection mode
       * @param mode The connection mode, @sa ConnMode
       * @note In the case that a mode is selected that the connection manager
       * or proxy does not support, gloox will fall back to using HTTP/1.0 connections,
       * which should work with any server.
       */
      void setMode( ConnMode mode ) { m_connMode = mode; }

      // reimplemented from ConnectionBase
      virtual ConnectionError connect();

      // reimplemented from ConnectionBase
      virtual ConnectionError recv( int timeout = -1 );

      // reimplemented from ConnectionBase
      virtual bool send( const std::string& data );

      // reimplemented from ConnectionBase
      virtual ConnectionError receive();

      // reimplemented from ConnectionBase
      virtual void disconnect();

      // reimplemented from ConnectionBase
      virtual void cleanup();

      // reimplemented from ConnectionBase
      virtual void getStatistics( long int& totalIn, long int& totalOut );

      // reimplemented from ConnectionDataHandler
      virtual void handleReceivedData( const ConnectionBase* connection, const std::string& data );

      // reimplemented from ConnectionDataHandler
      virtual void handleConnect( const ConnectionBase* connection );

      // reimplemented from ConnectionDataHandler
      virtual void handleDisconnect( const ConnectionBase* connection, ConnectionError reason );

      // reimplemented from ConnectionDataHandler
      virtual ConnectionBase* newInstance() const;

      // reimplemented from TagHandler
      virtual void handleTag( Tag* tag );

    private:
      ConnectionBOSH& operator=( const ConnectionBOSH& );
      void initInstance( ConnectionBase* connection, const std::string& xmppServer, const int xmppPort );
      bool sendRequest( const std::string& xml );
      bool sendXML();
      const std::string getHTTPField( const std::string& field );
      ConnectionBase* getConnection();
      ConnectionBase* activateConnection();
      void putConnection();

      //ConnectionBase *m_connection;
      const LogSink& m_logInstance;

      Parser m_parser;   // Used for parsing XML section of responses
      std::string m_boshHost;   // The hostname of the BOSH connection manager
      std::string m_boshedHost;   // The hostname of the BOSH connection manager + : + port
      std::string m_path;   // The path part of the URL that we need to request

      // BOSH parameters
      unsigned long m_rid;
      std::string m_sid;

      bool m_initialStreamSent;
      int m_openRequests;
      int m_maxOpenRequests;
      int m_wait;
      int m_hold;

      bool m_streamRestart;   // Set to true if we are waiting for an acknowledgement of a stream restart

      time_t m_lastRequestTime;
      unsigned long m_minTimePerRequest;

      std::string m_buffer;   // Buffer of received data
      std::string m_bufferHeader;   // HTTP header of data currently in buffer // FIXME doens't need to be member
      std::string::size_type m_bufferContentLength;   // Length of the data in the current response

      std::string m_sendBuffer;   // Data waiting to be sent

      typedef std::list<ConnectionBase*> ConnectionList;
      ConnectionList m_activeConnections;
      ConnectionList m_connectionPool;
      ConnMode m_connMode;

  };

}

#endif // CONNECTIONBOSH_H__
