/***************************************************************************
                          application.cpp  -  description
                             -------------------
    begin                : Thu Mar 20 2003
    copyright            : (C) 2003 by Mike K. Bennett
    email                : mkb137b@hotmail.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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "application.h"

#include "../../currentaccount.h"
#include "../../kmessdebug.h"
#include "../mimemessage.h"

#include <stdlib.h>

#include <KLocale>



/**
 * @brief The constructor
 */
Application::Application(const QString &contactHandle)
 : QObject(),
   chat_(0),
   closing_(false),
   contactHandle_(contactHandle),
   doDelayDeletion_(false),
   mode_(APP_MODE_NORMAL),
   type_(ChatMessage::TYPE_APPLICATION),
   userAnswered_(false),
   userStartedApp_(true),
   waitingForUser_(false)
{
  setObjectName("*Application");
}



/**
 * @brief The destructor
 *
 * Marks the application as aborting.
 */
Application::~Application()
{
  closing_ = true;  // for consistency
#ifdef KMESSDEBUG_APPLICATION
  kmDebug() << "DESTROYED. [handle=" << contactHandle_ << "]";
#endif
}



/**
 * @brief The contact aborted the session
 *
 * This method is called when one of the derived classes detect
 * the contact aborted the session while data was transferred.
 * Requests to terminate the application by calling endApplication().
 *
 * @param message Optional message to display, defaults to getContactAbortMessage().
 */
void Application::contactAborted(const QString &message)
{
#ifdef KMESSDEBUG_APPLICATION
  kmDebug();
#endif
  if( closing_ )
  {
    kmWarning() << "Attempted to close application twice (contact=" << contactHandle_ << ")";
  }

  // Remove the accept links
  modifyOfferMessage();

  if( message.isEmpty() )
  {
    showEventMessage( getContactAbortMessage(), ChatMessage::CONTENT_APP_CANCELED, true );
  }
  else
  {
    showEventMessage( message, ChatMessage::CONTENT_APP_CANCELED, true );
  }

  endApplication();
}



/**
 * @brief The contact declined the invitation
 *
 * This method is called when one of the derived classes detect
 * the contact declined the invitation for a session.
 * Requests to terminate the application by calling endApplication().
 *
 * @param message Optional message to display, defaults to getContactRejectMessage().
 */
void Application::contactRejected(const QString &message)
{
#ifdef KMESSDEBUG_APPLICATION
  kmDebug();
#endif
  if( closing_ )
  {
    kmWarning() << "Attempted to close application twice (contact=" << contactHandle_ << ")";
  }

  // Remove the accept links
  modifyOfferMessage();

  if( message.isEmpty() )
  {
    showEventMessage( getContactRejectMessage(), ChatMessage::CONTENT_APP_CANCELED, true );
  }
  else
  {
    showEventMessage( message, ChatMessage::CONTENT_APP_CANCELED, true );
  }

  endApplication();
}



/**
 * @brief Step 1 of a contact-started chat: the contact invites the user
 *
 * By default the invitation will be declined with a CANCEL_NOT_INSTALLED message
 * unless a derived class overrides this method.
 *
 * The derived method can display an accept/cancel message with offerAcceptCancel(),
 * or call contactStarted2_UserAccepts() directly to accept an invitation by default.
 *
 * @param message The invitation message sent by the contact.
 */
void Application::contactStarted1_ContactInvitesUser(const MimeMessage& /*message*/)
{
#ifdef KMESSDEBUG_APPLICATION
  kmDebug() << "(rejects invitation)";
#endif

  // We can assume the switchboard already displayed a better error message
#ifdef KMESTEST
  kmWarning() << "The Application subclass didn't implement contactStarted1_ContactInvitesUser!";
#endif

  // Cancel the invitation
  sendCancelMessage( CANCEL_NOT_INSTALLED );
  // The application should terminate automatically..
}



/**
 * @brief Step 2 of a contact-started chat: the user accepts
 *
 * This method is called by gotCommand() when the user hits
 * the 'accept' link in the chat window.
 * This method should be implemented by a derived class.
 * Most invitations require some confirmation to be sent back.
 */
void Application::contactStarted2_UserAccepts()
{
#ifdef KMESSDEBUG_APPLICATION
  kmDebug();
#endif
}



/**
 * Step 3 of a contact-started chat: the contact confirms the accept messsage.
 *
 * This method should be implemented by a derived class.
 * @param message The confirmation message sent by the contact.
 */
