/*
    This file is part of KMail.
    Copyright (c) 2003 Andreas Gungl <a.gungl@gmx.de>

    KMail is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License, version 2, as
    published by the Free Software Foundation.

    KMail 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

    In addition, as a special exception, the copyright holders give
    permission to link the code of this program with any edition of
    the TQt library by Trolltech AS, Norway (or with modified versions
    of TQt that use the same license as TQt), and distribute linked
    combinations including the two.  You must obey the GNU General
    Public License in all respects for all of the code used other than
    TQt.  If you modify this file, you may extend this exception to
    your version of the file, but you are not obligated to do so.  If
    you do not wish to do so, delete this exception statement from
    your version.
*/

#include "antispamwizard.h"
#include "kcursorsaver.h"
#include "accountmanager.h"
#include "kmfilter.h"
#include "kmfilteraction.h"
#include "kmfiltermgr.h"
#include "kmkernel.h"
#include "kmfoldertree.h"
#include "kmmainwin.h"
#include "networkaccount.h"
#include "folderrequester.h"

#include <tdeaction.h>
#include <tdeapplication.h>
#include <kdebug.h>
#include <kdialog.h>
#include <kiconloader.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kprocess.h>

#include <tqdom.h>
#include <tqlabel.h>
#include <tqlayout.h>
#include <tqtooltip.h>
#include <tqwhatsthis.h>

using namespace KMail;

AntiSpamWizard::AntiSpamWizard( WizardMode mode,
                                TQWidget* parent, KMFolderTree * mainFolderTree )
  : KWizard( parent ),
    mInfoPage( 0 ),
    mSpamRulesPage( 0 ),
    mVirusRulesPage( 0 ),
    mSummaryPage( 0 ),
    mMode( mode )
{
  // read the configuration for the anti-spam tools
  ConfigReader reader( mMode, mToolList );
  reader.readAndMergeConfig();
  mToolList = reader.getToolList();

#ifndef NDEBUG
  if ( mMode == AntiSpam )
    kdDebug(5006) << endl << "Considered anti-spam tools: " << endl;
  else
    kdDebug(5006) << endl << "Considered anti-virus tools: " << endl;
#endif
  for ( TQValueListIterator<SpamToolConfig> it = mToolList.begin();
        it != mToolList.end(); ++it ) {
#ifndef NDEBUG
    kdDebug(5006) << "Predefined tool: " << (*it).getId() << endl;
    kdDebug(5006) << "Config version: " << (*it).getVersion() << endl;
    kdDebug(5006) << "Selection priority: " << (*it).getPrio() << endl;
    kdDebug(5006) << "Displayed name: " << (*it).getVisibleName() << endl;
    kdDebug(5006) << "Executable: " << (*it).getExecutable() << endl;
    kdDebug(5006) << "WhatsThis URL: " << (*it).getWhatsThisText() << endl;
    kdDebug(5006) << "Filter name: " << (*it).getFilterName() << endl;
    kdDebug(5006) << "Detection command: " << (*it).getDetectCmd() << endl;
    kdDebug(5006) << "Learn spam command: " << (*it).getSpamCmd() << endl;
    kdDebug(5006) << "Learn ham command: " << (*it).getHamCmd() << endl;
    kdDebug(5006) << "Detection header: " << (*it).getDetectionHeader() << endl;
    kdDebug(5006) << "Detection pattern: " << (*it).getDetectionPattern() << endl;
    kdDebug(5006) << "Use as RegExp: " << (*it).isUseRegExp() << endl;
    kdDebug(5006) << "Supports Bayes Filter: " << (*it).useBayesFilter() << endl;
    kdDebug(5006) << "Type: " << (*it).getType() << endl << endl;
#endif
  }

  setCaption( ( mMode == AntiSpam ) ? i18n( "Anti-Spam Wizard" )
                                    : i18n( "Anti-Virus Wizard" ) );
  mInfoPage = new ASWizInfoPage( mMode, 0, "" );
  addPage( mInfoPage,
           ( mMode == AntiSpam )
           ? i18n( "Welcome to the KMail Anti-Spam Wizard" )
           : i18n( "Welcome to the KMail Anti-Virus Wizard" ) );
  connect( mInfoPage, TQ_SIGNAL( selectionChanged( void ) ),
            this, TQ_SLOT( checkProgramsSelections( void ) ) );

  if ( mMode == AntiSpam ) {
    mSpamRulesPage = new ASWizSpamRulesPage( 0, "", mainFolderTree );
    addPage( mSpamRulesPage, i18n( "Options to fine-tune the handling of spam messages" ));
    connect( mSpamRulesPage, TQ_SIGNAL( selectionChanged( void ) ),
             this, TQ_SLOT( slotBuildSummary( void ) ) );
  }
  else {
    mVirusRulesPage = new ASWizVirusRulesPage( 0, "", mainFolderTree );
    addPage( mVirusRulesPage, i18n( "Options to fine-tune the handling of virus messages" ));
    connect( mVirusRulesPage, TQ_SIGNAL( selectionChanged( void ) ),
             this, TQ_SLOT( checkVirusRulesSelections( void ) ) );
  }

  connect( this, TQ_SIGNAL( helpClicked( void) ),
            this, TQ_SLOT( slotHelpClicked( void ) ) );

  setNextEnabled( mInfoPage, false );

  if ( mMode == AntiSpam ) {
    mSummaryPage = new ASWizSummaryPage( 0, "" );
    addPage( mSummaryPage, i18n( "Summary of changes to be made by this wizard" ) );
    setNextEnabled( mSpamRulesPage, true );
    setFinishEnabled( mSummaryPage, true );
  }

  TQTimer::singleShot( 0, this, TQ_SLOT( checkToolAvailability( void ) ) );
}


