// kmfiltermgr.cpp

// my header
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "kmfiltermgr.h"

// other kmail headers
#include "filterlog.h"
using KMail::FilterLog;
#include "kmfilterdlg.h"
#include "kmfolderindex.h"
#include "filterimporterexporter.h"
using KMail::FilterImporterExporter;
#include "kmfoldermgr.h"
#include "kmmsgdict.h"
#include "messageproperty.h"
using KMail::MessageProperty;

// other KDE headers
#include <kdebug.h>
#include <tdelocale.h>
#include <tdeconfig.h>

// other TQt headers
#include <tqregexp.h>
#include <tqvaluevector.h>

// other headers
#include <assert.h>


//-----------------------------------------------------------------------------
KMFilterMgr::KMFilterMgr( bool popFilter )
  : mEditDialog( 0 ),
    bPopFilter( popFilter ),
    mShowLater( false ),
    mDirtyBufferedFolderTarget( true ),
    mBufferedFolderTarget( true ),
    mRefCount( 0 )
{
  connect( kmkernel, TQ_SIGNAL( folderRemoved( KMFolder* ) ),
           this, TQ_SLOT( slotFolderRemoved( KMFolder* ) ) );
}


//-----------------------------------------------------------------------------
KMFilterMgr::~KMFilterMgr()
{
  deref( true );
  writeConfig( false );
  clear();
}

void KMFilterMgr::clear()
{
  mDirtyBufferedFolderTarget = true;
  for ( TQValueListIterator<KMFilter*> it = mFilters.begin() ;
        it != mFilters.end() ; ++it ) {
    delete *it;
  }
}

//-----------------------------------------------------------------------------
void KMFilterMgr::readConfig(void)
{
  TDEConfig* config = KMKernel::config();
  clear();
  
  if (bPopFilter) {
    TDEConfigGroupSaver saver(config, "General");
    mShowLater = config->readNumEntry("popshowDLmsgs",0);
  } 
  mFilters = FilterImporterExporter::readFiltersFromConfig( config, bPopFilter );
}

//-----------------------------------------------------------------------------
void KMFilterMgr::writeConfig(bool withSync)
{
  TDEConfig* config = KMKernel::config();

  // Now, write out the new stuff:  
  FilterImporterExporter::writeFiltersToConfig( mFilters, config, bPopFilter );
  TDEConfigGroupSaver saver(config, "General");
  if (bPopFilter)
      config->writeEntry("popshowDLmsgs", mShowLater);

  if (withSync) config->sync();
}

int KMFilterMgr::processPop( KMMessage * msg ) const {
  for ( TQValueListConstIterator<KMFilter*> it = mFilters.constBegin();
        it != mFilters.constEnd() ; ++it )
    if ( (*it)->pattern()->matches( msg ) )
      return (*it)->action();
  return NoAction;
}

bool KMFilterMgr::beginFiltering(KMMsgBase *msgBase) const
{
  if (MessageProperty::filtering( msgBase ))
    return false;
  MessageProperty::setFiltering( msgBase, true );
  MessageProperty::setFilterFolder( msgBase, 0 );
  if ( FilterLog::instance()->isLogging() ) {
    FilterLog::instance()->addSeparator();
  }
  return true;
}

int KMFilterMgr::moveMessage(KMMessage *msg) const
{
  if (MessageProperty::filterFolder(msg)->moveMsg( msg ) == 0) {
    if ( kmkernel->folderIsTrash( MessageProperty::filterFolder( msg )))
      KMFilterAction::sendMDN( msg, KMime::MDN::Deleted );
  } else {
    kdDebug(5006) << "KMfilterAction - couldn't move msg" << endl;
    return 2;
  }
  return 0;
}

void KMFilterMgr::endFiltering(KMMsgBase *msgBase) const
{
  KMFolder *parent = msgBase->parent();
  if ( parent ) {
    if ( parent == MessageProperty::filterFolder( msgBase ) ) {
      parent->take( parent->find( msgBase ) );
    }
    else if ( ! MessageProperty::filterFolder( msgBase ) ) {
      int index = parent->find( msgBase );
      KMMessage *msg = parent->getMsg( index );
      parent->take( index );
      parent->addMsgKeepUID( msg );
    }
  }
  MessageProperty::setFiltering( msgBase, false );
}

