/*
 * kmail: KDE mail client
 * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "kmfilter.h"
#include "kmkernel.h"
#include "accountmanager.h"
using KMail::AccountManager;
#include "kmacctimap.h"
#include "kmfilteraction.h"
#include "kmglobal.h"
#include "filterlog.h"
using KMail::FilterLog;

#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kdebug.h>
#include <tdeconfig.h>

#include <assert.h>


KMFilter::KMFilter( TDEConfig* aConfig, bool popFilter )
  : bPopFilter(popFilter)
{
  if (!bPopFilter)
    mActions.setAutoDelete( true );

  if ( aConfig )
    readConfig( aConfig );
  else if ( bPopFilter )
    mAction = Down;
  else {
    bApplyOnInbound = true;
    bApplyOnOutbound = false;
    bApplyOnExplicit = true;
    bStopProcessingHere = true;
    bConfigureShortcut = false;
    bConfigureToolbar = false;
    bAutoNaming = true;
    mApplicability = All;
  }
}


KMFilter::KMFilter( const KMFilter & aFilter )
{
  bPopFilter = aFilter.isPopFilter();

  if ( !bPopFilter )
    mActions.setAutoDelete( true );

  mPattern = aFilter.mPattern;

  if ( bPopFilter ){
    mAction = aFilter.mAction;
  } else {
    bApplyOnInbound = aFilter.applyOnInbound();
    bApplyOnOutbound = aFilter.applyOnOutbound();
    bApplyOnExplicit = aFilter.applyOnExplicit();
    bStopProcessingHere = aFilter.stopProcessingHere();
    bConfigureShortcut = aFilter.configureShortcut();
    bConfigureToolbar = aFilter.configureToolbar();
    mApplicability = aFilter.applicability();
    mIcon = aFilter.icon();
    mShortcut = aFilter.shortcut();

    TQPtrListIterator<KMFilterAction> it( aFilter.mActions );
    for ( it.toFirst() ; it.current() ; ++it ) {
      KMFilterActionDesc *desc = (*kmkernel->filterActionDict())[ (*it)->name() ];
      if ( desc ) {
        KMFilterAction *f = desc->create();
        if ( f ) {
          f->argsFromString( (*it)->argsAsString() );
          mActions.append( f );
        }
      }
    }

    mAccounts.clear();
    TQValueListConstIterator<int> it2;
    for ( it2 = aFilter.mAccounts.begin() ; it2 != aFilter.mAccounts.end() ; ++it2 )
      mAccounts.append( *it2 );
  }
}

// only for !bPopFilter
KMFilter::ReturnCode KMFilter::execActions( KMMessage* msg, bool& stopIt ) const
{
  ReturnCode status = NoResult;

  TQPtrListIterator<KMFilterAction> it( mActions );
  for ( it.toFirst() ; it.current() ; ++it ) {

    if ( FilterLog::instance()->isLogging() ) {
      TQString logText( i18n( "<b>Applying filter action:</b> %1" )
                       .arg( (*it)->displayString() ) );
      FilterLog::instance()->add( logText, FilterLog::appliedAction );
    }

    KMFilterAction::ReturnCode result = (*it)->process( msg );

    switch ( result ) {
    case KMFilterAction::CriticalError:
      if ( FilterLog::instance()->isLogging() ) {
        TQString logText = TQString( "<font color=#FF0000>%1</font>" )
          .arg( i18n( "A critical error occurred. Processing stops here." ) );
        FilterLog::instance()->add( logText, FilterLog::appliedAction );
      }
      // in case it's a critical error: return immediately!
      return CriticalError;
    case KMFilterAction::ErrorButGoOn:
      if ( FilterLog::instance()->isLogging() ) {
        TQString logText = TQString( "<font color=#FF0000>%1</font>" )
          .arg( i18n( "A problem was found while applying this action." ) );
        FilterLog::instance()->add( logText, FilterLog::appliedAction );
      }
    default:
      break;
    }
  }

  if ( status == NoResult ) // No filters matched, keep copy of message
    status = GoOn;

  stopIt = stopProcessingHere();

  return status;
}

bool KMFilter::requiresBody( KMMsgBase* msg )
{
  if (pattern() && pattern()->requiresBody())
    return true; // no pattern means always matches?
  TQPtrListIterator<KMFilterAction> it( *actions() );
  for ( it.toFirst() ; it.current() ; ++it )
    if ((*it)->requiresBody( msg ))
      return true;
  return false;
}

/** No descriptions */
// only for bPopFilter
void KMFilter::setAction(const KMPopFilterAction aAction)
{
  mAction = aAction;
}

