/***************************************************************************
                          keditscheduledlg.cpp  -  description
                             -------------------
    begin                : Mon Sep  3 2007
    copyright            : (C) 2007 by Thomas Baumgart
    email                : Thomas Baumgart <ipwizard@users.sourceforge.net>
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

// ----------------------------------------------------------------------------
// TQt Includes

#include <tqtimer.h>
#include <tqwidgetlist.h>
#include <tqcheckbox.h>
#include <tqlabel.h>
#include <tqlayout.h>
#include <tqvaluevector.h>

// ----------------------------------------------------------------------------
// TDE Includes

#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kpushbutton.h>
#include <kstdguiitem.h>
#include <klineedit.h>
#include <knuminput.h>

// ----------------------------------------------------------------------------
// Project Includes

#include <kmymoney/register.h>
#include <kmymoney/transactionform.h>
#include <kmymoney/transaction.h>
#include <kmymoney/transactioneditor.h>
#include <kmymoney/kmymoneylineedit.h>
#include <kmymoney/kmymoneydateinput.h>
#include <kmymoney/kmymoneycombo.h>
#include <kmymoney/kguiutils.h>
#include <kmymoney/kmymoneyutils.h>

#include "keditscheduledlg.h"
#include "../kmymoney2.h"

class KEditScheduleDlg::Private {
public:
  MyMoneySchedule                m_schedule;
  KMyMoneyRegister::Transaction* m_item;
  TQWidgetList                    m_tabOrderWidgets;
  TransactionEditor*             m_editor;
  kMandatoryFieldGroup*          m_requiredFields;
};

KEditScheduleDlg::KEditScheduleDlg(const MyMoneySchedule& schedule, TQWidget *parent, const char *name) :
  KEditScheduleDlgDecl(parent, name, true),
  d(new Private)
{
  d->m_schedule = schedule;
  d->m_editor = 0;

  buttonOk->setGuiItem(KStdGuiItem::ok());
  buttonCancel->setGuiItem(KStdGuiItem::cancel());
  buttonHelp->setGuiItem(KStdGuiItem::help());

  d->m_requiredFields = new kMandatoryFieldGroup (this);
  d->m_requiredFields->setOkButton(buttonOk); // button to be enabled when all fields present

  // make sure, we have a tabbar with the form
  // insert it after the horizontal line
  m_paymentInformationLayout->insertWidget(2, m_form->tabBar(m_form->parentWidget()));

  // we never need to see the register
  m_register->hide();

  // ... setup the form ...
  m_form->setupForm(d->m_schedule.account());

  // ... and the register ...
  m_register->clear();

  // ... now add the transaction to register and form ...
  MyMoneyTransaction t = transaction();
  d->m_item = KMyMoneyRegister::Register::transactionFactory(m_register, t, d->m_schedule.transaction().splits()[0], 0);
  m_register->selectItem(d->m_item);
  // show the account row
  d->m_item->setShowRowInForm(0, true);

  m_form->slotSetTransaction(d->m_item);

  // setup widget contents
  m_nameEdit->setText(d->m_schedule.name());

  m_frequencyEdit->setCurrentItem(d->m_schedule.occurencePeriod());
  if(m_frequencyEdit->currentItem() == -1)
    m_frequencyEdit->setCurrentItem(MyMoneySchedule::OCCUR_MONTHLY);
  slotFrequencyChanged(m_frequencyEdit->currentItem());
  m_frequencyNoEdit->setValue(d->m_schedule.occurenceMultiplier());

  // load option widgets
  m_paymentMethodEdit->insertItem(i18n("Direct deposit"), MyMoneySchedule::STYPE_DIRECTDEPOSIT);
  m_paymentMethodEdit->insertItem(i18n("Manual deposit"), MyMoneySchedule::STYPE_MANUALDEPOSIT);
  m_paymentMethodEdit->insertItem(i18n("Direct debit"), MyMoneySchedule::STYPE_DIRECTDEBIT);
  m_paymentMethodEdit->insertItem(i18n("Standing order"), MyMoneySchedule::STYPE_STANDINGORDER);
  m_paymentMethodEdit->insertItem(i18n("Bank transfer"), MyMoneySchedule::STYPE_BANKTRANSFER);
  m_paymentMethodEdit->insertItem(i18n("Write check"), MyMoneySchedule::STYPE_WRITECHEQUE);
  m_paymentMethodEdit->insertItem(i18n("Other"), MyMoneySchedule::STYPE_OTHER);

  MyMoneySchedule::paymentTypeE method = d->m_schedule.paymentType();
  if(method == MyMoneySchedule::STYPE_ANY)
    method = MyMoneySchedule::STYPE_OTHER;
  m_paymentMethodEdit->setCurrentItem(method);

  switch(d->m_schedule.weekendOption()) {
    case MyMoneySchedule::MoveNothing:
      m_weekendOptionEdit->setCurrentItem(0);
      break;
    case MyMoneySchedule::MoveFriday:
      m_weekendOptionEdit->setCurrentItem(1);
      break;
    case MyMoneySchedule::MoveMonday:
      m_weekendOptionEdit->setCurrentItem(2);
      break;
  }
  m_estimateEdit->setChecked(!d->m_schedule.isFixed());
  m_autoEnterEdit->setChecked(d->m_schedule.autoEnter());
  m_endSeriesEdit->setChecked(d->m_schedule.willEnd());

  m_endOptionsFrame->setEnabled(d->m_schedule.willEnd());
  if(d->m_schedule.willEnd()) {
    m_RemainingEdit->setValue(d->m_schedule.transactionsRemaining());
    m_FinalPaymentEdit->setDate(d->m_schedule.endDate());
  }

  connect(m_RemainingEdit, TQ_SIGNAL(valueChanged(int)),
    this, TQ_SLOT(slotRemainingChanged(int)));
  connect(m_FinalPaymentEdit, TQ_SIGNAL(dateChanged(const TQDate&)),
    this, TQ_SLOT(slotEndDateChanged(const TQDate&)));
  connect(m_frequencyEdit, TQ_SIGNAL(itemSelected(int)),
    this, TQ_SLOT(slotFrequencyChanged(int)));
  connect(m_frequencyNoEdit, TQ_SIGNAL(valueChanged(int)),
    this, TQ_SLOT(slotOccurenceMultiplierChanged(int)));
  connect(buttonHelp, TQ_SIGNAL(clicked()), this, TQ_SLOT(slotShowHelp()));

  // force the initial height to be as small as possible
  TQTimer::singleShot(0, this, TQ_SLOT(slotSetupSize()));

  // we just hide the variation field for now and enable the logic
  // once we have a respective member in the MyMoneySchedule object
  m_variation->hide();
}

KEditScheduleDlg::~KEditScheduleDlg()
{
  delete d;
}

void KEditScheduleDlg::slotSetupSize(void)
{
  resize(width(), minimumSizeHint().height());
}

TransactionEditor* KEditScheduleDlg::startEdit(void)
{
  KMyMoneyRegister::SelectedTransactions list(m_register);
  TransactionEditor* editor = d->m_item->createEditor(m_form, list, TQDate());

  // check that we use the same transaction commodity in all selected transactions
  // if not, we need to update this in the editor's list. The user can also bail out
  // of this operation which means that we have to stop editing here.
  if(editor && !d->m_schedule.account().id().isEmpty()) {
    if(!editor->fixTransactionCommodity(d->m_schedule.account())) {
      // if the user wants to quit, we need to destroy the editor
      // and bail out
      delete editor;
      editor = 0;
    }
  }

  if(editor) {
    connect(editor, TQ_SIGNAL(transactionDataSufficient(bool)), buttonOk, TQ_SLOT(setEnabled(bool)));
    connect(editor, TQ_SIGNAL(escapePressed()), buttonCancel, TQ_SLOT(animateClick()));
    connect(editor, TQ_SIGNAL(returnPressed()), buttonOk, TQ_SLOT(animateClick()));

    connect(MyMoneyFile::instance(), TQ_SIGNAL(dataChanged()), editor, TQ_SLOT(slotReloadEditWidgets()));
    // connect(editor, TQ_SIGNAL(finishEdit(const KMyMoneyRegister::SelectedTransactions&)), this, TQ_SLOT(slotLeaveEditMode(const KMyMoneyRegister::SelectedTransactions&)));
    connect(editor, TQ_SIGNAL(createPayee(const TQString&, TQString&)), kmymoney2, TQ_SLOT(slotPayeeNew(const TQString&, TQString&)));
    connect(editor, TQ_SIGNAL(createCategory(MyMoneyAccount&, const MyMoneyAccount&)), kmymoney2, TQ_SLOT(slotCategoryNew(MyMoneyAccount&, const MyMoneyAccount&)));
    connect(editor, TQ_SIGNAL(createSecurity(MyMoneyAccount&, const MyMoneyAccount&)), kmymoney2, TQ_SLOT(slotInvestmentNew(MyMoneyAccount&, const MyMoneyAccount&)));
    connect(MyMoneyFile::instance(), TQ_SIGNAL(dataChanged()), editor, TQ_SLOT(slotReloadEditWidgets()));

    // create the widgets, place them in the parent and load them with data
    // setup tab order
    d->m_tabOrderWidgets.clear();
    KMyMoneyRegister::Action action = KMyMoneyRegister::ActionWithdrawal;
    switch(d->m_schedule.type()) {
      case MyMoneySchedule::TYPE_DEPOSIT:
        action = KMyMoneyRegister::ActionDeposit;
        break;
      case MyMoneySchedule::TYPE_BILL:
        action = KMyMoneyRegister::ActionWithdrawal;
        break;
      case MyMoneySchedule::TYPE_TRANSFER:
        action = KMyMoneyRegister::ActionTransfer;
        break;
      default:
        // if we end up here, we don't have a known schedule type (yet). in this case, we just glimpse
        // into the transaction and determine the type. in case we don't have a transaction with splits
        // we stick with the default action already set up
        if(d->m_schedule.transaction().splits().count() > 0) {
          TQValueList<MyMoneySplit>::const_iterator it_s;
          bool isDeposit = false;
          bool isTransfer = false;
          for(it_s = d->m_schedule.transaction().splits().begin(); it_s != d->m_schedule.transaction().splits().end(); ++it_s) {
            if((*it_s).accountId() == d->m_schedule.account().id()) {
              isDeposit = !((*it_s).shares().isNegative());
            } else {
              MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId());
              if(acc.isAssetLiability() && d->m_schedule.transaction().splits().count() == 2) {
                isTransfer = true;
              }
            }
          }

          if(isTransfer)
            action = KMyMoneyRegister::ActionTransfer;
          else if(isDeposit)
            action = KMyMoneyRegister::ActionDeposit;
        }
        break;
    }
    editor->setup(d->m_tabOrderWidgets, d->m_schedule.account(), action);

    // if it's not a check, then we need to clear
    // a possibly assigned check number
    if(d->m_schedule.paymentType() != MyMoneySchedule::STYPE_WRITECHEQUE) {
      TQWidget* w = editor->haveWidget("number");
      if(w)
        dynamic_cast<kMyMoneyLineEdit*>(w)->loadText(TQString());
    }

    Q_ASSERT(!d->m_tabOrderWidgets.isEmpty());

    // don't forget our three buttons and additional widgets
    d->m_tabOrderWidgets.append(m_weekendOptionEdit);
    d->m_tabOrderWidgets.append(m_estimateEdit);
    d->m_tabOrderWidgets.append(m_variation);
    d->m_tabOrderWidgets.append(m_autoEnterEdit);
    d->m_tabOrderWidgets.append(m_endSeriesEdit);
    d->m_tabOrderWidgets.append(m_RemainingEdit);
    d->m_tabOrderWidgets.append(m_FinalPaymentEdit);

    d->m_tabOrderWidgets.append(buttonOk);
    d->m_tabOrderWidgets.append(buttonCancel);
    d->m_tabOrderWidgets.append(buttonHelp);
    d->m_tabOrderWidgets.append(m_nameEdit);
    d->m_tabOrderWidgets.append(m_frequencyNoEdit);
    d->m_tabOrderWidgets.append(m_frequencyEdit);
    d->m_tabOrderWidgets.append(m_paymentMethodEdit);
    d->m_tabOrderWidgets.append(m_form);

    // install event filter in all taborder widgets
    TQWidget* w;
    for(w = d->m_tabOrderWidgets.first(); w; w = d->m_tabOrderWidgets.next()) {
      w->installEventFilter(this);
      w->installEventFilter(editor);
    }

    // connect the postdate modification signal to our update routine
    kMyMoneyDateInput* dateEdit = dynamic_cast<kMyMoneyDateInput*>(editor->haveWidget("postdate"));
    if(dateEdit)
      connect(dateEdit, TQ_SIGNAL(dateChanged(const TQDate&)), this, TQ_SLOT(slotPostDateChanged(const TQDate&)));


    m_nameEdit->setFocus();

    // add the required fields to the mandatory group
    d->m_requiredFields->add(m_nameEdit);
    d->m_requiredFields->add(editor->haveWidget("account"));
    d->m_requiredFields->add(editor->haveWidget("category"));

    // fix labels
    TQLabel* label = dynamic_cast<TQLabel*>(editor->haveWidget("date-label"));
    if(label) {
      label->setText(i18n("Next due date"));
    }

    d->m_editor = editor;
    slotSetPaymentMethod(d->m_schedule.paymentType());

    connect(m_paymentMethodEdit, TQ_SIGNAL(itemSelected(int)), this, TQ_SLOT(slotSetPaymentMethod(int)));
  }

  return editor;
}

void KEditScheduleDlg::accept(void)
{
  // Force the focus to be on the OK button. This will trigger creation
  // of any unknown objects (payees, categories etc.)
  buttonOk->setFocus();

  // only accept if the button is really still enabled. We could end
  // up here, if the user filled all fields, the focus is on the category
  // field, but the category is not yet existant. When the user presses the
  // OK button in this context, he will be asked if he wants to create
  // the category or not. In case he decides no, we end up here with no
  // category filled in, so we don't run through the final acceptance.
  if(buttonOk->isEnabled())
    KEditScheduleDlgDecl::accept();
}

const MyMoneySchedule& KEditScheduleDlg::schedule(void) const
{
  if(d->m_editor) {
    MyMoneyTransaction t = transaction();
    if(d->m_schedule.nextDueDate() != t.postDate())
        d->m_schedule.setNextDueDate(t.postDate());
    d->m_schedule.setTransaction(t);
    d->m_schedule.setName(m_nameEdit->text());
    d->m_schedule.setFixed(!m_estimateEdit->isChecked());
    d->m_schedule.setOccurencePeriod(static_cast<MyMoneySchedule::occurenceE>(m_frequencyEdit->currentItem()));
    d->m_schedule.setOccurenceMultiplier( m_frequencyNoEdit->value() );

    switch(m_weekendOptionEdit->currentItem())  {
      case 0:
        d->m_schedule.setWeekendOption(MyMoneySchedule::MoveNothing);
        break;
      case 1:
        d->m_schedule.setWeekendOption(MyMoneySchedule::MoveFriday);
        break;
      case 2:
        d->m_schedule.setWeekendOption(MyMoneySchedule::MoveMonday);
        break;
    }

    d->m_schedule.setType(MyMoneySchedule::TYPE_BILL);
    KMyMoneyTransactionForm::TabBar* tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(d->m_editor->haveWidget("tabbar"));
    if(tabbar) {
      switch(static_cast<KMyMoneyRegister::Action>(tabbar->currentTab())) {
        case KMyMoneyRegister::ActionDeposit:
          d->m_schedule.setType(MyMoneySchedule::TYPE_DEPOSIT);
          break;
        default:
        case KMyMoneyRegister::ActionWithdrawal:
          d->m_schedule.setType(MyMoneySchedule::TYPE_BILL);
          break;
        case KMyMoneyRegister::ActionTransfer:
          d->m_schedule.setType(MyMoneySchedule::TYPE_TRANSFER);
          break;
      }
    } else {
      tqDebug("No tabbar found in KEditScheduleDlg::schedule(). Defaulting type to BILL");
    }

    d->m_schedule.setAutoEnter(m_autoEnterEdit->isChecked());
    d->m_schedule.setPaymentType(static_cast<MyMoneySchedule::paymentTypeE>(m_paymentMethodEdit->currentItem()));
    if(m_endSeriesEdit->isEnabled() && m_endSeriesEdit->isChecked()) {
      d->m_schedule.setEndDate(m_FinalPaymentEdit->date());
    } else {
      d->m_schedule.setEndDate(TQDate());
    }
  }
  return d->m_schedule;
}

MyMoneyTransaction KEditScheduleDlg::transaction(void) const
{
  MyMoneyTransaction t = d->m_schedule.transaction();

  if(d->m_editor) {
    d->m_editor->createTransaction(t, d->m_schedule.transaction(), d->m_schedule.transaction().splits()[0], false);
  }

  t.clearId();
  t.setEntryDate(TQDate());
  return t;
}

bool KEditScheduleDlg::focusNextPrevChild(bool next)
{
  bool  rc = false;

  // tqDebug("KEditScheduleDlg::focusNextPrevChild(editmode=%s)", m_inEditMode ? "true" : "false");
  TQWidget *w = 0;
  TQWidget *currentWidget;

  w = tqApp->focusWidget();
  while(w && d->m_tabOrderWidgets.find(w) == -1) {
    // tqDebug("'%s' not in list, use parent", w->className());
    w = w->parentWidget();
  }
  // if(w) tqDebug("tab order is at '%s'", w->className());
  currentWidget = d->m_tabOrderWidgets.current();
  w = next ? d->m_tabOrderWidgets.next() : d->m_tabOrderWidgets.prev();

  do {
    if(!w) {
      w = next ? d->m_tabOrderWidgets.first() : d->m_tabOrderWidgets.last();
    }

    if(w != currentWidget
    && ((w->focusPolicy() & TQWidget::TabFocus) == TQWidget::TabFocus)
    && w->isVisible() && w->isEnabled()) {
      // tqDebug("Selecting '%s' as focus", w->className());
      w->setFocus();
      rc = true;
      break;
    }
    w = next ? d->m_tabOrderWidgets.next() : d->m_tabOrderWidgets.prev();
  } while(w != currentWidget);

  return rc;
}

void KEditScheduleDlg::resizeEvent(TQResizeEvent* ev)
{
  m_register->resize(KMyMoneyRegister::DetailColumn);
  m_form->resize(KMyMoneyTransactionForm::ValueColumn1);
  KEditScheduleDlgDecl::resizeEvent(ev);
}


void KEditScheduleDlg::slotRemainingChanged(int value)
{
  // Make sure the required fields are set
  kMyMoneyDateInput* dateEdit = dynamic_cast<kMyMoneyDateInput*>(d->m_editor->haveWidget("postdate"));
  d->m_schedule.setNextDueDate(dateEdit->date());
  d->m_schedule.setOccurencePeriod(static_cast<MyMoneySchedule::occurenceE>(m_frequencyEdit->currentItem()));
  d->m_schedule.setOccurenceMultiplier(m_frequencyNoEdit->value());

  if(d->m_schedule.transactionsRemaining() != value) {
    m_FinalPaymentEdit->blockSignals(true);
    m_FinalPaymentEdit->setDate(d->m_schedule.dateAfter(value));
    m_FinalPaymentEdit->blockSignals(false);
  }
}

void KEditScheduleDlg::slotEndDateChanged(const TQDate& date)
{
  // Make sure the required fields are set
  kMyMoneyDateInput* dateEdit = dynamic_cast<kMyMoneyDateInput*>(d->m_editor->haveWidget("postdate"));
  d->m_schedule.setNextDueDate(dateEdit->date());
  d->m_schedule.setOccurencePeriod(static_cast<MyMoneySchedule::occurenceE>(m_frequencyEdit->currentItem()));
  d->m_schedule.setOccurenceMultiplier(m_frequencyNoEdit->value());

  if(d->m_schedule.endDate() != date) {
    d->m_schedule.setEndDate(date);
    updateTransactionsRemaining();
  }
}

void KEditScheduleDlg::slotPostDateChanged(const TQDate& date)
{
  if(d->m_schedule.nextDueDate() != date) {
    if (m_endOptionsFrame->isEnabled()) {
      d->m_schedule.setNextDueDate(date);
      d->m_schedule.setOccurenceMultiplier(m_frequencyNoEdit->value());
      d->m_schedule.setOccurencePeriod(static_cast<MyMoneySchedule::occurenceE>(m_frequencyEdit->currentItem()));
      d->m_schedule.setEndDate(m_FinalPaymentEdit->date());
      updateTransactionsRemaining();
    }
  }
}

void KEditScheduleDlg::slotSetPaymentMethod(int item)
{
  kMyMoneyLineEdit* dateEdit = dynamic_cast<kMyMoneyLineEdit*>(d->m_editor->haveWidget("number"));
  if(dateEdit) {
    dateEdit->setShown(item == MyMoneySchedule::STYPE_WRITECHEQUE);

    // hiding the label does not work, because the label underneath will shine
    // through. So we either write the label or a blank
    TQLabel* label = dynamic_cast<TQLabel *>(d->m_editor->haveWidget("number-label"));
    if(label) {
      label->setText((item == MyMoneySchedule::STYPE_WRITECHEQUE) ? i18n("Number") : " ");
    }
  }
}

void KEditScheduleDlg::slotFrequencyChanged(int item)
{
  m_endSeriesEdit->setEnabled(item != MyMoneySchedule::OCCUR_ONCE);
  bool isEndSeries = m_endSeriesEdit->isChecked();
  if(isEndSeries )
    m_endOptionsFrame->setEnabled(item != MyMoneySchedule::OCCUR_ONCE);
  switch( item )
  {
    case MyMoneySchedule::OCCUR_DAILY:
    case MyMoneySchedule::OCCUR_WEEKLY:
    case MyMoneySchedule::OCCUR_EVERYHALFMONTH:
    case MyMoneySchedule::OCCUR_MONTHLY:
    case MyMoneySchedule::OCCUR_YEARLY:
      // Supports Frequency Number
      m_frequencyNoEdit->setEnabled(true);
      break;
    default:
      // Multiplier is always 1
      m_frequencyNoEdit->setEnabled(false);
      m_frequencyNoEdit->setValue(1);
      break;
  }
  if ( isEndSeries && ( item != MyMoneySchedule::OCCUR_ONCE ) )
  {
    // Changing the frequency changes the number
    // of remaining transactions
    kMyMoneyDateInput* dateEdit = dynamic_cast<kMyMoneyDateInput*>(d->m_editor->haveWidget("postdate"));
    d->m_schedule.setNextDueDate(dateEdit->date());
    d->m_schedule.setOccurenceMultiplier(m_frequencyNoEdit->value());
    d->m_schedule.setOccurencePeriod(static_cast<MyMoneySchedule::occurenceE>(item));
    d->m_schedule.setEndDate(m_FinalPaymentEdit->date());
    updateTransactionsRemaining();
  }
}

void KEditScheduleDlg::slotOccurenceMultiplierChanged(int multiplier)
{
  // Make sure the required fields are set
  int oldOccurenceMultiplier = d->m_schedule.occurenceMultiplier();
  if ( multiplier != oldOccurenceMultiplier )
  {
    if (m_endOptionsFrame->isEnabled())
    {
      kMyMoneyDateInput* dateEdit = dynamic_cast<kMyMoneyDateInput*>(d->m_editor->haveWidget("postdate"));
      d->m_schedule.setNextDueDate(dateEdit->date());
      d->m_schedule.setOccurenceMultiplier(multiplier);
      d->m_schedule.setOccurencePeriod(static_cast<MyMoneySchedule::occurenceE>(m_frequencyEdit->currentItem()));
      d->m_schedule.setEndDate(m_FinalPaymentEdit->date());
      updateTransactionsRemaining();
    }
  }
}

void KEditScheduleDlg::updateTransactionsRemaining(void)
{
  int remain = d->m_schedule.transactionsRemaining();
  if ( remain != m_RemainingEdit->value() )
  {
    m_RemainingEdit->blockSignals(true);
    m_RemainingEdit->setValue(remain);
    m_RemainingEdit->blockSignals(false);
  }
}

void KEditScheduleDlg::slotShowHelp(void)
{
  kapp->invokeHelp("details.schedules.intro");
}

#include <keditscheduledlg.moc>
