/*
 *   kmail: KDE mail client
 *   This file: Copyright (C) 2006 Dmitry Morozhnikov <dmiceman@mail.ru>
 *
 *   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.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include <config.h>

#include <tqstring.h>
#include <tqdatetime.h>
#include <tdelocale.h>
#include <kcalendarsystem.h>
#include <kmime_util.h>
#include <tdeglobal.h>
#include <tdeprocess.h>
#include <tqregexp.h>
#include <tqfile.h>
#include <tdemessagebox.h>
#include <kshell.h>
#include <tqfileinfo.h>

#include "kmmessage.h"
#include "kmmsgbase.h"
#include "kmfolder.h"
#include "templatesconfiguration.h"
#include "templatesconfiguration_kfg.h"
#include "customtemplates_kfg.h"
#include "globalsettings_base.h"
#include "kmkernel.h"
#include <libkpimidentities/identity.h>
#include <libkpimidentities/identitymanager.h>
#include "partNode.h"
#include "attachmentcollector.h"
#include "objecttreeparser.h"
#include "util.h"

#include "templateparser.h"
#include <mimelib/bodypart.h>

using namespace KMail;

TemplateParser::TemplateParser( KMMessage *amsg, const Mode amode ) :
  mMode( amode ), mFolder( 0 ), mIdentity( 0 ),
  mAllowDecryption( false ),
  mDebug( false ), mQuoteString( "> " ), mAppend( false ), mOrigRoot( 0 )
{
  mMsg = amsg;
}

void TemplateParser::setSelection( const TQString &selection )
{
  mSelection = selection;
}

void TemplateParser::setAllowDecryption( const bool allowDecryption )
{
  mAllowDecryption = allowDecryption;
}

bool TemplateParser::shouldStripSignature() const
{
  // Only strip the signature when replying, it should be preserved when forwarding
  return ( mMode == Reply || mMode == ReplyAll) && GlobalSettings::stripSignature();
}

TemplateParser::~TemplateParser()
{
  delete mOrigRoot;
  mOrigRoot = 0;
}

int TemplateParser::parseQuotes( const TQString &prefix, const TQString &str,
                                 TQString &quote ) const
{
  int pos = prefix.length();
  int len;
  int str_len = str.length();
  TQChar qc = '"';
  TQChar prev = 0;

  pos++;
  len = pos;

  while ( pos < str_len ) {
    TQChar c = str[pos];

    pos++;
    len++;

    if ( prev ) {
      quote.append( c );
      prev = 0;
    } else {
      if ( c == '\\' ) {
        prev = c;
      } else if ( c == qc ) {
        break;
      } else {
        quote.append( c );
      }
    }
  }

  return len;
}

TQString TemplateParser::getFName( const TQString &str )
{
  // simple logic:
  // if there is ',' in name, than format is 'Last, First'
  // else format is 'First Last'
  // last resort -- return 'name' from 'name@domain'
  int sep_pos;
  TQString res;
  if ( ( sep_pos = str.find( '@' ) ) > 0 ) {
    int i;
    for ( i = (sep_pos - 1); i >= 0; --i ) {
      TQChar c = str[i];
      if ( c.isLetterOrNumber() ) {
        res.prepend( c );
      } else {
        break;
      }
    }
  } else if ( ( sep_pos = str.find(',') ) > 0 ) {
    unsigned int i;
    bool begin = false;
    for ( i = sep_pos; i < str.length(); ++i ) {
      TQChar c = str[i];
      if ( c.isLetterOrNumber() ) {
        begin = true;
        res.append( c );
      } else if ( begin ) {
        break;
      }
    }
  } else {
    unsigned int i;
    for ( i = 0; i < str.length(); ++i ) {
      TQChar c = str[i];
      if ( c.isLetterOrNumber() ) {
        res.append( c );
      } else {
        break;
      }
    }
  }
  return res;
}

TQString TemplateParser::getLName( const TQString &str )
{
  // simple logic:
  // if there is ',' in name, than format is 'Last, First'
  // else format is 'First Last'
  int sep_pos;
  TQString res;
  if ( ( sep_pos = str.find(',') ) > 0 ) {
    int i;
    for ( i = sep_pos; i >= 0; --i ) {
      TQChar c = str[i];
      if ( c.isLetterOrNumber() ) {
        res.prepend( c );
      } else {
        break;
      }
    }
  } else {
    if ( ( sep_pos = str.find( ' ' ) ) > 0 ) {
      unsigned int i;
      bool begin = false;
      for ( i = sep_pos; i < str.length(); ++i ) {
        TQChar c = str[i];
        if ( c.isLetterOrNumber() ) {
          begin = true;
          res.append( c );
        } else if ( begin ) {
          break;
        }
      }
    }
  }
  return res;
}

void TemplateParser::process( KMMessage *aorig_msg, KMFolder *afolder, bool append )
{
  mAppend = append;
  mOrigMsg = aorig_msg;
  mFolder = afolder;
  TQString tmpl = findTemplate();
  return processWithTemplate( tmpl );
}

void TemplateParser::process( const TQString &tmplName, KMMessage *aorig_msg,
                              KMFolder *afolder, bool append )
{
  mAppend = append;
  mOrigMsg = aorig_msg;
  mFolder = afolder;
  TQString tmpl = findCustomTemplate( tmplName );
  return processWithTemplate( tmpl );
}

void TemplateParser::processWithTemplate( const TQString &tmpl )
{
  TQString body;
  int tmpl_len = tmpl.length();
  bool dnl = false;
  for ( int i = 0; i < tmpl_len; ++i ) {
    TQChar c = tmpl[i];
    // kdDebug() << "Next char: " << c << endl;
    if ( c == '%' ) {
      TQString cmd = tmpl.mid( i + 1 );

      if ( cmd.startsWith( "-" ) ) {
        // dnl
        kdDebug() << "Command: -" << endl;
        dnl = true;
        i += 1;

      } else if ( cmd.startsWith( "REM=" ) ) {
        // comments
        kdDebug() << "Command: REM=" << endl;
        TQString q;
        int len = parseQuotes( "REM=", cmd, q );
        i += len;

      } else if ( cmd.startsWith( "INSERT=" ) ) {
        // insert content of specified file as is
        kdDebug() << "Command: INSERT=" << endl;
        TQString q;
        int len = parseQuotes( "INSERT=", cmd, q );
        i += len;
        TQString path = KShell::tildeExpand( q );
        TQFileInfo finfo( path );
        if (finfo.isRelative() ) {
          path = KShell::homeDir( "" );
          path += '/';
          path += q;
        }
        TQFile file( path );
        if ( file.open( IO_ReadOnly ) ) {
          TQByteArray content = file.readAll();
          TQString str = TQString::fromLocal8Bit( content, content.size() );
          body.append( str );
        } else if ( mDebug ) {
          KMessageBox::error( 0,
                              i18n( "Cannot insert content from file %1: %2" ).
                              arg( path ).arg( file.errorString() ) );
        }

      } else if ( cmd.startsWith( "SYSTEM=" ) ) {
        // insert content of specified file as is
        kdDebug() << "Command: SYSTEM=" << endl;
        TQString q;
        int len = parseQuotes( "SYSTEM=", cmd, q );
        i += len;
        TQString pipe_cmd = q;
        TQString str = pipe( pipe_cmd, "" );
        body.append( str );

      } else if ( cmd.startsWith( "PUT=" ) ) {
        // insert content of specified file as is
        kdDebug() << "Command: PUT=" << endl;
        TQString q;
        int len = parseQuotes( "PUT=", cmd, q );
        i += len;
        TQString path = KShell::tildeExpand( q );
        TQFileInfo finfo( path );
        if (finfo.isRelative() ) {
          path = KShell::homeDir( "" );
          path += '/';
          path += q;
        }
        TQFile file( path );
        if ( file.open( IO_ReadOnly ) ) {
          TQByteArray content = file.readAll();
          body.append( TQString::fromLocal8Bit( content, content.size() ) );
        } else if ( mDebug ) {
          KMessageBox::error( 0,
                              i18n( "Cannot insert content from file %1: %2").
                              arg( path ).arg(file.errorString() ));
        }

      } else if ( cmd.startsWith( "QUOTEPIPE=" ) ) {
        // pipe message body throw command and insert it as quotation
        kdDebug() << "Command: QUOTEPIPE=" << endl;
        TQString q;
        int len = parseQuotes( "QUOTEPIPE=", cmd, q );
        i += len;
        TQString pipe_cmd = q;
        if ( mOrigMsg ) {
          TQString str = pipe( pipe_cmd, messageText( false ) );
          TQString quote = mOrigMsg->asQuotedString( "", mQuoteString, str,
                                                    shouldStripSignature(), mAllowDecryption );
          body.append( quote );
        }

      } else if ( cmd.startsWith( "QUOTE" ) ) {
        kdDebug() << "Command: QUOTE" << endl;
        i += strlen( "QUOTE" );
        if ( mOrigMsg ) {
          TQString quote = mOrigMsg->asQuotedString( "", mQuoteString, messageText( true ),
                                                    shouldStripSignature(), mAllowDecryption );
          body.append( quote );
        }

      } else if ( cmd.startsWith( "QHEADERS" ) ) {
        kdDebug() << "Command: TQHEADERS" << endl;
        i += strlen( "QHEADERS" );
        if ( mOrigMsg ) {
          TQString quote = mOrigMsg->asQuotedString( "", mQuoteString,
                                                    mOrigMsg->headerAsSendableString(),
                                                    false, false );
          body.append( quote );
        }

      } else if ( cmd.startsWith( "HEADERS" ) ) {
        kdDebug() << "Command: HEADERS" << endl;
        i += strlen( "HEADERS" );
        if ( mOrigMsg ) {
          TQString str = mOrigMsg->headerAsSendableString();
          body.append( str );
        }

      } else if ( cmd.startsWith( "TEXTPIPE=" ) ) {
        // pipe message body throw command and insert it as is
        kdDebug() << "Command: TEXTPIPE=" << endl;
        TQString q;
        int len = parseQuotes( "TEXTPIPE=", cmd, q );
        i += len;
        TQString pipe_cmd = q;
        if ( mOrigMsg ) {
          TQString str = pipe(pipe_cmd, messageText( false ) );
          body.append( str );
        }

      } else if ( cmd.startsWith( "MSGPIPE=" ) ) {
        // pipe full message throw command and insert result as is
        kdDebug() << "Command: MSGPIPE=" << endl;
        TQString q;
        int len = parseQuotes( "MSGPIPE=", cmd, q );
        i += len;
        TQString pipe_cmd = q;
        if ( mOrigMsg ) {
          TQString str = pipe(pipe_cmd, mOrigMsg->asString() );
          body.append( str );
        }

      } else if ( cmd.startsWith( "BODYPIPE=" ) ) {
        // pipe message body generated so far throw command and insert result as is
        kdDebug() << "Command: BODYPIPE=" << endl;
        TQString q;
        int len = parseQuotes( "BODYPIPE=", cmd, q );
        i += len;
        TQString pipe_cmd = q;
        TQString str = pipe( pipe_cmd, body );
        body.append( str );

      } else if ( cmd.startsWith( "CLEARPIPE=" ) ) {
        // pipe message body generated so far throw command and
        // insert result as is replacing current body
        kdDebug() << "Command: CLEARPIPE=" << endl;
        TQString q;
        int len = parseQuotes( "CLEARPIPE=", cmd, q );
        i += len;
        TQString pipe_cmd = q;
        TQString str = pipe( pipe_cmd, body );
        body = str;
        mMsg->setCursorPos( 0 );

      } else if ( cmd.startsWith( "TEXT" ) ) {
        kdDebug() << "Command: TEXT" << endl;
        i += strlen( "TEXT" );
        if ( mOrigMsg ) {
          TQString quote = messageText( false );
          body.append( quote );
        }

      } else if ( cmd.startsWith( "OTEXTSIZE" ) ) {
        kdDebug() << "Command: OTEXTSIZE" << endl;
        i += strlen( "OTEXTSIZE" );
        if ( mOrigMsg ) {
          TQString str = TQString( "%1" ).arg( mOrigMsg->body().length() );
          body.append( str );
        }

      } else if ( cmd.startsWith( "OTEXT" ) ) {
        kdDebug() << "Command: OTEXT" << endl;
        i += strlen( "OTEXT" );
        if ( mOrigMsg ) {
          TQString quote = messageText( false );
          body.append( quote );
        }

      } else if ( cmd.startsWith( "OADDRESSEESADDR" ) ) {
        kdDebug() << "Command: OADDRESSEESADDR" << endl;
        i += strlen( "OADDRESSEESADDR" );
        const TQString to = mOrigMsg->to();
        const TQString cc = mOrigMsg->cc();
        if ( !to.isEmpty() )
          body.append( i18n( "To:" ) + ' ' + to );
        if ( !to.isEmpty() && !cc.isEmpty() )
          body.append( '\n' );
        if ( !cc.isEmpty() )
          body.append( i18n( "CC:" ) + ' ' +  cc );

      } else if ( cmd.startsWith( "CCADDR" ) ) {
        kdDebug() << "Command: CCADDR" << endl;
        i += strlen( "CCADDR" );
        TQString str = mMsg->cc();
        body.append( str );

      } else if ( cmd.startsWith( "CCNAME" ) ) {
        kdDebug() << "Command: CCNAME" << endl;
        i += strlen( "CCNAME" );
        TQString str = mMsg->ccStrip();
        body.append( str );

      } else if ( cmd.startsWith( "CCFNAME" ) ) {
        kdDebug() << "Command: CCFNAME" << endl;
        i += strlen( "CCFNAME" );
        TQString str = mMsg->ccStrip();
        body.append( getFName( str ) );

      } else if ( cmd.startsWith( "CCLNAME" ) ) {
        kdDebug() << "Command: CCLNAME" << endl;
        i += strlen( "CCLNAME" );
        TQString str = mMsg->ccStrip();
        body.append( getLName( str ) );

      } else if ( cmd.startsWith( "TOADDR" ) ) {
        kdDebug() << "Command: TOADDR" << endl;
        i += strlen( "TOADDR" );
        TQString str = mMsg->to();
        body.append( str );

      } else if ( cmd.startsWith( "TONAME" ) ) {
        kdDebug() << "Command: TONAME" << endl;
        i += strlen( "TONAME" );
        TQString str = mMsg->toStrip();
        body.append( str );

      } else if ( cmd.startsWith( "TOFNAME" ) ) {
        kdDebug() << "Command: TOFNAME" << endl;
        i += strlen( "TOFNAME" );
        TQString str = mMsg->toStrip();
        body.append( getFName( str ) );

      } else if ( cmd.startsWith( "TOLNAME" ) ) {
        kdDebug() << "Command: TOLNAME" << endl;
        i += strlen( "TOLNAME" );
        TQString str = mMsg->toStrip();
        body.append( getLName( str ) );

      } else if ( cmd.startsWith( "TOLIST" ) ) {
        kdDebug() << "Command: TOLIST" << endl;
        i += strlen( "TOLIST" );
        TQString str = mMsg->to();
        body.append( str );

      } else if ( cmd.startsWith( "FROMADDR" ) ) {
        kdDebug() << "Command: FROMADDR" << endl;
        i += strlen( "FROMADDR" );
        TQString str = mMsg->from();
        body.append( str );

      } else if ( cmd.startsWith( "FROMNAME" ) ) {
        kdDebug() << "Command: FROMNAME" << endl;
        i += strlen( "FROMNAME" );
        TQString str = mMsg->fromStrip();
        body.append( str );

      } else if ( cmd.startsWith( "FROMFNAME" ) ) {
        kdDebug() << "Command: FROMFNAME" << endl;
        i += strlen( "FROMFNAME" );
        TQString str = mMsg->fromStrip();
        body.append( getFName( str ) );

      } else if ( cmd.startsWith( "FROMLNAME" ) ) {
        kdDebug() << "Command: FROMLNAME" << endl;
        i += strlen( "FROMLNAME" );
        TQString str = mMsg->fromStrip();
        body.append( getLName( str ) );

      } else if ( cmd.startsWith( "FULLSUBJECT" ) ) {
        kdDebug() << "Command: FULLSUBJECT" << endl;
        i += strlen( "FULLSUBJECT" );
        TQString str = mMsg->subject();
        body.append( str );

      } else if ( cmd.startsWith( "FULLSUBJ" ) ) {
        kdDebug() << "Command: FULLSUBJ" << endl;
        i += strlen( "FULLSUBJ" );
        TQString str = mMsg->subject();
        body.append( str );

      } else if ( cmd.startsWith( "MSGID" ) ) {
        kdDebug() << "Command: MSGID" << endl;
        i += strlen( "MSGID" );
        TQString str = mMsg->id();
        body.append( str );

      } else if ( cmd.startsWith( "OHEADER=" ) ) {
        // insert specified content of header from original message
        kdDebug() << "Command: OHEADER=" << endl;
        TQString q;
        int len = parseQuotes( "OHEADER=", cmd, q );
        i += len;
        if ( mOrigMsg ) {
          TQString hdr = q;
          TQString str = mOrigMsg->headerFields(hdr.local8Bit() ).join( ", " );
          body.append( str );
        }

      } else if ( cmd.startsWith( "HEADER=" ) ) {
        // insert specified content of header from current message
        kdDebug() << "Command: HEADER=" << endl;
        TQString q;
        int len = parseQuotes( "HEADER=", cmd, q );
        i += len;
        TQString hdr = q;
        TQString str = mMsg->headerFields(hdr.local8Bit() ).join( ", " );
        body.append( str );

      } else if ( cmd.startsWith( "HEADER( " ) ) {
        // insert specified content of header from current message
        kdDebug() << "Command: HEADER( " << endl;
        TQRegExp re = TQRegExp( "^HEADER\\((.+)\\)" );
        re.setMinimal( true );
        int res = re.search( cmd );
        if ( res != 0 ) {
          // something wrong
          i += strlen( "HEADER( " );
        } else {
          i += re.matchedLength();
          TQString hdr = re.cap( 1 );
          TQString str = mMsg->headerFields( hdr.local8Bit() ).join( ", " );
          body.append( str );
        }

      } else if ( cmd.startsWith( "OCCADDR" ) ) {
        kdDebug() << "Command: OCCADDR" << endl;
        i += strlen( "OCCADDR" );
        if ( mOrigMsg ) {
          TQString str = mOrigMsg->cc();
          body.append( str );
        }

      } else if ( cmd.startsWith( "OCCNAME" ) ) {
        kdDebug() << "Command: OCCNAME" << endl;
        i += strlen( "OCCNAME" );
        if ( mOrigMsg ) {
          TQString str = mOrigMsg->ccStrip();
          body.append( str );
        }

      } else if ( cmd.startsWith( "OCCFNAME" ) ) {
        kdDebug() << "Command: OCCFNAME" << endl;
        i += strlen( "OCCFNAME" );
        if ( mOrigMsg ) {
          TQString str = mOrigMsg->ccStrip();
          body.append( getFName( str ) );
        }

      } else if ( cmd.startsWith( "OCCLNAME" ) ) {
        kdDebug() << "Command: OCCLNAME" << endl;
        i += strlen( "OCCLNAME" );
        if ( mOrigMsg ) {
          TQString str = mOrigMsg->ccStrip();
          body.append( getLName( str ) );
        }

      } else if ( cmd.startsWith( "OTOADDR" ) ) {
        kdDebug() << "Command: OTOADDR" << endl;
        i += strlen( "OTOADDR" );
        if ( mOrigMsg ) {
          TQString str = mOrigMsg->to();
          body.append( str );
        }

      } else if ( cmd.startsWith( "OTONAME" ) ) {
        kdDebug() << "Command: OTONAME" << endl;
        i += strlen( "OTONAME" );
        if ( mOrigMsg ) {
          TQString str = mOrigMsg->toStrip();
          body.append( str );
        }

      } else if ( cmd.startsWith( "OTOFNAME" ) ) {
        kdDebug() << "Command: OTOFNAME" << endl;
        i += strlen( "OTOFNAME" );
        if ( mOrigMsg ) {
          TQString str = mOrigMsg->toStrip();
          body.append( getFName( str ) );
        }

      } else if ( cmd.startsWith( "OTOLNAME" ) ) {
        kdDebug() << "Command: OTOLNAME" << endl;
        i += strlen( "OTOLNAME" );
        if ( mOrigMsg ) {
          TQString str = mOrigMsg->toStrip();
          body.append( getLName( str ) );
        }

      } else if ( cmd.startsWith( "OTOLIST" ) ) {
        kdDebug() << "Command: OTOLIST" << endl;
        i += strlen( "OTOLIST" );
        if ( mOrigMsg ) {
          TQString str = mOrigMsg->to();
          body.append( str );
        }

      } else if ( cmd.startsWith( "OTO" ) ) {
        kdDebug() << "Command: OTO" << endl;
        i += strlen( "OTO" );
        if ( mOrigMsg ) {
          TQString str = mOrigMsg->to();
          body.append( str );
        }

      } else if ( cmd.startsWith( "OFROMADDR" ) ) {
        kdDebug() << "Command: OFROMADDR" << endl;
        i += strlen( "OFROMADDR" );
        if ( mOrigMsg ) {
          TQString str = mOrigMsg->from();
          body.append( str );
        }

      } else if ( cmd.startsWith( "OFROMNAME" ) ) {
        kdDebug() << "Command: OFROMNAME" << endl;
        i += strlen( "OFROMNAME" );
        if ( mOrigMsg ) {
          TQString str = mOrigMsg->fromStrip();
          body.append( str );
        }

      } else if ( cmd.startsWith( "OFROMFNAME" ) ) {
        kdDebug() << "Command: OFROMFNAME" << endl;
        i += strlen( "OFROMFNAME" );
        if ( mOrigMsg ) {
          TQString str = mOrigMsg->fromStrip();
          body.append( getFName( str ) );
        }

      } else if ( cmd.startsWith( "OFROMLNAME" ) ) {
        kdDebug() << "Command: OFROMLNAME" << endl;
        i += strlen( "OFROMLNAME" );
        if ( mOrigMsg ) {
          TQString str = mOrigMsg->fromStrip();
          body.append( getLName( str ) );
        }

      } else if ( cmd.startsWith( "OFULLSUBJECT" ) ) {
        kdDebug() << "Command: OFULLSUBJECT" << endl;
        i += strlen( "OFULLSUBJECT" );
        if ( mOrigMsg ) {
          TQString str = mOrigMsg->subject();
          body.append( str );
        }

      } else if ( cmd.startsWith( "OFULLSUBJ" ) ) {
        kdDebug() << "Command: OFULLSUBJ" << endl;
        i += strlen( "OFULLSUBJ" );
        if ( mOrigMsg ) {
          TQString str = mOrigMsg->subject();
          body.append( str );
        }

      } else if ( cmd.startsWith( "OMSGID" ) ) {
        kdDebug() << "Command: OMSGID" << endl;
        i += strlen( "OMSGID" );
        if ( mOrigMsg ) {
          TQString str = mOrigMsg->id();
          body.append( str );
        }

      } else if ( cmd.startsWith( "DATEEN" ) ) {
        kdDebug() << "Command: DATEEN" << endl;
        i += strlen( "DATEEN" );
        TQDateTime date = TQDateTime::currentDateTime();
        TDELocale locale( "C" );
        TQString str = locale.formatDate( date.date(), false );
        body.append( str );

      } else if ( cmd.startsWith( "DATESHORT" ) ) {
        kdDebug() << "Command: DATESHORT" << endl;
        i += strlen( "DATESHORT" );
        TQDateTime date = TQDateTime::currentDateTime();
        TQString str = TDEGlobal::locale()->formatDate( date.date(), true );
        body.append( str );

      } else if ( cmd.startsWith( "DATE" ) ) {
        kdDebug() << "Command: DATE" << endl;
        i += strlen( "DATE" );
        TQDateTime date = TQDateTime::currentDateTime();
        TQString str = TDEGlobal::locale()->formatDate( date.date(), false );
        body.append( str );

      } else if ( cmd.startsWith( "DOW" ) ) {
        kdDebug() << "Command: DOW" << endl;
        i += strlen( "DOW" );
        TQDateTime date = TQDateTime::currentDateTime();
        TQString str = TDEGlobal::locale()->calendar()->weekDayName( date.date(), false );
        body.append( str );

      } else if ( cmd.startsWith( "TIMELONGEN" ) ) {
        kdDebug() << "Command: TIMELONGEN" << endl;
        i += strlen( "TIMELONGEN" );
        TQDateTime date = TQDateTime::currentDateTime();
        TDELocale locale( "C");
        TQString str = locale.formatTime( date.time(), true );
        body.append( str );

      } else if ( cmd.startsWith( "TIMELONG" ) ) {
        kdDebug() << "Command: TIMELONG" << endl;
        i += strlen( "TIMELONG" );
        TQDateTime date = TQDateTime::currentDateTime();
        TQString str = TDEGlobal::locale()->formatTime( date.time(), true );
        body.append( str );

      } else if ( cmd.startsWith( "TIME" ) ) {
        kdDebug() << "Command: TIME" << endl;
        i += strlen( "TIME" );
        TQDateTime date = TQDateTime::currentDateTime();
        TQString str = TDEGlobal::locale()->formatTime( date.time(), false );
        body.append( str );

      } else if ( cmd.startsWith( "ODATEEN" ) ) {
        kdDebug() << "Command: ODATEEN" << endl;
        i += strlen( "ODATEEN" );
        if ( mOrigMsg ) {
          TQDateTime date;
          date.setTime_t( mOrigMsg->date() );
          TDELocale locale( "C");
          TQString str = locale.formatDate( date.date(), false );
          body.append( str );
        }

      } else if ( cmd.startsWith( "ODATESHORT") ) {
        kdDebug() << "Command: ODATESHORT" << endl;
        i += strlen( "ODATESHORT");
        if ( mOrigMsg ) {
          TQDateTime date;
          date.setTime_t( mOrigMsg->date() );
          TQString str = TDEGlobal::locale()->formatDate( date.date(), true );
          body.append( str );
        }

      } else if ( cmd.startsWith( "ODATE") ) {
        kdDebug() << "Command: ODATE" << endl;
        i += strlen( "ODATE");
        if ( mOrigMsg ) {
          TQDateTime date;
          date.setTime_t( mOrigMsg->date() );
          TQString str = TDEGlobal::locale()->formatDate( date.date(), false );
          body.append( str );
        }

      } else if ( cmd.startsWith( "ODOW") ) {
        kdDebug() << "Command: ODOW" << endl;
        i += strlen( "ODOW");
        if ( mOrigMsg ) {
          TQDateTime date;
          date.setTime_t( mOrigMsg->date() );
          TQString str = TDEGlobal::locale()->calendar()->weekDayName( date.date(), false );
          body.append( str );
        }

      } else if ( cmd.startsWith( "OTIMELONGEN") ) {
        kdDebug() << "Command: OTIMELONGEN" << endl;
        i += strlen( "OTIMELONGEN");
        if ( mOrigMsg ) {
          TQDateTime date;
          date.setTime_t( mOrigMsg->date() );
          TDELocale locale( "C");
          TQString str = locale.formatTime( date.time(), true );
          body.append( str );
        }

      } else if ( cmd.startsWith( "OTIMELONG") ) {
        kdDebug() << "Command: OTIMELONG" << endl;
        i += strlen( "OTIMELONG");
        if ( mOrigMsg ) {
          TQDateTime date;
          date.setTime_t( mOrigMsg->date() );
          TQString str = TDEGlobal::locale()->formatTime( date.time(), true );
          body.append( str );
        }

      } else if ( cmd.startsWith( "OTIME") ) {
        kdDebug() << "Command: OTIME" << endl;
        i += strlen( "OTIME");
        if ( mOrigMsg ) {
          TQDateTime date;
          date.setTime_t( mOrigMsg->date() );
          TQString str = TDEGlobal::locale()->formatTime( date.time(), false );
          body.append( str );
        }

      } else if ( cmd.startsWith( "BLANK" ) ) {
        // do nothing
        kdDebug() << "Command: BLANK" << endl;
        i += strlen( "BLANK" );

      } else if ( cmd.startsWith( "NOP" ) ) {
        // do nothing
        kdDebug() << "Command: NOP" << endl;
        i += strlen( "NOP" );

      } else if ( cmd.startsWith( "CLEAR" ) ) {
        // clear body buffer; not too useful yet
        kdDebug() << "Command: CLEAR" << endl;
        i += strlen( "CLEAR" );
        body = "";
        mMsg->setCursorPos( 0 );

      } else if ( cmd.startsWith( "DEBUGOFF" ) ) {
        // turn off debug
        kdDebug() << "Command: DEBUGOFF" << endl;
        i += strlen( "DEBUGOFF" );
        mDebug = false;

      } else if ( cmd.startsWith( "DEBUG" ) ) {
        // turn on debug
        kdDebug() << "Command: DEBUG" << endl;
        i += strlen( "DEBUG" );
        mDebug = true;

      } else if ( cmd.startsWith( "CURSOR" ) ) {
        // turn on debug
        kdDebug() << "Command: CURSOR" << endl;
        i += strlen( "CURSOR" );
        mMsg->setCursorPos( body.length() );

      } else {
        // wrong command, do nothing
        body.append( c );
      }

    } else if ( dnl && ( c == '\n' || c == '\r') ) {
      // skip
      if ( ( c == '\n' && tmpl[i + 1] == '\r' ) ||
           ( c == '\r' && tmpl[i + 1] == '\n' ) ) {
        // skip one more
        i += 1;
      }
      dnl = false;
    } else {
      body.append( c );
    }
  }

  addProcessedBodyToMessage( body );
}

TQString TemplateParser::messageText( bool allowSelectionOnly )
{
  if ( !mSelection.isEmpty() && allowSelectionOnly )
    return mSelection;

  // No selection text, therefore we need to parse the object tree ourselves to get
  partNode *root = parsedObjectTree();
  return mOrigMsg->asPlainTextFromObjectTree( root, shouldStripSignature(), mAllowDecryption );
}

partNode* TemplateParser::parsedObjectTree()
{
  if ( mOrigRoot )
    return mOrigRoot;

  mOrigRoot = partNode::fromMessage( mOrigMsg );
  ObjectTreeParser otp; // all defaults are ok
  otp.parseObjectTree( mOrigRoot );
  return mOrigRoot;
}

void TemplateParser::addProcessedBodyToMessage( const TQString &body )
{
  if ( mAppend ) {

    // ### What happens here if the body is multipart or in some way encoded?
    TQCString msg_body = mMsg->body();
    msg_body.append( body.utf8() );
    mMsg->setBody( msg_body );
  }
  else {

    // Get the attachments of the original mail
    partNode *root = parsedObjectTree();
    AttachmentCollector ac;
    ac.collectAttachmentsFrom( root );

    // Now, delete the old content and set the new content, which
    // is either only the new text or the new text with some attachments.
    mMsg->deleteBodyParts();

    // Set To and CC from the template
    if ( mMode == Forward ) {
      if ( !mTo.isEmpty() ) {
        mMsg->setTo( mMsg->to() + ',' + mTo );
      }
      if ( !mCC.isEmpty() )
        mMsg->setCc( mMsg->cc() + ',' + mCC );
    }

    // If we have no attachment, simply create a text/plain part and
    // set the processed template text as the body
    if ( ac.attachments().empty() || mMode != Forward ) {
      mMsg->headers().ContentType().FromString( DwString() ); // to get rid of old boundary
      mMsg->headers().ContentType().Parse();
      mMsg->headers().ContentType().SetType( DwMime::kTypeText );
      mMsg->headers().ContentType().SetSubtype( DwMime::kSubtypePlain );
      mMsg->headers().Assemble();
      mMsg->setBodyFromUnicode( body );
      mMsg->assembleIfNeeded();
    }

    // If we have some attachments, create a multipart/mixed mail and
    // add the normal body as well as the attachments
    else
    {
      mMsg->headers().ContentType().SetType( DwMime::kTypeMultipart );
      mMsg->headers().ContentType().SetSubtype( DwMime::kSubtypeMixed );
      mMsg->headers().ContentType().CreateBoundary( 0 );

      KMMessagePart textPart;
      textPart.setBodyFromUnicode( body );
      mMsg->addDwBodyPart( mMsg->createDWBodyPart( &textPart ) );
      mMsg->assembleIfNeeded();

      int attachmentNumber = 1;
      for ( std::vector<partNode*>::const_iterator it = ac.attachments().begin();
            it != ac.attachments().end(); ++it, attachmentNumber++ ) {

        // When adding this body part, make sure to _not_ add the next bodypart
        // as well, which mimelib would do, therefore creating a mail with many
        // duplicate attachments (so many that KMail runs out of memory, in fact).
        // Body::AddBodyPart is very misleading here...
        ( *it )->dwPart()->SetNext( 0 );

        DwBodyPart *cloned = static_cast<DwBodyPart*>( ( *it )->dwPart()->Clone() );

        // If the content type has no name or filename parameter, add one, since otherwise the name
        // would be empty in the attachment view of the composer, which looks confusing
        if ( cloned->Headers().HasContentType() ) {
          DwMediaType &ct = cloned->Headers().ContentType();

          // Converting to a string here, since DwMediaType does not have a HasParameter() function
          TQString ctStr = ct.AsString().c_str();
          if ( !ctStr.lower().contains( "name=" ) && !ctStr.lower().contains( "filename=" ) ) {
            DwParameter *nameParameter = new DwParameter;
            nameParameter->SetAttribute( "name" );
            nameParameter->SetValue( Util::dwString( KMMsgBase::encodeRFC2231StringAutoDetectCharset(
                       i18n( "Attachment %1" ).arg( attachmentNumber ) ) ) );
            ct.AddParameter( nameParameter );
          }
        }

        mMsg->addDwBodyPart( cloned );
        mMsg->assembleIfNeeded();
      }
    }
  }
}

TQString TemplateParser::findCustomTemplate( const TQString &tmplName )
{
  CTemplates t( tmplName );
  mTo = t.to();
  mCC = t.cC();
  TQString content = t.content();
  if ( !content.isEmpty() ) {
    return content;
  } else {
    return findTemplate();
  }
}

TQString TemplateParser::findTemplate()
{
  // import 'Phrases' if it not done yet
  if ( !GlobalSettings::self()->phrasesConverted() ) {
    TemplatesConfiguration::importFromPhrases();
  }

  // kdDebug() << "Trying to find template for mode " << mode << endl;

  TQString tmpl;

  if ( !mFolder ) { // find folder message belongs to
    mFolder = mMsg->parent();
    if ( !mFolder ) {
      if ( mOrigMsg ) {
        mFolder = mOrigMsg->parent();
      }
      if ( !mFolder ) {
        kdDebug(5006) << "Oops! No folder for message" << endl;
      }
    }
  }
  kdDebug(5006) << "Folder found: " << mFolder << endl;

  if ( mFolder )  // only if a folder was found
  {
    TQString fid = mFolder->idString();
    Templates fconf( fid );
    if ( fconf.useCustomTemplates() ) {   // does folder use custom templates?
      switch( mMode ) {
      case NewMessage:
        tmpl = fconf.templateNewMessage();
        break;
      case Reply:
        tmpl = fconf.templateReply();
        break;
      case ReplyAll:
        tmpl = fconf.templateReplyAll();
        break;
      case Forward:
        tmpl = fconf.templateForward();
        break;
      default:
        kdDebug(5006) << "Unknown message mode: " << mMode << endl;
        return "";
      }
      mQuoteString = fconf.quoteString();
      if ( !tmpl.isEmpty() ) {
        return tmpl;  // use folder-specific template
      }
    }
  }

  if ( !mIdentity ) { // find identity message belongs to
    mIdentity = mMsg->identityUoid();
    if ( !mIdentity && mOrigMsg ) {
      mIdentity = mOrigMsg->identityUoid();
    }
    mIdentity = kmkernel->identityManager()->identityForUoidOrDefault( mIdentity ).uoid();
    if ( !mIdentity ) {
      kdDebug(5006) << "Oops! No identity for message" << endl;
    }
  }
  kdDebug(5006) << "Identity found: " << mIdentity << endl;

  TQString iid;
  if ( mIdentity ) {
    iid = TQString("IDENTITY_%1").arg( mIdentity );	// templates ID for that identity
  }
  else {
    iid = "IDENTITY_NO_IDENTITY"; // templates ID for no identity
  }

  Templates iconf( iid );
  if ( iconf.useCustomTemplates() ) { // does identity use custom templates?
    switch( mMode ) {
    case NewMessage:
      tmpl = iconf.templateNewMessage();
      break;
    case Reply:
      tmpl = iconf.templateReply();
      break;
    case ReplyAll:
      tmpl = iconf.templateReplyAll();
      break;
    case Forward:
      tmpl = iconf.templateForward();
      break;
    default:
      kdDebug(5006) << "Unknown message mode: " << mMode << endl;
      return "";
    }
    mQuoteString = iconf.quoteString();
    if ( !tmpl.isEmpty() ) {
      return tmpl;  // use identity-specific template
    }
  }

  switch( mMode ) { // use the global template
  case NewMessage:
    tmpl = GlobalSettings::self()->templateNewMessage();
    break;
  case Reply:
    tmpl = GlobalSettings::self()->templateReply();
    break;
  case ReplyAll:
    tmpl = GlobalSettings::self()->templateReplyAll();
    break;
  case Forward:
    tmpl = GlobalSettings::self()->templateForward();
    break;
  default:
    kdDebug(5006) << "Unknown message mode: " << mMode << endl;
    return "";
  }

  mQuoteString = GlobalSettings::self()->quoteString();
  return tmpl;
}

TQString TemplateParser::pipe( const TQString &cmd, const TQString &buf )
{
  mPipeOut = "";
  mPipeErr = "";
  mPipeRc = 0;

  TDEProcess proc;
  TQCString data = buf.local8Bit();

  // kdDebug() << "Command data: " << data << endl;

  proc << KShell::splitArgs( cmd, KShell::TildeExpand );
  proc.setUseShell( true );
  connect( &proc, TQ_SIGNAL( receivedStdout( TDEProcess *, char *, int ) ),
           this, TQ_SLOT( onReceivedStdout( TDEProcess *, char *, int ) ) );
  connect( &proc, TQ_SIGNAL( receivedStderr( TDEProcess *, char *, int ) ),
           this, TQ_SLOT( onReceivedStderr( TDEProcess *, char *, int ) ) );
  connect( &proc, TQ_SIGNAL( wroteStdin( TDEProcess * ) ),
           this, TQ_SLOT( onWroteStdin( TDEProcess * ) ) );

  if ( proc.start( TDEProcess::NotifyOnExit, TDEProcess::All ) ) {

    bool pipe_filled = proc.writeStdin( data, data.length() );
    if ( pipe_filled ) {
      proc.closeStdin();

      bool exited = proc.wait( PipeTimeout );
      if ( exited ) {

        if ( proc.normalExit() ) {

          mPipeRc = proc.exitStatus();
          if ( mPipeRc != 0 && mDebug ) {
            if ( mPipeErr.isEmpty() ) {
              KMessageBox::error( 0,
                                  i18n( "Pipe command exit with status %1: %2").
                                  arg( mPipeRc ).arg( cmd ) );
            } else {
              KMessageBox::detailedError( 0,
                                          i18n( "Pipe command exit with status %1: %2" ).
                                          arg( mPipeRc ).arg( cmd ), mPipeErr );
            }
          }

        } else {

          mPipeRc = -( proc.exitSignal() );
          if ( mPipeRc != 0 && mDebug ) {
            if ( mPipeErr.isEmpty() ) {
              KMessageBox::error( 0,
                                  i18n( "Pipe command killed by signal %1: %2" ).
                                  arg( -(mPipeRc) ).arg( cmd ) );
            } else {
              KMessageBox::detailedError( 0,
                                          i18n( "Pipe command killed by signal %1: %2" ).
                                          arg( -(mPipeRc) ).arg( cmd ), mPipeErr );
            }
          }
        }

      } else {
        // process does not exited after TemplateParser::PipeTimeout seconds, kill it
        proc.kill();
        proc.detach();
        if ( mDebug ) {
          KMessageBox::error( 0,
                              i18n( "Pipe command did not finish within %1 seconds: %2" ).
                              arg( PipeTimeout ).arg( cmd ) );
        }
      }

    } else {
      // can`t write to stdin of process
      proc.kill();
      proc.detach();
      if ( mDebug ) {
        if ( mPipeErr.isEmpty() ) {
          KMessageBox::error( 0,
                              i18n( "Cannot write to process stdin: %1" ).arg( cmd ) );
        } else {
          KMessageBox::detailedError( 0,
                                      i18n( "Cannot write to process stdin: %1" ).
                                      arg( cmd ), mPipeErr );
        }
      }
    }

  } else if ( mDebug ) {
    KMessageBox::error( 0,
                        i18n( "Cannot start pipe command from template: %1" ).
                        arg( cmd ) );
  }

  return mPipeOut;
}

void TemplateParser::onProcessExited( TDEProcess *proc )
{
  Q_UNUSED( proc );
  // do nothing for now
}

void TemplateParser::onReceivedStdout( TDEProcess *proc, char *buffer, int buflen )
{
  Q_UNUSED( proc );
  mPipeOut += TQString::fromLocal8Bit( buffer, buflen );
}

void TemplateParser::onReceivedStderr( TDEProcess *proc, char *buffer, int buflen )
{
  Q_UNUSED( proc );
  mPipeErr += TQString::fromLocal8Bit( buffer, buflen );
}

void TemplateParser::onWroteStdin( TDEProcess *proc )
{
  proc->closeStdin();
}

#include "templateparser.moc"