void Application::contactStarted3_ContactConfirmsAccept(const MimeMessage& /*message*/)
{
#ifdef KMESSDEBUG_APPLICATION
  kmDebug();
#endif
}



// Request the application to not delete itself while doing external tasks, like displaying a dialog.
void Application::delayDeletion( bool doDelay )
{
  doDelayDeletion_ = doDelay;
}



/**
 * @brief Request to delete this application.
 *
 * Marks the application as closing (isClosing() will return true),
 * The deleteMe() signal is fired afterwards.
 */
void Application::endApplication()
{
  closing_ = true;

  // Signal that this instance should be deleted, if there are no ongoing tasks that would break if the class gets deleted.
  if( doDelayDeletion_ )
  {
    // This happens when a KFileDialog or messagebox is dislayed. A sub event loop is started
    // which could cause a delete signal to get through. In effect, KMess will crash because once
    // the KFileDialog returns the code runs in that deleted part.
#ifdef KMESSDEBUG_APPLICATION
    kmDebug() << "delayDeletion is in effect, waiting for the derived class to call endApplication() again.";
#endif
  }
  else
  {
#ifdef KMESSDEBUG_APPLICATION
    kmDebug() << "Requesting removal";
#endif

    emit deleteMe(this);
  }
}



/**
 * @brief Generate a random cookie value.
 *
 * This value is used in chat window hyperlinks and used by MimeApplication derived classes.
 *
 * @return The generated cookie.
 */
QString Application::generateCookie() const
{
  int     number, maxNumber;
  QString cookie;

  // The maximum size for a cookie is supposedly 2^32 - 1, though I've
  //  never seen one go higher than 100,000.
  maxNumber = 1048575;
  // Get a random number in the given range.
  number = rand()%maxNumber;
  // Convert the number to a QCString
  cookie.sprintf("%d", number);
  // Return the cookie
  return cookie;
}



/**
 * @brief Return the chat the application was originally created for (may be null).
 *
 * This pointer is used by ChatMaster to display a MsnObject (display picture, wink or emoticon)
 * in the correct chat.
 */
Chat* Application::getChat() const
{
  return chat_;
}



/**
 * @brief Return the handle of the other contact.
 * @return The peer contact this application exchanges data with.
 */
const QString& Application::getContactHandle() const
{
  return contactHandle_;
}



/**
 * @brief Return an abort message to display.
 *
 * @returns Returns the message "The contact cancelled the session", unless this method is overwritten by a derived class.
 */
QString Application::getContactAbortMessage() const
{
  return i18n("The contact cancelled the session.");
}



/**
 * @brief Return a reject message to display.
 *
 * @returns Returns the message "The contact rejected the invitation", unless this method is overwritten by a derived class.
 */
QString Application::getContactRejectMessage() const
{
  return i18n("The contact rejected the invitation.");
}



/**
 * @brief Return the application's identifying cookie
 *
 * This cookie is used in chat window hyperlinks, and sent by MimeApplication derived classes.
 * @return The cookie identifying the application.
 */
const QString& Application::getCookie() const
{
  return cookie_;
}



/**
 * @brief Return the external IP address.
 *
 * This is a convenience function, accessing CurrentAccount::getExternalIp().
 * It's the IP address the external MSN servers see when the user connected.
 *
 * @return The external IP address of the current user.
 */
const QString& Application::getExternalIp() const
{
  // A wrapper for now, makes the Application API easier to use.
  return CurrentAccount::instance()->getExternalIp();
}



/**
 * Return the local IP address
 *
 * This is a convenience function, accessing CurrentAccount::getLocalIp().
 * It's the IP address of the local socket, which could differ
 * from the external IP in NAT-routed environments.
 *
 * @return THe internal IP address of the current user.
 */
const QString& Application::getLocalIp() const
{
  // A wrapper for now, makes the Application API easier to use.
  return CurrentAccount::instance()->getLocalIp();
}



/**
 * @brief Return the current mode of the application
 *
 * This is used by P2PApplication to reject messages properly.
 *
 * @return The current mode, one of the values of the ApplicationMode enum.
 */
int Application::getMode() const
{
  return mode_;
}



/**
 * @brief Return an abort message to display.
 *
 * @returns Returns the message "You have cancelled the session", unless this method is overwritten by a derived class.
 */
