/***************************************************************************
                          p2papplicationbase.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 P2PAPPLICATIONBASE_H
#define P2PAPPLICATIONBASE_H

#include "application.h"

#include "../extra/p2pfragmenttracker.h"
#include "../p2pmessage.h"

#include <QPointer>
#include <QList>


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



/**
 * @brief An Application subclass implementing MSN6 P2P-style conversations.
 *
 * This base class aims to hide all complex details of the P2P communication.
 * The derived P2PApplication class adds handling of the MSNSLP protocol to this featureset.
 * To implement a P2P service, create a derived class of P2PApplication which
 * implements the userStarted*, contactStarted* and getAppId() methods.
 *
 * P2P messages can be received from switchboard or direct connections.
 * The message flow looks like:
 * - the MsnSwitchboardConnection receives the message.
 * - the ChatMaster relays it to the correct contact.
 * - an ApplicationList relays it to the correct P2P session.
 * - an instance of P2PApplication processes it.
 * The incoming message is received at the gotMessage() method.
 *
 * The data flow starts with the gotMessage() method,
 * which is called by the ApplicationList class with the incoming P2P message.
 *
 * A user-started application is initiated with start(),
 * which calls userStarted1_UserInvitesContact() to send the first message.
 *
 * When a data transfer is active, the methods showTransferMessage(), showTransferProgress(), showTransferComplete()
 * are called to inform the derived class of the status. It can update the GUI from these methods.
 *
 *
 * --- internals ----
 *
 * Internally, this code ensures valid P2P messages are sent.
 *
 * A P2P message consists of a a 48 byte binary header followed by an optional pyload.
 * The payload which can be binary data or plain text MIME data (the MSNSLP messages,
 * which are handled by the derived P2PApplication class).
 *
 * The messages can be sent over the switchboard, or direct connection.
 * When the P2P messages are sent over the switchboard, they will be wrapped
 * in a standard MIME message and a footer code will be appended.
 *
 * This is a P2P message with SLP payload sent over the switchboard:
 * @code
MSG contact@kmessdemo.org Test%20user 465
@endcode
@code
MIME-Version: 1.0
Content-Type: application/x-msnmsgrp2p
P2P-Dest: user@kmessdemo.org
     (blank line)
@endcode
@code
00000000 0d6ff300 00000000 00000000
52010000 00000000 52010000 00000000
c08d4900 00000000 00000000 00000000
@endcode
@code
MSNSLP/1.0 200 OK
To: <msnmsgr:user@kmessdemo.org>
From: <msnmsgr:contact@kmessdemo.org>
Via: MSNSLP/1.0/TLP ;branch={EA820F90-802C-48A3-AE53-F660111220FF}
CSeq: 1
Call-ID: {44AAC3F3-30D7-49B7-9732-AF7D32BD36B3}
Max-Forwards: 0
Content-Type: application/x-msnmsgr-sessionreqbody
Content-Length: 22

SessionID: 114164
00
@endcode
@code
00000000
@endcode
 *
 * The message is built of the following parts:
 * - The Switchboad payload command with total payload length
 * - The MIME header, with a blank line as separator.
 * - The 48 byte P2P header (written in hex notation here).
 * - The P2P payload (SLP preamble, header and body).
 * - A footer code, only appended when the message is sent over the switchboard.
 *
 * There is a limit of 1202 bytes for the P2P payload, longer payloads are splitted into multiple P2P messages.
 * In practise, that only happens with the SLP INVITE and file data messages.
 *
 * The binary P2P header has the following layout:
 * @code
0    4    8        16        24   28   32   36   40        48
|....|....|....|....|....|....|....|....|....|....|....|....|
|sid |mid |offset   |totalsize|size|flag|uid |auid|a-datasz |
@endcode
 * The fields are:
 * - <tt>sid</tt>: Session-ID, true identifier of the session. It will always be zero for SLP messages.
 * - <tt>mid</tt>: Message-ID, identifier of a payload, used to match splitted payload parts.
 * - <tt>offset</tt>: current byte position of the part.
 * - <tt>totalsize</tt>: total size the payload. When the payload only needs one part, it's equal to 'size'.
 * - <tt>size</tt>: size of this message part.
 * - <tt>flag</tt>: the message type.
 * - <tt>uid</tt>: unique ID of the message, is used to trace ACK responses back.
 * - <tt>auid</tt>: zero for normal messages.
 * - <tt>a-datasz</tt>: zero for normal messages.
 *
 * For ACK messages some the fields are used differently:
 * - <tt>sid</tt>: contains the Session-ID of the previous message.
 * - <tt>mid</tt>: has a new generated value.
 * - <tt>totalsize</tt>: contains the total size of the ACKed message.
 * - <tt>uid</tt>: contains the Message-ID of the ACKed message.
 * - <tt>auid</tt>: contains the Unique-ID of the ACKed message.
 * - <tt>a-datasz</tt>: ACK Data size, contains the 'size' field of the ACKed message.
 *
 * For direct connection handshake messages, the last 3 fields will be replaced with the Nonce value.
 *
 * The flag field can be one of these values:
 * - <tt>0x02</tt>: A normal ACK message.
 * - <tt>0x04</tt> and <tt>0x06</tt>: A timeout ACK message, the contact was waiting for something!
 * - <tt>0x08</tt>: An error message, aborts the session instantly (much like TCP RST).
 * - <tt>0x20</tt>: MsnObject data.
 * - <tt>0x40</tt>: some BYE error ack.
 * - <tt>0x80</tt>: some BYE error ack.
 * - <tt>0x100</tt>: Direct connection handshake.
 * - <tt>0x1000030</tt>: File data.
 *
 * These fields and flags appear to make it utterly complex.
 * However, P2P messages can be divided into three groups:
 * - Messages without flags but with payload. These messages have a plain text MIME payload (the MSNSLP message),
 *   used to negotiate the session parameters. The Session-ID field is always zero for these messages.
 * - Messages with flags and payload. These messages are used to transfer the actual data.
 * - Messages with flags but without payload. These messages are used as "control messages".
 *
 * Each message expects to get some ACK message back, clients actually wait to send more
 * data until an ACK or error message is received. KMess uses timers to detect whether
 * a contact did not sent a message back. The application will be aborted automatically
 * to avoid stale application in the memory.
 *
 * To match a incoming P2P message with a P2PApplication instance, the following identifiers are used:
 * - <tt>sid</tt> or session-id: this is the true Session-ID of the complete session.
 *   If the session-id is zero however and there is a payload, different rules apply.
 *   In that case, the payload contains an SLP message (header + body) with the following idenfiers in the SLP header:
 *   - <tt>Call-ID</tt>: a guid identifier of the "SLP call". It spans the entire session, so other SLP messages (like 200 OK, or BYE) can be linked to the session which got the INVITE in the first place.
 *   - <tt>branch</tt>: a guid identifier the the single SLP message, it's used by SLP responses to reffer to the specific INVITE message in the session.
 * - <tt>mid</tt> or message-id: the identifier of a complete payload. If a payload is too large, it's splitted across multiple P2P messages. Each fragment has the same message-id.
 * - <tt>uid</tt> or unique-id: a real unique id for a single P2P message.
 *   P2P responses (messages with flags but without payload) reffer to this ID to point out which message is in error.
 *
 * The ApplicationList::gotMessage() method uses these identifiers to find the correct P2PApplication
 * instance for a message. Note that the session-id is also transferred in the body of an SLP INVITE message.
 * The P2P packets are only routed by the ApplicationList using the binary header fields and SLP header.
 *
 * Some third party clients (including KMess 1.4.x) seam to send incorrect message fields.
 * This class is able to handle those not-entirely valid messages as well.
 * The situations where this happens are marked as "HACK" in the code.
 *
 * @author Diederik van der Boor
 * @ingroup Applications
 */
