// kmsearchpatternedit.cpp
// Author: Marc Mutz <Marc@Mutz.com>
// This code is under GPL

#include <config.h>
#include "kmsearchpatternedit.h"

#include "kmsearchpattern.h"
#include "rulewidgethandlermanager.h"
using KMail::RuleWidgetHandlerManager;

#include <tdelocale.h>
#include <kdialog.h>
#include <kdebug.h>

#include <tqradiobutton.h>
#include <tqcombobox.h>
#include <tqbuttongroup.h>
#include <tqwidgetstack.h>
#include <tqlayout.h>

#include <assert.h>

// Definition of special rule field strings
// Note: Also see KMSearchRule::matches() and ruleFieldToEnglish() if
//       you change the following i18n-ized strings!
// Note: The index of the values in the following array has to correspond to
//       the value of the entries in the enum in KMSearchRuleWidget.
static const struct {
  const char *internalName;
  const char *displayName;
} SpecialRuleFields[] = {
  { "<message>",     I18N_NOOP( "Complete Message" )       },
  { "<body>",        I18N_NOOP( "Body of Message" )          },
  { "<any header>",  I18N_NOOP( "Anywhere in Headers" )    },
  { "<recipients>",  I18N_NOOP( "All Recipients" )    },
  { "<size>",        I18N_NOOP( "Size in Bytes" ) },
  { "<age in days>", I18N_NOOP( "Age in Days" )   },
  { "<status>",      I18N_NOOP( "Message Status" )        },
  { "Subject",       I18N_NOOP( "Subject" )  },
  { "From",          I18N_NOOP( "From" )  },
  { "To",            I18N_NOOP( "To" )  },
  { "CC",            I18N_NOOP( "CC" )  },
  { "Reply-To",      I18N_NOOP( "Reply To" )  },
  { "Organization",  I18N_NOOP( "Organization" )  }
};
static const int SpecialRuleFieldsCount =
  sizeof( SpecialRuleFields ) / sizeof( *SpecialRuleFields );

//=============================================================================
//
// class KMSearchRuleWidget
//
//=============================================================================

KMSearchRuleWidget::KMSearchRuleWidget( TQWidget *parent, KMSearchRule *aRule,
                                        const char *name, bool headersOnly,
                                        bool absoluteDates )
  : TQWidget( parent, name ),
    mRuleField( 0 ),
    mFunctionStack( 0 ),
    mValueStack( 0 ),
    mAbsoluteDates( absoluteDates )
{
  initFieldList( headersOnly, absoluteDates );
  initWidget();

  if ( aRule )
    setRule( aRule );
  else
    reset();
}

void KMSearchRuleWidget::setHeadersOnly( bool headersOnly )
{
  KMSearchRule* srule = rule();
  TQCString currentText = srule->field();
  delete srule;
  initFieldList( headersOnly, mAbsoluteDates );

  mRuleField->clear();
  mRuleField->insertStringList( mFilterFieldList );
  mRuleField->setSizeLimit( mRuleField->count() );
  mRuleField->adjustSize();

  if (( currentText != "<message>") &&
      ( currentText != "<body>"))
    mRuleField->changeItem( TQString(TQString::fromAscii( currentText )), 0 );
  else
    mRuleField->changeItem( TQString(), 0 );
}

void KMSearchRuleWidget::initWidget()
{
  TQHBoxLayout * hlay = new TQHBoxLayout( this, 0, KDialog::spacingHint() );

  // initialize the header field combo box
  mRuleField = new TQComboBox( true, this, "mRuleField" );
  mRuleField->insertStringList( mFilterFieldList );
  // don't show sliders when popping up this menu
  mRuleField->setSizeLimit( mRuleField->count() );
  mRuleField->adjustSize();
  hlay->addWidget( mRuleField );

  // initialize the function/value widget stack
  mFunctionStack = new TQWidgetStack( this, "mFunctionStack" );
  //Don't expand the widget in vertical direction
  mFunctionStack->setSizePolicy( TQSizePolicy::Preferred,TQSizePolicy::Fixed );

  hlay->addWidget( mFunctionStack );

  mValueStack = new TQWidgetStack( this, "mValueStack" );
  mValueStack->setSizePolicy( TQSizePolicy::Preferred,TQSizePolicy::Fixed );
  hlay->addWidget( mValueStack );
  hlay->setStretchFactor( mValueStack, 10 );

  RuleWidgetHandlerManager::instance()->createWidgets( mFunctionStack,
                                                       mValueStack,
                                                       TQT_TQOBJECT(this) );

  // redirect focus to the header field combo box
  setFocusProxy( mRuleField );

  connect( mRuleField, TQT_SIGNAL( activated( const TQString & ) ),
	   this, TQT_SLOT( slotRuleFieldChanged( const TQString & ) ) );
  connect( mRuleField, TQT_SIGNAL( textChanged( const TQString & ) ),
	   this, TQT_SLOT( slotRuleFieldChanged( const TQString & ) ) );
  connect( mRuleField, TQT_SIGNAL( textChanged( const TQString & ) ),
           this, TQT_SIGNAL( fieldChanged( const TQString & ) ) );
}