int KMFilterMgr::process( KMMessage * msg, const KMFilter * filter ) {
  if ( !msg || !filter || !beginFiltering( msg ))
    return 1;
  bool stopIt = false;
  int result = 1;

  if ( FilterLog::instance()->isLogging() ) {
    TQString logText( i18n( "<b>Evaluating filter rules:</b> " ) );
    logText.append( filter->pattern()->asString() );
    FilterLog::instance()->add( logText, FilterLog::patternDesc );
  }

  if (filter->pattern()->matches( msg )) {
    if ( FilterLog::instance()->isLogging() ) {
      FilterLog::instance()->add( i18n( "<b>Filter rules have matched.</b>" ),
                                  FilterLog::patternResult );
    }
    if (filter->execActions( msg, stopIt ) == KMFilter::CriticalError)
      return 2;

    KMFolder *folder = MessageProperty::filterFolder( msg );

    endFiltering( msg );
    if (folder) {
      tempOpenFolder( folder );
      result = folder->moveMsg( msg );
    }
  } else {
    endFiltering( msg );
    result = 1;
  }
  return result;
}

int KMFilterMgr::process( TQ_UINT32 serNum, const KMFilter *filter )
{
  bool stopIt = false;
  int result = 1;

  if ( !filter )
    return 1;

  if ( isMatching( serNum, filter ) ) {
    KMFolder *folder = 0;
    int idx = -1;
    // get the message with the serNum
    KMMsgDict::instance()->getLocation( serNum, &folder, &idx );
    if ( !folder || ( idx == -1 ) || ( idx >= folder->count() ) ) {
      return 1;
    }
    KMFolderOpener openFolder(folder, "filtermgr");
    KMMsgBase *msgBase = folder->getMsgBase( idx );
    bool unGet = !msgBase->isMessage();
    KMMessage *msg = folder->getMsg( idx );
    // do the actual filtering stuff
    if ( !msg || !beginFiltering( msg ) ) {
      if ( unGet )
        folder->unGetMsg( idx );
      return 1;
    }
    if ( filter->execActions( msg, stopIt ) == KMFilter::CriticalError ) {
      if ( unGet )
        folder->unGetMsg( idx );
      return 2;
    }

    KMFolder *targetFolder = MessageProperty::filterFolder( msg );

    endFiltering( msg );
    if ( targetFolder ) {
      tempOpenFolder( targetFolder );
      msg->setTransferInProgress( false );
      result = targetFolder->moveMsg( msg );
      msg->setTransferInProgress( true );
    }
    if ( unGet )
      folder->unGetMsg( idx );
  } else {
    result = 1;
  }
  return result;
}

