/***************************************************************************
                          ConfigElem.cpp  -  description
                             -------------------
    begin                : Tue May 9 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 "configelem.h"

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


ConfigElem::ConfigElem( ) : TQObject()
{
  //initialize account
  init();

  //set default values
  m_url.setProtocol( "pop3" );
  m_url.setPort( 110 );
  m_bActive = false;
  appConfig = NULL;
  m_strAccount = "";
}

ConfigElem::ConfigElem( ConfigList* config ) : TQObject()
{
  //initialize account
  init();

  m_url.setProtocol( "pop3" );
  m_url.setPort (110);

  m_bActive = false;

  appConfig = config;

}

ConfigElem::ConfigElem( ConfigElem* pElem ) : TQObject()
{
  //initialize account
  init();

  //set active by default
  m_bActive = pElem->isActive();

  //copy some interesting stuff from the sample
  //the url object contains all necessary information about the server
  m_strAccount = pElem->getAccountName();
  m_url = pElem->getURL();
  appConfig = pElem->appConfig;

}

ConfigElem::ConfigElem( ConfigList* config, const TQString& account ) : TQObject()
{
  //initialize account
  init();

  //set account name
  m_strAccount = account;

  //deactivate it by default
  m_bActive = false;

  //set the pointer to the general app configuration
  appConfig = config;
}

void ConfigElem::init( )
{
  //initialize timeout timer
  pop3Timer = new TQTimer( this );
  connect( pop3Timer, TQ_SIGNAL( timeout() ), this, TQ_SLOT( slotTimeout() ) );

  //state is idle
  state = AccountIdle;

  //create new empty mail list
  m_pshowrecord = new ShowRecord();

  //the account has no appropriate account list view item yet
  m_pViewItem = NULL;

  //set default values
  PasswordStorage = DEFAULT_ACCOUNT_PASSWORD_STORAGE;
  filterApplied = false;
  deletionPerformedByFilters = false;
  refreshPerformedByFilters = false;
  downloadActionsInvoked = false;

  //initialize counters
  moveCounter = 0;
  nmbDeletedMailsLastRefresh = 0;
  nmbDeletedMailsLastStart = 0;
  nmbMovedMailsLastRefresh = 0;
  nmbMovedMailsLastStart = 0;
  nmbIgnoredMails = 0;
}


ConfigElem::~ConfigElem()
{
  // do not delete m_pshowrecord here
}

void ConfigElem::saveOptions( TQDomDocument& doc, TQDomElement& parent )
{
  //get application config
  TDEConfig* config = TDEApplication::kApplication()->config();

  //save the active state
  config->setGroup( getAccountName() );
  config->writeEntry( CONFIG_ENTRY_ACCOUNT_ACTIVE, m_bActive );
  config->sync();

  //save the stored mails inside this account
  parent.setAttribute( ATTRIBUTE_ACCOUNT_NAME, m_strAccount );
  m_pshowrecord->saveOptions( doc, parent );
}

void ConfigElem::readStoredMails( TQDomElement& parent )
{
  //get mails
  m_pshowrecord->readStoredMails( parent );
}


int ConfigElem::count()
{
  return m_pshowrecord->count();
}

bool ConfigElem::isActive( ) const
{
  return m_bActive;
}

void ConfigElem::setActive( bool active )
{
  m_bActive = active;
}

TQString ConfigElem::getAccountName( ) const
{
  return m_strAccount;
}

void ConfigElem::setAccountName( TQString name )
{
  if( name != NULL )
    m_strAccount = name;
}

TQString ConfigElem::getPassword( ) const
{
  return m_url.pass();
}

void ConfigElem::setPassword( const TQString& password )
{
    m_url.setPass( password );
}

KURL ConfigElem::getURL( ) const
{
  return m_url;
}

bool ConfigElem::hasPassword( ) const
{
  return m_url.hasPass();
}

void ConfigElem::setListViewItem( TQListViewItem* item )
{
  m_pViewItem = item;
}

TQListViewItem * ConfigElem::getListViewItem( )
{
  return m_pViewItem;
}

bool ConfigElem::isSelected( ) const
{
  if( m_pViewItem == NULL )

    return false;

  else

    return m_pViewItem->isSelected();
}

void ConfigElem::clearMailList( )
{
  if( m_pshowrecord == NULL )
    //there is no mail list yet, create a one
    m_pshowrecord = new ShowRecord;
  else
    //clear the existing mail list
    m_pshowrecord->clear();
}

void ConfigElem::setHost( const TQString& host )
{
  m_url.setHost( host );
}

void ConfigElem::setProtocol( const TQString& protocol )
{
  m_url.setProtocol( protocol );
}

void ConfigElem::setPort( unsigned short int port )
{
  m_url.setPort( port );
}

void ConfigElem::setUser( const TQString & user )
{
  m_url.setUser( user );
}

TQString ConfigElem::getUser( ) const
{
  return m_url.user();
}

TQString ConfigElem::getHost( ) const
{
  return m_url.host();
}

void ConfigElem::deleteSelectedMails( )
{
  //return if this account has no selected mails or
  //the account is not idle or the account is not active
  if( !m_pshowrecord->hasSelectedMails() || state != AccountIdle || !isActive() )
  {
    emit sigDeleteReady( m_strAccount );
    return;
  }

  //check whether we have a password for this account
  //if not, ask for it
  //return when no password is available
  if( !assertPassword() )
  {
    emit sigDeleteReady( m_strAccount );
    return;
  }

  //get the numbers of all selected mails
  MailsToDelete = m_pshowrecord->getSelectedMails();
  if( MailsToDelete.empty() )
  {
    kdError() << "ConfigElem::deleteSelectedMails (Account " << m_strAccount << "): The account has selected mails to delete but ShowRecord::getSelectedMails has returned an empty list." << endl;
    emit sigDeleteReady( m_strAccount );
    return;
  }

  //set account state
  state = AccountDeleting;

  //start the deleting of all mails in MailsToDelete
  deleteNextMail();
}

bool ConfigElem::assertPassword( bool force )
{
  //is a password stored?
  if ( !hasPassword() || force )
  {
    //no password found, we will ask the user!
    //set normal cursor
    while( TQApplication::overrideCursor() )
      TQApplication::restoreOverrideCursor();

    TQString password;      //for the password dialog to store the password
    int result = KPasswordDialog::getPassword( password, i18n( "Please type in the password for %1" ).arg( getAccountName() ) );

    //set waiting cursor
    TQApplication::setOverrideCursor( TQt::waitCursor );

    //let's look, what the user has done :o)
    if( result == KPasswordDialog::Accepted )
    {
      //the user has clicked OK in the password dialog
      //store the password
      setPassword( password );

      //save password in file or TDEWallet
      TDEConfig* config = TDEApplication::kApplication()->config();
      config->setGroup( getAccountName() );

      if( PasswordStorage == CONFIG_VALUE_ACCOUNT_PASSWORD_SAVE_FILE )
        config->writeEntry( CONFIG_ENTRY_ACCOUNT_PASSWORD, crypt( m_url ) );
      else
        config->writeEntry( CONFIG_ENTRY_ACCOUNT_PASSWORD, TQString::null );

      if( PasswordStorage == CONFIG_VALUE_ACCOUNT_PASSWORD_SAVE_TDEWALLET )
        TDEWalletAccess::savePassword( getAccountName(), m_url.pass() );

      config->sync();

      //emit configuration changed signal
      emit ( sigConfigChanged() );

      //tell we have a password
      return true;
    }
    else
      //the user has clicked Cancel in the password dialog; we don't have a password
      return false;
  }
  else
    //we have already a password for this account
    return true;

}

void ConfigElem::deleteNextMail( )
{
  //if the list of mails to delete is empty, finalize the deletion and return
  if( MailsToDelete.empty() )
  {
    if( deletionPerformedByFilters )
    {
      applyFiltersDeleted();
    }
    else
    {
      commitDeletion();
    }
    return;
  }

  //start job
  startKIOJob( TQString( "/remove/%1" ).arg( *MailsToDelete.begin() ) );
  connect( pop3Job, TQ_SIGNAL( result( TDEIO::Job* ) ), this, TQ_SLOT( slotMailDeleted( TDEIO::Job* ) ) );
}

void ConfigElem::slotMailDeleted( TDEIO::Job* job )
{
  //stop timeout timer
  pop3Timer->stop();

  //check for errors
  //if an error is occured, the deletion will be canceled
  //or will ask for a new password
  if( job->error() == TDEIO::ERR_COULD_NOT_LOGIN )
  {
    //login failed, ask for a new password
    job->showErrorDialog();
    bool res = assertPassword( true );
    if( res == false )
    {
      //we have not got a new password; cancel delete
      if( deletionPerformedByFilters )
      {
        applyFiltersDeleted();
      }
      else
      {
        slotFinalizeDeletion( NULL );
      }
      return;
    }
    //if we have got a new password, it jumps to the end of the if-statement
  }
  else if( job->error() != 0 )
  {
    //unknown error, show message and cancel delete
    job->showErrorDialog();
    if( deletionPerformedByFilters )
    {
      applyFiltersDeleted();
    }
    else
    {
      slotFinalizeDeletion( NULL );
    }
    return;
  }
  else
  {
    //operation was successful
    //remove the deleted mail from the internal mail list
    m_pshowrecord->removeMail( *MailsToDelete.begin() );

    //remove the first item of the list of mails to delete
    MailsToDelete.remove( MailsToDelete.begin() );

    //if the list of mails to delete is empty, finalize the deletion and return
    if( MailsToDelete.empty() )
    {
      if( deletionPerformedByFilters )
      {
        applyFiltersDeleted();
      }
      else
      {
        commitDeletion();
      }
      return;
    }
  }

  //delete next mail in list
  deleteNextMail();


}

void ConfigElem::slotFinalizeDeletion( TDEIO::Job* )
{
  //stop timeout time
  pop3Timer->stop();

  //set account state to idle
  state = AccountIdle;

  //emit signal to report the deletion is ready
  emit sigDeleteReady( m_strAccount );
}

void ConfigElem::startKIOJob( const TQString & path )
{
  TDEIO::MetaData options;           //options for the pop3 job

  //set options
  options.insert( "progress", "off" );
  options.insert( "pipelining", "off" );

  if( useTLS )
    options.insert( "tls", "on" );
  else
    options.insert( "tls", "off" );

  //Where is secure login?
  //I have decided against a configurable secure login because the used POP3 tdeioslave
  //always tries to login with APOP, if the server has sent a timestap (inside of the greeting string) for this authentification type.
  //It just follows the auth-metadata, if the server doesn't support APOP (no timestamp inside of the greeting string).
  //But I think, there is no server, which support a SASL authentification without also provide APOP.
  //Ulrich Weigelt

  //set the given command and parameters
  m_url.setPath( path );

  //print debug message
  kdDebug() << "ConfigElem::startKIOJob: start TDEIO job on URL " << m_url.url() << endl;

  //start the job and get handle to it
  pop3Job = TDEIO::get( m_url, false, false );

  //put options to the job
  pop3Job->addMetaData( options );

  //start timeout timer
  pop3Timer->start( getTimeoutTime() * 1000, true );
}

Types::AccountState_Type ConfigElem::getState( )
{
  return state;
}

void ConfigElem::commitDeletion( )
{
  //start job to commit
  startKIOJob( TQString( "/commit" ) );
  connect( pop3Job, TQ_SIGNAL( result( TDEIO::Job* ) ), this, TQ_SLOT( slotFinalizeDeletion( TDEIO::Job* ) ) );
}

unsigned int ConfigElem::getTimeoutTime( )
{
  //return default time, if the configuration is not accessable
  if( appConfig == NULL )
    return DEFAULT_TIMEOUT_TIME;

  //get time from configuration
  unsigned int time = appConfig->getTimeoutTime();

  //take minimum time, if get time is less
  if( time < MINIMUM_TIMEOUT_TIME )
    time = MINIMUM_TIMEOUT_TIME;

  return time;
}

void ConfigElem::slotTimeout( )
{
  //kill a running job
  if( pop3Job != NULL )
    pop3Job->kill( true );

  //show error message (during refresh if desired only)
  kdError() << "Timeout error!" << endl;

  if( state != AccountRefreshing || appConfig->showConnectionErrors() )
    KMessageBox::error( NULL, TQString( i18n( "Time out on %1. The operation could not be finished on time" ) ).arg( m_strAccount ), i18n( "Time Out" ) );

  //call the appropriate finalize methode
  switch( state )
  {
    case AccountIdle        : break;
    case AccountDeleting    : slotFinalizeDeletion( NULL ); break;
    case AccountDownloading : slotFinalizeShowMail( NULL ); break;
    case AccountRefreshing  : cancelRefresh(); break;

    default : break;
  }
}

TQStringList ConfigElem::getSelectedSubjects( ) const
{
  return m_pshowrecord->getSelectedSubjects();
}

bool ConfigElem::hasSelectedMails( )
{
  return m_pshowrecord->hasSelectedMails();
}

void ConfigElem::showSelectedMails( )
{
  //return if this account has no selected mails or
  //the account is not idle or the account is not active
  if( !m_pshowrecord->hasSelectedMails() || state != AccountIdle || !isActive() )
  {
    emit sigShowBodiesReady( m_strAccount );
    return;
  }

  //check whether we have a password for this account
  //if not, ask for it
  //return when no password is available
  if( !assertPassword() )
  {
    emit sigShowBodiesReady( m_strAccount );
    return;
  }

  //get the numbers of all selected mails
  MailsToShow = m_pshowrecord->getSelectedMails();
  if( MailsToShow.empty() )
  {
    kdError() << "ConfigElem::showSelectedMails (Account " << m_strAccount << "): The account has selected mails to show but ShowRecord::getSelectedMails has returned an empty list." << endl;
    emit sigShowBodiesReady( m_strAccount );
    return;
  }

  //set account state
  state = AccountDownloading;

  //start the deleting of all mails in MailsToDelete
  showNextMail();

}

void ConfigElem::showNextMail( )
{
  //if the list of mails to show is empty, finalize it and return
  if( MailsToShow.empty() )
  {
    slotFinalizeShowMail( NULL );
    return;
  }

  //clear the class variable mailbody, which contains the downloaded mail body
  mailbody.resize( 0 );

  //start job
  startKIOJob( TQString( "/download/%1" ).arg( *MailsToShow.begin() ) );
  connect( pop3Job, TQ_SIGNAL( data( TDEIO::Job*, const TQByteArray & ) ), TQ_SLOT( slotDataMailBody( TDEIO::Job*, const TQByteArray & ) ) );
  connect( pop3Job, TQ_SIGNAL( result( TDEIO::Job* ) ), this, TQ_SLOT( slotBodyDownloaded( TDEIO::Job* ) ) );

}

void ConfigElem::slotBodyDownloaded( TDEIO::Job * job )
{
  //stop timeout timer
  pop3Timer->stop();

  //check for errors
  //if an error has occured, the download will be canceled
  //or will ask for a new password
  if( job->error() == TDEIO::ERR_COULD_NOT_LOGIN )
  {
    //login failed, ask for a new password
    job->showErrorDialog();
    bool res = assertPassword( true );
    if( res == false )
    {
      //we have not got a new password; cancel delete
      slotFinalizeShowMail( NULL );
      return;
    }
    //if we have got a new password, jump to the end of the if-statement
  }
  else if( job->error() != 0 )
  {
    job->showErrorDialog();
    slotFinalizeShowMail( NULL );
    return;
  }
  else
  {
    //succesful download
    //show mail
    int currentMail = *MailsToShow.begin();
    TQString tsender = m_pshowrecord->getSenderOf( currentMail );
    TQString tdate = m_pshowrecord->getDateOf( currentMail );
    TQString tsize = m_pshowrecord->getSizeOf( currentMail );
    TQString tsubject = m_pshowrecord->getSubjectOf( currentMail );
    TQString tmailbody( m_pshowrecord->decodeMailBody( mailbody, currentMail, appConfig->allowHTML() ) );

    //emit signal to notify the opening of a window
    emit sigMessageWindowOpened();

    //create and open the window
    ShowMailDialog dlg( kapp->mainWidget(), m_strAccount, appConfig->allowHTML(), tsender, tdate, tsize, tsubject, tmailbody );
    int ret = dlg.exec();

    //emit signal to notify the closing of a window
    emit sigMessageWindowClosed();

    //cancel the download if desired
    if( ret == KDialogBase::Rejected )
    {
      MailsToShow.clear();
      commitDownloading();
      return;
    }

    //remove the first item of the list of mails to show
    MailsToShow.remove( MailsToShow.begin() );

    //if the list of mails is empty, finalize the showing and return
    if( MailsToShow.empty() )
    {
      commitDownloading();
      return;
    }
  }


  //show next mail in list
  showNextMail();
}

void ConfigElem::slotFinalizeShowMail( TDEIO::Job* )
{
  //stop timeout time
  pop3Timer->stop();

  //set account state to idle
  state = AccountIdle;

  //emit signal to report the download is ready
  emit sigShowBodiesReady( m_strAccount );
}

void ConfigElem::slotDataMailBody( TDEIO::Job *, const TQByteArray & datas )
{
  if( !datas.isEmpty() )
  {
    //we get the next part of the mail
    //append it
    uint lastSize = mailbody.size();
    mailbody.resize( lastSize + datas.size() );
    for( uint i = 0; i < datas.size(); i++ )
      mailbody[ lastSize + i ] = datas[ i ];
  }
}

void ConfigElem::commitDownloading( )
{
  //start job to commit
  startKIOJob( TQString( "/commit" ) );
  connect( pop3Job, TQ_SIGNAL( result( TDEIO::Job* ) ), this, TQ_SLOT( slotFinalizeShowMail( TDEIO::Job* ) ) );
}

void ConfigElem::refreshMailList( FilterLog* log )
{
  //store pointer to log
  if( log != NULL )
    FLog = log;

  //return, if account is not active
  if( !isActive() )
  {
    emit sigRefreshReady( m_strAccount );
    return;
  }

  //check whether we have a password for this account
  //if not, ask for it
  //return when no password is available
  if( !assertPassword() )
  {
    emit sigRefreshReady( m_strAccount );
    return;
  }

  //create a new ShowRecord instance
  //When the refresh has finished successfully, this will
  //replace the old mail list
  tempMailList = new ShowRecord();

  //set account state
  state = AccountRefreshing;

  //init counter
  if( !refreshPerformedByFilters )
  {
    nmbDeletedMailsLastRefresh = 0;
    nmbMovedMailsLastRefresh = 0;
    nmbIgnoredMails = 0;
  }

  //the first step is to get the UIDs
  getUIDs();
}

void ConfigElem::getUIDs( )
{
  //clears the TQString list, which contains all received UIDs
  receivedUIDs.clear();

  //start job
  startKIOJob( TQString( "/uidl" ) );
  connect( pop3Job, TQ_SIGNAL( data( TDEIO::Job*, const TQByteArray & ) ), TQ_SLOT( slotReceiveUID( TDEIO::Job*, const TQByteArray & ) ) );
  connect( pop3Job, TQ_SIGNAL( result( TDEIO::Job* ) ), this, TQ_SLOT( slotUIDsReceived( TDEIO::Job* ) ) );

}

void ConfigElem::slotReceiveUID( TDEIO::Job*, const TQByteArray& data )
{
  //return, when data is empty
  if( data.isEmpty() ) return;

 //cast the data to TQString
  TQString uid( data );

  //insert the uid at the end of the UID list
  receivedUIDs.append( uid );
}

void ConfigElem::slotUIDsReceived( TDEIO::Job * job )
{
  int number;                 //an extracted mail number
  TQString uid;                //an extracted uid
  bool corruptData = false;   //set to true, if a data is corrupt
  bool isNew = false;         //state of the received mail

  //stop timeout timer
  pop3Timer->stop();

  //check for errors
  //if an error has occured, the refresh will be canceled
  //or will ask for a new password
  if( job->error() == TDEIO::ERR_COULD_NOT_LOGIN )
  {
    //login failed, ask for a new password
    job->showErrorDialog();
    bool res = assertPassword( true );
    if( res == true )
    {
      //we have got a new password, try again
      delete tempMailList;
      refreshMailList();
    }
    else
      //we have not got a new password; cancel refresh
      cancelRefresh();

    return;
  }
  else if( job->error() != 0 )
  {
    //show error message if desired
    if( appConfig->showConnectionErrors() )
      job->showErrorDialog();

    cancelRefresh();
    return;
  }

  //analyze UIDs
  if( !receivedUIDs.isEmpty() )
  {
    //iterate over all UIDs in the list
    for ( TQStringList::Iterator it = receivedUIDs.begin(); it != receivedUIDs.end(); ++it )
    {
      TQString line = *it;

      //every line has the format "number UID", e.g.: 1 bf10d38018de7c1d628d65288d722f6a
      //get the position of the separating space
      int positionOfSpace = line.find( " " );

      //if no space was found, the line is corrupt
      if( positionOfSpace == -1 )
      {
        kdError() << "ConfigElem::slotUIDsReceived: get a corrupt UID from " << dynamic_cast<TDEIO::SimpleJob*>(job)->url().host() << ". No space. : " << line << endl;
        corruptData = true;
      }
      else
      {
        //extract mail number and uid
        bool isNumber;
        number = line.left( positionOfSpace ).toInt( &isNumber );
        //check number
        if( !isNumber )
        {
          //the first part is not a number
          kdError() << "ConfigElem::slotUIDsReceived: get a corrupt UID from " << dynamic_cast<TDEIO::SimpleJob*>(job)->url().host() << ". No number found at begin. : " << line << endl;
          corruptData = true;
        }
        else
        {
          //number is ok; extract uid
          uid = line.mid( positionOfSpace + 1 );

          //determine about new mail or not
          if( !m_pshowrecord->hasMail( uid ) )
          {
            //the old list doesn't contain a mail with this uid
            //the mail is new
            isNew = true;
          }
          else if( ( appConfig->keepNew() || refreshPerformedByFilters ) && m_pshowrecord->isNew( uid ) )
          {
            //the mail is already in the old list
            //but we will leave the state of formerly new mails, because the user wants it or this refresh is performed by filters
            isNew = true;
          }
          else
            isNew = false;

          //append mail to the list
          tempMailList->appendNewMail( number, uid, isNew );

        }
      }
    }

    //if the data are ok, start the second step: get sizes
    //otherwise cancel the refresh
    if( !corruptData )
      getSizes();
    else
      cancelRefresh();
  }
  else
  {
    //we haven't received any UIDs. The account has no mails.
    //finalize the refresh
    swapMailLists();
  }

}

void ConfigElem::cancelRefresh()
{
  //print error message
  kdError() << m_strAccount << ": " << "Refresh canceled" << endl;

  //delete the new mail list
  delete tempMailList;

  //delete old mail list and create a new empty one
  delete m_pshowrecord;
  m_pshowrecord = new ShowRecord();

  //emit signal
  emit sigRefreshReady( m_strAccount );

  //set account state to idle
  state = AccountIdle;

  //we don't need an error message, because the TDEIO job has shown one
}

void ConfigElem::slotFinalizeRefresh( TDEIO::Job* )
{
  //stop timeout time
  pop3Timer->stop();

  //unset the flag
  refreshPerformedByFilters = false;

  //emit signal
  emit sigRefreshReady( m_strAccount );

  //set account state to idle
  state = AccountIdle;


}

void ConfigElem::commitRefresh( )
{
  //start job to commit
  startKIOJob( TQString( "/commit" ) );
  connect( pop3Job, TQ_SIGNAL( result( TDEIO::Job* ) ), this, TQ_SLOT( slotFinalizeRefresh( TDEIO::Job* ) ) );
}

void ConfigElem::getSizes( )
{
  //clears the TQString list, which contains all received UIDs
  receivedSizes.clear();

  //start job
  startKIOJob( TQString( "/index" ) );
  connect( pop3Job, TQ_SIGNAL( data( TDEIO::Job*, const TQByteArray & ) ), TQ_SLOT( slotReceiveSize( TDEIO::Job*, const TQByteArray & ) ) );
  connect( pop3Job, TQ_SIGNAL( result( TDEIO::Job* ) ), this, TQ_SLOT( slotSizesReceived( TDEIO::Job* ) ) );

}

void ConfigElem::slotSizesReceived( TDEIO::Job * job )
{
  int number;                 //an extracted mail number
  long size;                   //an extracted size
  bool corruptData = false;   //set to true, if a data is corrupt

  //stop timeout timer
  pop3Timer->stop();

  //check for errors
  //if an error has occured, the refresh will be canceled
  if( job->error() != 0 )
  {
    //show error message if desired
    if( appConfig->showConnectionErrors() )
      job->showErrorDialog();
    cancelRefresh();
    return;
  }

  //analyze UIDs
  if( !receivedSizes.isEmpty() )
  {
    //iterate over all sizes in the list
    for ( TQStringList::Iterator it = receivedSizes.begin(); it != receivedSizes.end(); ++it )
    {
      TQString line = *it;

      //every line has the format "number size", e.g.: 1 1234
      //get the position of the separating space
      int positionOfSpace = line.find( " " );

      //if no space was found, the line is corrupt
      if( positionOfSpace == -1 )
      {
        kdError() << "ConfigElem::slotSizesReceived: get a corrupt size from " << dynamic_cast<TDEIO::SimpleJob*>(job)->url().host() << ". No space. : " << line << endl;
        corruptData = true;
      }
      else
      {
        //extract mail number and size
        bool isNumber;
        number = line.left( positionOfSpace ).toInt( &isNumber );
        //check number
        if( !isNumber )
        {
          //the first part is not a number
          kdError() << "ConfigElem::slotSizesReceived: get a corrupt size from " << dynamic_cast<TDEIO::SimpleJob*>(job)->url().host() << ". No number found at begin. : " << line << endl;
          corruptData = true;
        }
        else
        {
          //number is ok; extract size
          size = line.mid( positionOfSpace + 1 ).toLong( &isNumber );

          //check size
          if( !isNumber )
          {
            //the second part of the string is not a number
            kdError() << "ConfigElem::slotSizesReceived: get a corrupt size from " << dynamic_cast<TDEIO::SimpleJob*>(job)->url().host() << ". No size found at end. : " << line << endl;
            corruptData = true;
          }
          else
          {
            //size is ok
            //set it
            tempMailList->setSize( number, size );
          }
        }
      }
    }

    //if the data are ok, start the third step: get headers
    //otherwise cancel the refresh
    if( !corruptData )
      getHeaders();
    else
      cancelRefresh();
  }
}

void ConfigElem::slotReceiveSize( TDEIO::Job *, const TQByteArray & data )
{
  //return, when data is empty
  if( data.isEmpty() ) return;

 //cast the data to TQString
  TQString size( data );

  //insert the uid at the end of the sizes list
  receivedSizes.append( size );

}

void ConfigElem::getHeaders( )
{
  //get the numbers of all new mails
  newMails = tempMailList->getNewMails();
  if( newMails.empty() )
  {
    //no new mails available; copy the known headers from the old mail list
    copyHeaders();
    return;
  }

  //get the headers
  getNextHeader();
}

void ConfigElem::getNextHeader( )
{
  //if the list of mails empty, copy the known headers from the old mail list
  if( newMails.empty() )
  {
    copyHeaders();
    return;
  }

  //clear temporary header store
  receivedHeader.resize( 0 );

  //start job
  startKIOJob( TQString( "/headers/%1" ).arg( *newMails.begin() ) );
  connect( pop3Job, TQ_SIGNAL( data( TDEIO::Job*, const TQByteArray & ) ), this, TQ_SLOT( slotReceiveHeader( TDEIO::Job*, const TQByteArray & ) ) );
  connect( pop3Job, TQ_SIGNAL( result( TDEIO::Job* ) ), this, TQ_SLOT( slotHeaderDownloaded( TDEIO::Job* ) ) );

}

void ConfigElem::slotHeaderDownloaded( TDEIO::Job * job )
{
  //stop timeout timer
  pop3Timer->stop();

  //check for errors
  //if an error is occured, the download will be canceled
  if( job->error() != 0 )
  {
    //show error message if desired
    if( appConfig->showConnectionErrors() )
      job->showErrorDialog();

    cancelRefresh();
    return;
  }

  //store header
  tempMailList->setHeader( *newMails.begin(), TQString( receivedHeader ) );

  //remove the first item of the list of new mails
  newMails.remove( newMails.begin() );

  //if the list of new mails is empty, copy the headers of old mails to the new list
  if( newMails.empty() )
  {
    copyHeaders();
    return;
  }

  //get next header
  getNextHeader();
}

void ConfigElem::copyHeaders( )
{
  //get the UIDs of the old mails in the temporary mail list
  TQStringList UIDs = tempMailList->getUIDsOfOldMails();

  //iterate over all members of the list,
  //get the header from the old list and store it in the new one
  TQStringList::iterator it;
  for ( it = UIDs.begin(); it != UIDs.end(); ++it )
  {
    TQString header = m_pshowrecord->getHeaderOf( *it );
    tempMailList->setHeader( *it, header );
  }

  //now we have the a complete new mail list
  swapMailLists();
}

void ConfigElem::slotReceiveHeader( TDEIO::Job *, const TQByteArray & data )
{
  if( !data.isEmpty() )
  {
    //we get the next part of the mail
    //append it
    uint lastSize = receivedHeader.size();
    receivedHeader.resize( lastSize + data.size() );
    for( uint i = 0; i < data.size(); i++ )
      receivedHeader[ lastSize + i ] = data[ i ];
  }
}

int ConfigElem::getNumberNewMails( )
{
  return m_pshowrecord->getNumberNewMails();
}

int ConfigElem::getNumberMails( )
{
  return m_pshowrecord->getNumberMails();
}

long ConfigElem::getTotalSize( )
{
  return m_pshowrecord->getTotalSize();
}

void ConfigElem::fillMailListView( KshowmailView* view )
{
  m_pshowrecord->fillMailListView( view, m_strAccount );
}

void ConfigElem::refreshAccountListItem( )
{
  if( m_pViewItem != NULL )
  {
    if( isActive() )
    {
      m_pViewItem->setText( 4, TQString( "%1" ).arg( getNumberMails(), 3 ) );
      m_pViewItem->setText( 5, TQString( "%1" ).arg( getTotalSize(), 8 ) );
    }
    else
    {
      m_pViewItem->setText( 4, TQString( "???" ) );
      m_pViewItem->setText( 5, TQString( "???" ) );
    }
  }
}

void ConfigElem::killPOP3Job( )
{
  //just try to kill, if it is not idle
  if( state != AccountIdle )
  {
    //kill a running job
    if( pop3Job != NULL )
      pop3Job->kill( true );

    //stop timeout timer
    pop3Timer->stop();

    //call the appropriate finalize method
    switch( state )
    {
      case AccountDeleting    : slotFinalizeDeletion( NULL ); break;
      case AccountDownloading : slotFinalizeShowMail( NULL ); break;
      case AccountRefreshing  : cancelRefresh(); break;

      default : break;
    }
  }
}

int ConfigElem::showSelectedHeaders( )
{
  //return, if no mails are selected
  if( !hasSelectedMails() )
    return ConfigElem::continueShowHeaders;

  //order the mail list to show the headers of the selected mails
  int ret = m_pshowrecord->showSelectedHeaders( m_strAccount );

  return ret == ShowRecord::continueShowHeaders ? ConfigElem::continueShowHeaders : ConfigElem::cancelShowHeaders;
}

void ConfigElem::printSetup( ) const
{
  kdDebug() << "Setup of " << m_strAccount << ":" << endl;
  kdDebug() << "Host: " << m_url.host() << endl;
  kdDebug() << "Protocol: " << m_url.protocol() << endl;
  kdDebug() << "Port: " << m_url.port() << endl;
  kdDebug() << "User: " << m_url.user() << endl;
  kdDebug() << "Password: " << m_url.pass() << endl;

  switch( PasswordStorage )
  {
    case CONFIG_VALUE_ACCOUNT_PASSWORD_DONT_SAVE    : kdDebug() << "Password Storage: don't save" << endl; break;
    case CONFIG_VALUE_ACCOUNT_PASSWORD_SAVE_FILE    : kdDebug() << "Password Storage: save in file" << endl; break;
    case CONFIG_VALUE_ACCOUNT_PASSWORD_SAVE_TDEWALLET : kdDebug() << "Password Storage: use TDEWallet" << endl; break;
    default                                         : kdDebug() << "Password Storage: invalid value" << endl;

  }

  kdDebug() << "active: " << m_bActive << endl << endl;


}

void ConfigElem::setPasswordStorage( int storage )
{
  if( storage == CONFIG_VALUE_ACCOUNT_PASSWORD_DONT_SAVE ||
      storage == CONFIG_VALUE_ACCOUNT_PASSWORD_SAVE_FILE ||
      storage == CONFIG_VALUE_ACCOUNT_PASSWORD_SAVE_TDEWALLET )

    PasswordStorage = storage;

  else

    PasswordStorage =  DEFAULT_ACCOUNT_PASSWORD_STORAGE;
}

int ConfigElem::getPasswordStorage( ) const
{
  return PasswordStorage;
}

TQString ConfigElem::getProtocol( bool upperCase ) const
{
  if( upperCase )
    return m_url.protocol().upper();
  else
    return m_url.protocol();
}

unsigned short int ConfigElem::getPort( ) const
{
  return m_url.port();
}

void ConfigElem::setTLS( bool tls )
{
  useTLS = tls;
}

bool ConfigElem::getTLS( ) const
{
  return useTLS;
}

void ConfigElem::reloadFilterSettings( )
{
  headerFilter.load();
}

void ConfigElem::applyFilters( )
{
  //are we executed by the MOVE routines?
  if( !downloadActionsInvoked )
  {
    //this is the first call (at the current refresh cycle) of this methode
    //we get the lists of mails to delete an move and call the MOVE routines if necessary

    //OK, the filters were applied
    filterApplied = true;

    //order the mail list to apply the header filters
    //it returns lists of mail numbers which shall be deleted or moved
    //the marking will be done by the mail list itself
    //the mail list removes all mails which shall be ignored itself
    MailsToDelete.clear();
    m_pshowrecord->applyHeaderFilter( &headerFilter, getAccountName(), MailsToDelete, MailsToDownload, nmbIgnoredMails, FLog );
    nmbDeletedMailsLastRefresh += MailsToDelete.count();
    nmbDeletedMailsLastStart += MailsToDelete.count();

    //This part will be executed, if mails shall be downloaded
    if( !MailsToDownload.empty() )
    {
      downloadActionsInvoked = true;
      doDownloadActions();

      //we quit this methode at this point, because after the bodies are downloaded and written this methode will recalled.
      //At this time the else branch of this IF-statement will be executed and the methode continues
      return;
    }

  }
  else
  {
    //this is the second call (at the current refresh cycle) of this methode.
    //it is called by the Move routines.
    //the downloading of the mailbodies and writing it to the mailboxes has ended.
    //A second call was just exceuted, if there was mails to move
    downloadActionsInvoked = false;

    //after an move error there are maybe some mails leftover in MailsToMove
    MailsToDownload.clear();
  }



  //we have get the list of mails to delete and the all mails to move are written to its mailboxes
  //now we delete this mails (the moved mails too)

  if( !MailsToDelete.empty() )
  {
    //there are mails to delete
    //we delete they
    //after the delete cycle has done its job, it will call applyFiltersDeleted()
    deletionPerformedByFilters = true;  //this is set to indicate the deletion is performed by filters and not by user
                                        //the deletion methodes need it to decide on branch targets
    deleteNextMail();
  }
  else
  {
    //if we need not to start a second refresh cycle (no mails was deleted or moved)
    //we just commit the refresh and let the filter applied flag to false for the next regular refresh
    commitRefresh();
    filterApplied = false;
  }
}

void ConfigElem::swapMailLists( )
{
  //delete old mail list
  delete m_pshowrecord;

  //assign the new list
  if( tempMailList != NULL )
    m_pshowrecord = tempMailList;
  else
    m_pshowrecord = new ShowRecord();

  //if the filters were not applied yet, we do it now
  //applyFilters() will either start a second refresh cycle if it did some deletions
  //or call commitRefresh() to commit the refresh cycle.
  //if the filters were already applied we commit the refresh.
  if( filterApplied | !headerFilter.isActive() )
  {
    commitRefresh();
    filterApplied = false;
    return;
  }
  else
  {
    applyFilters();
    return;
  }
}

void ConfigElem::applyFiltersDeleted( )
{
  //unset the flag
  deletionPerformedByFilters = false;

  //start the second refresh cycle
  refreshPerformedByFilters = true;
  
  //this sends a commit and restart the refresh
  commitBeforeRefresh();
  return;
  //refreshMailList();
}


bool ConfigElem::writeToMailBox( const TQString & mail, const TQString & box )
{
  TQDir mailDir( box );

  //check whether the given path is a maildir
  if( !isMailDir( mailDir ) )
  {
    //show an error message
    KMessageBox::error( NULL, i18n( "%1 is not a mailbox." ).arg( box ) );
    return false;
  }

  //create unique file name according http://cr.yp.to/proto/maildir.html
  TQString partTime = TQString::number( time( NULL ) );   //left part, output of time()

  char hname[256];                                      //right part, the hostname
  TQString partHostname;
  if( gethostname( hname, 255 ) == 0 )
    partHostname = TQString( hname );
  else
  {
    //the hostname is not readable
    //show an error message and exit
    KMessageBox::error( NULL, i18n( "Can't read the hostname of your computer. But KShowmail need it to write a mail into the mailbox." ) );
    return false;
  }

  TQString partPID = TQString::number( getpid() );      //middle part, the PID

  TQString partCounter = TQString::number( moveCounter++ );

  TQString uniqueName( partTime + "." + partPID + partCounter + "." + partHostname );

  //build absolute path
  mailDir.cd( "tmp" );
  TQString absFile = mailDir.filePath( uniqueName );

  //and writing!
  TQFile file( absFile );
  if( file.open( IO_WriteOnly ) )
  {
    TQTextStream stream( &file );
    stream << mail << endl;
    file.close();
  }
  else
  {
    KMessageBox::detailedError( NULL, i18n( "Could not file a mail to %1." ).arg( box ), i18n( file.errorString().utf8() ) );
    return false;
  }

  //now we move it to the "new" subdirectory
  mailDir.cdUp();
  mailDir.cd( "new" );
  TQString absNewFile = mailDir.filePath( uniqueName );

  if( rename( absFile.ascii(), absNewFile.ascii() ) == -1 )
  {
    KMessageBox::error( NULL, i18n( "Could not move a mail from %1 to %2." ).arg( absFile ).arg( absNewFile ) );
    return false;
  }

  //the writing was successful
  return true;
}

void ConfigElem::doDownloadActions()
{
  //get first mail
  getNextMailForDownloadActions();
}

bool ConfigElem::isMailDir( const TQDir & path )
{
  //get a list of all subdirectories in this directory
  const TQStringList entries = path.entryList( TQDir::Dirs | TQDir::Readable | TQDir::Writable | TQDir::Hidden, TQDir::Name | TQDir::IgnoreCase | TQDir::LocaleAware );

  //a maildir folder must contains the folders "cur", "new" and "tmp"
  bool curFound = false;
  bool newFound = false;
  bool tmpFound = false;

  //iterate over all directories and look for the three necessary dirs
  TQStringList::const_iterator it = entries.begin();
  while( it != entries.end() && !( curFound && newFound && tmpFound ) )
  {
    if( *it == "tmp" )
      tmpFound = true;
    else if( *it == "cur" )
      curFound = true;
    else if( *it == "new" )
      newFound = true;

    ++it;
  }

  return curFound && newFound && tmpFound;
}

void ConfigElem::getNextMailForDownloadActions()
{
    //if the list of mails to move is empty return to applyFilters
  if( MailsToDownload.empty() )
  {
    applyFilters();
    return;
  }

  //clear the class variable mailbody, which contains the downloaded mail body
  mailbody.resize( 0 );

  //start job
  startKIOJob( TQString( "/download/%1" ).arg( MailsToDownload.begin().key() ) );
  connect( pop3Job, TQ_SIGNAL( data( TDEIO::Job*, const TQByteArray & ) ), TQ_SLOT( slotDataMailBody( TDEIO::Job*, const TQByteArray & ) ) );
  connect( pop3Job, TQ_SIGNAL( result( TDEIO::Job* ) ), this, TQ_SLOT( slotMailDownloadedForAction( TDEIO::Job* ) ) );

}

void ConfigElem::slotMailDownloadedForAction(TDEIO::Job * job)
{
  //stop timeout timer
  pop3Timer->stop();

  //check for errors
  //if an error has occured, the download will be canceled
  //or will ask for a new password
  if( job->error() == TDEIO::ERR_COULD_NOT_LOGIN )
  {
    //login failed, ask for a new password
    job->showErrorDialog();
    bool res = assertPassword( true );
    if( res == false )
    {
      //we have not got a new password; cancel delete
      applyFilters();
      return;
    }
    //if we have got a new password, jump to the end of the if-statement
  }
  else if( job->error() != 0 )
  {
    job->showErrorDialog();
    applyFilters();
    return;
  }
  else
  {
    //succesful download
    //do action
    MailToDownloadMap_Type::Iterator firstMail = MailsToDownload.begin();
    int currentMailNumber = firstMail.key();  //get mail number
    TQString currentMailBox( firstMail.data().mailbox ); //get mailbox
    TQString mail( mailbody );                 //convert mailtext
    FilterAction_Type action = firstMail.data().action; //get action

    bool resultMove = false;    //true - mail is written into the mailbox
    bool resultSpam = false;  //true - mail is Spam
    bool deleteIt = false;    //true - mail shall be deleted
    bool resultAction = false;  //True - the action was succesful performed

    switch( action )
    {
      case FActMove       : resultMove = writeToMailBox( mail, currentMailBox );
                            //log entry is made by ShowRecordElem::applyHeaderFilter
                            if( resultMove == true )
                            {
                              nmbMovedMailsLastRefresh++;
                              nmbMovedMailsLastStart++;

                              resultAction = true;
                              deleteIt = true;
                            }
                            else
                            {
                              resultAction = false;
                              deleteIt = false;
                            }
                            break;

      case FActSpamcheck  : resultSpam = isSpam( mailbody );  //it is spam?
                            if( resultSpam == true )          //yes, it is spam! Arrgghh! Torture it!!!
                            {
                              switch( appConfig->getSpamAction() )
                              {
                                case FActMove   : resultMove = writeToMailBox( mail, appConfig->getSpamMailbox() );
                                                  if( resultMove == true )
                                                  {
                                                    nmbMovedMailsLastRefresh++;
                                                    nmbMovedMailsLastStart++;

                                                    if( FLog != NULL )
                                                      m_pshowrecord->writeToMoveLog( FLog, currentMailNumber, getAccountName(), appConfig->getSpamMailbox() );
                                                    resultAction = true;
                                                    deleteIt = true;
                                                  }
                                                  else
                                                  {
                                                    resultAction = false;
                                                    deleteIt = false;
                                                  }
                                                  break;

                                case FActMark   : m_pshowrecord->setMarkAtNextViewRefresh( currentMailNumber );
                                                  resultAction = true;
                                                  deleteIt = false;
                                                  break;

                                case FActDelete : if( FLog != NULL )
                                                    m_pshowrecord->writeToDeleteLog( FLog, currentMailNumber, getAccountName() );

                                                  nmbDeletedMailsLastRefresh++;
                                                  nmbDeletedMailsLastStart++;
                                                  resultAction = true;
                                                  deleteIt = true;
                                                  break;

                                default         : kdError() << "invalid action for spam mail" << endl;
                                                  resultAction = false;
                                                  deleteIt = false;
                                                  break;

                              }
                            }
                            else    //mail is not spam
                            {
                              resultAction = true;
                              deleteIt = false;
                            }
                            break;

      default             : deleteIt = false;
                            resultAction = false;

    }

    if( resultAction == true )
    {
      //Action was successful
      //remove this mail from the list
      MailsToDownload.remove( firstMail );

      //maybe add this mail to list of mails to delete
      if( deleteIt )
        MailsToDelete.append( currentMailNumber );
    }
    else
    {
      //Action was not successful
      //returns to applyFilters() to continue the filtering
      applyFilters();
      return;
    }


    //if the list of mails is empty, return to applyFilters() to continue the filtering
    if( MailsToDownload.empty() )
    {
      applyFilters();
      return;
    }
  }


  //show next mail in list
  getNextMailForDownloadActions();
}

bool ConfigElem::isSpam( TQByteArray mail ) const
{
  //check for a running spamassassin
  if( !isSpamAssassinRunning() )
  {
    KMessageBox::information( NULL, i18n( "You want to check your mails for spam, but SpamAssassin is not running.\nKShowmail skips the spam check." ), i18n( "SpamAssassin is not running" ), "ConfigElemNoSpamAssassinRunning" );
    return false;
  }

  //append an \0 at the end of the string
  int size = mail.size();
  if( mail[ size - 1 ] != '\0' )
  {
    mail.resize( size + 1 );
    mail[ size ] = '\0';
  }

  //calls spmac and get an file pointer to stdin of it
  FILE *write_fp;
  write_fp = popen( "spamc -E", "w" );

  //forward the mail to SpamAssassin
  if( write_fp != NULL )
  {
    fwrite( mail.data(), sizeof( char), mail.size(), write_fp );

    //check exit code of spamc and return result
    int excode = pclose( write_fp );
    if(  excode == 0 )
      return false;
    else
      return true;
  }
  else
  {
    kdError() << "Could not call the command spamc of SpamAssassin." << endl;
    return false;
  }

  return false;
}

bool ConfigElem::isSpamAssassinRunning( ) const
{
  FILE *read_fp;
  char buffer[ BUFSIZ + 1 ];
  int chars_read;
  bool found = false;

  memset( buffer, '\0', sizeof( buffer ) );
  read_fp = popen( "ps -eo comm", "r" );
  if( read_fp != NULL )
  {
    chars_read = fread( buffer, sizeof( char ), BUFSIZ, read_fp );
    while( chars_read > 0 )
    {
      buffer[ chars_read - 1 ] = '\0';
      TQString output( buffer );
      found = output.contains( NAME_SPAMASSASSIN_DAEMON ) > 0;
      chars_read = fread( buffer, sizeof( char ), BUFSIZ, read_fp );
    }
    pclose( read_fp );
  }

  return found;
}

int ConfigElem::numberDeletedMailsLastRefresh( )
{
  return nmbDeletedMailsLastRefresh;
}

int ConfigElem::numberDeletedMailsStart( )
{
  return nmbDeletedMailsLastStart;
}

int ConfigElem::numberMovedMailsLastRefresh( )
{
  return nmbMovedMailsLastRefresh;
}

int ConfigElem::numberMovedMailsStart( )
{
  return nmbMovedMailsLastStart;
}

int ConfigElem::numberIgnoredMails( )
{
  return nmbIgnoredMails;
}

TQStringList ConfigElem::getSelectedSenders( ) const
{
  return m_pshowrecord->getSelectedSenders();
}




void ConfigElem::commitBeforeRefresh()
{
  //start job to commit
  startKIOJob( TQString( "/commit" ) );
  connect( pop3Job, TQ_SIGNAL( result( TDEIO::Job* ) ), this, TQ_SLOT( slotCommitBeforeRefreshDone( TDEIO::Job* ) ) );

}

void ConfigElem::slotCommitBeforeRefreshDone(TDEIO::Job *)
{
  //after a commit was send, we start a new refresh cyle
  refreshMailList();
}

#include "configelem.moc"