class P2PApplicationBase : public Application
{
  Q_OBJECT

  public: // public methods

    // The constructor
                           P2PApplicationBase(ApplicationList *applicationList);

    // The destructor
    virtual               ~P2PApplicationBase();

    // Return the message ID used in the previous message received from the contact.
    unsigned long          getLastContactMessageID() const;
    // Returns the session ID.
    virtual quint32        getSessionID() const = 0;
    // Parse a received message... (implements pure virtual method from base class)
    void                   gotMessage(const P2PMessage &p2pMessage);
    // Verify whether the application can handle the given ACK message.
    bool                   hasUnAckedMessage(const P2PMessage &p2pMessage);
    // Called when the connection is ready to send more file data.
    bool                   sendNextDataParts( int preferredFragments );


  protected: // protected mehods

    /**
     * @brief The meta type of a P2P message.
     *
     * These types are used by gotAck() to link the ACK message
     * to the original the sent message, sent by sendP2PMessage().
     */
    enum P2PMessageType
    {
      P2P_MSG_UNKNOWN             =  0,  ///< Default value, message type is not relevant.
      P2P_MSG_SESSION_INVITATION  =  1,  ///< The SLP INVITE message.
      P2P_MSG_SESSION_OK          =  2,  ///< The SLP OK message.
      P2P_MSG_TRANSFER_INVITATION =  9,  ///< The SLP INVITE message for the transfer.
      P2P_MSG_TRANSFER_OK         =  3,  ///< The SLP OK message for the transfer invitation.
      P2P_MSG_DATA_PREPARATION    =  4,  ///< The data preparation message.
      P2P_MSG_DATA                =  5,  ///< The actual data message.
      P2P_MSG_SESSION_BYE         =  6,  ///< The SLP BYE message.
      P2P_MSG_SESSION_DECLINE     = 10,  ///< The SLP BYE message.
      P2P_MSG_TRANSFER_DECLINE    =  7,  ///< The SLP 603 Decline message.
      P2P_MSG_SLP_ERROR           =  8,  ///< One of the possible SLP error messages.
      P2P_MSG_WEBCAM_SETUP        = 11,  ///< One of the possible webcam setup messages.
      P2P_MSG_WEBCAM_SYN          = 12,  ///< Webcam setup 'syn' message.
      P2P_MSG_WEBCAM_ACK          = 13   ///< Webcam setup 'ack' message.
    };