int KMFilterMgr::process( KMMessage * msg, FilterSet set,
			  bool account, uint accountId ) {
  if ( bPopFilter )
    return processPop( msg );

  if ( set == NoSet ) {
    kdDebug(5006) << "KMFilterMgr: process() called with not filter set selected"
		  << endl;
    return 1;
  }

  bool stopIt = false;
  bool atLeastOneRuleMatched = false;

  if (!beginFiltering( msg ))
    return 1;
  for ( TQValueListConstIterator<KMFilter*> it = mFilters.constBegin();
        !stopIt && it != mFilters.constEnd() ; ++it ) {

    if ( ( ( (set&Inbound) && (*it)->applyOnInbound() ) &&
	   ( !account ||
	     ( account && (*it)->applyOnAccount( accountId ) ) ) ) ||
         ( (set&Outbound)  && (*it)->applyOnOutbound() ) ||
         ( (set&Explicit) && (*it)->applyOnExplicit() ) ) {
        // filter is applicable

      if ( FilterLog::instance()->isLogging() ) {
        TQString logText( i18n( "<b>Evaluating filter rules:</b> " ) );
        logText.append( (*it)->pattern()->asString() );
        FilterLog::instance()->add( logText, FilterLog::patternDesc );
      }
      if ( (*it)->pattern()->matches( msg ) ) {
        // filter matches
        if ( FilterLog::instance()->isLogging() ) {
          FilterLog::instance()->add( i18n( "<b>Filter rules have matched.</b>" ),
                                      FilterLog::patternResult );
        }
        atLeastOneRuleMatched = true;
        // execute actions:
        if ( (*it)->execActions(msg, stopIt) == KMFilter::CriticalError )
          return 2;
      }
    }
  }

  KMFolder *folder = MessageProperty::filterFolder( msg );
  /* endFilter does a take() and addButKeepUID() to ensure the changed
   * message is on disk. This is unnessecary if nothing matched, so just
   * reset state and don't update the listview at all. */
  if ( atLeastOneRuleMatched )
    endFiltering( msg );
  else
    MessageProperty::setFiltering( msg, false );
  if (folder) {
    tempOpenFolder( folder );
    folder->moveMsg(msg);
    return 0;
  }
  return 1;
}

bool KMFilterMgr::isMatching( TQ_UINT32 serNum, const KMFilter *filter )
{
  bool result = false;
  if ( FilterLog::instance()->isLogging() ) {
    TQString logText( i18n( "<b>Evaluating filter rules:</b> " ) );
    logText.append( filter->pattern()->asString() );
    FilterLog::instance()->add( logText, FilterLog::patternDesc );
  }
  if ( filter->pattern()->matches( serNum ) ) {
    if ( FilterLog::instance()->isLogging() ) {
      FilterLog::instance()->add( i18n( "<b>Filter rules have matched.</b>" ),
                                  FilterLog::patternResult );
    }
    result = true;
  }
  return result;
}

bool KMFilterMgr::atLeastOneFilterAppliesTo( unsigned int accountID ) const
{
  TQValueListConstIterator<KMFilter*> it = mFilters.constBegin();
  for ( ; it != mFilters.constEnd() ; ++it ) {
    if ( (*it)->applyOnAccount( accountID ) ) {
      return true;
    }
  }
  return false;
}

bool KMFilterMgr::atLeastOneIncomingFilterAppliesTo( unsigned int accountID ) const
{
  TQValueListConstIterator<KMFilter*> it = mFilters.constBegin();
  for ( ; it != mFilters.constEnd() ; ++it ) {
    if ( (*it)->applyOnInbound() && (*it)->applyOnAccount( accountID ) ) {
      return true;
    }
  }
  return false;
}

bool KMFilterMgr::atLeastOneOnlineImapFolderTarget()
{
  if (!mDirtyBufferedFolderTarget)
    return mBufferedFolderTarget;

  mDirtyBufferedFolderTarget = false;

  TQValueListConstIterator<KMFilter*> it = mFilters.constBegin();
  for ( ; it != mFilters.constEnd() ; ++it ) {
    KMFilter *filter = *it;
    TQPtrListIterator<KMFilterAction> jt( *filter->actions() );
    for ( jt.toFirst() ; jt.current() ; ++jt ) {
      KMFilterActionWithFolder *f = dynamic_cast<KMFilterActionWithFolder*>(*jt);
      if (!f)
	continue;
      TQString name = f->argsAsString();
      KMFolder *folder = kmkernel->imapFolderMgr()->findIdString( name );
      if (folder) {
	mBufferedFolderTarget = true;
	return true;
      }
    }
  }
  mBufferedFolderTarget = false;
  return false;
}

//-----------------------------------------------------------------------------
void KMFilterMgr::ref(void)
{
  mRefCount++;
}

//-----------------------------------------------------------------------------
void KMFilterMgr::deref(bool force)
{
  if (!force)
    mRefCount--;
  if (mRefCount < 0)
    mRefCount = 0;
  if (mRefCount && !force)
    return;
  TQValueVector< KMFolder *>::const_iterator it;
  for ( it = mOpenFolders.constBegin(); it != mOpenFolders.constEnd(); ++it )
    (*it)->close("filtermgr");
  mOpenFolders.clear();
}


