/***************************************************************************
                          showrecord.cpp  -  description
                             -------------------
    begin                : Thu Dec 28 2000
    copyright            : (C) 2000-2001 by Eggert Ehmke
    email                : eggert.ehmke@berlin.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 "showrecordelem.h"

int const ShowRecordElem::continueShowHeaders( 0 );
int const ShowRecordElem::cancelShowHeaders( 1 );

ShowRecordElem::ShowRecordElem ()
{
  //set default values
  m_from = "???";
  m_subject = "???";
  m_size = 0;
  m_pItem = NULL;
  m_new = false;
  markAtViewRefresh = false;
}

ShowRecordElem::ShowRecordElem( int number, QString& uid, bool isNew )
{
  //set default values
  m_from = "???";
  m_subject = "???";
  m_size = 0;
  m_pItem = NULL;
  markAtViewRefresh = false;

  //set given values
  m_nNumber = number;
  m_uid = uid;
  m_new = isNew;
}


QCString ShowRecordElem::scanHeader( const QString& item ) const
{
  QCString headerline( "" );      //found header line

  //get e.g. the "From:" line, starting with cr,lf,"From:" and ending
  //with a carriage return

  //build the search string
  QString searchstring( QString( "\r\n%1:" ).arg( item ) );

  //searching...
  int pos1 = m_header.find( searchstring, 0, FALSE );
  int pos2 = m_header.find( '\r', pos1 + 2 );

  //cut out the interesting content, if we have found a matching line
  //if we have found nothing, the returned string will be ""
  if( pos1 >= 0 )
  {
    headerline = m_header.mid( pos1 + searchstring.length(), pos2 - pos1 - searchstring.length() );
  }

  return headerline;
}

void ShowRecordElem::setHeader( const QString& header )
{
  //store given header
  m_header = header.ascii();

  //extract sender and store it
  QCString from = scanHeader( "From" );
  from = from.simplifyWhiteSpace();
  setFrom( from );

  //extract addressee and store it
  QCString to = scanHeader( "To" );
  to = to.simplifyWhiteSpace();
  setTo (to);

  //extract subject and store it
  QCString subject = scanHeader( "Subject" );
  subject = subject.simplifyWhiteSpace();
  setSubject( subject );

  //extract date and store it
  QCString date = scanHeader( "Date" );
  setDate( date );

  //extract content type
  QCString content = scanHeader( "Content-Type" );
  content = content.simplifyWhiteSpace ();

  //remove the stuff after the content type; see RFC 2045
  int posSemicolon = content.find( ';' );
  if( posSemicolon != -1 )
  {
    content.remove( posSemicolon, content.length() - posSemicolon );
  }

  //store content type
  setContent (content);
}

void ShowRecordElem::setDate( const QCString& date )
{
  DwDateTime dwDate;      //this class represents an RFC-822 date-time;
                          //see mimelib/datetime.h

  //convert and store the date-time
  dwDate.FromString( date );
  dwDate.Parse();
  m_unixDate.setTime_t( dwDate.AsUnixTime() );
}

QString ShowRecordElem::from() const
{
  return Codecs::decodeRFC2047( m_from );
}

QString ShowRecordElem::to() const
{
  return Codecs::decodeRFC2047( m_to );
}

QString ShowRecordElem::subject() const
{
  return Codecs::decodeRFC2047( m_subject );
}

QString ShowRecordElem::date() const
{
  return KGlobal::locale()->formatDateTime( m_unixDate, true, true );
}

QString ShowRecordElem::strUnixTime() const
{
  return m_unixDate.toString( Qt::ISODate );
}

QString ShowRecordElem::strSize() const
{
  return QString( "%1" ).arg( m_size, 8 );
}

QString ShowRecordElem::state() const
{
  if( m_new )
    return i18n( "new" );
  else
    return i18n( "old" );
}

void ShowRecordElem::saveOptions( QDomDocument& doc, QDomElement& parent )
{
  //build item tag of this mail( with mail number)
  QString hdr = QString( ITEM_MESSAGE );
  hdr.append( "%1" );
  hdr = hdr.arg( m_nNumber );

  //create a new element and store the mail meta data in it
  QDomElement elem = doc.createElement( hdr );
  elem.setAttribute( ATTRIBUTE_MAIL_NUMBER, m_nNumber );
  elem.setAttribute( ATTRIBUTE_MAIL_SIZE, m_size );
  elem.setAttribute( ATTRIBUTE_MAIL_UID, m_uid );

  //create a sub element for the mail header in store the header in it
  QDomElement subelem = doc.createElement( ITEM_MAIL_HEADER );
  subelem.appendChild( doc.createTextNode( m_header ) );

  //add header element to the mail element
  elem.appendChild( subelem );

  //add mail element to the account (parent) element
  parent.appendChild( elem );
}

void ShowRecordElem::readOptions( QDomElement& elem )
{
  //get number, size and uid
  setNumber( elem.attribute( ATTRIBUTE_MAIL_NUMBER ).toInt() );
  setSize( elem.attribute( ATTRIBUTE_MAIL_SIZE ).toInt() );
  setUIDL( elem.attribute( ATTRIBUTE_MAIL_UID ) );

  //search for the header item and read it
  QDomElement subelem = elem.namedItem( ITEM_MAIL_HEADER ).toElement();
  setHeader( subelem.text() );

  //the mail is not new
  setNew( false );
}


void ShowRecordElem::setFrom( const QCString & from )
{
  m_from = from;
}

void ShowRecordElem::setTo( const QCString & to )
{
  m_to = to;
}

void ShowRecordElem::setSubject( const QCString & subject )
{
  m_subject = subject;
}

void ShowRecordElem::setContent( const QCString& content )
{
  m_content = content;
}

QString ShowRecordElem::header( ) const
{
  return QString( m_header );
}

void ShowRecordElem::setUIDL( const QString & uid )
{
  m_uid = uid;
}

QString ShowRecordElem::uidl( ) const
{
  return m_uid;
}

void ShowRecordElem::setSize( int size )
{
  m_size = size;
}

int ShowRecordElem::size( ) const
{
  return m_size;
}

void ShowRecordElem::setNew( bool isnew )
{
  m_new = isnew;
}

bool ShowRecordElem::isNew( ) const
{
  return m_new;
}

void ShowRecordElem::setNumber( int n )
{
  m_nNumber = n;
}

int ShowRecordElem::number( ) const
{
  return m_nNumber;
}

QString ShowRecordElem::content( ) const
{
  return m_content;
}

void ShowRecordElem::setViewItem( ShowListViewItem* item )
{
  m_pItem = item;

  //marks the new entry if recommend by the filter
  if( markAtViewRefresh )
  {
    //mark entry
    item->setSelected( true );

    //delete flag
    markAtViewRefresh = false;
  }
}

ShowListViewItem * ShowRecordElem::viewItem( ) const
{
  return m_pItem;
}

bool ShowRecordElem::isSelected( ) const
{
  if( m_pItem != NULL )
    return m_pItem->isSelected();
  else
    return false;
}

QString ShowRecordElem::strSizePrefix( ) const
{
  QString size;

  if( m_size >= 1024 * 1024 )
  {
    //prefix is mega
    size = QString( "%L1M" ).arg( ( (double)m_size / ( 1024 * 1024 ) ), 0, 'f', 1 );
  }
  else if( m_size >= 1024 )
  {
    //prefix is kilo
    size = QString( "%L1K" ).arg( ( (double)m_size / 1024 ), 0, 'f', 1 );
  }
  else
    //no prefix
    size = QString( "%L1" ).arg( m_size );

  return size;
}

QString ShowRecordElem::decodeMailBody( QByteArray body, bool preferHTML ) const
{
  QString charset;    //charset of the content
  QString encoding;   //content transfer encoding

  //cast given body to a QCString
  //class QCString needs a null terminated char array to create
  //an object. Therefore we append an null byte to the given mail body
  body.resize( body.size() + 1 );
  body[ body.size() - 1 ] = '\0';
  QCString strBody( (char *)body.data() );

  //normalize line ends; remove all \r characters
  for( uint i = 0; i < strBody.size(); i++ )
    if( strBody[ i ] == '\r' )
      strBody.remove( i, 1 );

  //get boundary that is separating the parts of a multipart message
  //if the header doesn't contain a boundary attribute, this messsage
  //has just one part
  QString boundary = getBoundary();

  //process body subject to it is a multipart messsage or not
  if( boundary == "" )
  {
    //the message has just one body part

    //get the position of the first blank line
    int posBlankLine = strBody.find( "\n\n" );

    //truncate body; the found blank line is separating the
    //header from the message
    strBody = strBody.mid( posBlankLine + 2 );
    if( !strBody.isEmpty() )    //fixed bug 1773636
      while( strBody[ 0 ] == '\n')
        strBody.remove( 0, 1 );


    //get charset of the message; it is behind the
    //content type attribute in the header
    charset = getCharset();

    //get transfer encoding type from the header
    encoding = getTransferEncoding();
  }
  else
  {
    //the message has multiple parts

    //get positions of a plain text and html flag (value of the content type attribute)
    int posPlainFlag = strBody.find( "text/plain", 0, false );
    int posHTMLFlag = strBody.find( "text/html", 0, false );

    //just decode the body, if a plain text or a HTML part is available
    if( posPlainFlag != -1 || posHTMLFlag != -1 )
    {
      //do we want to take the HTML part?
      bool hasHTML = posHTMLFlag != -1;
      bool takeHTML = ( hasHTML && preferHTML ) || posPlainFlag == -1;

      //now we want to extract the designated part
      //While the (truncated) mail text (or the header at the first pass)
      //contains a boundary attribute we will extract the designated part
      //between the boundaries
      int posInside;    //a position inside the designated part
      while( boundary != "" )
      {
        //get a position inside the designated part
        if( takeHTML )
          posInside = strBody.find( "text/html", 0, false );
        else
          posInside = strBody.find( "text/plain", 0, false );

        //get length of the boundary
        int lengthBoundary = boundary.length();

        //calculate the begin and end of the part to extract
        int beginPart = strBody.findRev( boundary.ascii(), posInside ) + lengthBoundary + 1;
        int lengthPart = strBody.findRev( '\n', strBody.find( boundary.ascii(), posInside ) ) - beginPart;

        strBody = strBody.mid( beginPart, lengthPart );

        //looking for a further boundary attribute
        //get the position of the first occurance of "boundary="
        int posBoundary = strBody.find( "boundary=", 0, false );

        if( posBoundary >= 0 )
        {
          //calculate positon of the first quote
          int posFirstQuote = posBoundary + 9;

          //get the position of closing quote
          int posSecondQuote = strBody.find( '"', posFirstQuote + 1 );

          //get boundary string
          boundary.append( strBody.mid( posFirstQuote + 1, posSecondQuote - posFirstQuote - 1 ) );
        }
        else
          boundary = "";
      }

      //now we get charset and transfer encoding if available in the extracted
      //part

      //get the position of the first occurance of "charset="
      int posCharset = strBody.find( "charset=", 0, false );

      //continue, if a charset attribute was found
      if( posCharset >= 0 )
      {
        //calculate positon of the value
        int posBeginValue = posCharset + 8;

        //get end of the value
        int posEndValue = strBody.find( '\n', posBeginValue ) - 1;

        //get charset
        charset.append( strBody.mid( posBeginValue, posEndValue - posBeginValue + 1 ) );

        //remove quotes
        charset.remove( '"' );
        //remove all content after the first semicolon (inclusive)
        int posSemicolon = charset.find( ';' );
        charset = charset.left( posSemicolon );
      }

      //get the position of the first occurance of "charset="
      int posEncoding = strBody.find( "Content-Transfer-Encoding:", 0, false );

      //continue, if a charset attribute was found
      if( posEncoding >= 0 )
      {
        //calculate positon of the value
        int posBeginValue = posEncoding + 26;

        //get end of the value
        int posEndValue = strBody.find( '\n', posBeginValue ) - 1;

        //get charset
        encoding.append( strBody.mid( posBeginValue, posEndValue - posBeginValue + 1 ) );

        //remove quotes and spaces
        encoding = encoding.stripWhiteSpace();
        encoding.remove( '"' );
      }

      //cut off the part header; the found blank line is separating the
      //part header from the message
      if( posCharset != -1 || posEncoding != -1 )
      {
        int posBlankLine = strBody.find( "\n\n" );
        strBody = strBody.mid( posBlankLine + 2 );
        if( !strBody.isEmpty() )  //fixed bug 1773636
          while( strBody[ 0 ] == '\n')
            strBody.remove( 0, 1 );
      }
    }
  }

  //Good things come to those who wait. We have extract the message.
  //Now we have to decode the message, if it is encoded
  if( encoding == "quoted-printable" && !strBody.isEmpty() )  //fixed bug 1773636
  {
    strBody = KCodecs::quotedPrintableDecode( strBody );
  }

  return QString( strBody );
}

QString ShowRecordElem::getBoundary( ) const
{
  QString boundary;

  //check, whether it is a multipart message
  if( m_content.contains( "multipart", false ) )
  {
    //it is a multipart message

    //get the position of the first occurance of "boundary="
    int posBoundary = m_header.find( "boundary=", 0, false );

    //continue, if a boundary attribute was found
    if( posBoundary >= 0 )
    {
      //calculate positon of the first quote
      int posFirstQuote = posBoundary + 9;

      //get the position of closing quote
      int posSecondQuote = m_header.find( '"', posFirstQuote + 1 );

      //get boundary string
      boundary.append( m_header.mid( posFirstQuote + 1, posSecondQuote - posFirstQuote - 1 ) );
    }
  }

  return boundary;
}

QString ShowRecordElem::getCharset( ) const
{
  QString charset;

  //get the position of the first occurance of "charset="
  int posCharset = m_header.find( "charset=", 0, false );

  //continue, if a charset attribute was found
  if( posCharset >= 0 )
  {
    //calculate positon of the value
    int posBeginValue = posCharset + 8;

    //get end of the value
    int posEndValue = m_header.find( '\r', posBeginValue ) - 1;

    //get charset
    charset.append( m_header.mid( posBeginValue, posEndValue - posBeginValue + 1 ) );

    //remove quotes
    charset.remove( '"' );
    //remove all content after the first semicolon (inclusive)
    int posSemicolon = charset.find( ';' );
    charset = charset.left( posSemicolon );
  }

  return QString( charset );
}

QString ShowRecordElem::getTransferEncoding( ) const
{
  QString encoding;

  //get the position of the first occurance of "charset="
  int posEncoding = m_header.find( "Content-Transfer-Encoding:", 0, false );

  //continue, if a charset attribute was found
  if( posEncoding >= 0 )
  {
    //calculate positon of the value
    int posBeginValue = posEncoding + 26;

    //get end of the value
    int posEndValue = m_header.find( '\r', posBeginValue ) - 1;

    //get charset
    encoding.append( m_header.mid( posBeginValue, posEndValue - posBeginValue + 1 ) );

    //remove quotes and spaces
    encoding = encoding.stripWhiteSpace();
    encoding.remove( '"' );
  }

  return QString( encoding );

}

int ShowRecordElem::showHeader( QString& account )
{
  //show header
  QString tsubject = subject();
  QString tmailheader = header();

  //create and open the window
  ShowHeaderDialog dlg( kapp->mainWidget(), account, tsubject, tmailheader );
  int ret = dlg.exec();

  //returns the matching value
  return ret == QDialog::Accepted ? ShowRecordElem::continueShowHeaders : ShowRecordElem::cancelShowHeaders;
}

FilterAction_Type ShowRecordElem::applyHeaderFilter( HeaderFilter* filter, QString account, QString& mailbox, FilterLog* log )
{
  FilterAction_Type action = filter->check( from(), to(), size(), subject(), header(), account, mailbox );

  //if the action is MARK, the related view entry shall be marked at the next view refresh
  if( action == FActMark ) markAtViewRefresh = true;

  //if the action is DELETE, we add a entry to the log
  if( log == NULL )
    kdError( "ShowRecordElem::applyHeaderFilter: Pointer to the filter log is NULL. Can't write to log." );
  if( action == FActDelete && log != NULL )
    log->addDeletedMail( sentDateTime(), from(), account, subject() );
  if( action == FActMove && log != NULL )
    log->addMovedMail( sentDateTime(), from(), account, subject(), mailbox );


  return action;
}

QDateTime ShowRecordElem::sentDateTime() const
{
  return m_unixDate;
}

void ShowRecordElem::writeToMoveLog( FilterLog * log, QString account, QString mailbox )
{
  log->addMovedMail( sentDateTime(), from(), account, subject(), mailbox );
}

void ShowRecordElem::writeToDeleteLog( FilterLog * log, QString account )
{
  log->addDeletedMail( sentDateTime(), from(), account, subject() );
}

void ShowRecordElem::setMarkAtNextViewRefresh( )
{
  markAtViewRefresh = true;
}


