/***************************************************************************
                          chatview.cpp  -  description
                             -------------------
    begin                : Wed Jan 15 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 "chatview.h"

#include <qcolor.h>
#include <qdatetime.h>
#include <qpushbutton.h>
#include <qregexp.h>
#include <qstylesheet.h>
#include <qtextbrowser.h>
#include <qtextedit.h>

#include <kdebug.h>
#include <kglobal.h>
#include <klocale.h>
#include <krun.h>
#include <ktextbrowser.h>
#include <kurl.h>

#include "../contact/contact.h"
#include "../currentaccount.h"
#include "../kmessdebug.h"
#include "../emoticoncollection.h"
#include "../emoticon.h"
#include "../msnstring.h"
#include "emoticonchooser.h"

// The constructor
ChatView::ChatView(QWidget *parent, const char *name )
 : ChatViewInterface(parent,name),
   currentAccount_(0),
   doSendTypingMessages_(true),
   emoticonChooser_(0),
   initialized_(false),
   isEmpty_(true)
{
  sendButton_->setEnabled( false );
  chatBrowser_->setFocus();

  // Make the chat browser and edit backgrounds white
  chatBrowser_->setPaletteBackgroundColor( QColor( 255, 255, 255 ) );
  messageEdit_->setPaletteBackgroundColor( QColor( 255, 255, 255 ) );
  messageEdit_->setPaletteForegroundColor( QColor( 0, 0, 0 ) );

  // Connect the message edit so that if its displayed color changes,
  //  it checked to match the user's chosen color.
  connect( messageEdit_, SIGNAL( currentColorChanged( const QColor & ) ),
           this,         SLOT  (  editorColorChanged( const QColor & ) ) );
}



// The destructor
ChatView::~ChatView()
{
}



// Add the given text to the chat browser and scroll to the end
void ChatView::addTextToBrowser( QString text )
{
  // Set the text to the browser window
  chatBrowser_->append( text );
  isEmpty_ = false;
  scrollChatToBottom();
}



// Delete the newline behind the message edit's cursor.
void ChatView::deleteNewlineAtCursor()
{
  int      endpara, endindex, startpara, startindex;

  // Get the cursor's position in the message edit
  messageEdit_->getCursorPosition(&endpara, &endindex);

  // The position before the cursor will be one index behind the end,
  //  unless the index is zero.
  if ( endindex == 0 )
  {
    startpara = endpara - 1;
    startindex = messageEdit_->paragraphLength( startpara );
    // Select the character behind the cursor
    messageEdit_->setSelection(startpara, startindex, endpara, endindex);
    // Delete the selection, which should be a newline.
    messageEdit_->del();
  }
}



// The color in the text box changed.
void ChatView::editorColorChanged(const QColor &color)
{
#ifdef KMESSTEST
  ASSERT( currentAccount_ != 0 );
#endif
  // Sometimes the text box color seems to spontaneously reset to black.
  // If this happened, set the color back to the user's color.
  if ( color.name() != currentAccount_->getFontColor() )
  {
#ifdef KMESSDEBUG_CHATVIEW
    kdDebug() << "ChatView - Restore the color to the account setting by calling 'updateEditorFont'." << endl;
#endif
    updateEditorFont();
  }
}



// The user pressed return in the message editor, so send the message
void ChatView::enterPressed()
{
  if(! messageEdit_->text().isEmpty())
  {
    // Don't send any typing messages while preparing to send the message in the text box...
    doSendTypingMessages_ = false;

    // Pressing enter caused a newline to be entered under the cursor... remove it.
    deleteNewlineAtCursor();

    // Send the message.
    sendMessage();

    // Now messages can again be sent
    doSendTypingMessages_ = true;
  }
}



// Initialize the object
bool ChatView::initialize()
{
  if ( initialized_ )
  {
    kdDebug() << "ChatView already initialized." << endl;
    return false;
  }
  if ( !initializeCurrentAccount() )
  {
    return false;
  }
  if ( !initializeEmoticonChooser() )
  {
    return false;
  }
  if ( !initializeEmoticons() )
  {
    return false;
  }

  chatBrowser_->setTextFormat( RichText );
  chatBrowser_->setText( "<qt>" );

  initialized_ = true;
  return true;
}



// Initialize the current account
bool ChatView::initializeCurrentAccount()
{
  currentAccount_ = CurrentAccount::instance();
  if ( currentAccount_ == 0 )
  {
    kdDebug() << "ChatView - Couldn't get the instance of the current account." << endl;
    return false;
  }
  connect( currentAccount_, SIGNAL( changedFontSettings() ),
           this,            SLOT  (    updateEditorFont() ) );
  updateEditorFont();
  return true;
}



// Create the emoticons
bool ChatView::initializeEmoticons()
{
  emoticons_ = EmoticonCollection::instance();

  if ( emoticons_ == 0 )
  {
    kdDebug() << "ChatView - Unable to get instance of emoticon collection." << endl;
    return false;
  }

  return true;
}



// Initialize the emoticon chooser
bool ChatView::initializeEmoticonChooser()
{
  // Create the emoticon chooser
  emoticonChooser_ = new EmoticonChooser( this, "emoticonChooser" );
  // Connect up the emoticon chooser
  // Connect the chooser's insertEmoticon signal
  connect( emoticonChooser_, SIGNAL( insertEmoticon( QString ) ),
           this,             SLOT  ( insertEmoticon( QString ) ) );
  return true;
}



// Insert an emoticon into the message editor
void ChatView::insertEmoticon( QString emoticonText )
{
  // Insert the text at the cursor.
  messageEdit_->insert( emoticonText );
  // Delete the newline there
  deleteNewlineAtCursor();
}



// Whether or not the message area is empty
bool ChatView::isEmpty() const
{
  return isEmpty_;
}



// The message text changed, so the user is typing
void ChatView::messageTextChanged()
{
  if ( doSendTypingMessages_ )
  {
    // Disable or enable the send button depending on whether or not the message edit is empty
    if ( messageEdit_->text().isEmpty() )
    {
      sendButton_->setEnabled( false );
    }
    else
    {
      sendButton_->setEnabled( true );

      // If the last character of the message is a newline, sending the typing signal will
      //  cause the actual text message to be received twice, so check the last character...
      if ( messageEdit_->text().right(1) != "\n" )
      {
        // If the typing timer isn't already going...
        if ( !userTypingTimer_.isActive() )
        {
          emit userIsTyping();
          userTypingTimer_.start( 4000, true );
        }
      }
    }
  }
}



// The user clicked the new line button so insert a new line in the editor
void ChatView::newLineClicked()
{
  messageEdit_->insert("\n");
}



// Do some effects characters (ie, bold, underline and italic specials)
void ChatView::parseEffects(QString &text) const
{
  int       offset = 0, effectsNumber;
  bool      startFree, endFree;
  QRegExp   effectsSearch;
  QString   effectsCharacter, markupString;
  QChar     boundaryCharacter;

  for (effectsNumber = 0; effectsNumber < 3; effectsNumber++)
  {
    switch( effectsNumber )
    {
      case 0:
        effectsCharacter = "_";
        markupString = "u";
        break;
      case 1:
        effectsCharacter = "\\*";
        markupString = "b";
        break;
      case 2:
        effectsCharacter = "/";
        markupString = "i";
        break;
    }

    effectsSearch.setPattern( effectsCharacter + "([a-zA-Z0-9]+)" + effectsCharacter );

    offset = 0;

    while( offset >= 0 )
    {
      offset = effectsSearch.search( text, offset );

      if( offset >= 0 )
      {

        startFree = endFree = false;

        if ( offset == 0 )
        {
          startFree = true;
        }
        else
        {
          boundaryCharacter = text.at( offset - 1 );
          if ( boundaryCharacter.isSpace() || boundaryCharacter.isPunct() ) startFree = true;
        }

        if ( ( offset + effectsSearch.matchedLength() ) == text.length() )
        {
          endFree = true;
        }
        else
        {
          boundaryCharacter = text.at( offset + effectsSearch.matchedLength() );
          if ( boundaryCharacter.isSpace() || boundaryCharacter.isPunct() ) endFree = true;
        }

        if ( startFree && endFree )
        {
          text.replace( offset, 1, "<" + markupString + ">");
          text.replace( offset + effectsSearch.matchedLength() + markupString.length(), 1, "</" + markupString + ">");
        }
      }

      offset+= effectsSearch.matchedLength();
    }
  }

}



// Replace any urls with a real clickable URL.
void ChatView::parseUrls(QString &text) const
{
  int      wwwPosition;
  QString httpText;

  // First, replace all "www."s with "http://www."
// This should work, but doesn't, so do it a cruder way
//  text = text.replace( QRegExp("(?!http://)www."), "http://www." );
  wwwPosition = text.find("www.");
  while ( wwwPosition >= 0 )
  {
    httpText = text.mid( wwwPosition - 7, 7 );
    if ( httpText != "http://" && httpText != "ttps://" )
    {
      text.insert( wwwPosition, "http://" );
    }
    wwwPosition = text.find("www.", wwwPosition + 11);
  }
  // Then parse for common expressions.
  parseUrlsForTag( text, "https:" );
  parseUrlsForTag( text, "http:" );
  parseUrlsForTag( text, "ftp:" );
  parseUrlsForTag( text, "mailto:" );
}



// Parse the text for the given tag and insert a clickable URL for it.
void ChatView::parseUrlsForTag(QString &text, QString tag) const
{
  int      newline, space, tagPosition, tagEndPosition;
  QString tagText, fullTag, fullEndTag;

  tagPosition = text.find( tag );

  // While the tag exists in the text...
  while ( tagPosition >= 0 )
  {
    // If the tag is found, find the end of the tag.
    // The end of the tag is at the first space or newling after the tag start.
    space = text.find( " ", tagPosition );
    newline = text.find( "\n", tagPosition );
    if ( space >= 0 )
    {
      if ( newline >= 0 )
      {
        if ( space < newline )
        {
          tagEndPosition = space;
        }
        else
        {
          tagEndPosition = newline;
        }
      }
      else
      {
        tagEndPosition = space;
      }
    }
    else
    {
      if ( newline >= 0 )
      {
        tagEndPosition = newline;
      }
      else
      {
        tagEndPosition = text.length();
      }
    }
    // Check that the character at the tag position isn't
    //  punctuation.
    if (    ( text.mid( tagEndPosition - 1, 1 ) == "."  )
         || ( text.mid( tagEndPosition - 1, 1 ) == "?"  )
         || ( text.mid( tagEndPosition - 1, 1 ) == "\"" ) )
    {
      tagEndPosition = tagEndPosition - 1;
    }
    // Extract the text of the tag
    tagText = text.mid( tagPosition, tagEndPosition - tagPosition );
    // Insert the end of the tag.
    fullEndTag = "</a>";
    text.insert( tagEndPosition, fullEndTag );
    // Insert the start of the tag.
    fullTag = "<a href=\"" + tagText + "\">";
    text.insert( tagPosition, fullTag );
    // Find the next occurence of "tag" in the text.
    tagPosition = text.find( tag, tagEndPosition + fullTag.length() + fullEndTag.length() );
  }
}



// Scroll to the bottom of the chat browser
void ChatView::scrollChatToBottom()
{
  // Make sure the browser scrolls to the bottom.
  chatBrowser_->repaintContents( 0, 0, chatBrowser_->contentsWidth(), chatBrowser_->contentsHeight(), true);
  chatBrowser_->scrollToBottom();
}



// The user clicked send, so send the message
void ChatView::sendClicked()
{
  sendMessage();
}



// Send a message via the server
void ChatView::sendMessage()
{
  uint maxSendableMessageLength = 1400;

  QString text = messageEdit_->text();

  if(! text.isEmpty())
  {
    messageEdit_->clear();
    // Since the message will be sent as UTF8, it's the UTF8 length we have to consider.

    // If the text is longer than the sendable amount, put the remainder back in the text edit.
    if ( text.utf8().length() > maxSendableMessageLength )
    {
      // If so, then divide the text into the first part and a remainder.
      QCString remainder = text.utf8().right( text.utf8().length() - maxSendableMessageLength );
      text = QString::fromUtf8( text.utf8().left( maxSendableMessageLength ) );
      // Return the remainder to the message edit.
      messageEdit_->setText( QString::fromUtf8( remainder ) );
    }
    // Replace "\n"s with "\r\n"s
    text = text.replace( QRegExp("\n"), "\r\n" );
    // Ask the server to send the message to the contact(s)
    emit sendMessageToContact( text );
    // Show the message in the browser window
    showMessage( currentAccount_->getHandle(), currentAccount_->getFriendlyName(), text, currentAccount_->getFont(), currentAccount_->getFontColor(), "#006A00" );
  }
}



// Show a preformatted application message in the message browser.
void ChatView::showAppMessage(QString html)
{
  addTextToBrowser(html + "<br>");
}



// Show the emoticon chooser at the given point.
void ChatView::showEmoticonChooser(QPoint point)
{
  // Popup the emoticon chooser at that position.
  emoticonChooser_->popup( point );
}



// Add the given message to the message browser.
void ChatView::showMessage(QString handle, QString name, QString text, QFont font, QString color, QString nameColor)
{
  MsnString parsedName, parsedText;

#ifdef KMESSTEST
  ASSERT( currentAccount_ != 0 );
#endif
#ifdef KMESSDEBUG_CHATVIEW
  kdDebug() << "ChatView - Show message from " << name << endl;
#endif
  QString time, formattedMessage, parsedMessage, fontBefore, fontAfter;

  // See if this is a contact's message and not the user
  if ( handle != currentAccount_->getHandle() )
  {
     // If the user wants to replace the contact's font with a font of his choosing...
     if ( currentAccount_->getUseContactFont() )
     {
      // Replace the given font with the user's stored contact font
      font = currentAccount_->getContactFont();
      color = currentAccount_->getContactFontColor();
    }
  }
  // Get the time
  time = KGlobal::locale()->formatTime( QTime::currentTime(), true );

  parsedName.setText( name );
  parsedText.setText( text );

  // for now...
  //  later all the parsing done here will be done in the MsnString class
  name = parsedName.getText();
  text = parsedText.getText();

  // Parse URLs in the text.
  parseUrls( text );

  // Parse effects characters (_, *, /)
  if ( currentAccount_->getUseFontEffects() )
  {
    parseEffects( text );
    parseEffects( name );
  }

  // Replace any "\r\n"s in the message with "<br>" so that carriage returns will show properly.
  text = text.replace( "\r\n", "<br>" )
             .replace( '\r',   "<br>" )
             .replace( '\n',   "<br>" );

  // Create the font HTML for the message
  fontBefore = "";
  if ( font.bold() )
    fontBefore += "<b>";
  if ( font.italic() )
    fontBefore += "<i>";
  if ( font.underline() )
    fontBefore += "<u>";

  fontBefore += "<font face=\"" + font.family() + "\" color=\"" + color + "\" >";
  fontAfter  = "</font>";

  if ( font.underline() )
    fontAfter  += "</u>";
  if ( font.italic() )
    fontAfter  += "</i>";
  if ( font.bold() )
    fontAfter  += "</b>";

  // Get the user's set message format, wrapped in the font and color of the user name
  parsedMessage = "<font color=\"" + nameColor + "\" >" + currentAccount_->getChatFormat() + "</font>";
  // Replace returns in the format with breaks
  parsedMessage = parsedMessage.replace( QRegExp("\n"), "<br>" );
  // Replace the variables in the format with their proper formatted values.
  parsedMessage = parsedMessage.replace( QRegExp("\\{" + i18n( "time"    ) +  "\\}"), time );
  parsedMessage = parsedMessage.replace( QRegExp("\\{" + i18n( "name"    ) +  "\\}"), name );
  parsedMessage = parsedMessage.replace( QRegExp("\\{" + i18n( "message" ) +  "\\}"), fontBefore + text + fontAfter );
  parsedMessage = parsedMessage.replace( QRegExp("  "), "&nbsp;&nbsp;" );

  // Add the message to the browser
  addTextToBrowser( parsedMessage );
}



// Show a specially formatted system message in the message browser.
void ChatView::showSystemMessage(QString message)
{
  QString html;

  // Put the system message in html form between some nice lines.
  html = "<hr size=2 color=red><font color=\"red\">";
  html += message;
  html += "</font><hr size=2 color=red>";

  addTextToBrowser( html );
}



// Update the editor's font to match the account's font
void ChatView::updateEditorFont()
{
#ifdef KMESSTEST
  ASSERT( currentAccount_ != 0 );
#endif
  QColor color;

  if ( currentAccount_ != 0 )
  {
    // Change the color to the user's color
    color.setNamedColor( currentAccount_->getFontColor() );
    messageEdit_->setColor( color );
    messageEdit_->setPaletteForegroundColor( color );
    messageEdit_->setFont( currentAccount_->getFont() );
    messageEdit_->setCurrentFont( currentAccount_->getFont() );
    messageEdit_->setPointSize( currentAccount_->getFont().pointSize() );
/*
    browserFont = account_->getFont();
    messageBrowser_->setCurrentFont( browserFont );
*/
  }
}



// The user clicked a url in the chat browser
void ChatView::urlClicked(const QString &url)
{
  KURL theurl(url);

  if ( theurl.protocol() == "kmess" )
  {
    QString method = theurl.host();
    QString cookie = theurl.query().mid( 1 );

#ifdef KMESSDEBUG_CHATVIEW
  kdDebug() << "ChatWindowView::urlClick( url = " << url << " )" << endl;
  kdDebug() << "CWV:  Method = " << method << " Cookie = " << cookie << endl;
#endif

    emit appCommand( cookie, method );
  }
  else
  {

#ifdef KMESSDEBUG_CHATVIEW
    kdDebug() << "CWV:  User clicked on url: " << url << endl;
#endif
    new KRun(theurl);
  }
}

#include "chatview.moc"