QString Application::getUserAbortMessage() const
{
  return i18n("You have cancelled the session.");
}



/**
 * @brief Return a reject message to display.
 *
 * @returns Returns the message "You have rejected the invitation", unless this method is overwritten by a derived class.
 */
QString Application::getUserRejectMessage() const
{
  return i18n("You have rejected the invitation.");
}



/**
 * @brief A command for the application was received (i.e. "Accept" or "Reject")
 *
 * This method is invoked when the user hits a hyperlink in the chat window.
 * The method calls caontactStarted2_UserAccepts(), userAborted() or userRejected(),
 * depending on the provided command value.
 *
 * @param  command  The command, can be 'accept', 'cancel', or 'reject'.
 */
void Application::gotCommand(QString command)
{
#ifdef KMESSDEBUG_APPLICATION
  kmDebug() << "Got command " << command;
#endif

  waitingForUser_ = false;

  if( userAnswered_ )
  {
#ifdef KMESSDEBUG_APPLICATION
    kmDebug() << "User has already answered this application before, ignoring.";
#endif
    return;
  }

  userAnswered_ = true;

  if ( command == "accept" )
  {
    contactStarted2_UserAccepts();
  }
  else if ( command == "cancel" )
  {
    userAborted();
  }
  else if ( command == "reject" )
  {
    userRejected();
  }
  else
  {
    kmWarning() << "Received unhandled command " << command << ".";
  }
}



/**
 * @brief Indicate whether the application is closing or not.
 *
 * When an application is aborting, callers should not attempt to abort it again.
 *
 * @return Returns true when the application is aborting (endApplication() or destructor called).
 */
bool Application::isClosing() const
{
  return closing_;
}



/**
 * @brief Returns whether the application can operate in a multi-chat session, or requires a private chat.
 *
 * Most applications require a private chat, only some can operate in a multi-chat session.
 * Overwrite this method in a derived class when needed.
 *
 * @return Default true, unless overwritten by a derived class.
 */
bool Application::isPrivateChatRequired() const
{
  // Default answer is yes, only a few applications don't (picture/msnobject transfer).
  return true;
}



/**
 * @brief Return the "user started this app" state
 *
 * @return Returns true when user initiated the invitation, false if the contact invited the user.
 */
bool Application::isUserStartedApp() const
{
  return userStartedApp_;
}



/**
 * @brief Return true if we're waiting for the user to accept.
 *
 * @return Returns true when the application is waiting for the user to press the accept link.
 */
bool Application::isWaitingForUser() const
{
  return waitingForUser_;
}



/**
 * @brief Replace an application's accept/reject/cancel links with another text
 *
 * Removes from the chat the confirmation links for the application, and replaces
 * them with some other informative message.
 *
 * @param newMessage The new displayed message
 */
void Application::modifyOfferMessage( const QString& newMessage )
{
#ifdef KMESSDEBUG_APPLICATION
  kmDebug() << "Changing offer links in chat";
#endif

  emit updateApplicationMessage( getCookie(), newMessage );
}



/**
 * @brief Let the user accept or reject the application.
 *
 * Displays the accept and cancel link in the chat window.
 * with a custom HTML-based message above.
 * The user should choose to accept or reject the invitation from the contact.
 *
 * @param appHtml A custom message to display.
 */
void Application::offerAcceptOrReject(const QString& appHtml)
{
#ifdef KMESSDEBUG_APPLICATION
  kmDebug() << "Displaying accept/reject message in chat";
#endif

  QString html;
  const QString cookie( getCookie() );
  const QString handle( getContactHandle() );

  // Create the message, starting with the application's particulars
  html += "<span class=\"acceptMessage\">";
  html += appHtml;
  html += "</span>";
  html += "<br />";
  html += "<span id=\"app" + cookie + "-links-block\" class=\"acceptMessage\">";
  html += i18n( "Do you want to <a href=%1>accept</a> or <a href=%2>cancel</a>?",
                "\"kmess://application/accept/" + handle + "?"  + cookie + "\"",
                "\"kmess://application/reject/" + handle + "?"  + cookie + "\"" );
  html += "</span>";

  // Send the message to the chat window.
  waitingForUser_ = true;
  userAnswered_   = false;
  showEventMessage( html, ChatMessage::CONTENT_APP_INVITE, true );

#ifdef KMESSDEBUG_APPLICATION
  kmDebug() << "Waiting for user to accept..";
#endif
}