// only for bPopFilter
KMPopFilterAction KMFilter::action()
{
  return mAction;
}

// only for !bPopFilter
bool KMFilter::folderRemoved( KMFolder* aFolder, KMFolder* aNewFolder )
{
  bool rem = false;

  TQPtrListIterator<KMFilterAction> it( mActions );
  for ( it.toFirst() ; it.current() ; ++it )
    if ( (*it)->folderRemoved( aFolder, aNewFolder ) )
      rem = true;

  return rem;
}

void KMFilter::setApplyOnAccount( uint id, bool aApply )
{
  if (aApply && !mAccounts.contains( id )) {
    mAccounts.append( id );
  } else if (!aApply && mAccounts.contains( id )) {
    mAccounts.remove( id );
  }
}

bool KMFilter::applyOnAccount( uint id ) const
{
  if ( applicability() == All )
    return true;
  if ( applicability() == ButImap ) {
    KMAccount *account = kmkernel->acctMgr()->find( id );
    bool result =  account && !dynamic_cast<KMAcctImap*>(account);
    return result;
  }
  if ( applicability() == Checked )
    return mAccounts.contains( id );

  return false;
}


//-----------------------------------------------------------------------------
void KMFilter::readConfig(TDEConfig* config)
{
  // MKSearchPattern::readConfig ensures
  // that the pattern is purified.
  mPattern.readConfig(config);

  if (bPopFilter) {
    // get the action description...
    TQString action = config->readEntry( "action" );
    if ( action == "down" )
      mAction = Down;
    else if ( action == "later" )
      mAction = Later;
    else if ( action == "delete" )
      mAction = Delete;
    else
      mAction = NoAction;
  }
  else {
    TQStringList sets = config->readListEntry("apply-on");
    if ( sets.isEmpty() && !config->hasKey("apply-on") ) {
      bApplyOnOutbound = false;
      bApplyOnInbound = true;
      bApplyOnExplicit = true;
      mApplicability = ButImap;
    } else {
      bApplyOnInbound = bool(sets.contains("check-mail"));
      bApplyOnOutbound = bool(sets.contains("send-mail"));
      bApplyOnExplicit = bool(sets.contains("manual-filtering"));
      mApplicability = (AccountType)config->readNumEntry( "Applicability", ButImap );
    }

    bStopProcessingHere = config->readBoolEntry("StopProcessingHere", true);
    bConfigureShortcut = config->readBoolEntry("ConfigureShortcut", false);
    TQString shortcut( config->readEntry( "Shortcut" ) );
    if ( !shortcut.isEmpty() ) {
      TDEShortcut sc( shortcut );
      setShortcut( sc );
    }
    bConfigureToolbar = config->readBoolEntry("ConfigureToolbar", false);
    bConfigureToolbar = bConfigureToolbar && bConfigureShortcut;
    mIcon = config->readEntry( "Icon", "gear" );
    bAutoNaming = config->readBoolEntry("AutomaticName", false);

    int i, numActions;
    TQString actName, argsName;

    mActions.clear();

    numActions = config->readNumEntry("actions",0);
    if (numActions > FILTER_MAX_ACTIONS) {
      numActions = FILTER_MAX_ACTIONS ;
      KMessageBox::information( 0, i18n("<qt>Too many filter actions in filter rule <b>%1</b>.</qt>").arg( mPattern.name() ) );
    }

    for ( i=0 ; i < numActions ; i++ ) {
      actName.sprintf("action-name-%d", i);
      argsName.sprintf("action-args-%d", i);
      // get the action description...
      KMFilterActionDesc *desc = (*kmkernel->filterActionDict())[ config->readEntry( actName ) ];
      if ( desc ) {
        //...create an instance...
        KMFilterAction *fa = desc->create();
        if ( fa ) {
          //...load it with it's parameter...
          fa->argsFromString( config->readEntry( argsName ) );
          //...check if it's emoty and...
          if ( !fa->isEmpty() )
            //...append it if it's not and...
            mActions.append( fa );
          else
            //...delete is else.
            delete fa;
        }
      } else
        KMessageBox::information( 0 /* app-global modal dialog box */,
                                  i18n("<qt>Unknown filter action <b>%1</b><br>in filter rule <b>%2</b>.<br>Ignoring it.</qt>")
                                       .arg( config->readEntry( actName ) ).arg( mPattern.name() ) );
    }

    mAccounts = config->readIntListEntry( "accounts-set" );
  }
}