void AntiSpamWizard::accept()
{
  if ( mSpamRulesPage ) {
    kdDebug( 5006 ) << "Folder name for messages classified as spam is "
                    << mSpamRulesPage->selectedSpamFolderName() << endl;
    kdDebug( 5006 ) << "Folder name for messages classified as unsure is "
                    << mSpamRulesPage->selectedUnsureFolderName() << endl;
  }
  if ( mVirusRulesPage )
    kdDebug( 5006 ) << "Folder name for viruses is "
                    << mVirusRulesPage->selectedFolderName() << endl;

  KMFilterActionDict dict;
  TQValueList<KMFilter*> filterList;
  bool replaceExistingFilters = false;

  // Let's start with virus detection and handling,
  // so we can avoid spam checks for viral messages
  if ( mMode == AntiVirus ) {
    for ( TQValueListIterator<SpamToolConfig> it = mToolList.begin();
          it != mToolList.end(); ++it ) {
      if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) &&
         ( mVirusRulesPage->pipeRulesSelected() && (*it).isVirusTool() ) )
      {
        // pipe messages through the anti-virus tools,
        // one single filter for each tool
        // (could get combined but so it's easier to understand for the user)
        KMFilter* pipeFilter = new KMFilter();
        TQPtrList<KMFilterAction>* pipeFilterActions = pipeFilter->actions();
        KMFilterAction* pipeFilterAction = dict["filter app"]->create();
        pipeFilterAction->argsFromString( (*it).getDetectCmd() );
        pipeFilterActions->append( pipeFilterAction );
        KMSearchPattern* pipeFilterPattern = pipeFilter->pattern();
        pipeFilterPattern->setName( uniqueNameFor( (*it).getFilterName() ) );
        pipeFilterPattern->append( KMSearchRule::createInstance( "<size>",
                                   KMSearchRule::FuncIsGreaterOrEqual, "0" ) );
        pipeFilter->setApplyOnOutbound( false);
        pipeFilter->setApplyOnInbound();
        pipeFilter->setApplyOnExplicit();
        pipeFilter->setStopProcessingHere( false );
        pipeFilter->setConfigureShortcut( false );

        filterList.append( pipeFilter );
      }
    }

    if ( mVirusRulesPage->moveRulesSelected() )
    {
      // Sort out viruses depending on header fields set by the tools
      KMFilter* virusFilter = new KMFilter();
      TQPtrList<KMFilterAction>* virusFilterActions = virusFilter->actions();
      KMFilterAction* virusFilterAction1 = dict["transfer"]->create();
      virusFilterAction1->argsFromString( mVirusRulesPage->selectedFolderName() );
      virusFilterActions->append( virusFilterAction1 );
      if ( mVirusRulesPage->markReadRulesSelected() ) {
        KMFilterAction* virusFilterAction2 = dict["set status"]->create();
        virusFilterAction2->argsFromString( "R" ); // Read
        virusFilterActions->append( virusFilterAction2 );
      }
      KMSearchPattern* virusFilterPattern = virusFilter->pattern();
      virusFilterPattern->setName( uniqueNameFor( i18n( "Virus handling" ) ) );
      virusFilterPattern->setOp( KMSearchPattern::OpOr );
      for ( TQValueListIterator<SpamToolConfig> it = mToolList.begin();
            it != mToolList.end(); ++it ) {
        if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ))
        {
          if ( (*it).isVirusTool() )
          {
              const TQCString header = (*it).getDetectionHeader().ascii();
              const TQString & pattern = (*it).getDetectionPattern();
              if ( (*it).isUseRegExp() )
                virusFilterPattern->append(
                  KMSearchRule::createInstance( header,
                  KMSearchRule::FuncRegExp, pattern ) );
              else
                virusFilterPattern->append(
                  KMSearchRule::createInstance( header,
                  KMSearchRule::FuncContains, pattern ) );
          }
        }
      }
      virusFilter->setApplyOnOutbound( false);
      virusFilter->setApplyOnInbound();
      virusFilter->setApplyOnExplicit();
      virusFilter->setStopProcessingHere( true );
      virusFilter->setConfigureShortcut( false );

      filterList.append( virusFilter );
    }
  }
  else { // AntiSpam mode
    // TODO Existing filters with same name are replaced. This is hardcoded
    // ATM and needs to be replaced with a value from a (still missing)
    // checkbox in the GUI. At least, the replacement is announced in the GUI.
    replaceExistingFilters = true;
    for ( TQValueListIterator<SpamToolConfig> it = mToolList.begin();
          it != mToolList.end(); ++it ) {
      if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) &&
         (*it).isSpamTool() && !(*it).isDetectionOnly() )
      {
        // pipe messages through the anti-spam tools,
        // one single filter for each tool
        // (could get combined but so it's easier to understand for the user)
        KMFilter* pipeFilter = new KMFilter();
        TQPtrList<KMFilterAction>* pipeFilterActions = pipeFilter->actions();
        KMFilterAction* pipeFilterAction = dict["filter app"]->create();
        pipeFilterAction->argsFromString( (*it).getDetectCmd() );
        pipeFilterActions->append( pipeFilterAction );
        KMSearchPattern* pipeFilterPattern = pipeFilter->pattern();
        if ( replaceExistingFilters )
          pipeFilterPattern->setName( (*it).getFilterName() );
        else
          pipeFilterPattern->setName( uniqueNameFor( (*it).getFilterName() ) );
        pipeFilterPattern->append( KMSearchRule::createInstance( "<size>",
                                   KMSearchRule::FuncIsLessOrEqual, "256000" ) );
        pipeFilter->setApplyOnOutbound( false);
        pipeFilter->setApplyOnInbound();
        pipeFilter->setApplyOnExplicit();
        pipeFilter->setStopProcessingHere( false );
        pipeFilter->setConfigureShortcut( false );

        filterList.append( pipeFilter );
      }
    }

    // Sort out spam depending on header fields set by the tools
    KMFilter* spamFilter = new KMFilter();
    TQPtrList<KMFilterAction>* spamFilterActions = spamFilter->actions();
    if ( mSpamRulesPage->moveSpamSelected() )
    {
      KMFilterAction* spamFilterAction1 = dict["transfer"]->create();
      spamFilterAction1->argsFromString( mSpamRulesPage->selectedSpamFolderName() );
      spamFilterActions->append( spamFilterAction1 );
    }
    KMFilterAction* spamFilterAction2 = dict["set status"]->create();
    spamFilterAction2->argsFromString( "P" ); // Spam
    spamFilterActions->append( spamFilterAction2 );
    if ( mSpamRulesPage->markAsReadSelected() ) {
      KMFilterAction* spamFilterAction3 = dict["set status"]->create();
      spamFilterAction3->argsFromString( "R" ); // Read
      spamFilterActions->append( spamFilterAction3 );
    }
    KMSearchPattern* spamFilterPattern = spamFilter->pattern();
    if ( replaceExistingFilters )
      spamFilterPattern->setName( i18n( "Spam handling" ) );
    else
      spamFilterPattern->setName( uniqueNameFor( i18n( "Spam handling" ) ) );
    spamFilterPattern->setOp( KMSearchPattern::OpOr );
    for ( TQValueListIterator<SpamToolConfig> it = mToolList.begin();
          it != mToolList.end(); ++it ) {
      if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) )
      {
          if ( (*it).isSpamTool() )
          {
            const TQCString header = (*it).getDetectionHeader().ascii();
            const TQString & pattern = (*it).getDetectionPattern();
            if ( (*it).isUseRegExp() )
              spamFilterPattern->append(
                KMSearchRule::createInstance( header,
                KMSearchRule::FuncRegExp, pattern ) );
            else
              spamFilterPattern->append(
                KMSearchRule::createInstance( header,
                KMSearchRule::FuncContains, pattern ) );
          }
      }
    }
    spamFilter->setApplyOnOutbound( false);
    spamFilter->setApplyOnInbound();
    spamFilter->setApplyOnExplicit();
    spamFilter->setStopProcessingHere( true );
    spamFilter->setConfigureShortcut( false );
    filterList.append( spamFilter );

    if ( mSpamRulesPage->moveUnsureSelected() )
    {
      // Sort out messages classified as unsure
      bool atLeastOneUnsurePattern = false;
      KMFilter* unsureFilter = new KMFilter();
      TQPtrList<KMFilterAction>* unsureFilterActions = unsureFilter->actions();
      KMFilterAction* unsureFilterAction1 = dict["transfer"]->create();
      unsureFilterAction1->argsFromString( mSpamRulesPage->selectedUnsureFolderName() );
      unsureFilterActions->append( unsureFilterAction1 );
      KMSearchPattern* unsureFilterPattern = unsureFilter->pattern();
      if ( replaceExistingFilters )
        unsureFilterPattern->setName( i18n( "Semi spam (unsure) handling" ) );
      else
        unsureFilterPattern->setName( uniqueNameFor( i18n( "Semi spam (unsure) handling" ) ) );
      unsureFilterPattern->setOp( KMSearchPattern::OpOr );
      for ( TQValueListIterator<SpamToolConfig> it = mToolList.begin();
            it != mToolList.end(); ++it ) {
        if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) )
        {
            if ( (*it).isSpamTool() && (*it).hasTristateDetection())
            {
              atLeastOneUnsurePattern = true;
              const TQCString header = (*it).getDetectionHeader().ascii();
              const TQString & pattern = (*it).getDetectionPattern2();
              if ( (*it).isUseRegExp() )
                unsureFilterPattern->append(
                  KMSearchRule::createInstance( header,
                  KMSearchRule::FuncRegExp, pattern ) );
              else
                unsureFilterPattern->append(
                  KMSearchRule::createInstance( header,
                  KMSearchRule::FuncContains, pattern ) );
            }
        }
      }
      unsureFilter->setApplyOnOutbound( false);
      unsureFilter->setApplyOnInbound();
      unsureFilter->setApplyOnExplicit();
      unsureFilter->setStopProcessingHere( true );
      unsureFilter->setConfigureShortcut( false );

      if ( atLeastOneUnsurePattern )
        filterList.append( unsureFilter );
      else
        delete unsureFilter;
    }

    // Classify messages manually as Spam
    KMFilter* classSpamFilter = new KMFilter();
    classSpamFilter->setIcon( "mail_spam" );
    TQPtrList<KMFilterAction>* classSpamFilterActions = classSpamFilter->actions();
    KMFilterAction* classSpamFilterActionFirst = dict["set status"]->create();
    classSpamFilterActionFirst->argsFromString( "P" );
    classSpamFilterActions->append( classSpamFilterActionFirst );
    for ( TQValueListIterator<SpamToolConfig> it = mToolList.begin();
          it != mToolList.end(); ++it ) {
      if ( mInfoPage->isProgramSelected( (*it).getVisibleName() )
          && (*it).useBayesFilter() && !(*it).isDetectionOnly() )
      {
        KMFilterAction* classSpamFilterAction = dict["execute"]->create();
        classSpamFilterAction->argsFromString( (*it).getSpamCmd() );
        classSpamFilterActions->append( classSpamFilterAction );
      }
    }
    if ( mSpamRulesPage->moveSpamSelected() )
    {
      KMFilterAction* classSpamFilterActionLast = dict["transfer"]->create();
      classSpamFilterActionLast->argsFromString( mSpamRulesPage->selectedSpamFolderName() );
      classSpamFilterActions->append( classSpamFilterActionLast );
    }

    KMSearchPattern* classSpamFilterPattern = classSpamFilter->pattern();
    if ( replaceExistingFilters )
      classSpamFilterPattern->setName( i18n( "Classify as spam" ) );
    else
      classSpamFilterPattern->setName( uniqueNameFor( i18n( "Classify as spam" ) ) );
    classSpamFilterPattern->append( KMSearchRule::createInstance( "<size>",
                                    KMSearchRule::FuncIsGreaterOrEqual, "0" ) );
    classSpamFilter->setApplyOnOutbound( false);
    classSpamFilter->setApplyOnInbound( false );
    classSpamFilter->setApplyOnExplicit( false );
    classSpamFilter->setStopProcessingHere( true );
    classSpamFilter->setConfigureShortcut( true );
    classSpamFilter->setConfigureToolbar( true );
    filterList.append( classSpamFilter );

    // Classify messages manually as not Spam / as Ham
    KMFilter* classHamFilter = new KMFilter();
    classHamFilter->setIcon( "mail_ham" );
    TQPtrList<KMFilterAction>* classHamFilterActions = classHamFilter->actions();
    KMFilterAction* classHamFilterActionFirst = dict["set status"]->create();
    classHamFilterActionFirst->argsFromString( "H" );
    classHamFilterActions->append( classHamFilterActionFirst );
    for ( TQValueListIterator<SpamToolConfig> it = mToolList.begin();
          it != mToolList.end(); ++it ) {
      if ( mInfoPage->isProgramSelected( (*it).getVisibleName() )
          && (*it).useBayesFilter() && !(*it).isDetectionOnly() )
      {
        KMFilterAction* classHamFilterAction = dict["execute"]->create();
        classHamFilterAction->argsFromString( (*it).getHamCmd() );
        classHamFilterActions->append( classHamFilterAction );
      }
    }
    KMSearchPattern* classHamFilterPattern = classHamFilter->pattern();
    if ( replaceExistingFilters )
      classHamFilterPattern->setName( i18n( "Classify as NOT spam" ) );
    else
      classHamFilterPattern->setName( uniqueNameFor( i18n( "Classify as NOT spam" ) ) );
    classHamFilterPattern->append( KMSearchRule::createInstance( "<size>",
                                    KMSearchRule::FuncIsGreaterOrEqual, "0" ) );
    classHamFilter->setApplyOnOutbound( false);
    classHamFilter->setApplyOnInbound( false );
    classHamFilter->setApplyOnExplicit( false );
    classHamFilter->setStopProcessingHere( true );
    classHamFilter->setConfigureShortcut( true );
    classHamFilter->setConfigureToolbar( true );
    filterList.append( classHamFilter );
    }

    /* Now that all the filters have been added to the list, tell
     * the filter manager about it. That will emit filterListUpdate
     * which will result in the filter list in kmmainwidget being
     * initialized. This should happend only once. */
    KMKernel::self()->filterMgr()->appendFilters( filterList );

  /* Now that all the filters have been added to the list, tell
   * the filter manager about it. That will emit filterListUpdate
   * which will result in the filter list in kmmainwidget being
   * initialized. This should happend only once. */
  if ( !filterList.isEmpty() )
    KMKernel::self()->filterMgr()->appendFilters(
          filterList, replaceExistingFilters );

  TQDialog::accept();
}


