/***************************************************************************
                          applicationlist.h -  description
                             -------------------
    begin                : Mon 01 16 2005
    copyright            : (C) 2005 by Diederik van der Boor
    email                : "vdboor" --at-- "codingdomain.com"
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#ifndef APPLICATIONLIST_H
#define APPLICATIONLIST_H

#include <QList>
#include <QTimer>


class Application;
class MimeApplication;
class P2PApplication;
class P2PApplicationBase;
class P2PMessage;
class MimeMessage;
class ChatMessage;
class MsnObject;
class DirectConnectionPool;
class MsnDirectConnection;
class MsnSwitchboardConnection;



/**
 * @brief Maintenance of applications and direct connections for one contact.
 *
 * This class is used in the following message flow:
 * - the MsnSwitchboardConnection receives a P2P or mime message.
 * - the ChatMaster relays it to the correct contact.
 * - the ApplicationList of that contact relays it to the correct session (MimeApplication or P2PApplication).
 * - an Application instance of processes it.
 *
 * This class has the intermediate role between the ChatMaster and Application classes.
 * Each Contact has a reference to one ApplicationList object, created with Contact::createApplicationList().
 * Each P2PApplication class has a reference to this class as well to access the direct connection.
 * The direct connection is re-used by applications of the same contact.
 *
 * The ChatMaster delivers the message with gotMessage().
 * When an incoming message starts a new invitation (creating a new application object),
 * it's registered automatically in this class. The application signals putMsg() and deleteMe()
 * are handled internally by this class. Other signals (notably status messages) are not handled.
 * Instead, the newApplication() signal is fired so the ChatMaster can connect to those signals.
 *
 * When a mesage needs to be sent back, the application calls sendMessage().
 * This method either delivers the message through the direct connection, or it emits a putMsg() signal.
 * This signal is picked up be the ChatMaster to deliver the message at one of the available switchboard connections.
 * Sometimes, a switchboard connection is unable to send more messages.
 * The ChatMaster calls pauseApplications() so the queue won't be flooded.
 *
 * When a P2PApplication needs to establish a direct connection (a MsnDirectConnection class),
 * it calls addConnection() or addServerConnection(). This class reports back whether
 * a connection attempt succeeded, or all connection attempts failed.
 * Internally, a DirectConnectionPool is used to handle multiple connection attempts.
 *
 * The "in-between role" of this class appeared to be a must for peer-to-peer applications (P2PApplication instances),
 * especially in combination with direct connections. The MSNP2P features pose some difficult design choices:
 * - P2P applications are not limited to one swithboard session (aka chat session).
 * - Clients may use any active switchboard or direct connection to deliver MSNP2P packets to the peer contact.
 * - A direct connection is even re-used by other invitations with the same contact, for both new and existing invitations!
 * - The initialized P2P applications can remain active through the direct connection after the switchboard is closed.
 * - Multiple P2P applications could require a direct connection at the same time, but only the first one creates the connection. The other applications have to wait until the connection attempt succeeds or fails.
 *
 * This protocol logic makes the client implementations heavier, but it's more efficient on network resources
 * (hence MSNC* is about moving protocol logic to the clients).
 *
 * The method calls to start a P2P data transfer are pretty complex.
 * This happens because each situation needs a fallback to keep the transfer running.
 * The usual path for transfers is:
 * - the application calls addConnection() or addServerConnection()
 * - the application receives the connectionEstablished() signal.
 * - the application authenticates the direct connection.
 * - the application receives the connectionAuthorized() signal.
 * - the application starts the data transfer by calling registerDataSendingApplication().
 * - internally, slotConnectionReadyWrite() is called.
 * - the application is informed to send more data by the P2PApplication::sendNextDataParts() method.
 *
 * If the connection attempt or authenticaion fails, the connectionFailed() signal is called.
 * This causes the applications to start their transfer as well with registerDataSendingApplication().
 * In this case, the transfer takes place over the switchboard. Internally, the slotSwitchboardTestReadyWrite()
 * invokes the calls to the P2PApplication::sendNextDataParts() method.
 *
 * If the connection is closed in any event, all transfers switch
 * from slotConnectionReadyWrite() to slotSwitchboardTestReadyWrite().
 * The reverse happens when a different invitation manages to establish a direct connection.
 * This guarantees the transfer continues, or takes advantage of high speed whenever possible.
 *
 * @author Diederik van der Boor
 * @ingroup Applications
 */