void KMFilter::writeConfig(TDEConfig* config) const
{
  mPattern.writeConfig(config);

  if (bPopFilter) {
    switch ( mAction ) {
    case Down:
      config->writeEntry( "action", "down" );
      break;
    case Later:
      config->writeEntry( "action", "later" );
      break;
    case Delete:
      config->writeEntry( "action", "delete" );
      break;
    default:
      config->writeEntry( "action", "" );
    }
  } else {
    TQStringList sets;
    if ( bApplyOnInbound )
      sets.append( "check-mail" );
    if ( bApplyOnOutbound )
      sets.append( "send-mail" );
    if ( bApplyOnExplicit )
      sets.append( "manual-filtering" );
    config->writeEntry( "apply-on", sets );

    config->writeEntry( "StopProcessingHere", bStopProcessingHere );
    config->writeEntry( "ConfigureShortcut", bConfigureShortcut );
    if ( !mShortcut.isNull() )
      config->writeEntry( "Shortcut", mShortcut.toString() );
    config->writeEntry( "ConfigureToolbar", bConfigureToolbar );
    config->writeEntry( "Icon", mIcon );
    config->writeEntry( "AutomaticName", bAutoNaming );
    config->writeEntry( "Applicability", mApplicability );

    TQString key;
    int i;

    TQPtrListIterator<KMFilterAction> it( mActions );
    for ( i=0, it.toFirst() ; it.current() ; ++it, ++i ) {
      config->writeEntry( key.sprintf("action-name-%d", i),
                          (*it)->name() );
      config->writeEntry( key.sprintf("action-args-%d", i),
                          (*it)->argsAsString() );
    }
    config->writeEntry( "actions", i );
    config->writeEntry( "accounts-set", mAccounts );
  }
}

void KMFilter::purify()
{
  mPattern.purify();

  if (!bPopFilter) {
    TQPtrListIterator<KMFilterAction> it( mActions );
    it.toLast();
    while ( it.current() )
      if ( (*it)->isEmpty() )
        mActions.remove ( (*it) );
      else
        --it;

    // Remove invalid accounts from mAccounts - just to be tidy
    TQValueListIterator<int> it2 = mAccounts.begin();
    while ( it2 != mAccounts.end() ) {
      if ( !kmkernel->acctMgr()->find( *it2 ) )
         it2 = mAccounts.remove( it2 );
      else
         ++it2;
    }
  }
}

bool KMFilter::isEmpty() const
{
  if (bPopFilter)
    return mPattern.isEmpty();
  else
    return mPattern.isEmpty() && mActions.isEmpty() && mAccounts.isEmpty();
}

#ifndef NDEBUG
const TQString KMFilter::asString() const
{
  TQString result;

  result += mPattern.asString();

  if (bPopFilter){
    result += "    action: ";
    result += mAction;
    result += "\n";
  }
  else {
    TQPtrListIterator<KMFilterAction> it( mActions );
    for ( it.toFirst() ; it.current() ; ++it ) {
      result += "    action: ";
      result += (*it)->label();
      result += " ";
      result += (*it)->argsAsString();
      result += "\n";
    }
    result += "This filter belongs to the following sets:";
    if ( bApplyOnInbound )
      result += " Inbound";
    if ( bApplyOnOutbound )
      result += " Outbound";
    if ( bApplyOnExplicit )
      result += " Explicit";
    result += "\n";
    if ( bApplyOnInbound && mApplicability == All ) {
      result += "This filter applies to all accounts.\n";
    } else if ( bApplyOnInbound && mApplicability == ButImap ) {
      result += "This filter applies to all but online IMAP accounts.\n";
    } else if ( bApplyOnInbound ) {
      TQValueListConstIterator<int> it2;
      result += "This filter applies to the following accounts:";
      if ( mAccounts.isEmpty() )
        result += " None";
      else for ( it2 = mAccounts.begin() ; it2 != mAccounts.end() ; ++it2 )
        if ( kmkernel->acctMgr()->find( *it2 ) )
          result += " " + kmkernel->acctMgr()->find( *it2 )->name();
      result += "\n";
    }
    if ( bStopProcessingHere )
      result += "If it matches, processing stops at this filter.\n";
  }
  return result;
}
#endif
