/***************************************************************************
                          kendingbalancedlg.cpp
                             -------------------
    copyright            : (C) 2000,2003 by Michael Edwardes, Thomas Baumgart
    email                : mte@users.sourceforge.net
                           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.                                   *
 *                                                                         *
 ***************************************************************************/

// ----------------------------------------------------------------------------
// QT Includes

#include <tqpixmap.h>

// ----------------------------------------------------------------------------
// KDE Includes

#include <tdeglobal.h>
#include <kstandarddirs.h>
#include <tdeapplication.h>
#include <kactivelabel.h>

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

#include "kendingbalancedlg.h"
#include <kmymoney/kmymoneyedit.h>
#include <kmymoney/mymoneysplit.h>
#include <kmymoney/mymoneyfile.h>
#include <kmymoney/kmymoneycategory.h>
#include <kmymoney/kmymoneyaccountselector.h>

#include "../dialogs/kcurrencycalculator.h"

class KEndingBalanceDlg::Private
{
public:
  MyMoneyTransaction        m_tInterest;
  MyMoneyTransaction        m_tCharges;
  MyMoneyAccount            m_account;
  TQMap<TQWidget*, TQString>   m_helpAnchor;
};

class KEndingBalanceLoanDlg::Private
{
public:
  MyMoneyTransaction        m_tInterest;
  MyMoneyTransaction        m_tCharges;
  MyMoneyAccountLoan        m_account;
  TQMap<TQWidget*, TQString>   m_helpAnchor;
};

KEndingBalanceDlg::KEndingBalanceDlg(const MyMoneyAccount& account, TQWidget *parent, const char *name) :
  KEndingBalanceDlgDecl(parent, name, true),
  d(new Private)
{
  TQString value;
  MyMoneyMoney endBalance, startBalance;

  d->m_account = account;

  MyMoneySecurity currency = MyMoneyFile::instance()->security(account.currencyId());
  m_enterInformationLabel->setText(TQString("<qt>")+i18n("Please enter the following fields with the information as you find them on your statement. Make sure to enter all values in <b>%1</b>.").arg(currency.name())+TQString("</qt>"));

  m_statementDate->setDate(TQDate::currentDate());

  // If the previous reconciliation was postponed,
  // we show a different first page
  value = account.value("lastReconciledBalance");
  if(value.isEmpty()) {
    // if the last statement has been entered long enough ago (more than one month),
    // then take the last statement date and add one month and use that as statement
    // date.
    TQDate lastStatementDate = account.lastReconciliationDate();
    if(lastStatementDate.addMonths(1) < TQDate::currentDate()) {
      m_statementDate->setDate(lastStatementDate.addMonths(1));
    }

    slotUpdateBalances();

    setAppropriate(m_startPageCheckings, true);
    setAppropriate(m_pagePreviousPostpone, false);
    setAppropriate(m_interestChargeCheckings, true);
    setFinishEnabled(m_interestChargeCheckings, true);
  } else {
    setAppropriate(m_startPageCheckings, false);
    setAppropriate(m_pagePreviousPostpone, true);
    removePage(m_interestChargeCheckings);
    setFinishEnabled(m_statementInfoPageCheckings, true);
    // make sure, we show the correct start page
    showPage(m_pagePreviousPostpone);

    MyMoneyMoney factor(1,1);
    if(d->m_account.accountGroup() == MyMoneyAccount::Liability)
      factor = -factor;

    startBalance = MyMoneyMoney(value)*factor;
    value = account.value("statementBalance");
    endBalance = MyMoneyMoney(value)*factor;

    m_previousBalance->setValue(startBalance);
    m_endingBalance->setValue(endBalance);
  }

  // We don't need to add the default into the list (see ::help() why)
  // m_helpAnchor[m_startPageCheckings] = TQString("");
  d->m_helpAnchor[m_interestChargeCheckings] = TQString("details.reconcile.wizard.interest");
  d->m_helpAnchor[m_statementInfoPageCheckings] = TQString("details.reconcile.wizard.statement");

  value = account.value("statementDate");
  if(!value.isEmpty())
    m_statementDate->setDate(TQDate::fromString(value, Qt::ISODate));

  m_lastStatementDate->setText(TQString());
  if(account.lastReconciliationDate().isValid()) {
    m_lastStatementDate->setText(i18n("Last reconciled statement: %1")
      .arg(TDEGlobal::locale()->formatDate(account.lastReconciliationDate(), true)));
  }

  // remove all unwanted pages
  removePage(m_startPageLoan);
  removePage(m_checkPaymentsPage);
  removePage(m_adjustmentTransactionPage);

  // connect the signals with the slots
  connect(MyMoneyFile::instance(), TQT_SIGNAL(dataChanged()), this, TQT_SLOT(slotReloadEditWidgets()));
  connect(m_payeeEdit, TQT_SIGNAL(createItem(const TQString&, TQString&)), this, TQT_SIGNAL(createPayee(const TQString&, TQString&)));
  connect(m_interestCategoryEdit, TQT_SIGNAL(createItem(const TQString&, TQString&)), this, TQT_SLOT(slotCreateInterestCategory(const TQString&, TQString&)));
  connect(m_chargesCategoryEdit, TQT_SIGNAL(createItem(const TQString&, TQString&)), this, TQT_SLOT(slotCreateChargesCategory(const TQString&, TQString&)));

  connect(m_interestEdit, TQT_SIGNAL(textChanged(const TQString&)), this, TQT_SLOT(slotCheckPageFinished(void)));
  connect(m_interestCategoryEdit, TQT_SIGNAL(textChanged(const TQString&)), this, TQT_SLOT(slotCheckPageFinished(void)));
  connect(m_chargesEdit, TQT_SIGNAL(textChanged(const TQString&)), this, TQT_SLOT(slotCheckPageFinished(void)));
  connect(m_chargesCategoryEdit, TQT_SIGNAL(textChanged(const TQString&)), this, TQT_SLOT(slotCheckPageFinished(void)));
  connect(m_statementDate, TQT_SIGNAL(dateChanged(const TQDate&)), this, TQT_SLOT(slotUpdateBalances()));

  slotReloadEditWidgets();

  // preset payee if possible
  try {
    // if we find a payee with the same name as the institution,
    // than this is what we use as payee.
    if(!d->m_account.institutionId().isEmpty()) {
      MyMoneyInstitution inst = MyMoneyFile::instance()->institution(d->m_account.institutionId());
      MyMoneyPayee payee = MyMoneyFile::instance()->payeeByName(inst.name());
      m_payeeEdit->setSelectedItem(payee.id());
    }
  } catch(MyMoneyException *e) {
    delete e;
  }
}

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

