/***************************************************************************
    copyright            : (C) 2003-2006 by Robby Stephenson
    email                : robby@periapsis.org
 ***************************************************************************/

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

// The tqlayout borrows heavily from kmsearchpatternedit.cpp in kmail
// which is authored by Marc Mutz <Marc@Mutz.com> under the GPL

#include "filterdialog.h"
#include "tellico_kernel.h"
#include "document.h"
#include "collection.h"
#include "fieldcompletion.h"
#include "../tellico_debug.h"

#include <tdelocale.h>
#include <kcombobox.h>
#include <klineedit.h>
#include <kpushbutton.h>
#include <tdeparts/componentfactory.h>
#include <kregexpeditorinterface.h>
#include <kiconloader.h>

#include <tqlayout.h>
#include <tqgroupbox.h>
#include <tqradiobutton.h>
#include <tqvbuttongroup.h>
#include <tqhbox.h>
#include <tqvbox.h>
#include <tqlabel.h>
#include <tqapplication.h>

using Tellico::FilterRuleWidget;
using Tellico::FilterRuleWidgetLister;
using Tellico::FilterDialog;

FilterRuleWidget::FilterRuleWidget(FilterRule* rule_, TQWidget* parent_, const char* name_/*=0*/)
    : TQHBox(parent_, name_), m_editRegExp(0), m_editRegExpDialog(0) {
  initLists();
  initWidget();

  if(rule_) {
    setRule(rule_);
  } else {
    reset();
  }
}

void FilterRuleWidget::initLists() {
  //---------- initialize list of filter fields
  if(m_ruleFieldList.isEmpty()) {
    m_ruleFieldList.append('<' + i18n("Any Field") + '>');
    m_ruleFieldList += Kernel::self()->fieldTitles();
  }

  //---------- initialize list of filter operators
  if(m_ruleFuncList.isEmpty()) {
    // also see FilterRule::matches() and FilterRule::Function
    // if you change the following strings!
    m_ruleFuncList.append(i18n("contains"));
    m_ruleFuncList.append(i18n("does not contain"));
    m_ruleFuncList.append(i18n("equals"));
    m_ruleFuncList.append(i18n("does not equal"));
    m_ruleFuncList.append(i18n("matches regexp"));
    m_ruleFuncList.append(i18n("does not match regexp"));
  }
}

void FilterRuleWidget::initWidget() {
  setSpacing(4);

  m_ruleField = new KComboBox(this);
  connect(m_ruleField, TQ_SIGNAL(activated(int)), TQ_SIGNAL(signalModified()));
  connect(m_ruleField, TQ_SIGNAL(activated(int)), TQ_SLOT(slotRuleFieldChanged(int)));

  m_ruleFunc = new KComboBox(this);
  connect(m_ruleFunc, TQ_SIGNAL(activated(int)), TQ_SIGNAL(signalModified()));
  m_ruleValue = new KLineEdit(this);
  connect(m_ruleValue, TQ_SIGNAL(textChanged(const TQString&)), TQ_SIGNAL(signalModified()));

  if(!TDETrader::self()->query(TQString::fromLatin1("KRegExpEditor/KRegExpEditor")).isEmpty()) {
    m_editRegExp = new KPushButton(i18n("Edit..."), this);
    connect(m_editRegExp, TQ_SIGNAL(clicked()), this, TQ_SLOT(slotEditRegExp()));
    connect(m_ruleFunc, TQ_SIGNAL(activated(int)), this, TQ_SLOT(slotRuleFunctionChanged(int)));
    slotRuleFunctionChanged(m_ruleFunc->currentItem());
  }

  m_ruleField->insertStringList(m_ruleFieldList);
  // don't show sliders when popping up this menu
//  m_ruleField->setSizeLimit(m_ruleField->count());
//  m_ruleField->adjustSize();

  m_ruleFunc->insertStringList(m_ruleFuncList);
//  m_ruleFunc->adjustSize();

//  connect(m_ruleField, TQ_SIGNAL(textChanged(const TQString &)),
//          this, TQ_SIGNAL(fieldChanged(const TQString &)));
//  connect(m_ruleValue, TQ_SIGNAL(textChanged(const TQString &)),
//          this, TQ_SIGNAL(contentsChanged(const TQString &)));
}