    /**
     * @brief  The data type being sent.
     *
     * This value is used to set the footer code of P2P messages which are sent over the switchboard.
     */
    enum P2PDataType
    {
      P2P_TYPE_NEGOTIATION = 0  ///< Default value, the session is negotiating.
    , P2P_TYPE_PICTURE     = 1  ///< Packet contains MsnObject data.
    , P2P_TYPE_FILE        = 2  ///< Packet contains file data.
    , P2P_TYPE_INK         = 3  ///< Packet contains an Ink message.
    , P2P_TYPE_WEBCAM      = 4  ///< Packet contains a webcam setup message.
    };

    /**
     * @brief An indication which message is expected from the contact.
     *
     * This value is mostly used for debugging.
     * Incoming acks are nowadays handled by gotAck() using a #P2PMessageType value.
     * For some situations the waiting state is used to detect errors.
     *
     * The slotCleanup() method uses this value to output a descriptive error message.
     */
    enum P2PWaitingState
    {
      // It's recommended to keep these values, as they're also printed by kmWarning() statements!

      P2P_WAIT_DEFAULT             = 0,  ///< Not waiting at all.
      P2P_WAIT_FOR_SLP_OK_ACK      = 1,  ///< Waiting for remote client to ack the the SLP OK message.
      P2P_WAIT_FOR_FILE_DATA       = 2,  ///< Waiting for remote client to send file data.
      P2P_WAIT_FOR_WEBCAM_DATA     = 19, ///< Waiting for remote client to send webcam invite data.
      P2P_WAIT_FOR_PREPARE         = 3,  ///< Waiting for remote client to send some prepare message.
      P2P_WAIT_FOR_PREPARE_ACK     = 4,  ///< Waiting for remote client to ack the data preparation.
      P2P_WAIT_FOR_SLP_BYE         = 5,  ///< Waiting for remote client to send the SLP BYE.
      P2P_WAIT_FOR_SLP_BYE_ACK     = 6,  ///< Waiting for remote client to ack the SLP BYE message.
      P2P_WAIT_FOR_SLP_ERR_ACK     = 7,  ///< Waiting for remote client to ack the SLP error message.