void KEndingBalanceDlg::slotUpdateBalances(void)
{
  MYMONEYTRACER(tracer);

  // determine the beginning balance and ending balance based on the following
  // forumulas:
  //
  // end balance   = current balance - sum(all non cleared transactions)
  //                                 - sum(all cleared transactions posted
  //                                        after statement date)
  // start balance = end balance - sum(all cleared transactions
  //                                        up to statement date)
  MyMoneyTransactionFilter filter(d->m_account.id());
  filter.addState(MyMoneyTransactionFilter::notReconciled);
  filter.addState(MyMoneyTransactionFilter::cleared);
  filter.setReportAllSplits(true);

  TQValueList<TQPair<MyMoneyTransaction, MyMoneySplit> > transactionList;
  TQValueList<TQPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it;

    // retrieve the list from the engine
  MyMoneyFile::instance()->transactionList(transactionList, filter);

  MyMoneyMoney balance = MyMoneyFile::instance()->balance(d->m_account.id());
  MyMoneyMoney factor(1,1);
  if(d->m_account.accountGroup() == MyMoneyAccount::Liability)
    factor = -factor;

  MyMoneyMoney endBalance, startBalance;
  balance = balance * factor;
  endBalance = startBalance = balance;

  tracer.printf("total balance = %s", endBalance.formatMoney("", 2).data());

  for(it = transactionList.begin(); it != transactionList.end(); ++it) {
    const MyMoneySplit& split = (*it).second;
    balance -= split.shares() * factor;
    if((*it).first.postDate() > m_statementDate->date()) {
      tracer.printf("Reducing balances by %s because postdate of %s/%s(%s) is past statement date", (split.shares() * factor).formatMoney("", 2).data(), (*it).first.id().data(),split.id().data(), (*it).first.postDate().toString(Qt::ISODate).data());
      endBalance -= split.shares() * factor;
      startBalance -= split.shares() * factor;
    } else {
      switch(split.reconcileFlag()) {
        case MyMoneySplit::NotReconciled:
          tracer.printf("Reducing balances by %s because %s/%s(%s) is not reconciled", (split.shares() * factor).formatMoney("", 2).data(), (*it).first.id().data(), split.id().data(), (*it).first.postDate().toString(Qt::ISODate).data());
          endBalance -= split.shares() * factor;
          startBalance -= split.shares() * factor;
          break;
        case MyMoneySplit::Cleared:
          tracer.printf("Reducing start balance by %s because %s/%s(%s) is cleared", (split.shares() * factor).formatMoney("", 2).data(), (*it).first.id().data(), split.id().data(), (*it).first.postDate().toString(Qt::ISODate).data());
          startBalance -= split.shares() * factor;
          break;
        default:
          break;
      }
    }
  }
  m_previousBalance->setValue(startBalance);
  m_endingBalance->setValue(endBalance);
  tracer.printf("total balance = %s", endBalance.formatMoney("", 2).data());
  tracer.printf("start balance = %s", startBalance.formatMoney("", 2).data());

  m_interestDateEdit->setDate(m_statementDate->date());
  m_chargesDateEdit->setDate(m_statementDate->date());
}