void KMSearchRuleWidget::setRule( KMSearchRule *aRule )
{
  assert ( aRule );

//  kdDebug(5006) << "KMSearchRuleWidget::setRule( "
//                << aRule->asString() << " )" << endl;

  //--------------set the field
  int i = indexOfRuleField( aRule->field() );

  mRuleField->blockSignals( true );

  if ( i < 0 ) { // not found -> user defined field
    mRuleField->changeItem( TQString::fromLatin1( aRule->field() ), 0 );
    i = 0;
  } else { // found in the list of predefined fields
    mRuleField->changeItem( TQString(), 0 );
  }

  mRuleField->setCurrentItem( i );
  mRuleField->blockSignals( false );

  RuleWidgetHandlerManager::instance()->setRule( mFunctionStack, mValueStack,
                                                 aRule );
}

KMSearchRule* KMSearchRuleWidget::rule() const {
  const TQCString ruleField = ruleFieldToEnglish( mRuleField->currentText() );
  const KMSearchRule::Function function =
    RuleWidgetHandlerManager::instance()->function( ruleField,
                                                    mFunctionStack );
  const TQString value =
    RuleWidgetHandlerManager::instance()->value( ruleField, mFunctionStack,
                                                 mValueStack );

  return KMSearchRule::createInstance( ruleField, function, value );
}

void KMSearchRuleWidget::reset()
{
  mRuleField->blockSignals( true );
  mRuleField->changeItem( "", 0 );
  mRuleField->setCurrentItem( 0 );
  mRuleField->blockSignals( false );

  RuleWidgetHandlerManager::instance()->reset( mFunctionStack, mValueStack );
}

void KMSearchRuleWidget::slotFunctionChanged()
{
  const TQCString ruleField = ruleFieldToEnglish( mRuleField->currentText() );
  RuleWidgetHandlerManager::instance()->update( ruleField,
                                                mFunctionStack,
                                                mValueStack );
}

void KMSearchRuleWidget::slotValueChanged()
{
  const TQCString ruleField = ruleFieldToEnglish( mRuleField->currentText() );
  const TQString prettyValue =
    RuleWidgetHandlerManager::instance()->prettyValue( ruleField,
                                                       mFunctionStack,
                                                       mValueStack );
  emit contentsChanged( prettyValue );
}

TQCString KMSearchRuleWidget::ruleFieldToEnglish( const TQString & i18nVal )
{
  for ( int i = 0; i < SpecialRuleFieldsCount; ++i ) {
    if ( i18nVal == i18n( SpecialRuleFields[i].displayName ) )
      return SpecialRuleFields[i].internalName;
  }
  return i18nVal.latin1();
}

int KMSearchRuleWidget::ruleFieldToId( const TQString & i18nVal )
{
  for ( int i = 0; i < SpecialRuleFieldsCount; ++i ) {
    if ( i18nVal == i18n( SpecialRuleFields[i].displayName ) )
      return i;
  }
  return -1; // no pseudo header
}

static TQString displayNameFromInternalName( const TQString & internal )
{
  for ( int i = 0; i < SpecialRuleFieldsCount; ++i ) {
    if ( internal == SpecialRuleFields[i].internalName )
      return i18n(SpecialRuleFields[i].displayName);
  }
  return internal.latin1();
}



int KMSearchRuleWidget::indexOfRuleField( const TQCString & aName ) const
{
  if ( aName.isEmpty() )
    return -1;

  TQString i18n_aName = displayNameFromInternalName( aName );

  for ( int i = 1; i < mRuleField->count(); ++i ) {
    if ( mRuleField->text( i ) == i18n_aName )
      return i;
  }

  return -1;
}