void FilterRuleWidget::slotEditRegExp() {
  if(m_editRegExpDialog == 0) {
    m_editRegExpDialog = KParts::ComponentFactory::createInstanceFromQuery<TQDialog>(TQString::fromLatin1("KRegExpEditor/KRegExpEditor"),
                                                                                    TQString(), this);
  }

  KRegExpEditorInterface* iface = static_cast<KRegExpEditorInterface *>(m_editRegExpDialog->tqt_cast("KRegExpEditorInterface"));
  if(iface) {
    iface->setRegExp(m_ruleValue->text());
    if(m_editRegExpDialog->exec() == TQDialog::Accepted) {
      m_ruleValue->setText(iface->regExp());
    }
  }
}

void FilterRuleWidget::slotRuleFieldChanged(int which_) {
  Q_UNUSED(which_);
  TQString fieldTitle = m_ruleField->currentText();
  if(fieldTitle.isEmpty() || fieldTitle[0] == '<') {
    m_ruleValue->setCompletionObject(0);
    return;
  }
  Data::FieldPtr field = Data::Document::self()->collection()->fieldByTitle(fieldTitle);
  if(field && (field->flags() & Data::Field::AllowCompletion)) {
    FieldCompletion* completion = new FieldCompletion(field->flags() & Data::Field::AllowMultiple);
    completion->setItems(Kernel::self()->valuesByFieldName(field->name()));
    completion->setIgnoreCase(true);
    m_ruleValue->setCompletionObject(completion);
    m_ruleValue->setAutoDeleteCompletionObject(true);
  } else {
    m_ruleValue->setCompletionObject(0);
  }
}

void FilterRuleWidget::slotRuleFunctionChanged(int which_) {
  // The 5th and 6th functions are for regexps
  m_editRegExp->setEnabled(which_ == 4 || which_ == 5);
}

void FilterRuleWidget::setRule(const FilterRule* rule_) {
  if(!rule_) {
    reset();
    return;
  }

  blockSignals(true);

  if(rule_->fieldName().isEmpty()) {
    m_ruleField->setCurrentItem(0); // "All Fields"
  } else {
    m_ruleField->setCurrentText(Kernel::self()->fieldTitleByName(rule_->fieldName()));
  }

  //--------------set function and contents
  m_ruleFunc->setCurrentItem(static_cast<int>(rule_->function()));
  m_ruleValue->setText(rule_->pattern());

  if(m_editRegExp) {
    slotRuleFunctionChanged(static_cast<int>(rule_->function()));
  }

  blockSignals(false);
}

Tellico::FilterRule* FilterRuleWidget::rule() const {
  TQString field; // empty string
  if(m_ruleField->currentItem() > 0) { // 0 is "All Fields", field is empty
    field = Kernel::self()->fieldNameByTitle(m_ruleField->currentText());
  }

  return new FilterRule(field, m_ruleValue->text().stripWhiteSpace(),
                        static_cast<FilterRule::Function>(m_ruleFunc->currentItem()));
}

void FilterRuleWidget::reset() {
//  kdDebug() << "FilterRuleWidget::reset()" << endl;
  blockSignals(true);

  m_ruleField->setCurrentItem(0);
  m_ruleFunc->setCurrentItem(0);
  m_ruleValue->clear();

  if(m_editRegExp) {
    m_editRegExp->setEnabled(false);
  }

  blockSignals(false);
}

void FilterRuleWidget::setFocus() {
  m_ruleValue->setFocus();
}

/***************************************************************/

namespace {
  static const int FILTER_MIN_RULE_WIDGETS = 1;
  static const int FILTER_MAX_RULES = 8;
}

FilterRuleWidgetLister::FilterRuleWidgetLister(TQWidget* parent_, const char* name_)
    : KWidgetLister(FILTER_MIN_RULE_WIDGETS, FILTER_MAX_RULES, parent_, name_) {
//  slotClear();
}