//-----------------------------------------------------------------------------
int KMFilterMgr::tempOpenFolder(KMFolder* aFolder)
{
  assert( aFolder );

  int rc = aFolder->open("filermgr");
  if (rc) return rc;

  mOpenFolders.append( aFolder );
  return 0;
}


//-----------------------------------------------------------------------------
void KMFilterMgr::openDialog( TQWidget *, bool checkForEmptyFilterList )
{
  if( !mEditDialog )
  {
    //
    // We can't use the parent as long as the dialog is modeless
    // and there is one shared dialog for all top level windows.
    //
    mEditDialog = new KMFilterDlg( 0, "filterdialog", bPopFilter,
                                   checkForEmptyFilterList );
  }
  mEditDialog->show();
}


//-----------------------------------------------------------------------------
void KMFilterMgr::createFilter( const TQCString & field, const TQString & value )
{
  openDialog( 0, false );
  mEditDialog->createFilter( field, value );
}


//-----------------------------------------------------------------------------
const TQString KMFilterMgr::createUniqueName( const TQString & name )
{
  TQString uniqueName = name;
  int counter = 0;
  bool found = true;

  while ( found ) {
    found = false;
    for ( TQValueListConstIterator<KMFilter*> it = mFilters.constBegin();
          it != mFilters.constEnd(); ++it ) {
      if ( !( (*it)->name().compare( uniqueName ) ) ) {
        found = true;
        ++counter;
        uniqueName = name;
        uniqueName += TQString( " (" ) + TQString::number( counter )
                    + TQString( ")" );
        break;
      }
    }
  }
  return uniqueName;
}


//-----------------------------------------------------------------------------
void KMFilterMgr::appendFilters( const TQValueList<KMFilter*> &filters,
                                 bool replaceIfNameExists )
{
  mDirtyBufferedFolderTarget = true;
  beginUpdate();
  if ( replaceIfNameExists ) {
    TQValueListConstIterator<KMFilter*> it1 = filters.constBegin();
    for ( ; it1 != filters.constEnd() ; ++it1 ) {
      TQValueListConstIterator<KMFilter*> it2 = mFilters.constBegin();
      for ( ; it2 != mFilters.constEnd() ; ++it2 ) {
        if ( (*it1)->name() == (*it2)->name() ) {
          mFilters.remove( (*it2) );
          it2 = mFilters.constBegin();
        }
      }
    }
  }
  mFilters += filters;
  writeConfig( true );
  endUpdate();
}

void KMFilterMgr::setFilters( const TQValueList<KMFilter*> &filters )
{
  beginUpdate();
  clear();
  mFilters = filters;
  writeConfig( true );
  endUpdate();
}

void KMFilterMgr::slotFolderRemoved( KMFolder * aFolder )
{
  folderRemoved( aFolder, 0 );
}

//-----------------------------------------------------------------------------
bool KMFilterMgr::folderRemoved(KMFolder* aFolder, KMFolder* aNewFolder)
{
  mDirtyBufferedFolderTarget = true;
  bool rem = false;
  TQValueListConstIterator<KMFilter*> it = mFilters.constBegin();
  for ( ; it != mFilters.constEnd() ; ++it )
    if ( (*it)->folderRemoved(aFolder, aNewFolder) )
      rem = true;

  return rem;
}


//-----------------------------------------------------------------------------
#ifndef NDEBUG
void KMFilterMgr::dump(void) const
{

  TQValueListConstIterator<KMFilter*> it = mFilters.constBegin();
  for ( ; it != mFilters.constEnd() ; ++it ) {
    kdDebug(5006) << (*it)->asString() << endl;
  }
}
#endif

//-----------------------------------------------------------------------------
void KMFilterMgr::endUpdate(void)
{
  emit filterListUpdated();
}

#include "kmfiltermgr.moc"