      P2P_WAIT_FOR_INVITE_ACK      = 18, ///< Waiting for remote client to ack the SLP INVITE.
      P2P_WAIT_CONTACT_ACCEPT      = 8,  ///< Waiting for the contact to accept out SLP INVITE.
      P2P_WAIT_FOR_TRANSFER_ACCEPT = 9,  ///< Waiting for the transfer message to be accepted.
      P2P_WAIT_FOR_INVITE_TR_ACK   = 19, ///< Waiting for the transfer INVITE message to be acked.

      P2P_WAIT_FOR_CONNECTION      = 10, ///< Waiting until a direct connection is made or all failed
      P2P_WAIT_FOR_DATA_ACK        = 11, ///< Waiting for the ack message of the data we sent.
      P2P_WAIT_FOR_TRANSFER_ACK    = 12, ///< Waiting for contact to ack the SLP OK transfer message

      P2P_WAIT_FOR_HANDSHAKE       = 13, ///< Direct connection is established, waiting for handshake.
      P2P_WAIT_FOR_HANDSHAKE_OK    = 14, ///< Direct connection handshake is sent, waiting for response.

      P2P_WAIT_FOR_CONNECTION2     = 15, ///< Waiting until a connection is made to the remote contact.
      P2P_WAIT_USER_ACCEPT         = 16, ///< Waiting for the user to accept out SLP INVITE. Complimentary to Application::isWaitingForUser().

      P2P_WAIT_END_APPLICATION     = 17  ///< Waiting for a short while before destroying this object (like TCP TIME_WAIT state)
    };

    // Make sure no more data will be sent
    void                   abortDataSending();
    // Scheduelle termination, but wait for last incoming packets
    void                   endApplicationLater();
//    // 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();
    // Return the number of transferred bytes.
    unsigned long          getTransferredBytes() const;
    // Return the current waiting state.
    P2PWaitingState        getWaitingState() const;
    // Parse a ACK messsage (in certain situations we need to active some methods)
    virtual void           gotAck(const P2PMessage &message, const P2PMessageType messageType) = 0;
    // Called when data is received
    virtual void           gotData(const P2PMessage &message) = 0;
    // Called when all data is received.
    virtual void           gotDataComplete(const P2PMessage &lastMessage) = 0;
    // Called when the data preparation message is received
    virtual void           gotDataPreparation(const P2PMessage &message) = 0;
    // Got an direct connection handshake.
    virtual void           gotDirectConnectionHandshake(const P2PMessage &message) = 0;
    // Got a message with SessionID 0
    virtual void           gotNegotiationMessage(const MimeMessage &slpMessage, const QString &preamble) = 0;
    // Verify whether a given message is still unacked.
    bool                   hasUnAckedMessage(const P2PMessageType messageType);
    // Test whether the first message has been sent.
    bool                   isFirstMessageSent() const;
    // Test whether a P2P ack was sent for the current message.
    bool                   isP2PAckSent() const;
    // Test whether the transfer is active.
    bool                   isTransferActive() const;
    // Verify whether the transfer was initiated and was completed.
    bool                   isTransferComplete() const;
    // Test whether a given waiting state is set.
    bool                   isWaitingState( P2PWaitingState waitingState ) const;
    // Send the data
    void                   sendData(QIODevice *dataSource, const P2PDataType dataType);
    // Sends the data preparation message.
    void                   sendDataPreparation();
    // Method to be called from userStarted3_UserPrepares();
    void                   sendDataPreparationAck();
    // Send the direct connection handshake
    void                   sendDirectConnectionHandshake( const QString &nonce );
    // Send a low-level control message to terminate the session.
    void                   sendP2PAbort();
    // Send an low-level ACK message for a received message if this is needed.
    bool                   sendP2PAck();
    // Send a low-level P2P data message
    void                   sendP2PMessage(const QByteArray &messageData, int flagField = 0, P2PDataType footerCode = P2P_TYPE_NEGOTIATION, P2PMessageType messageType = P2P_MSG_UNKNOWN);
    // Send a low-level error message that the application is waiting for a certain message.
    void                   sendP2PWaitingError();
    // Send a given string using sendP2PMessage()
    void                   sendSlpMessage(const QString &slpMessage, P2PMessageType messageType);
    // Set the current message type, for debugging.
    void                   setCurrentMessageType( P2PMessageType messageType );
    // Notify this base class the user is aborting the session.
    void                   setUserAborted( bool userAborted );
    // Indicate which packet is expected next.
    void                   setWaitingState( P2PWaitingState waitingState, int timeout );
    // Show a timeout message because an expected message was not received.
    virtual void           showTimeoutMessage( P2PWaitingState waitingState ) = 0;
    // Stop the waiting timer
    bool                   stopWaitingTimer();
    // Test if the contact aborted the sending of data.
    void                   testDataSendingAborted();
    // Utility function to write P2P data to a file, also deals with message offsets.
    bool                   writeP2PDataToFile( const P2PMessage &message, QIODevice *file );