void FilterRuleWidgetLister::setFilter(Filter::Ptr filter_) {
//  if(mWidgetList.first()) { // move this below next 'if'?
//    mWidgetList.first()->blockSignals(true);
//  }

  if(filter_->isEmpty()) {
    slotClear();
//    mWidgetList.first()->blockSignals(false);
    return;
  }

  const int count = static_cast<int>(filter_->count());
  if(count > mMaxWidgets) {
    myDebug() << "FilterRuleWidgetLister::setFilter() - more rules than allowed!" << endl;
  }

  // set the right number of widgets
  setNumberOfShownWidgetsTo(TQMAX(count, mMinWidgets));

  // load the actions into the widgets
  TQPtrListIterator<TQWidget> wIt(mWidgetList);
  for(TQPtrListIterator<FilterRule> rIt(*filter_); rIt.current() && wIt.current(); ++rIt, ++wIt) {
    static_cast<FilterRuleWidget*>(*wIt)->setRule(*rIt);
  }
  for( ; wIt.current(); ++wIt) { // clear any remaining
    static_cast<FilterRuleWidget*>(*wIt)->reset();
  }

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

void FilterRuleWidgetLister::reset() {
  slotClear();
}

void FilterRuleWidgetLister::setFocus() {
  if(!mWidgetList.isEmpty()) {
    mWidgetList.getFirst()->setFocus();
  }
}

TQWidget* FilterRuleWidgetLister::createWidget(TQWidget* parent_) {
  TQWidget* w = new FilterRuleWidget(static_cast<Tellico::FilterRule*>(0), parent_);
  connect(w, TQ_SIGNAL(signalModified()), TQ_SIGNAL(signalModified()));
  return w;
}

void FilterRuleWidgetLister::clearWidget(TQWidget* widget_) {
  if(widget_) {
    static_cast<FilterRuleWidget*>(widget_)->reset();
  }
}

const TQPtrList<TQWidget>& FilterRuleWidgetLister::widgetList() const {
  return mWidgetList;
}

/***************************************************************/

namespace {
  static const int FILTER_MIN_WIDTH = 600;
}

// modal dialog so I don't have to worry about updating stuff
// don't show apply button if not saving, i.e. just modifying existing filter
FilterDialog::FilterDialog(Mode mode_, TQWidget* parent_, const char* name_/*=0*/)
    : KDialogBase(parent_, name_, true,
                  (mode_ == CreateFilter ? i18n("Advanced Filter") : i18n("Modify Filter")),
                  (mode_ == CreateFilter ? Help|Ok|Apply|Cancel : Help|Ok|Cancel),
                  Ok, false),
      m_filter(0), m_mode(mode_), m_saveFilter(0) {
  init();
}

void FilterDialog::init() {
  TQWidget* page = new TQWidget(this);
  setMainWidget(page);
  TQVBoxLayout* topLayout = new TQVBoxLayout(page, 0, KDialog::spacingHint());

  TQGroupBox* m_matchGroup = new TQGroupBox(1, TQt::Horizontal, i18n("Filter Criteria"), page);
  topLayout->addWidget(m_matchGroup);

  TQVButtonGroup* bg = new TQVButtonGroup(m_matchGroup);
  bg->setFrameShape(TQFrame::NoFrame);
  bg->setInsideMargin(0);
  m_matchAll = new TQRadioButton(i18n("Match a&ll of the following"), bg);
  m_matchAny = new TQRadioButton(i18n("Match an&y of the following"), bg);
  m_matchAll->setChecked(true);
  connect(bg, TQ_SIGNAL(clicked(int)), TQ_SLOT(slotFilterChanged()));

  m_ruleLister = new FilterRuleWidgetLister(m_matchGroup);
  connect(m_ruleLister, TQ_SIGNAL(widgetRemoved()), TQ_SLOT(slotShrink()));
  connect(m_ruleLister, TQ_SIGNAL(signalModified()), TQ_SLOT(slotFilterChanged()));
  m_ruleLister->setFocus();

  TQHBoxLayout* blay = new TQHBoxLayout(topLayout);
  blay->addWidget(new TQLabel(i18n("Filter name:"), page));

  m_filterName = new KLineEdit(page);
  blay->addWidget(m_filterName);
  connect(m_filterName, TQ_SIGNAL(textChanged(const TQString&)), TQ_SLOT(slotFilterChanged()));

  // only when creating a new filter can it be saved
  if(m_mode == CreateFilter) {
    m_saveFilter = new KPushButton(SmallIconSet(TQString::fromLatin1("filter")), i18n("&Save Filter"), page);
    blay->addWidget(m_saveFilter);
    m_saveFilter->setEnabled(false);
    connect(m_saveFilter, TQ_SIGNAL(clicked()), TQ_SLOT(slotSaveFilter()));
    enableButtonApply(false);
  }
  enableButtonOK(false); // disable at start
  actionButton(Help)->setDefault(false); // Help automatically becomes default when OK is disabled
  actionButton(Cancel)->setDefault(true); // Help automatically becomes default when OK is disabled
  setMinimumWidth(TQMAX(minimumWidth(), FILTER_MIN_WIDTH));
  setHelp(TQString::fromLatin1("filter-dialog"));
}

Tellico::FilterPtr FilterDialog::currentFilter() {
  if(!m_filter) {
    m_filter = new Filter(Filter::MatchAny);
  }

  if(m_matchAll->isChecked()) {
    m_filter->setMatch(Filter::MatchAll);
  } else {
    m_filter->setMatch(Filter::MatchAny);
  }

  m_filter->clear(); // deletes all old rules
  for(TQPtrListIterator<TQWidget> it(m_ruleLister->widgetList()); it.current(); ++it) {
    FilterRuleWidget* rw = static_cast<FilterRuleWidget*>(it.current());
    FilterRule* rule = rw->rule();
    if(rule && !rule->isEmpty()) {
      m_filter->append(rule);
    }
  }
  // only set name if it has rules
  if(!m_filter->isEmpty()) {
    m_filter->setName(m_filterName->text());
  }
  return m_filter;
}

void FilterDialog::setFilter(FilterPtr filter_) {
  if(!filter_) {
    slotClear();
    return;
  }

  if(filter_->op() == Filter::MatchAll) {
    m_matchAll->setChecked(true);
  } else {
    m_matchAny->setChecked(true);
  }

  m_ruleLister->setFilter(filter_);
  m_filterName->setText(filter_->name());
  m_filter = filter_;
}

void FilterDialog::slotOk() {
  slotApply();
  accept();
}

void FilterDialog::slotApply() {
  emit signalUpdateFilter(currentFilter());
}

void FilterDialog::slotClear() {
//  kdDebug() << "FilterDialog::slotClear()" << endl;
  m_matchAll->setChecked(true);
  m_ruleLister->reset();
  m_filterName->clear();
}

void FilterDialog::slotShrink() {
  updateGeometry();
  TQApplication::sendPostedEvents();
  resize(width(), sizeHint().height());
}

void FilterDialog::slotFilterChanged() {
  bool emptyFilter = currentFilter()->isEmpty();
  if(m_saveFilter) {
    m_saveFilter->setEnabled(!m_filterName->text().isEmpty() && !emptyFilter);
    enableButtonApply(!emptyFilter);
  }
  enableButtonOK(!emptyFilter);
  actionButton(Ok)->setDefault(!emptyFilter);
}

void FilterDialog::slotSaveFilter() {
  // non-op if editing an existing filter
  if(m_mode != CreateFilter) {
    return;
  }

  // in this case, currentFilter() either creates a new filter or
  // updates the current one. If creating a new one, then I want to copy it
  bool wasEmpty = (m_filter == 0);
  FilterPtr filter = new Filter(*currentFilter());
  if(wasEmpty) {
    m_filter = filter;
  }
  // this keeps the saving completely decoupled from the filter setting in the detailed view
  if(filter->isEmpty()) {
    m_filter = 0;
    return;
  }
  Kernel::self()->addFilter(filter);
}

#include "filterdialog.moc"