class ApplicationList : public QObject
{
  Q_OBJECT

  public:
    // The constructor
                           ApplicationList(const QString &contactHandle);
    // The destructor
    virtual               ~ApplicationList();

    // Add a new application to the list.
    void                   addApplication(MimeApplication *application);
    // Add a new application to the list.
    void                   addApplication(P2PApplication *application);
    // Attempt to establish a direct connection at the given ipaddress/port.
    bool                   addConnection(const QString &ipAddress, const quint16 port);
    // Attempt to establish a direct connection by listening at an random port.
    int                    addServerConnection();
    // Abort all applications, the contact is leaving a chat.
    bool                   contactLeavingChat(const MsnSwitchboardConnection *connection, bool userInitiated = false);
    // Abort all applications, the contact left a chat.
    bool                   contactLeftChat(bool userInitiated = false);
    // Find the application with uses the given cookie.
    Application *          getApplicationByCookie(const QString &cookie) const;
    // Return the handle of the contact this class manages.
    const QString &        getContactHandle() const;
    // Deliver a message to the correct application instance
    void                   gotMessage(const MimeMessage &message);
    // Deliver a message to the correct application instance
    void                   gotMessage(const P2PMessage &message);
    // Return the direct connection, if available.
    MsnDirectConnection *  getDirectConnection() const;
    // Return whether the direct connection is connected and authorized.
    bool                   hasAuthorizedDirectConnection() const;
    // Return whether the application has data sending applications.
    bool                   hasDataSendingApplications() const;
    // Return whether there is an active direct connection.
    bool                   hasDirectConnection() const;
    // Return whether an MsnObject transfer for the given msn object is already running.
    bool                   hasMsnObjectTransfer( const MsnObject &msnObject ) const;
    // Return whether the transfer invitation has been sent.
    bool                   hasPendingConnectionInvitation() const;
    // Return whether there are pending connection attempts.
    bool                   hasPendingConnections() const;
    // Return whether the application list does not have any applications.
    bool                   isEmpty() const;
    // Pause all applications, avoid flooding the switchboard message queue.
    void                   pauseApplications();
    // Register an application which is sending a file.
    void                   registerDataSendingApplication( P2PApplicationBase *application );
    // Resume all applications, allowing messages to be sent again.
    void                   resumeApplications(bool isPrivateChat = true);
    // Send a message for a P2P application over one of the available connections/bridges.
    bool                   sendMessage(const P2PApplicationBase *source, const QByteArray &header, const QByteArray &messageData = 0, uint footerCode = 0);
    // Whether an application is adding connections, don't send "all failed" signal.
    void                   setAddingConnections(bool state);
    // Set whether the transfer invitation has been sent.
    void                   setPendingConnectionInvitation(bool state);
    // Remove the registration of an application which sends a file.
    void                   unregisterDataSendingApplication( P2PApplicationBase *application );

  public slots:
    // Send a message for a Mime application.
    void                   sendMessage(const MimeApplication *source, const MimeMessage &messageData);

  private:  // private methods
    // Abort all applications, the connection is closing or closed
    bool                   abortApplications(const MsnSwitchboardConnection *abortingConnection = 0, bool userInitiated = false);
    // Connect the application object signals.
    void                   connectApplication(Application *app);
    // Create the application instance for the given invitation message.
    MimeApplication *      createApplication(const MimeMessage &message);
    // Create the application instance for the given invitation message.
    P2PApplication *       createApplication(const P2PMessage &message);
    // Create a P2PApplication instance for the given Application-GUID.
    MimeApplication *      createApplicationByGuid(const QString &applicationGuid);
    // Create a P2PApplication instance for the given EUF-GUID.
    P2PApplication *       createApplicationByEufGuid(const QString &eufGuid);
    // Find the application which should receive the ACK message.
    P2PApplication *       getApplicationByAckData(const P2PMessage &ackMessage) const;
    // Find the application which maintains the session for the given CallID.
    P2PApplication *       getApplicationByCallId(const QString &callID) const;
    // Find the application which received the previous message fragment from the contact.
    P2PApplication *       getApplicationByLastMessage(unsigned long messageID) const;
    // Find the application which authorizes the transfer for the given nonce.
    P2PApplication *       getApplicationByNonce(const QString &nonce) const;
    // Find the application identified with the given session ID.
    P2PApplication *       getApplicationBySessionId(unsigned long sessionID) const;
    // Create the direct connection pool
    void                   initializeDirectConnectionPool();
    // Reject an invitation for a mime-application.
    void                   sendMimeRejectMessage( const QString &invitationCookie, bool notInstalled );