void KEndingBalanceDlg::accept(void)
{
  if(createTransaction(d->m_tInterest, -1, m_interestEdit, m_interestCategoryEdit, m_interestDateEdit)
  && createTransaction(d->m_tCharges, 1, m_chargesEdit, m_chargesCategoryEdit, m_chargesDateEdit))
    KEndingBalanceDlgDecl::accept();
}

void KEndingBalanceDlg::slotCreateInterestCategory(const TQString& txt, TQString& id)
{
  createCategory(txt, id, MyMoneyFile::instance()->income());
}

void KEndingBalanceDlg::slotCreateChargesCategory(const TQString& txt, TQString& id)
{
  createCategory(txt, id, MyMoneyFile::instance()->expense());
}

void KEndingBalanceDlg::createCategory(const TQString& txt, TQString& id, const MyMoneyAccount& parent)
{
  MyMoneyAccount acc;
  acc.setName(txt);

  emit createCategory(acc, parent);

  id = acc.id();
}

const MyMoneyMoney KEndingBalanceDlg::endingBalance(void) const
{
  return adjustedReturnValue(m_endingBalance->value());
}

const MyMoneyMoney KEndingBalanceDlg::previousBalance(void) const
{
  return adjustedReturnValue(m_previousBalance->value());
}

const MyMoneyMoney KEndingBalanceDlg::adjustedReturnValue(const MyMoneyMoney& v) const
{
  return d->m_account.accountGroup() == MyMoneyAccount::Liability ? -v : v;
}

void KEndingBalanceDlg::slotReloadEditWidgets(void)
{
  TQString payeeId, interestId, chargesId;

  // keep current selected items
  payeeId = m_payeeEdit->selectedItem();
  interestId = m_interestCategoryEdit->selectedItem();
  chargesId = m_chargesCategoryEdit->selectedItem();

  // load the payee and category widgets with data from the engine
  m_payeeEdit->loadPayees(MyMoneyFile::instance()->payeeList());

  // a user request to show all categories in both selectors due to a valid use case.
  AccountSet aSet;
  aSet.addAccountGroup(MyMoneyAccount::Expense);
  aSet.addAccountGroup(MyMoneyAccount::Income);
  aSet.load(m_interestCategoryEdit->selector());
  aSet.load(m_chargesCategoryEdit->selector());

  // reselect currently selected items
  if(!payeeId.isEmpty())
    m_payeeEdit->setSelectedItem(payeeId);
  if(!interestId.isEmpty())
    m_interestCategoryEdit->setSelectedItem(interestId);
  if(!chargesId.isEmpty())
    m_chargesCategoryEdit->setSelectedItem(chargesId);
}

