/***************************************************************************
                          p2papplication.h -  description
                             -------------------
    begin                : Mon Nov 22 2004
    copyright            : (C) 2004 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 P2PAPPLICATION_H
#define P2PAPPLICATION_H

#include "application.h"

#include <qstring.h>
#include <qcstring.h>
#include "../p2pmessage.h"

class QIODevice;
class MimeMessage;
class QTimer;
class QBuffer;


/**
 * An Application subclass implementing MSN6 P2P-style conversations.
 * To implement a P2P service, create a derived class which
 * implements the userStarted*, contactStarted* and getAppId() methods.
 *
 * Each P2P Message received by the switchboard will be passed to
 * the correct P2PApplication instance. The gotMessage() method of that
 * particular P2PApplication will be called to deliver the message.
 *
 *
 * User-started invitations are handled this way:
 *
 * - start()
 *     Called externally to start the application.
 *     This method calls initializes the application and calls
 *     userStarted1_UserInvitesContact()
 *
 * - userStarted1_UserInvitesContact()
 *     In this method you create your invitation message and
 *     call sendSlpInvitation() to send it.
 *
 * - userStarted2_ContactAccepts()
 *     Once the 200/OK message is received from the other contact,
 *     this method is activated. So far, nothing special needs to be done here
 *     because the base class ACKs the MSNSLP 200/OK message automatically.
 *
 * - userStarted3_UserPrepares()
 *     Next, the contact sends the data-preparation message which activates this method.
 *     From here, launch or prepare your service. If this succeeds, call
 *     sendDataPreparationAck(). This causes the data-preparation ACK
 *     to be sent, the session will be aborted otherwise with an P2P-error message.
 *
 * - gotData()
 *     Called if file or picture data is received from the P2P session.
 *     Extract the data from the message, and store it in your file, etc..
 *     The session will terminate automatically if all data is received.
 *
 *
 * Contacted-started invitations are handled this way:
 *
 * - gotMessage()
 *     The application was spawned to handle an incoming message,
 *     and the gotMessage() method was called to parse it.
 *
 * - contactStarted1_ContactInvitesUser()
 *     In this method you can extract the details from the invitation message
 *     and call offerAcceptOrReject() or call contactStarted2_UserAccepts()
 *     directly to accept the invitation by default..
 *     To abort, call sendCancelMessage(CANCEL_SESSION), but don't run
 *     endApplication(). This method wil be called once the SLP error is ACK-ed.
 *
 * - contactStarted2_UserAccepts()
 *     If the user pressed the 'accept' link, this method is activated.
 *     In this method you can prepare your session and call
 *     sendSlpOkMessage() to send the correct accept-message.
 *     To abort, call sendCancelMessage(CANCEL_SESSION), but don't run
 *     endApplication(). This method wil be called once the SLP error is ACK-ed.
 *
 * - contactStarted3_ContactConfirmsAccept()
 *     If the contact ACKs our 200/OK message, this method is activated.
 *     From here, you can call sendDataPreparation() to send the data preparation message.
 *
 * - contactStarted4_ContactConfirmsPreparation()
 *     If the contact ACK-ed the data-preparation message, this method is activated.
 *     You can call sendData() from here to start the data transfer.
 *     The session will terminate automatically if all data has been sent.
 *
 * To cancel a running session, call userAborted()
 * or use sendCancelMessage() in combination with endApplication().
 *
 *
 * @author Diederik van der Boor
 */
class P2PApplication : public Application
{
  Q_OBJECT

  public: // public methods

    // The constructor
                           P2PApplication(const QString &localIP, const QString &contactHandle);

    // The destructor
    virtual               ~P2PApplication();

    // Returns the branch.
    QString                getBranch() const;
    // Returns the call ID (identifies the invitation).
    QString                getCallID() const;
    // Return the handle of the other contact
    QString                getContactHandle() const;
    // Returns the last unique message ID (sent by the contact)
    unsigned long          getLastContactAckMessageID() const;
    // Returns the last message ID. (sent by the contact)
    unsigned long          getLastContactMessageID() const;
    // Returns the last message ID (sent by the user).
    unsigned long          getLastUserMessageID() const;
    // Returns the last unique message ID (sent by the user).
    unsigned long          getLastUserUniqueID() const;
    // Returns the session ID.
    unsigned long          getSessionID() const;
    // Parse a received message... (implements pure virtual method from base class)
    void                   gotMessage(const MimeMessage &message);


  protected: // protected mehods

    enum P2PDataType
    {
      P2P_TYPE_NEGOTIATION = 0,
      P2P_TYPE_PICTURE     = 1,
      P2P_TYPE_FILE        = 2
    };

    // Call this method to acknowledge the message
    void                   acknowledgePreparation(bool ack = true);
    // Step one of a contact-started chat: the contact invites the user
    virtual void           contactStarted1_ContactInvitesUser(const MimeMessage& message);
    // Step four of a contact-started chat: the contact confirms the data-preparation message.
    virtual void           contactStarted4_ContactConfirmsPreparation();
    // Generate the random numbers for the session-id and message-id.
    static unsigned long   generateID();
    // Return the content type read from the invitation message
    const QString&         getInvitationContentType() const;
    // Return the session id read from the invitation message
    unsigned long          getInvitationSessionID() const;
    // Called when data is received
    virtual void           gotData(const P2PMessage &message);
    // Send a cancel message
    void                   sendCancelMessage(const ApplicationCancelReason cancelReason);
    // Send the data
    void                   sendData(QIODevice *dataSource, const P2PDataType type);
    // Sends the data preparation message.
    void                   sendDataPreparation();
    // Method to be called from userStarted3_UserPrepares();
    void                   sendDataPreparationAck();
    // Send an SLP invitation message
    void                   sendSlpInvitation(uint sessionID, const QString &contentType, const MimeMessage &message);
    // Send an SLP 200/OK message
    void                   sendSlpOkMessage(const MimeMessage &message);