  private slots:
    // The direct connection is authorized.
    void                   slotActiveConnectionAuthorized();
    // The direct connection was closed.
    void                   slotActiveConnectionClosed();
    // All direct connection attempts failed.
    void                   slotAllConnectionsFailed();
    // The direct connection is established.
    void                   slotConnectionEstablished();
    // The direct connection is ready to send more data.
    void                   slotConnectionReadyWrite();
    // A message was received from the direct connection.
    void                   slotGotDirectConnectionMessage(const QByteArray &messageData);
    // The switchboard is ready to send more data.
    void                   slotSwitchboardTestReadyWrite();
    // An application was finished and requested removal.
    void                   slotTerminateApplication(Application *application);


  private:  // private attributes
    // Whether an application is adding connections, don't send "all failed" signal.
    bool                   addingConnections_;
    // A list of all applications that are being aborted.
    QList<Application*>    abortingApplications_;
    // The contact this class was initiated for.
    QString                contactHandle_;
    // A list of pointers to all applications which are sending a file.
    QList<P2PApplicationBase*> dataSendingApplications_;
    // The direct connection established with the contact
    MsnDirectConnection   *directConnection_;
    // The list of direct connection attempts
    DirectConnectionPool  *directConnectionPool_;
    // A list of pointers to all open mime transfers.
    QList<MimeApplication*> mimeApplications_;
    // Whether the application sending is paused.
    bool                   pauseApplications_;
    // A list of pointers to all open p2p transfers.
    QList<P2PApplication*> p2pApplications_;
    // The direct connection invitation is sent.
    bool                   pendingConnectionInvitation_;
    // The ready-write tester for the switchboard
    QTimer                 sbReadyWriteTester_;

  signals:
    /**
     * @brief This signal is fired when all applications aborted.
     *
     * This is used by the MsnSwitchboardConnection to close it's sockets after all applications aborted nicely.
     * @param  contact  The contact all applications have been aborted for.
     */
    void                   applicationsAborted(const QString &contact);

    /**
     * @brief This signal is fired when the direct connection is authorized.
     *
     * This signal notifies other P2PApplication instances the
     * direct connection is ready to be used for data transfer.
     *
     * @see MsnDirectConnection::setAuthorized()
     */
    void                   connectionAuthorized();

    /**
     * @brief This signal is fired when the direct connection was closed.
     */
    void                   connectionClosed();

    /**
     * @brief This signal is fired when direct connection is established.
     *
     * The first P2PApplication can start to authenticate the connection.
     */
    void                   connectionEstablished();

    /**
     * @brief This signal is fired when all direct connection attempts failed.
     *
     * The P2PApplication objects should revert to the switchboard to send data.
     */
    void                   connectionFailed();

    /**
     * @brief This signal is fired when a new application was created.
     *
     * The ChatMaster uses it to connect to the signals of new applications.
     */
    void                   newApplication(Application *application);

    /**
     * @brief This signal is fired when the ChatMaster should deliver a message to the switchboard.
     * @param  message              The message to deliver. It can be an <code>text/x-msmsgsinvite</code> message for MIME applications, or an <code>application/x-msnmsgrp2p</code> message for wrapped P2P data.
     * @param  contactHandle        The contact to deliver the message to.
     * @param  privateChatRequired  Whether the message can only be delivered in an 1-on-1 chat conversion.
     */
    void                   putMsg(const MimeMessage &message, const QString &contactHandle, bool privateChatRequired);
};

#endif