void AntiSpamWizard::checkProgramsSelections()
{
  bool status = false;
  bool supportUnsure = false;

  mSpamToolsUsed = false;
  mVirusToolsUsed = false;
  for ( TQValueListIterator<SpamToolConfig> it = mToolList.begin();
        it != mToolList.end(); ++it ) {
    if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) )
    {
      status = true;
      if ( (*it).isSpamTool() ) {
        mSpamToolsUsed = true;
        if ( (*it).hasTristateDetection() )
          supportUnsure = true;
      }
      if ( (*it).isVirusTool() )
        mVirusToolsUsed = true;
    }
  }

  if ( mMode == AntiSpam ) {
    mSpamRulesPage->allowUnsureFolderSelection( supportUnsure );
    slotBuildSummary();
  }

  if ( ( mMode == AntiVirus ) && mVirusToolsUsed )
    checkVirusRulesSelections();

  setNextEnabled( mInfoPage, status );
}


void AntiSpamWizard::checkVirusRulesSelections()
{
  setFinishEnabled( mVirusRulesPage, anyVirusOptionChecked() );
}


void AntiSpamWizard::checkToolAvailability()
{
  // this can take some time to find the tools
  KCursorSaver busy( KBusyPtr::busy() );

  bool found = false;
  for ( TQValueListIterator<SpamToolConfig> it = mToolList.begin();
        it != mToolList.end(); ++it ) {
    TQString text( i18n("Scanning for %1...").arg( (*it).getId() ) );
    mInfoPage->setScanProgressText( text );
    if ( (*it).isSpamTool() && (*it).isServerBased() ) {
      // check the configured account for pattern in <server>
      TQString pattern = (*it).getServerPattern();
      kdDebug(5006) << "Testing for server pattern:" << pattern << endl;

      AccountManager* mgr = kmkernel->acctMgr();
      KMAccount* account = mgr->first();
      while ( account ) {
        if ( account->type() == "pop" || account->type().contains( "imap" ) ) {
          const NetworkAccount * n = dynamic_cast<const NetworkAccount*>( account );
          if ( n && n->host().lower().contains( pattern.lower() ) ) {
            mInfoPage->addAvailableTool( (*it).getVisibleName() );
            found = true;
          }
        }
        account = mgr->next();
      }
    }
    else {
      // check the availability of the application
      TDEApplication::kApplication()->processEvents( 200 );
      if ( !checkForProgram( (*it).getExecutable() ) ) {
        mInfoPage->addAvailableTool( (*it).getVisibleName() );
        found = true;
      }
    }
  }
  if ( found )
    mInfoPage->setScanProgressText( ( mMode == AntiSpam )
                                    ? i18n("Scanning for anti-spam tools finished.")
                                    : i18n("Scanning for anti-virus tools finished.") );
  else
    mInfoPage->setScanProgressText( ( mMode == AntiSpam )
                                    ? i18n("<p>No spam detection tools have been found. "
                                           "Install your spam detection software and "
                                           "re-run this wizard.</p>")
                                    : i18n("Scanning complete. No anti-virus tools found.") );
}