void KEndingBalanceDlg::slotCheckPageFinished(void)
{
  nextButton()->setEnabled(true);
  finishButton()->setEnabled(true);

  if(currentPage() == m_interestChargeCheckings) {
    int cnt1, cnt2;
    cnt1 = !m_interestEdit->value().isZero() + !m_interestCategoryEdit->selectedItem().isEmpty();
    cnt2 = !m_chargesEdit->value().isZero() + !m_chargesCategoryEdit->selectedItem().isEmpty();
    if(cnt1 == 1 || cnt2 == 1) {
      finishButton()->setEnabled(false);
      nextButton()->setEnabled(false);
    }
  }
}

const MyMoneyTransaction KEndingBalanceDlg::interestTransaction(void)
{
  return d->m_tInterest;
}

const MyMoneyTransaction KEndingBalanceDlg::chargeTransaction(void)
{
  return d->m_tCharges;
}

bool KEndingBalanceDlg::createTransaction(MyMoneyTransaction &t, const int sign, kMyMoneyEdit *amountEdit, KMyMoneyCategory *categoryEdit, kMyMoneyDateInput* dateEdit)
{
  t = MyMoneyTransaction();

  if(!amountEdit->isValid() || categoryEdit->selectedItem().isEmpty() || !dateEdit->date().isValid())
    return true;

  MyMoneySplit s1, s2;
  MyMoneyMoney val = amountEdit->value() * MyMoneyMoney(sign, 1);
  try {
    t.setPostDate(dateEdit->date());
    t.setCommodity(d->m_account.currencyId());

    s1.setPayeeId(m_payeeEdit->selectedItem());
    s1.setReconcileFlag(MyMoneySplit::Cleared);
    s1.setAccountId(d->m_account.id());
    s1.setValue(-val);
    s1.setShares(-val);

    s2 = s1;
    s2.setAccountId(categoryEdit->selectedItem());
    s2.setValue(val);

    t.addSplit(s1);
    t.addSplit(s2);

    TQMap<TQString, MyMoneyMoney> priceInfo; // just empty
    MyMoneyMoney shares;
    if(!KCurrencyCalculator::setupSplitPrice(shares, t, s2, priceInfo, this)) {
      t = MyMoneyTransaction();
      return false;
    }

    s2.setShares(shares);
    t.modifySplit(s2);

  } catch(MyMoneyException *e) {
    tqDebug("%s", e->what().data());
    delete e;
    t = MyMoneyTransaction();
    return false;
  }

  return true;
}

void KEndingBalanceDlg::help(void)
{
  TQString anchor = d->m_helpAnchor[currentPage()];
  if(anchor.isEmpty())
    anchor = TQString("details.reconcile.whatis");

  kapp->invokeHelp(anchor);
}

KEndingBalanceLoanDlg::KEndingBalanceLoanDlg(const MyMoneyAccount& account, TQWidget *parent, const char *name) :
  KEndingBalanceDlgDecl(parent, name, true),
  d(new Private)
{
  d->m_account = account;
  TQDate value = account.lastReconciliationDate();
  if(value.isValid())
    m_startDateEdit->setDate(value.addDays(1));
  else
    m_startDateEdit->setDate(d->m_account.openingDate());

  // make sure, we show the correct start page
  showPage(m_startPageLoan);

  // enable the finish button on the last page
  setAppropriate(m_checkPaymentsPage, true);

  // remove all unwanted pages
  removePage(m_startPageCheckings);
  removePage(m_statementInfoPageCheckings);
  removePage(m_pagePreviousPostpone);
  removePage(m_interestChargeCheckings);

  // connect the signals with the slots
  connect(m_amortizationTotalEdit, TQT_SIGNAL(textChanged(const TQString&)), this, TQT_SLOT(slotCheckPageFinished(void)));
  connect(m_interestTotalEdit, TQT_SIGNAL(textChanged(const TQString&)), this, TQT_SLOT(slotCheckPageFinished(void)));
  connect(m_accountEdit, TQT_SIGNAL(stateChanged(void)), this, TQT_SLOT(slotCheckPageFinished(void)));
  connect(m_categoryEdit, TQT_SIGNAL(stateChanged(void)), this, TQT_SLOT(slotCheckPageFinished(void)));
}

KEndingBalanceLoanDlg::~KEndingBalanceLoanDlg()
{
}