void KMSearchRuleWidget::initFieldList( bool headersOnly, bool absoluteDates )
{
  mFilterFieldList.clear();
  mFilterFieldList.append(""); // empty entry for user input
  if( !headersOnly ) {
    mFilterFieldList.append( i18n( SpecialRuleFields[Message].displayName ) );
    mFilterFieldList.append( i18n( SpecialRuleFields[Body].displayName ) );
  }
  mFilterFieldList.append( i18n( SpecialRuleFields[AnyHeader].displayName ) );
  mFilterFieldList.append( i18n( SpecialRuleFields[Recipients].displayName ) );
  mFilterFieldList.append( i18n( SpecialRuleFields[Size].displayName ) );
  if ( !absoluteDates )
    mFilterFieldList.append( i18n( SpecialRuleFields[AgeInDays].displayName ) );
  mFilterFieldList.append( i18n( SpecialRuleFields[Subject].displayName ) );
  mFilterFieldList.append( i18n( SpecialRuleFields[From].displayName ) );
  mFilterFieldList.append( i18n( SpecialRuleFields[To].displayName ) );
  mFilterFieldList.append( i18n( SpecialRuleFields[CC].displayName ) );
  mFilterFieldList.append( i18n( SpecialRuleFields[ReplyTo].displayName ) );
  mFilterFieldList.append( i18n( SpecialRuleFields[Organization].displayName ) );

  // these others only represent message headers and you can add to
  // them as you like
  mFilterFieldList.append("List-Id");
  mFilterFieldList.append("Resent-From");
  mFilterFieldList.append("X-Loop");
  mFilterFieldList.append("X-Mailing-List");
  mFilterFieldList.append("X-Spam-Flag");
}

void KMSearchRuleWidget::slotRuleFieldChanged( const TQString & field )
{
  RuleWidgetHandlerManager::instance()->update( ruleFieldToEnglish( field ),
                                                mFunctionStack,
                                                mValueStack );
}

//=============================================================================
//
// class KMFilterActionWidgetLister (the filter action editor)
//
//=============================================================================

KMSearchRuleWidgetLister::KMSearchRuleWidgetLister( TQWidget *parent, const char* name, bool headersOnly, bool absoluteDates )
  : KWidgetLister( 2, FILTER_MAX_RULES, parent, name )
{
  mRuleList = 0;
  mHeadersOnly = headersOnly;
  mAbsoluteDates = absoluteDates;
}

KMSearchRuleWidgetLister::~KMSearchRuleWidgetLister()
{
}

void KMSearchRuleWidgetLister::setRuleList( TQPtrList<KMSearchRule> *aList )
{
  assert ( aList );

  if ( mRuleList && mRuleList != aList )
    regenerateRuleListFromWidgets();

  mRuleList = aList;

  if ( mWidgetList.first() ) // move this below next 'if'?
    mWidgetList.first()->blockSignals(true);

  if ( aList->count() == 0 ) {
    slotClear();
    mWidgetList.first()->blockSignals(false);
    return;
  }

  int superfluousItems = (int)mRuleList->count() - mMaxWidgets ;
  if ( superfluousItems > 0 ) {
    kdDebug(5006) << "KMSearchRuleWidgetLister: Clipping rule list to "
		  << mMaxWidgets << " items!" << endl;

    for ( ; superfluousItems ; superfluousItems-- )
      mRuleList->removeLast();
  }

  // HACK to workaround regression in TQt 3.1.3 and TQt 3.2.0 (fixes bug #63537)
  setNumberOfShownWidgetsTo( TQMAX((int)mRuleList->count(),mMinWidgets)+1 );
  // set the right number of widgets
  setNumberOfShownWidgetsTo( TQMAX((int)mRuleList->count(),mMinWidgets) );

  // load the actions into the widgets
  TQPtrListIterator<KMSearchRule> rIt( *mRuleList );
  TQPtrListIterator<TQWidget> wIt( mWidgetList );
  for ( rIt.toFirst(), wIt.toFirst() ;
	rIt.current() && wIt.current() ; ++rIt, ++wIt ) {
    static_cast<KMSearchRuleWidget*>(*wIt)->setRule( (*rIt) );
  }
  for ( ; wIt.current() ; ++wIt )
    ((KMSearchRuleWidget*)(*wIt))->reset();

  assert( mWidgetList.first() );
  mWidgetList.first()->blockSignals(false);
}

void KMSearchRuleWidgetLister::setHeadersOnly( bool headersOnly )
{
  TQPtrListIterator<TQWidget> wIt( mWidgetList );
  for ( wIt.toFirst() ; wIt.current() ; ++wIt ) {
    (static_cast<KMSearchRuleWidget*>(*wIt))->setHeadersOnly( headersOnly );
  }
}

void KMSearchRuleWidgetLister::reset()
{
  if ( mRuleList )
    regenerateRuleListFromWidgets();

  mRuleList = 0;
  slotClear();
}