void AntiSpamWizard::slotHelpClicked()
{
  if ( mMode == AntiSpam )
    kapp->invokeHelp( "the-anti-spam-wizard", "kmail" );
  else
    kapp->invokeHelp( "the-anti-virus-wizard", "kmail" );
}


void AntiSpamWizard::slotBuildSummary()
{
  TQString text;
  TQString newFilters;
  TQString replaceFilters;

  if ( mMode == AntiVirus ) {
    text = ""; // TODO add summary for the virus part
  }
  else { // AntiSpam mode
    if ( mSpamRulesPage->markAsReadSelected() )
      text = i18n( "<p>Messages classified as spam are marked as read." );
    else
      text = i18n( "<p>Messages classified as spam are not marked as read." );

    if ( mSpamRulesPage->moveSpamSelected() )
      text += i18n( "<br>Spam messages are moved into the folder named <i>" )
            + mSpamRulesPage->selectedSpamFolderName() + "</i>.</p>";
    else
      text += i18n( "<br>Spam messages are not moved into a certain folder.</p>" );

    for ( TQValueListIterator<SpamToolConfig> it = mToolList.begin();
          it != mToolList.end(); ++it ) {
      if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) &&
         (*it).isSpamTool() && !(*it).isDetectionOnly() ) {
        sortFilterOnExistance( (*it).getFilterName(), newFilters, replaceFilters );
      }
    }
    sortFilterOnExistance( i18n( "Spam handling" ), newFilters, replaceFilters );

    // The need for a andling of status "probably spam" depends on the tools chosen
    if ( mSpamRulesPage->moveUnsureSelected() ) {
      bool atLeastOneUnsurePattern = false;
      for ( TQValueListIterator<SpamToolConfig> it = mToolList.begin();
            it != mToolList.end(); ++it ) {
        if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) ) {
            if ( (*it).isSpamTool() && (*it).hasTristateDetection())
              atLeastOneUnsurePattern = true;
        }
      }
      if ( atLeastOneUnsurePattern ) {
        sortFilterOnExistance( i18n( "Semi spam (unsure) handling" ),
                               newFilters, replaceFilters );
        text += i18n( "<p>The folder for messages classified as unsure (probably spam) is <i>" )
              + mSpamRulesPage->selectedUnsureFolderName() + "</i>.</p>";
      }
    }

    // Manual classification via toolbar icon / manually applied filter action
    sortFilterOnExistance( i18n( "Classify as spam" ),
                            newFilters, replaceFilters );
    sortFilterOnExistance( i18n( "Classify as NOT spam" ),
                            newFilters, replaceFilters );

    // Show the filters in the summary
    if ( !newFilters.isEmpty() )
      text += i18n( "<p>The wizard will create the following filters:<ul>" )
            + newFilters + "</ul></p>";
    if ( !replaceFilters.isEmpty() )
      text += i18n( "<p>The wizard will replace the following filters:<ul>" )
            + replaceFilters + "</ul></p>";
  }

  mSummaryPage->setSummaryText( text );
}