/**
 * @brief Let the user cancel the application
 *
 * Displays the cancel link in the chat window.
 * with a custom HTML-based message above.
 * It allows the user to revoke a sent invitation.
 *
 * @param appHtml  A status message to display.
 */
void Application::offerCancel(const QString& appHtml)
{
#ifdef KMESSDEBUG_APPLICATION
  kmDebug() << "Displaying cancel link in chat";
#endif

  QString html;
  const QString cookie( getCookie() );

  // Create the message, starting with the application's particulars
  html += "<span class=\"cancelMessage\">";
  html += appHtml;
  html += "</span>";
  html += "<br />";
  html += "<span id=\"app" + cookie + "-links-block\" class=\"cancelMessage\">";
  html += i18n( "Click to <a href=%1>cancel</a>.",
                "\"kmess://application/cancel/" + getContactHandle() + "?"  + cookie + "\"" );
  html += "</span>";

  // Send the message to the chat window.
  showEventMessage( html, ChatMessage::CONTENT_APP_INVITE, false );
}



/**
 * @brief Set the type of application we're starting
 *
 * This method may be called by derivated Application classes; to make the base class emit
 * the correct ContentsClass type when outputting messages which may get displayed in a
 * wrong way by notification balloons.
 */
void Application::setApplicationType( ChatMessage::MessageType type )
{
  switch( type )
  {
    case ChatMessage::TYPE_APPLICATION_FILE:
      type_ = type;
      break;
    default:
      type_ = ChatMessage::TYPE_APPLICATION;
      break;
  }
}



/**
 * @brief Set the chat the application was originally created for.
 *
 * This pointer is used by ChatMaster to display a MsnObject (display picture, wink or emoticon)
 * in the correct chat.
 */
void Application::setChat( Chat *chat )
{
  chat_ = chat;
}



/**
 * @brief Indicate the application is closing or not.
 *
 * This is true when an abort method or destructor is called.
 * Callers should check for this value to avoid aborting an application twice.
 *
 * @returns True when the application is closing.
 */
void Application::setClosing(bool closing)
{
  closing_ = closing;
}



/**
 * @brief Change the current mode
 *
 * This is currently used only to reject unknown messages with P2PApplication.
 *
 * @param mode The mode to set.
 */
void Application::setMode(ApplicationMode mode)
{
  mode_ = mode;
}



/**
 * @brief Show a message to notify the user of an event.
 *
 * This message type is used for positive, passive, status messages like:
 * - connecting to host
 * - the transfer is accepted.
 * - the transfer is complete.
 *
 * It emits the appMessage() signal
 * The chat themes display these messages as "notification" message.
 *
 * @param  message     The message to display.
 * @param  contents    A brief indication of the contents. This is used for notifications.
 * @param  isIncoming  Whether the message is incoming (true), or outgoing (false).
 */
void Application::showEventMessage( const QString &message, const ChatMessage::ContentsClass contents, bool isIncoming )
{
  // Error handling applications shouldn't show anything
  if( mode_ == APP_MODE_ERROR_HANDLER )
  {
    return;
  }

  emit applicationMessage( ChatMessage( type_, contents, isIncoming, message, getContactHandle() ) );
}



/**
 * @brief Show a message to notify about a system error
 *
 * This message type is used for serious errors like:
 * - invitation type not supported.
 * - the contact is offline.
 *
 * @param  message     The message to display.
 * @param  contents    A brief indication of the contents. This is used for notifications.
 * @param  isIncoming  Whether the message is incoming (true), or outgoing (false).
 */
void Application::showSystemMessage( const QString &message, const ChatMessage::ContentsClass contents, bool isIncoming )
{
  // Error handling applications shouldn't show anything
  if( mode_ == APP_MODE_ERROR_HANDLER )
  {
    return;
  }

  emit applicationMessage( ChatMessage( ChatMessage::TYPE_SYSTEM, contents, isIncoming, message, getContactHandle() ) );
}



/**
 * @brief Called when the transfer is complete.
 *
 * The actual action should be implemented by applications which transfer data.
 */
void Application::showTransferComplete()
{
#ifdef KMESSDEBUG_APPLICATION
  kmDebug() << "Transfer is complete.";
#endif
}