  private: // private methods
    // Generate the strings for the BranchID and call-ID.
    static QString         generateGUID();
    // Parse a ACK messsage (in certain situations we need to active some methods)
    void                   gotAck(const P2PMessage &message);
    // Got an closing ack (received when our BYE-ACK is transmitted). This method aborts the application.
    void                   gotClosingAck(const P2PMessage &message);
    // Called internally when data is received, this eventually calls gotData()
    void                   gotDataFragment(const P2PMessage &message);
    // Called when the data preparation message is received
    void                   gotDataPreparation(const P2PMessage &message);
    // Received a P2P message with the error-flag set. This method aborts the application.
    void                   gotErrorAck(const P2PMessage &message);
    // Got a message fragment with SessionID 0
    void                   gotNegotiationFragment(const P2PMessage &message);
    // Got a message with SessionID 0
    void                   gotNegotiationMessage(const QString &slpMessage);
    // Got an MSNSLP BYE message
    void                   gotSlpBye(const MimeMessage &slpMimeMessage);
    // Got an MSNSLP INVITE message
    void                   gotSlpInvite(const MimeMessage &slpMimeMessage);
    // Got an MSNSLP Status header
    void                   gotSlpStatus(const MimeMessage &slpMimeMessage, const QString &preamble);
    // Received a message with the timeout-flag set. This method aborts the application.
    void                   gotTimeoutAck(const P2PMessage &message);
    // Send a P2P ACK message
    void                   sendP2PAck(int ackType = P2PMessage::MSN_FLAG_ACK);
    // Send an SLP BYE message to close the session
    void                   sendSlpBye();
    // Send an SLP error message (to decline an invitation for example)
    void                   sendSlpError(const QString &statusLine, const uint sessionID = 0,
                                        const QString &messageContentType = 0);
    // Send a P2P message
    void                   sendP2PMessage(const QByteArray &messageData, int flagField = 0, uint footerCode = 0);
    // Send a given string using sendP2PMessage()
    void                   sendSlpMessage(const QString &slpMessage);


  private slots:
    // Cleanup function, called if the last ACK isn't received at all
    void                   slotCleanup();
    // Send the file data
    void                   slotSendData();


  private: // private fields

    enum P2PWaitingState
    {
      P2P_WAIT_DEFAULT         = 0,  // Not waiting at all.
      P2P_WAIT_FOR_SLP_OK_ACK  = 1,  // Waiting for contact to ACK the the 200 OK message.
      P2P_WAIT_FOR_FILE_DATA   = 2,  // Waiting for contact to send file data
      P2P_WAIT_FOR_PREPARE     = 3,  // Waiting for contact to send some prepare message..
      P2P_WAIT_FOR_PREPARE_ACK = 4,  // Waiting for contact to ACK the data preparation
      P2P_WAIT_FOR_SLP_BYE     = 5,  // Waiting for contact to send the BYE.
      P2P_WAIT_FOR_SLP_BYE_ACK = 6,  // Waiting for contact to ACK the BYE message
      P2P_WAIT_FOR_SLP_ERR_ACK = 7,  // Waiting for contact to ACK the SLP Error message.

      P2P_WAIT_CONTACT_ACCEPT  = 8,  // Waiting for the contact to accept our invite
      P2P_WAIT_FOR_CLOSING_ACK = 9,  // Waiting for the contact to send a closing ACK (we ACK-ed the BYE message)
      P2P_WAIT_FOR_DATA_ACK    = 11, // Waiting for the ack message of the data we sent.
    };

    // Message buffer, required to deal with splitted MSNSLP messages
    QBuffer               *buffer_;
    // Branch ID, identifies the INVITE-message
    QString                branch_;
    // Call ID, identifies the session at MSNSLP level.
    QString                callID_;
    // Contact handle
    QString                contactHandle_;
    // The data source
    QIODevice             *dataSource_;
    // The type of data to send.
    P2PDataType            dataType_;
    // True if we got an SLP message (requires a different error handling)
    bool                   gotSlpMessage_;
    // The last unique ID we sent in a message
    unsigned long          lastUniqueID_;
    // Content type from the invitation message
    QString                invitationContentType_;
    // CSeq field from the invitation message
    int                    invitationCSeq_;
    // SessionID field from the invitaiton message
    unsigned long          invitationSessionID_;
    // Data for ACK message:
    unsigned long          originalAckMessageID_;
    unsigned long          originalDataSize_;
    unsigned long          originalMessageID_;
    unsigned long          originalSessionID_;
    unsigned long          originalTotalSize_;
    // True if we sent a cancel message
    bool                   sentCancelMessage_;
    // Session ID, identifies the session at MSNP2P level.
    unsigned long          sessionID_;
    // Message ID, updated each time a message has been sent
    unsigned long          messageID_;
    // Message offset, if we're sending "fragmented" content
    unsigned long          messageOffset_;
    // Size of the message, if we're sending "fragmented" content
    unsigned long          messageTotalSize_;
    // True if an ACK was not sent yet.
    bool                   shouldSendAck_;
    // True if the user needs to acknowledge the message. (doesn't send an ACK automatically)
    bool                   userShouldAcknowledge_;
    // The current waiting state
    P2PWaitingState        waitingState_;
    // The waiting timer (for timeout events)
    QTimer                *waitingTimer_;
};

#endif