  private: // private methods

    /**
     * @brief Meta data on an unacked message.
     */
    struct UnAckedMessage
    {
      // Data for ACK message:
      quint32  dataSize;           ///< The sent data size.
      quint32  messageID;          ///< The sent message ID.
      quint32  totalSize;          ///< The sent total size.
      quint32  sessionID;          ///< The sent session ID.
      quint32  ackSessionID;       ///< The sent unique ID field.
      uint           sentTime;     ///< The time the message was sent.
      P2PMessageType messageType;  ///< The meta type of the message.
      unsigned int   footerCode;   ///< The message footer code.
    };

    // Find the unacked message record associated with the received P2P message.
    UnAckedMessage *       getUnAckedMessage(const P2PMessage &ackMessage) const;
    // Find the unacked message which corresponds with given message type.
    UnAckedMessage *       getUnAckedMessage(const P2PMessageType messageType) const;
    // Called internally when data is received, this eventually calls gotData()
    void                   gotDataFragment(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);
    // Received a message with the timeout-flag set. This method aborts the application.
    void                   gotTimeoutAck(const P2PMessage &message);
    // Received a P2P message with the aborted-flag set. This method aborts the application.
    void                   gotTransferAbortedAck(const P2PMessage &message);
    // Send a P2P ACK message
    void                   sendP2PAckImpl(int ackType = P2PMessage::MSN_FLAG_ACK, UnAckedMessage *originalMessageData = 0);
    // Send a P2P message
    bool                   sendP2PMessageImpl(const QByteArray &messageData, int flagField = 0, P2PDataType footerCode = P2P_TYPE_NEGOTIATION, P2PMessageType messageType = P2P_MSG_UNKNOWN, quint32 messageID = 0);
    // Test if there are still unacked messages.
    void                   testUnAckedMessages(bool sendError);


  private slots:
    // Crash prevention method
    void                   slotApplicationListDeleted();
    // Cleanup function, called if the last ACK isn't received at all
    void                   slotCleanup();


  private: // private fields
    // The application list, parent of all application objects.
    ApplicationList       *applicationList_;
    // Message buffer, required to deal with splitted MSNSLP messages
    QBuffer               *buffer_;
    // The data source
    QPointer<QIODevice>    dataSource_;
    // The type of data to send.
    P2PDataType            dataType_;
    // Message ID, if we're sending "fragmented" content
    quint32                fragmentMessageID_;
    // Message offset, if we're sending "fragmented" content
    quint32                fragmentOffset_;
    // Size of the message, if we're sending "fragmented" content
    quint32                fragmentTotalSize_;
    // Track the status of a fragmented message (incoming currently)
    P2PFragmentTracker     fragmentTracker_;
    // The last incoming message.
    UnAckedMessage         lastIncomingMessage_;
    // Message ID, updated each time a message has been sent
    quint32                nextMessageID_;
    // The list of outgoing messages to be acked
    QList<UnAckedMessage*> outgoingMessages_;
    // True if an ACK was not sent yet.
    bool                   shouldSendAck_;
    // The user asked the application to start aborting
    bool                   userAborted_;
    // The current waiting state
    P2PWaitingState        waitingState_;
    // The waiting timer (for timeout events)
    QTimer                *waitingTimer_;
};

#endif