void KEndingBalanceLoanDlg::slotCheckPageFinished(void)
{
  nextButton()->setEnabled(true);
  finishButton()->setEnabled(true);

  if(currentPage() == m_checkPaymentsPage) {
    MyMoneyMoney interest = totalInterest(m_startDateEdit->date(), m_endDateEdit->date());
    MyMoneyMoney amortization = totalAmortization(m_startDateEdit->date(), m_endDateEdit->date());

    if(interest == m_interestTotalEdit->value()
    && amortization == m_amortizationTotalEdit->value()) {
      if(indexOf(m_adjustmentTransactionPage) != -1) {
        removePage(m_adjustmentTransactionPage);
        // the following line forces to update the buttons
        showPage(m_checkPaymentsPage);
        nextButton()->setEnabled(true);
        finishButton()->setEnabled(true);
      }
    } else {
      if(indexOf(m_adjustmentTransactionPage) == -1) {
        addPage(m_adjustmentTransactionPage, i18n("Adjustment transaction"));
        // the following line forces to update the buttons
        showPage(m_checkPaymentsPage);
      }
    }
  } else if(currentPage() == m_adjustmentTransactionPage) {
    if(m_accountEdit->selectedItems().count() == 0) {
      nextButton()->setEnabled(false);
      finishButton()->setEnabled(false);

    } else if(m_categoryEdit->isEnabled()
    && m_categoryEdit->selectedItems().count() == 0) {
      nextButton()->setEnabled(false);
      finishButton()->setEnabled(false);
    }
  }
}

const MyMoneyMoney KEndingBalanceLoanDlg::totalInterest(const TQDate& start, const TQDate& end) const
{
  MyMoneyMoney  interest;
  MyMoneyTransactionFilter  filter(d->m_account.id());
  filter.setDateFilter(start, end);

  TQValueList<MyMoneyTransaction> list = MyMoneyFile::instance()->transactionList(filter);
  TQValueList<MyMoneyTransaction>::ConstIterator it_t;

  for(it_t = list.begin(); it_t != list.end(); ++it_t) {
    TQValueList<MyMoneySplit>::ConstIterator it_s;
    for(it_s = (*it_t).splits().begin(); it_s != (*it_t).splits().end(); ++it_s) {
      if((*it_s).action() == MyMoneySplit::ActionInterest) {
        interest += (*it_s).value();
      }
    }
  }
  return interest;
}

const MyMoneyMoney KEndingBalanceLoanDlg::totalAmortization(const TQDate& start, const TQDate& end) const
{
  MyMoneyMoney  amortization;
  MyMoneyMoney  adjust(1,1);
  MyMoneyTransactionFilter  filter(d->m_account.id());
  filter.setDateFilter(start, end);

  if(d->m_account.accountType() == MyMoneyAccount::AssetLoan)
    adjust = -adjust;

  TQValueList<MyMoneyTransaction> list = MyMoneyFile::instance()->transactionList(filter);
  TQValueList<MyMoneyTransaction>::ConstIterator it_t;

  for(it_t = list.begin(); it_t != list.end(); ++it_t) {
    TQValueList<MyMoneySplit>::ConstIterator it_s;
    for(it_s = (*it_t).splits().begin(); it_s != (*it_t).splits().end(); ++it_s) {
      if((*it_s).accountId() == d->m_account.id()
      && (*it_s).action() == MyMoneySplit::ActionAmortization
      && ((*it_s).value() * MyMoneyMoney(adjust, 1)).isPositive()) {
        amortization += (*it_s).value();
      }
    }
  }
  // make sure to return a positive number
  return amortization * adjust;
}