int AntiSpamWizard::checkForProgram( const TQString &executable )
{
  kdDebug(5006) << "Testing for executable:" << executable << endl;
  TDEProcess process;
  process << executable;
  process.setUseShell( true );
  process.start( TDEProcess::Block );
  return process.exitStatus();
}


bool AntiSpamWizard::anyVirusOptionChecked()
{
  return ( mVirusRulesPage->moveRulesSelected()
           || mVirusRulesPage->pipeRulesSelected() );
}


const TQString AntiSpamWizard::uniqueNameFor( const TQString & name )
{
  return KMKernel::self()->filterMgr()->createUniqueName( name );
}


void AntiSpamWizard::sortFilterOnExistance(
        const TQString & intendedFilterName,
        TQString & newFilters, TQString & replaceFilters )
{
  if ( uniqueNameFor( intendedFilterName ) == intendedFilterName )
    newFilters += "<li>" + intendedFilterName + "</li>";
  else
    replaceFilters += "<li>" + intendedFilterName + "</li>";
}


//---------------------------------------------------------------------------
AntiSpamWizard::SpamToolConfig::SpamToolConfig( TQString toolId,
      int configVersion, int prio, TQString name, TQString exec,
      TQString url, TQString filter, TQString detection, TQString spam, TQString ham,
      TQString header, TQString pattern, TQString pattern2, TQString serverPattern,
      bool detectionOnly, bool regExp, bool bayesFilter, bool tristateDetection,
      WizardMode type )
  : mId( toolId ), mVersion( configVersion ), mPrio( prio ),
    mVisibleName( name ), mExecutable( exec ), mWhatsThisText( url ),
    mFilterName( filter ), mDetectCmd( detection ), mSpamCmd( spam ),
    mHamCmd( ham ), mDetectionHeader( header ), mDetectionPattern( pattern ),
    mDetectionPattern2( pattern2 ), mServerPattern( serverPattern ),
    mDetectionOnly( detectionOnly ),
    mUseRegExp( regExp ), mSupportsBayesFilter( bayesFilter ),
    mSupportsUnsure( tristateDetection ), mType( type )
{
}


bool AntiSpamWizard::SpamToolConfig::isServerBased() const
{
  return !mServerPattern.isEmpty();
}


//---------------------------------------------------------------------------
AntiSpamWizard::ConfigReader::ConfigReader( WizardMode mode,
                                            TQValueList<SpamToolConfig> & configList )
  : mToolList( configList ),
    mMode( mode )
{
  if ( mMode == AntiSpam )
    mConfig = new TDEConfig( "kmail.antispamrc", true );
  else
    mConfig = new TDEConfig( "kmail.antivirusrc", true );
}

AntiSpamWizard::ConfigReader::~ConfigReader( )
{
  delete mConfig;
}


