/*
  Copyright (c) 2007-2019 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 SIPROFILEFT_H__
#define SIPROFILEFT_H__

#include "iqhandler.h"
#include "socks5bytestreammanager.h"
#include "siprofilehandler.h"
#include "sihandler.h"
#include "simanager.h"
#include "bytestreamhandler.h"

#include <string>
#include <map>

namespace gloox
{

  class ClientBase;
  class InBandBytestream;
  class IQ;
  class JID;
  class SIProfileFTHandler;
  class SOCKS5Bytestream;

  /**
   * @brief An implementation of the file transfer SI profile (@xep{0096}).
   *
   * An SIProfileFT object acts as a 'plugin' to the SIManager. SIProfileFT
   * manages most of the file transfer functionality. The naming comes from the fact that
   * File Transfer (FT) is a profile of Stream Initiation (SI).
   *
   * Usage:
   *
   * Create a new SIProfileFT object. It needs a ClientBase -derived object (e.g. Client)
   * as well as a SIProfileFTHandler -derived object that will receive file transfer-related events.
   * If you already use SI and the SIManager somewhere else, you should pass a pointer to that
   * SIManager object as third parameter to SIProfileFT's constructor.
   * @code
   * class MyFileTransferHandler : public SIProfileFTHandler
   * {
   *   // ...
   * };
   *
   * Client* client = new Client( ... );
   * // ...
   * MyFileTransferHandler* mh = new MyFileTransferHandler( ... );
   *
   * SIProfileFT* ft = new SIProfileFT( client, mh );
   * @endcode
   *
   * You are now, basically, ready to send and receive files.
   *
   * A couple of notes:
   * @li There are two (actually two and a half) possible "techniques" to transfer files
   * using SI. The first is using a peer-to-peer SOCKS5 bytestream, optionally via a
   * (special) SOCKS5 proxy.
   * The second techniques is using an in-band bytestream, i.e. the data is encapsulated in XMPP stanzas
   * and sent through the server.
   *
   * @li To be able to send files using the former method (SOCKS5 bytestreams), you may need
   * access to a SOCKS5 bytestream proxy (called StreamHost). This is especially true if either
   * or both of sender and receiver are behind NATs or are otherwise blocked from establishing
   * direct TCP connections. You should use Disco to query a potential SOCKS5 proxy
   * for its host and port parameters and feed that information into SIProfileFT:
   * @code
   * ft->addStreamHost( JID( "proxy.server.dom" ), "101.102.103.104", 6677 );
   * @endcode
   * You should @b not hard-code this information (esp. host/IP and port) into your app since
   * the proxy may go down occasionally or vanish completely.
   *
   * @li In addition to (or even instead of) using external SOCKS5 proxies, you can use a
   * SOCKS5BytestreamServer object that gloox provides:
   * @code
   * SOCKS5BytestreamServer* server = new SOCKS5BytestreamServer( client->logInstance(), 1234 );
   * if( server->listen() != ConnNoError )
   *   printf( "port in use\n" );
   *
   * ft->addStreamHost( client->jid(), my_ip, 1234 );
   * ft->registerSOCKS5BytestreamServer( server );
   * @endcode
   * This listening server should then be integrated into your mainloop to have its
   * @link gloox::SOCKS5BytestreamServer::recv() recv() @endlink method called from time to time.
   * It is safe to put the server into its own thread.
   *
   * @li When you finally receive a Bytestream via the SIProfileFTHandler, you will need
   * to integrate this bytestream with your mainloop, or put it into a separate thread (if
   * occasional blocking is not acceptable). You will need to call
   * @link gloox::Bytestream::connect() connect() @endlink on that Bytestream. For SOCKS5 bytestreams,
   * this function will try to connect to each of the given StreamHosts and block until it has established
   * a connection with one of them (or until all attempts failed). Further, if you want to receive
   * a file via the bytestream, you will have to call recv() on the object from time to time.
   * For in-band bytestreams, @link gloox::Bytestream::connect() connect() @endlink will send an "open the
   * bytestream" request to the contact.
   *
   * @li For both stream types,
   * @link gloox::BytestreamDataHandler::handleBytestreamOpen() BytestreamDataHandler::handleBytestreamOpen() @endlink
   * will announce the established bytestream. The stream then is ready to send and receive data.
   *
   * @li In general, both types of streams can be handled equally, i.e. there's no need to know whether
   * the underlying stream really is a SOCKS5Bytestream or an InBandBytestream.
   * @link gloox::Bytestream::type() Bytestream::type() @endlink tells anyway. Note, however, that
   * sending large amounts of data using in-band bytestreams may trigger rate limiting in some servers.
   *
   * @li If you e.g. told Client to connect through a @link gloox::ConnectionHTTPProxy HTTP proxy @endlink
   * or a @link gloox::ConnectionSOCKS5Proxy SOCKS5 proxy @endlink, or any other ConnectionBase -derived
   * method, or even chains thereof, SIProfileFT will use the same connection types with the same
   * configuration to connect to the Stream Host/SOCKS5 proxy. If this is inappropriate because you have
   * e.g. a local SOCKS5 proxy inside your local network, use SOCKS5Bytestream::setConnectionImpl() to
   * override the above default connection(s).
   *
   * @li Do @b not delete Bytestream objects manually. Use dispose() instead.
   *
   * @li When using the Client's JID as the first argument to addStreamHost() as in the code snippet
   * above, make sure the JID is actually a full JID. If you let the server pick a resource, the call
   * to Client::jid() needs to be made @b after the connection has been established and authenticated,
   * because only then Client knows its full JID. This is generally a good idea, since the server
   * may choose to change the resource, even if you provided one at login.
   *
   * @li The interal SOCKS5BytestreamServer will obviously not work across NATs.
   *
   * @li Using addStreamHost(), you can add as many potential StreamHosts as you like. However, you
   * should add the best options (e.g. the local SOCKS5BytestreamServer) first.
   *
   * When cleaning up, delete the objectes you created above in the opposite order of
   * creation:
   *
   * @code
   * delete server
   * delete ft;
   * delete client;
   * @endcode
   *
   * For usage examples see src/examples/ft_send.cpp and src/examples/ft_recv.cpp.
   *
   * @author Jakob Schröter <js@camaya.net>
   * @since 0.9
   */
  class GLOOX_API SIProfileFT : public SIProfileHandler, public SIHandler,
                                public BytestreamHandler, public IqHandler
  {
    public:
      /**
       * Supported stream types.
       */
      enum StreamType
      {
        FTTypeS5B     = 1,          /**< SOCKS5 Bytestreams. */
        FTTypeIBB     = 2,          /**< In-Band Bytestreams. */
        FTTypeOOB     = 4,          /**< Out-of-Band Data. */
        FTTypeAll     = 0xFF        /**< All types. */
      };

      /**
       * Constructor.
       * @param parent The ClientBase to use for signaling.
       * @param sipfth The SIProfileFTHandler to receive events.
       * @param manager An optional SIManager to register with. If this is zero, SIProfileFT
       * will create its own SIManager. You should pass a valid SIManager here if you are
       * already using one with the @c parent ClientBase above.
       * @param s5Manager An optional SOCKS5BytestreamManager to use. If this is zero, SIProfileFT
       * will create its own SOCKS5BytestreamManager. You should pass a valid SOCKS5BytestreamManager
       * here if you are already using one with the @c parent ClientBase above.
       * @note If you passed a SIManager and/or SOCKS5BytestreamManager and/or InBandBytestreamManager
       * to SIProfileFT's constructor, these objects will @b not be deleted on desctruction of SIProfileFT.
       */
      SIProfileFT( ClientBase* parent, SIProfileFTHandler* sipfth, SIManager* manager = 0,
                   SOCKS5BytestreamManager* s5Manager = 0 );

      /**
       * Virtual destructor.
       */
      virtual ~SIProfileFT();

      /**
       * Starts negotiating a file transfer with a remote entity.
       * @param to The entity to send the file to. Must be a full JID.
       * @param name The file's name. Mandatory and must not be empty.
       * @param size The file's size. Mandatory and must be > 0.
       * @param hash The file content's MD5 hash.
       * @param desc A description.
       * @param date The file's last modification date/time. See @xep{0082} for details.
       * @param mimetype The file's mime-type. Defaults to 'binary/octet-stream' if empty.
       * @param streamTypes ORed StreamType that can be used for this transfer.
       * @param from An optional 'from' address to stamp outgoing requests with.
       * Used in component scenario only. Defaults to empty JID.
       * @param sid Optionally specify a stream ID (SID). If empty, one will be generated.
       * @return The requested stream's ID (SID). Empty if conditions above (file name, size)
       * are not met.
       */
      const std::string requestFT( const JID& to, const std::string& name, long size,
                                   const std::string& hash = EmptyString,
                                   const std::string& desc = EmptyString,
                                   const std::string& date = EmptyString,
                                   const std::string& mimetype = EmptyString,
                                   int streamTypes = FTTypeAll,
                                   const JID& from = JID(),
                                   const std::string& sid = EmptyString );

      /**
       * Call this function to accept a file transfer request previously announced by means of
       * @link gloox::SIProfileFTHandler::handleFTRequest() SIProfileFTHandler::handleFTRequest() @endlink.
       * @param to The requestor.
       * @param sid The request's sid, as passed to SIProfileHandler::handleFTRequest().
       * @param type The desired stream type to use for this file transfer. Defaults to
       * SOCKS5 Bytestream. You should not use @c FTTypeAll here.
       * @param from An optional 'from' address to stamp outgoing stanzas with.
       * Used in component scenario only. Defaults to empty JID.
       */
      void acceptFT( const JID& to, const std::string& sid,
                     StreamType type = FTTypeS5B, const JID& from = JID() );

      /**
       * Call this function to decline a FT request previously announced by means of
       * @link gloox::SIProfileFTHandler::handleFTRequest() SIProfileFTHandler::handleFTRequest() @endlink.
       * @param to The requestor.
       * @param sid The request's sid, as passed to SIProfileFTHandler::handleFTRequest().
       * @param reason The reason for the reject.
       * @param text An optional human-readable text explaining the decline.
       */
      void declineFT( const JID& to, const std::string& sid, SIManager::SIError reason,
                      const std::string& text = EmptyString );

      /**
       * Cancels the given bytestream. Most useful for SOCKS5 bytestreams where no proxies could be found.
       * The given Bytestream will be deleted.
       * @param bs The Bytestream to cancel.
       * @note Can also be used with IBB.
       */
      void cancel( Bytestream* bs );

      /**
       * To get rid of a bytestream (i.e., close and delete it), call this function.
       * The remote entity will be notified about the closing of the stream.
       * @param bs The bytestream to dispose. It will be deleted here.
       */
      void dispose( Bytestream* bs );

      /**
       * Registers a handler that will be informed about incoming file transfer
       * requests, i.e. when a remote entity wishes to send a file.
       * @param sipfth A SIProfileFTHandler to register. Only one handler can be registered
       * at any one time.
       */
      void registerSIProfileFTHandler( SIProfileFTHandler* sipfth ) { m_handler = sipfth; }

      /**
       * Removes the previously registered file transfer request handler.
       */
      void removeSIProfileFTHandler() { m_handler = 0; }

      /**
       * Sets a list of StreamHosts that will be used for subsequent SOCKS5 bytestream requests.
       * @note At least one StreamHost is required.
       * @param hosts A list of StreamHosts.
       */
      void setStreamHosts( StreamHostList hosts );

      /**
       * Adds one StreamHost to the list of SOCKS5 StreamHosts.
       * @param jid The StreamHost's JID.
       * @param host The StreamHost's hostname.
       * @param port The StreamHost's port.
       */
      void addStreamHost( const JID& jid, const std::string& host, int port );

      /**
       * Tells the interal SOCKS5BytestreamManager which SOCKS5BytestreamServer handles
       * peer-2-peer SOCKS5 bytestreams.
       * @param server The SOCKS5BytestreamServer to use.
       */
      void registerSOCKS5BytestreamServer( SOCKS5BytestreamServer* server )
        { if( m_socks5Manager ) m_socks5Manager->registerSOCKS5BytestreamServer( server ); }

      /**
       * Un-registers any local SOCKS5BytestreamServer.
       */
      void removeSOCKS5BytestreamServer()
        { if( m_socks5Manager ) m_socks5Manager->removeSOCKS5BytestreamServer(); }

      // reimplemented from SIProfileHandler
      virtual void handleSIRequest( const JID& from, const JID& to, const std::string& id,
                                    const SIManager::SI& si );

      // reimplemented from SIHandler
      virtual void handleSIRequestResult( const JID& from, const JID& to, const std::string& sid,
                                          const SIManager::SI& si );

      // reimplemented from SIHandler
      virtual void handleSIRequestError( const IQ& iq, const std::string& sid );

      // reimplemented from BytestreamHandler
      virtual void handleIncomingBytestreamRequest( const std::string& sid, const JID& from );

      // reimplemented from BytestreamHandler
      virtual void handleIncomingBytestream( Bytestream* bs );

      // reimplemented from BytestreamHandler
      virtual void handleOutgoingBytestream( Bytestream* bs );

      // reimplemented from BytestreamHandler
      virtual void handleBytestreamError( const IQ& iq, const std::string& sid );

      // reimplemented from IqHandler.
      virtual bool handleIq( const IQ& iq ) { (void)iq; return false; }

      // reimplemented from IqHandler.
      virtual void handleIqID( const IQ& iq, int context );

    private:

      enum TrackEnum
      {
        OOBSent
      };

      ClientBase* m_parent;
      SIManager* m_manager;
      SIProfileFTHandler* m_handler;
      SOCKS5BytestreamManager* m_socks5Manager;
      StreamHostList m_hosts;
      StringMap m_id2sid;
      bool m_delManager;
      bool m_delS5Manager;

  };

}

#endif // SIPROFILEFT_H__