void KEndingBalanceLoanDlg::next(void)
{
  bool dontLeavePage = false;

  if(currentPage() == m_startPageLoan) {
    MyMoneyMoney interest = totalInterest(m_startDateEdit->date(), m_endDateEdit->date());
    MyMoneyMoney amortization = totalAmortization(m_startDateEdit->date(), m_endDateEdit->date());

    m_loanOverview->setText(i18n("KMyMoney has calculated the following amounts for "
                                 "interest and amortization according to recorded payments "
                                 "between %1 and %2.")
                                 .arg(TDEGlobal::locale()->formatDate(m_startDateEdit->date(), true))
                                 .arg(TDEGlobal::locale()->formatDate(m_endDateEdit->date(), true)));

    // preload widgets with calculated values if they are empty
    if(m_amortizationTotalEdit->value().isZero() && !amortization.isZero())
      m_amortizationTotalEdit->setValue(amortization);
    if(m_interestTotalEdit->value().isZero() && !interest.isZero())
      m_interestTotalEdit->setValue(interest);

  } else if(currentPage() == m_checkPaymentsPage) {
    AccountSet assetSet, incomeSet;
    assetSet.addAccountGroup(MyMoneyAccount::Asset);
    incomeSet.addAccountGroup(MyMoneyAccount::Income);
    assetSet.load(m_accountEdit);
    incomeSet.load(m_categoryEdit);
#if 0
    m_accountEdit->loadList(static_cast<KMyMoneyUtils::categoryTypeE>(KMyMoneyUtils::asset | KMyMoneyUtils::liability));
    m_categoryEdit->loadList(static_cast<KMyMoneyUtils::categoryTypeE>(KMyMoneyUtils::income | KMyMoneyUtils::expense));
#endif
    m_categoryEdit->setEnabled(false);

    MyMoneyMoney interest = totalInterest(m_startDateEdit->date(), m_endDateEdit->date());
    if(interest != m_interestTotalEdit->value()) {
      m_categoryEdit->setEnabled(true);
    }
  }

  if(!dontLeavePage)
    KEndingBalanceDlgDecl::next();

  slotCheckPageFinished();
}

const MyMoneyTransaction KEndingBalanceLoanDlg::adjustmentTransaction(void) const
{
  MyMoneyTransaction t;

  MyMoneyMoney interest = totalInterest(m_startDateEdit->date(), m_endDateEdit->date());
  MyMoneyMoney amortization = totalAmortization(m_startDateEdit->date(), m_endDateEdit->date());

  if(interest != m_interestTotalEdit->value()
  || amortization != m_amortizationTotalEdit->value()) {
    MyMoneySplit sAccount, sAmortization, sInterest;
    int          adjust = 1;

    if(d->m_account.accountType() == MyMoneyAccount::AssetLoan)
      adjust = -1;

    // fix sign if asset
    interest = interest * MyMoneyMoney(adjust,1);
    amortization = amortization * MyMoneyMoney(adjust,1);

    sAmortization.setValue((m_amortizationTotalEdit->value() - amortization) * MyMoneyMoney(adjust,1));
    sInterest.setValue((m_interestTotalEdit->value() - interest) * MyMoneyMoney(adjust,1));
    sAccount.setValue( -(sAmortization.value() + sInterest.value()));

    try {
      sAmortization.setAccountId(d->m_account.id());
      sAmortization.setPayeeId(d->m_account.payee());
      sAccount.setAccountId(m_accountEdit->selectedItems()[0]);
      sAccount.setPayeeId(d->m_account.payee());
      if(m_categoryEdit->isEnabled())
        sInterest.setAccountId(m_categoryEdit->selectedItems()[0]);

      sAccount.setMemo(i18n("Adjustment transaction"));
      sAmortization.setMemo(sAccount.memo());
      sInterest.setMemo(sAccount.memo());

      sAccount.setAction(MyMoneySplit::ActionAmortization);
      sAmortization.setAction(MyMoneySplit::ActionAmortization);
      sInterest.setAction(MyMoneySplit::ActionInterest);

      t.addSplit(sAccount);
      t.addSplit(sAmortization);
      if(!sInterest.value().isZero())
        t.addSplit(sInterest);

      t.setPostDate(m_endDateEdit->date());

    } catch(MyMoneyException *e) {
      tqDebug("Unable to create adjustment transaction for loan reconciliation: %s", e->what().data());
      delete e;
      return MyMoneyTransaction();
    }
  }
  return t;
}

void KEndingBalanceLoanDlg::help(void)
{
  TQString anchor = d->m_helpAnchor[currentPage()];
  if(anchor.isEmpty())
    anchor = TQString("details.reconcile.whatis");

  kapp->invokeHelp(anchor);
}

#include "kendingbalancedlg.moc"