void AntiSpamWizard::ConfigReader::readAndMergeConfig()
{
  TQString groupName = ( mMode == AntiSpam )
                      ? TQString("Spamtool #%1")
                      : TQString("Virustool #%1");
  // read the configuration from the global config file
  mConfig->setReadDefaults( true );
  TDEConfigGroup general( mConfig, "General" );
  int registeredTools = general.readNumEntry( "tools", 0 );
  for (int i = 1; i <= registeredTools; i++)
  {
    TDEConfigGroup toolConfig( mConfig, groupName.arg( i ) );
    if( !toolConfig.readBoolEntry( "HeadersOnly", false ) )
      mToolList.append( readToolConfig( toolConfig ) );
  }

  // read the configuration from the user config file
  // and merge newer config data
  mConfig->setReadDefaults( false );
  TDEConfigGroup user_general( mConfig, "General" );
  int user_registeredTools = user_general.readNumEntry( "tools", 0 );
  for (int i = 1; i <= user_registeredTools; i++)
  {
    TDEConfigGroup toolConfig( mConfig, groupName.arg( i ) );
    if( !toolConfig.readBoolEntry( "HeadersOnly", false ) )
      mergeToolConfig( readToolConfig( toolConfig ) );
  }
  // Make sure to have add least one tool listed even when the
  // config file was not found or whatever went wrong
  // Currently only works for spam tools
  if ( mMode == AntiSpam ) {
    if ( registeredTools < 1 && user_registeredTools < 1 )
      mToolList.append( createDummyConfig() );
    sortToolList();
  }
}


AntiSpamWizard::SpamToolConfig
    AntiSpamWizard::ConfigReader::readToolConfig( TDEConfigGroup & configGroup )
{
  TQString id = configGroup.readEntry( "Ident" );
  int version = configGroup.readNumEntry( "Version" );
#ifndef NDEBUG
  kdDebug(5006) << "Found predefined tool: " << id << endl;
  kdDebug(5006) << "With config version  : " << version << endl;
#endif
  int prio = configGroup.readNumEntry( "Priority", 1 );
  TQString name = configGroup.readEntry( "VisibleName" );
  TQString executable = configGroup.readEntry( "Executable" );
  TQString url = configGroup.readEntry( "URL" );
  TQString filterName = configGroup.readEntry( "PipeFilterName" );
  TQString detectCmd = configGroup.readEntry( "PipeCmdDetect" );
  TQString spamCmd = configGroup.readEntry( "ExecCmdSpam" );
  TQString hamCmd = configGroup.readEntry( "ExecCmdHam" );
  TQString header = configGroup.readEntry( "DetectionHeader" );
  TQString pattern = configGroup.readEntry( "DetectionPattern" );
  TQString pattern2 = configGroup.readEntry( "DetectionPattern2" );
  TQString serverPattern = configGroup.readEntry( "ServerPattern" );
  bool detectionOnly = configGroup.readBoolEntry( "DetectionOnly", false );
  bool useRegExp = configGroup.readBoolEntry( "UseRegExp" );
  bool supportsBayes = configGroup.readBoolEntry( "SupportsBayes", false );
  bool supportsUnsure = configGroup.readBoolEntry( "SupportsUnsure", false );
  return SpamToolConfig( id, version, prio, name, executable, url,
                         filterName, detectCmd, spamCmd, hamCmd,
                         header, pattern, pattern2, serverPattern,
                         detectionOnly, useRegExp,
                         supportsBayes, supportsUnsure, mMode );
}


AntiSpamWizard::SpamToolConfig AntiSpamWizard::ConfigReader::createDummyConfig()
{
  return SpamToolConfig( "spamassassin", 0, 1,
                        "SpamAssassin", "spamassassin -V",
                        "http://spamassassin.org", "SpamAssassin Check",
                        "spamassassin -L",
                        "sa-learn -L --spam --no-rebuild --single",
                        "sa-learn -L --ham --no-rebuild --single",
                        "X-Spam-Flag", "yes", "", "",
                        false, false, true, false, AntiSpam );
}


void AntiSpamWizard::ConfigReader::mergeToolConfig( AntiSpamWizard::SpamToolConfig config )
{
  bool found = false;
  for ( TQValueListIterator<SpamToolConfig> it = mToolList.begin();
        it != mToolList.end(); ++it ) {
#ifndef NDEBUG
    kdDebug(5006) << "Check against tool: " << (*it).getId() << endl;
    kdDebug(5006) << "Against version   : " << (*it).getVersion() << endl;
#endif
    if ( (*it).getId() == config.getId() )
    {
      found = true;
      if ( (*it).getVersion() < config.getVersion() )
      {
#ifndef NDEBUG
        kdDebug(5006) << "Replacing config ..." << endl;
#endif
        mToolList.remove( it );
        mToolList.append( config );
      }
      break;
    }
  }
  if ( !found )
    mToolList.append( config );
}


void AntiSpamWizard::ConfigReader::sortToolList()
{
  TQValueList<SpamToolConfig> tmpList;
  SpamToolConfig config;

  while ( !mToolList.isEmpty() ) {
    TQValueListIterator<SpamToolConfig> highest;
    int priority = 0; // ascending
    for ( TQValueListIterator<SpamToolConfig> it = mToolList.begin();
          it != mToolList.end(); ++it ) {
      if ( (*it).getPrio() > priority ) {
        priority = (*it).getPrio();
        highest = it;
      }
    }
    config = (*highest);
    tmpList.append( config );
    mToolList.remove( highest );
  }
  for ( TQValueListIterator<SpamToolConfig> it = tmpList.begin();
        it != tmpList.end(); ++it ) {
    mToolList.append( (*it) );
  }
}