/**
 * @brief Show a message to inform about a transfer event.
 *
 * This method defaults to showEventMessage(), but can be overwritten
 * to display the messages in a separate file transfer dialog.
 *
 * It's used for status messages like:
 * - connecting to host.
 * - awaiting connetion.
 * - negotiating transfer method.
 * - reverting to indirect file transfer.
 *
 * @param message The message to display.
 */
void Application::showTransferMessage(const QString &message)
{
  // Defaults to event messages, can be overwritten.
  showEventMessage( message, ChatMessage::CONTENT_APP_INFO, true );
}



/**
 * @brief Show the progress made during a transfer.
 *
 * This method can be overwritten in a derived class to show the progress somewhere.
 *
 * @param bytesTransferred  The number of bytes transferred.
 */
void Application::showTransferProgress( const ulong bytesTransferred )
{
#ifdef KMESSDEBUG_APPLICATION
  kmDebug() << "Transferred " << bytesTransferred << " bytes.";
#else
  Q_UNUSED( bytesTransferred ); // Avoid compiler warning
#endif
}



/**
 * @brief Start the application.
 *
 * This is the first method to call to send an invitation.
 * This method starts the application and calls userStarted1_UserInvitesContact().
 */
void Application::start()
{
  userStartedApp_ = true;
  cookie_         = generateCookie();

  // Start the application
  userStarted1_UserInvitesContact();
}



/**
 * @brief Start the application internally (from an invite message)
 *
 * This method is used to initialize the application with a received invitation cookie.
 */
void Application::startByInvite(const QString &invitationCookie)
{
  userStartedApp_ = false;
  cookie_         = invitationCookie;
}



/**
 * @brief You have cancelled the session.
 *
 * This method is called to abort the application (e.g. the chat window was closed).
 * It does the following things:
 * - marks the application as aborting.
 * - sends a cancel message to the contact.
 * - displays a status message returned by getUserAbortMessage().
 * - requests deletion of the application.
 */
void Application::userAborted()
{
#ifdef KMESSDEBUG_APPLICATION
  kmDebug();
#endif
  if( closing_ )
  {
    kmWarning() << "Attempted to close application twice (contact=" << contactHandle_ << ")";

    // The application could already have been deleted, just end it before something leads to a crash.
    endApplication();
  }

  closing_ = true;

  // Remove the accept links
  modifyOfferMessage();
  showEventMessage( getUserAbortMessage(), ChatMessage::CONTENT_APP_CANCELED, false );
  sendCancelMessage( CANCEL_ABORT );
  // The application should terminate automatically..
}



/**
 * @brief You have rejected the invitation
 *
 * This method is called when the user hits the cancel hyperlink in a chat window.
 * It does the following things:
 * - marks the application as aborting.
 * - sends a cancel message to the contact.
 * - displays a status message returned by getUserRejectMessage().
 * - requests deletion of the application.
 */
void Application::userRejected()
{
#ifdef KMESSDEBUG_APPLICATION
  kmDebug();
#endif
  if( closing_ )
  {
    kmWarning() << "Attempted to close application twice (contact=" << contactHandle_ << ")";

    // The application could already have been deleted, just end it before something leads to a crash.
    endApplication();
  }

  closing_ = true;
  sendCancelMessage( CANCEL_INVITATION );
  // The application should terminate automatically..
}



/**
 * @brief Step 1 of a user-started chat: the user invites the contact
 *
 * This method should be implemented by a derived class,
 * sending the invitation message to the contact.
 */
void Application::userStarted1_UserInvitesContact()
{
#ifdef KMESSDEBUG_APPLICATION
  kmDebug();
#endif
}



/**
 * @brief Step 2 of a user-started chat: the contact accepts
 *
 * This method should be implemented by a derived class, handling the accept message.
 * Either this method should call userStarted3_UserPrepares() (typically for MimeApplication classes),
 * or asks the contact to send a prepare message (for P2PApplication classes).
 */
void Application::userStarted2_ContactAccepts(const MimeMessage& /*message*/)
{
#ifdef KMESSDEBUG_APPLICATION
  kmDebug();
#endif
}



/**
 * @brief Step 3 of a user-started chat: the user prepares for the session
 *
 * The application can now start an application, open a connection, or initiate the data transfer.
 */
void Application::userStarted3_UserPrepares()
{
#ifdef KMESSDEBUG_APPLICATION
  kmDebug();
#endif
}



#include "application.moc"