TQWidget* KMSearchRuleWidgetLister::createWidget( TQWidget *parent )
{
  return new KMSearchRuleWidget(parent, 0, 0, mHeadersOnly, mAbsoluteDates);
}

void KMSearchRuleWidgetLister::clearWidget( TQWidget *aWidget )
{
  if ( aWidget )
    ((KMSearchRuleWidget*)aWidget)->reset();
}

void KMSearchRuleWidgetLister::regenerateRuleListFromWidgets()
{
  if ( !mRuleList ) return;

  mRuleList->clear();

  TQPtrListIterator<TQWidget> it( mWidgetList );
  for ( it.toFirst() ; it.current() ; ++it ) {
    KMSearchRule *r = ((KMSearchRuleWidget*)(*it))->rule();
    if ( r )
      mRuleList->append( r );
  }
}




//=============================================================================
//
// class KMSearchPatternEdit
//
//=============================================================================

KMSearchPatternEdit::KMSearchPatternEdit(TQWidget *parent, const char *name, bool headersOnly, bool absoluteDates )
  : TQGroupBox( 1/*columns*/, Qt::Horizontal, parent, name )
{
  setTitle( i18n("Search Criteria") );
  initLayout( headersOnly, absoluteDates );
}

KMSearchPatternEdit::KMSearchPatternEdit(const TQString & title, TQWidget *parent, const char *name, bool headersOnly, bool absoluteDates)
  : TQGroupBox( 1/*column*/, Qt::Horizontal, title, parent, name )
{
  initLayout( headersOnly, absoluteDates );
}

KMSearchPatternEdit::~KMSearchPatternEdit()
{
}

void KMSearchPatternEdit::initLayout(bool headersOnly, bool absoluteDates)
{
  //------------the radio buttons
  mAllRBtn = new TQRadioButton( i18n("Match a&ll of the following"), this, "mAllRBtn" );
  mAnyRBtn = new TQRadioButton( i18n("Match an&y of the following"), this, "mAnyRBtn" );

  mAllRBtn->setChecked(true);
  mAnyRBtn->setChecked(false);

  TQButtonGroup *bg = new TQButtonGroup( this );
  bg->hide();
  bg->insert( mAllRBtn, (int)KMSearchPattern::OpAnd );
  bg->insert( mAnyRBtn, (int)KMSearchPattern::OpOr );

  //------------the list of KMSearchRuleWidget's
  mRuleLister = new KMSearchRuleWidgetLister( this, "swl", headersOnly, absoluteDates );
  mRuleLister->slotClear();

  //------------connect a few signals
  connect( bg, TQT_SIGNAL(clicked(int)),
	   this, TQT_SLOT(slotRadioClicked(int)) );

  KMSearchRuleWidget *srw = (KMSearchRuleWidget*)mRuleLister->mWidgetList.first();
  if ( srw ) {
    connect( srw, TQT_SIGNAL(fieldChanged(const TQString &)),
	     this, TQT_SLOT(slotAutoNameHack()) );
    connect( srw, TQT_SIGNAL(contentsChanged(const TQString &)),
	     this, TQT_SLOT(slotAutoNameHack()) );
  } else
    kdDebug(5006) << "KMSearchPatternEdit: no first KMSearchRuleWidget, though slotClear() has been called!" << endl;
}

void KMSearchPatternEdit::setSearchPattern( KMSearchPattern *aPattern )
{
  assert( aPattern );

  mRuleLister->setRuleList( aPattern );

  mPattern = aPattern;

  blockSignals(true);
  if ( mPattern->op() == KMSearchPattern::OpOr )
    mAnyRBtn->setChecked(true);
  else
    mAllRBtn->setChecked(true);
  blockSignals(false);

  setEnabled( true );
}

void KMSearchPatternEdit::setHeadersOnly( bool headersOnly )
{
  mRuleLister->setHeadersOnly( headersOnly );
}

void KMSearchPatternEdit::reset()
{
  mRuleLister->reset();

  blockSignals(true);
  mAllRBtn->setChecked( true );
  blockSignals(false);

  setEnabled( false );
}

void KMSearchPatternEdit::slotRadioClicked(int aIdx)
{
  if ( mPattern )
    mPattern->setOp( (KMSearchPattern::Operator)aIdx );
}

void KMSearchPatternEdit::slotAutoNameHack()
{
  mRuleLister->regenerateRuleListFromWidgets();
  emit maybeNameChanged();
}

#include "kmsearchpatternedit.moc"