//---------------------------------------------------------------------------
ASWizPage::ASWizPage( TQWidget * parent, const char * name,
                      const TQString *bannerName )
  : TQWidget( parent, name )
{
  TQString banner = "kmwizard.png";
  if ( bannerName && !bannerName->isEmpty() )
    banner = *bannerName;

  mLayout = new TQHBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
  mBannerLabel = new TQLabel( this );
  mBannerLabel->setPixmap( UserIcon(banner) );
  mBannerLabel->setScaledContents( false );
  mBannerLabel->setFrameShape( TQFrame::StyledPanel );
  mBannerLabel->setFrameShadow( TQFrame::Sunken );

  mLayout->addWidget( mBannerLabel );
  mLayout->addItem( new TQSpacerItem( 5, 5, TQSizePolicy::Minimum, TQSizePolicy::Expanding ) );
}


//---------------------------------------------------------------------------
ASWizInfoPage::ASWizInfoPage( AntiSpamWizard::WizardMode mode,
                              TQWidget * parent, const char * name )
  : ASWizPage( parent, name )
{
  TQBoxLayout * layout = new TQVBoxLayout( mLayout );

  mIntroText = new TQLabel( this );
  mIntroText->setText(
    ( mode == AntiSpamWizard::AntiSpam )
    ? i18n(
      "The wizard will search for any tools to do spam detection\n"
      "and setup KMail to work with them."
      )
    : i18n(
      "<p>Here you can get some assistance in setting up KMail's filter "
      "rules to use some commonly-known anti-virus tools.</p>"
      "<p>The wizard can detect those tools on your computer as "
      "well as create filter rules to classify messages using these "
      "tools and to separate messages containing viruses. "
      "The wizard will not take any existing filter "
      "rules into consideration: it will always append the new rules.</p>"
      "<p><b>Warning:</b> As KMail appears to be frozen during the scan of the "
      "messages for viruses, you may encounter problems with "
      "the responsiveness of KMail because anti-virus tool "
      "operations are usually time consuming; please consider "
      "deleting the filter rules created by the wizard to get "
      "back to the former behavior."
      ) );
  layout->addWidget( mIntroText );

  mScanProgressText = new TQLabel( this );
  mScanProgressText->setText( "" ) ;
  layout->addWidget( mScanProgressText );

  mToolsList = new TDEListBox( this );
  mToolsList->hide();
  mToolsList->setSelectionMode( TQListBox::Multi );
  mToolsList->setRowMode( TQListBox::FixedNumber );
  mToolsList->setRowMode( 10 );
  layout->addWidget( mToolsList );
  connect( mToolsList, TQ_SIGNAL(selectionChanged()),
           this, TQ_SLOT(processSelectionChange(void)) );

  mSelectionHint = new TQLabel( this );
  mSelectionHint->setText( "" );
  layout->addWidget( mSelectionHint );

  layout->addStretch();
}


void ASWizInfoPage::setScanProgressText( const TQString &toolName )
{
  mScanProgressText->setText( toolName );
}


void ASWizInfoPage::addAvailableTool( const TQString &visibleName )
{
  TQString listName = visibleName;
  mToolsList->insertItem( listName );
  if ( !mToolsList->isVisible() )
  {
    mToolsList->show();
    mToolsList->setSelected( 0, true );
    mSelectionHint->setText( i18n("<p>Please select the tools to be used "
                                  "for the detection and go "
                                  "to the next page.</p>") );
  }
}

bool ASWizInfoPage::isProgramSelected( const TQString &visibleName )
{
  TQString listName = visibleName;
  return mToolsList->isSelected( mToolsList->findItem( listName ) );
}


void ASWizInfoPage::processSelectionChange()
{
  emit selectionChanged();
}


//---------------------------------------------------------------------------
ASWizSpamRulesPage::ASWizSpamRulesPage( TQWidget * parent, const char * name,
                                        KMFolderTree * mainFolderTree )
  : ASWizPage( parent, name )
{
  TQVBoxLayout *layout = new TQVBoxLayout( mLayout );

  mMarkRules = new TQCheckBox( i18n("&Mark detected spam messages as read"), this );
  TQWhatsThis::add( mMarkRules,
      i18n( "Mark messages which have been classified as spam as read.") );
  layout->addWidget( mMarkRules);

  mMoveSpamRules = new TQCheckBox( i18n("Move &known spam to:"), this );
  TQWhatsThis::add( mMoveSpamRules,
      i18n( "The default folder for spam messages is the trash folder, "
            "but you may change that in the folder view below.") );
  layout->addWidget( mMoveSpamRules );

  mFolderReqForSpamFolder = new FolderRequester( this, mainFolderTree );
  mFolderReqForSpamFolder->setFolder( "trash" );
  mFolderReqForSpamFolder->setMustBeReadWrite( true );
  mFolderReqForSpamFolder->setShowOutbox( false );
  mFolderReqForSpamFolder->setShowImapFolders( false );

  TQHBoxLayout *hLayout1 = new TQHBoxLayout( layout );
  hLayout1->addSpacing( KDialog::spacingHint() * 3 );
  hLayout1->addWidget( mFolderReqForSpamFolder );

  mMoveUnsureRules = new TQCheckBox( i18n("Move &probable spam to:"), this );
  TQWhatsThis::add( mMoveUnsureRules,
      i18n( "The default folder is the inbox folder, but you may change that "
            "in the folder view below.<p>"
            "Not all tools support a classification as unsure. If you haven't "
            "selected a capable tool, you can't select a folder as well.") );
  layout->addWidget( mMoveUnsureRules );

  mFolderReqForUnsureFolder = new FolderRequester( this, mainFolderTree );
  mFolderReqForUnsureFolder->setFolder( "inbox" );
  mFolderReqForUnsureFolder->setMustBeReadWrite( true );
  mFolderReqForUnsureFolder->setShowOutbox( false );
  mFolderReqForUnsureFolder->setShowImapFolders( false );

  TQHBoxLayout *hLayout2 = new TQHBoxLayout( layout );
  hLayout2->addSpacing( KDialog::spacingHint() * 3 );
  hLayout2->addWidget( mFolderReqForUnsureFolder );

  layout->addStretch();

  connect( mMarkRules, TQ_SIGNAL(clicked()),
            this, TQ_SLOT(processSelectionChange(void)) );
  connect( mMoveSpamRules, TQ_SIGNAL(clicked()),
            this, TQ_SLOT(processSelectionChange(void)) );
  connect( mMoveUnsureRules, TQ_SIGNAL(clicked()),
            this, TQ_SLOT(processSelectionChange(void)) );
  connect( mFolderReqForSpamFolder, TQ_SIGNAL(folderChanged(KMFolder*)),
            this, TQ_SLOT(processSelectionChange(KMFolder*)) );
  connect( mFolderReqForUnsureFolder, TQ_SIGNAL(folderChanged(KMFolder*)),
            this, TQ_SLOT(processSelectionChange(KMFolder*)) );

  mMarkRules->setChecked( true );
  mMoveSpamRules->setChecked( true );
}


bool ASWizSpamRulesPage::markAsReadSelected() const
{
  return mMarkRules->isChecked();
}


bool ASWizSpamRulesPage::moveSpamSelected() const
{
  return mMoveSpamRules->isChecked();
}


bool ASWizSpamRulesPage::moveUnsureSelected() const
{
  return mMoveUnsureRules->isChecked();
}


TQString ASWizSpamRulesPage::selectedSpamFolderName() const
{
  TQString name = "trash";
  if ( mFolderReqForSpamFolder->folder() )
    name = mFolderReqForSpamFolder->folder()->idString();
  return name;
}


TQString ASWizSpamRulesPage::selectedUnsureFolderName() const
{
  TQString name = "inbox";
  if ( mFolderReqForUnsureFolder->folder() )
    name = mFolderReqForUnsureFolder->folder()->idString();
  return name;
}


void ASWizSpamRulesPage::processSelectionChange()
{
  mFolderReqForSpamFolder->setEnabled( mMoveSpamRules->isChecked() );
  mFolderReqForUnsureFolder->setEnabled( mMoveUnsureRules->isChecked() );
  emit selectionChanged();
}


void ASWizSpamRulesPage::processSelectionChange( KMFolder* )
{
  processSelectionChange();
}


void ASWizSpamRulesPage::allowUnsureFolderSelection( bool enabled )
{
  mMoveUnsureRules->setEnabled( enabled );
  mMoveUnsureRules->setShown( enabled );
  mFolderReqForUnsureFolder->setEnabled( enabled );
  mFolderReqForUnsureFolder->setShown( enabled );
}


//---------------------------------------------------------------------------
ASWizVirusRulesPage::ASWizVirusRulesPage( TQWidget * parent, const char * name,
                                  KMFolderTree * mainFolderTree )
  : ASWizPage( parent, name )
{
  TQGridLayout *grid = new TQGridLayout( mLayout, 5, 1, KDialog::spacingHint() );

  mPipeRules = new TQCheckBox( i18n("Check messages using the anti-virus tools"), this );
  TQWhatsThis::add( mPipeRules,
      i18n( "Let the anti-virus tools check your messages. The wizard "
            "will create appropriate filters. The messages are usually "
            "marked by the tools so that following filters can react "
            "on this and, for example, move virus messages to a special folder.") );
  grid->addWidget( mPipeRules, 0, 0 );

  mMoveRules = new TQCheckBox( i18n("Move detected viral messages to the selected folder"), this );
  TQWhatsThis::add( mMoveRules,
      i18n( "A filter to detect messages classified as virus-infected and to move "
            "those messages into a predefined folder is created. The "
            "default folder is the trash folder, but you may change that "
            "in the folder view.") );
  grid->addWidget( mMoveRules, 1, 0 );

  mMarkRules = new TQCheckBox( i18n("Additionally, mark detected viral messages as read"), this );
  mMarkRules->setEnabled( false );
  TQWhatsThis::add( mMarkRules,
      i18n( "Mark messages which have been classified as "
            "virus-infected as read, as well as moving them "
            "to the selected folder.") );
  grid->addWidget( mMarkRules, 2, 0 );

  TQString s = "trash";
  mFolderTree = new SimpleFolderTree( this, mainFolderTree, s, true );
  grid->addWidget( mFolderTree, 3, 0 );

  connect( mPipeRules, TQ_SIGNAL(clicked()),
            this, TQ_SLOT(processSelectionChange(void)) );
  connect( mMoveRules, TQ_SIGNAL(clicked()),
            this, TQ_SLOT(processSelectionChange(void)) );
  connect( mMarkRules, TQ_SIGNAL(clicked()),
            this, TQ_SLOT(processSelectionChange(void)) );
  connect( mMoveRules, TQ_SIGNAL( toggled( bool ) ),
           mMarkRules, TQ_SLOT( setEnabled( bool ) ) );
}

bool ASWizVirusRulesPage::pipeRulesSelected() const
{
  return mPipeRules->isChecked();
}


bool ASWizVirusRulesPage::moveRulesSelected() const
{
  return mMoveRules->isChecked();
}

bool ASWizVirusRulesPage::markReadRulesSelected() const
{
  return mMarkRules->isChecked();
}


TQString ASWizVirusRulesPage::selectedFolderName() const
{
  TQString name = "trash";
  if ( mFolderTree->folder() )
    name = mFolderTree->folder()->idString();
  return name;
}

void ASWizVirusRulesPage::processSelectionChange()
{
  emit selectionChanged();
}


//---------------------------------------------------------------------------
ASWizSummaryPage::ASWizSummaryPage( TQWidget * parent, const char * name )
  : ASWizPage( parent, name )
{
  TQBoxLayout * layout = new TQVBoxLayout( mLayout );

  mSummaryText = new TQLabel( this );
  layout->addWidget( mSummaryText );
  layout->addStretch();
}


void ASWizSummaryPage::setSummaryText( const TQString & text )
{
  mSummaryText->setText( text );
}


#include "antispamwizard.moc"
