/***************************************************************************
                          mymoneyscheduled.cpp
                             -------------------
    copyright            : (C) 2000-2002 by Michael Edwardes
                           (C) 2007 by Thomas Baumgart
    email                : mte@users.sourceforge.net
                           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.                                   *
 *                                                                         *
 ***************************************************************************/
// ----------------------------------------------------------------------------
// QT Includes

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

#include <tdelocale.h>

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

#include "mymoneyscheduled.h"
#include "mymoneyexception.h"
#include "mymoneyfile.h"

MyMoneySchedule::MyMoneySchedule() :
  MyMoneyObject()
{
  // Set up the default values
  m_occurence = OCCUR_ANY;
  m_occurenceMultiplier = 1;
  m_type = TYPE_ANY;
  m_paymentType = STYPE_ANY;
  m_fixed = false;
  m_autoEnter = false;
  m_startDate = TQDate();
  m_endDate = TQDate();
  m_lastPayment = TQDate();
  m_weekendOption = MoveNothing;
}

MyMoneySchedule::MyMoneySchedule(const TQString& name, typeE type,
                                 occurenceE occurence, int occurenceMultiplier,
                                 paymentTypeE paymentType,
                                 const TQDate& /* startDate */,
                                 const TQDate& endDate,
                                 bool fixed, bool autoEnter) :
  MyMoneyObject()
{
  // Set up the default values
  m_name = name;
  m_occurence = occurence;
  m_occurenceMultiplier = occurenceMultiplier;
  simpleToCompoundOccurence(m_occurenceMultiplier,m_occurence);
  m_type = type;
  m_paymentType = paymentType;
  m_fixed = fixed;
  m_autoEnter = autoEnter;
  m_startDate = TQDate();
  m_endDate = endDate;
  m_lastPayment = TQDate();
  m_weekendOption = MoveNothing;
}

MyMoneySchedule::MyMoneySchedule(const TQDomElement& node) :
  MyMoneyObject(node)
{
  if("SCHEDULED_TX" != node.tagName())
    throw new MYMONEYEXCEPTION("Node was not SCHEDULED_TX");

  m_name = node.attribute("name");
  m_startDate = stringToDate(node.attribute("startDate"));
  m_endDate = stringToDate(node.attribute("endDate"));
  m_lastPayment = stringToDate(node.attribute("lastPayment"));

  m_type = static_cast<MyMoneySchedule::typeE>(node.attribute("type").toInt());
  m_paymentType = static_cast<MyMoneySchedule::paymentTypeE>(node.attribute("paymentType").toInt());
  m_occurence = static_cast<MyMoneySchedule::occurenceE>(node.attribute("occurence").toInt());
  m_occurenceMultiplier = node.attribute("occurenceMultiplier", "1").toInt();
  // Convert to compound occurence
  simpleToCompoundOccurence(m_occurenceMultiplier,m_occurence);
  m_autoEnter = static_cast<bool>(node.attribute("autoEnter").toInt());
  m_fixed = static_cast<bool>(node.attribute("fixed").toInt());
  m_weekendOption = static_cast<MyMoneySchedule::weekendOptionE>(node.attribute("weekendOption").toInt());

  // read in the associated transaction
  TQDomNodeList nodeList = node.elementsByTagName("TRANSACTION");
  if(nodeList.count() == 0)
    throw new MYMONEYEXCEPTION("SCHEDULED_TX has no TRANSACTION node");

  setTransaction(MyMoneyTransaction(nodeList.item(0).toElement(), false), true);

  // some old versions did not remove the entry date and post date fields
  // in the schedule. So if this is the case, we deal with a very old transaction
  // and can't use the post date field as next due date. Hence, we wipe it out here
  if(m_transaction.entryDate().isValid()) {
    m_transaction.setPostDate(TQDate());
    m_transaction.setEntryDate(TQDate());
  }

  // readin the recorded payments
  nodeList = node.elementsByTagName("PAYMENTS");
  if(nodeList.count() > 0) {
    nodeList = nodeList.item(0).toElement().elementsByTagName("PAYMENT");
    for(unsigned int i = 0; i < nodeList.count(); ++i) {
      m_recordedPayments << stringToDate(nodeList.item(i).toElement().attribute("date"));
    }
  }

  // if the next due date is not set (comes from old version)
  // then set it up the old way
  if(!nextDueDate().isValid() && !m_lastPayment.isValid()) {
    m_transaction.setPostDate(m_startDate);
    // clear it, because the schedule has never been used
    m_startDate = TQDate();
  }

  // There are reports that lastPayment and nextDueDate are identical or
  // that nextDueDate is older than lastPayment. This could
  // be caused by older versions of the application. In this case, we just
  // clear out the nextDueDate and let it calculate from the lastPayment.
  if(nextDueDate().isValid() && nextDueDate() <= m_lastPayment) {
    m_transaction.setPostDate(TQDate());
  }

  if(!nextDueDate().isValid()) {
    m_transaction.setPostDate(m_startDate);
    m_transaction.setPostDate(nextPayment(m_lastPayment.addDays(1)));
  }
}

MyMoneySchedule::MyMoneySchedule(const TQString& id, const MyMoneySchedule& right) :
  MyMoneyObject(id)
{
  *this = right;
  setId(id);
}

MyMoneySchedule::occurenceE MyMoneySchedule::occurence(void) const
{
  MyMoneySchedule::occurenceE occ = m_occurence;
  int mult = m_occurenceMultiplier;
  compoundToSimpleOccurence(mult, occ);
  return occ;
}

void MyMoneySchedule::setStartDate(const TQDate& date)
{
  m_startDate = date;
}

void MyMoneySchedule::setPaymentType(paymentTypeE type)
{
  m_paymentType = type;
}

void MyMoneySchedule::setFixed(bool fixed)
{
  m_fixed = fixed;
}

void MyMoneySchedule::setTransaction(const MyMoneyTransaction& transaction)
{
  setTransaction(transaction, false);
}

void MyMoneySchedule::setTransaction(const MyMoneyTransaction& transaction, bool noDateCheck)
{
  MyMoneyTransaction t = transaction;
  if(!noDateCheck) {
    // don't allow a transaction that has no due date
    // if we get something like that, then we use the
    // the current next due date. If that is also invalid
    // we can't help it.
    if(!t.postDate().isValid()) {
      t.setPostDate(m_transaction.postDate());
    }

    if(!t.postDate().isValid())
      return;
  }

  // make sure to clear out some unused information in scheduled transactions
  // we need to do this for the case that the transaction passed as argument
  // is a matched or imported transaction.
  TQValueList<MyMoneySplit> splits = t.splits();
  if(splits.count() > 0) {
    TQValueList<MyMoneySplit>::const_iterator it_s;
    for(it_s = splits.begin(); it_s != splits.end(); ++it_s) {
      MyMoneySplit s = *it_s;
      // clear out the bankID
      if(!(*it_s).bankID().isEmpty()) {
        s.setBankID(TQString());
        t.modifySplit(s);
      }

      // only clear payees from second split onwards
      if(it_s == splits.begin())
        continue;

      if(!(*it_s).payeeId().isEmpty()) {
        // but only if the split references an income/expense category
        MyMoneyFile* file = MyMoneyFile::instance();
        // some unit tests don't have a storage attached, so we
        // simply skip the test
        // Don't check for accounts with an id of 'Phony-ID' which is used
        // internally for non-existing accounts (during creation of accounts)
        if(file->storageAttached() && s.accountId() != TQString("Phony-ID")) {
          MyMoneyAccount acc = file->account(s.accountId());
          if(acc.isIncomeExpense()) {
            s.setPayeeId(TQString());
            t.modifySplit(s);
          }
        }
      }
    }
  }

  m_transaction = t;
  // make sure that the transaction does not have an id so that we can enter
  // it into the engine
  m_transaction.clearId();
}

void MyMoneySchedule::setEndDate(const TQDate& date)
{
  m_endDate = date;
}

void MyMoneySchedule::setAutoEnter(bool autoenter)
{
  m_autoEnter = autoenter;
}

const TQDate& MyMoneySchedule::startDate(void) const
{
  if(m_startDate.isValid())
    return m_startDate;
  return nextDueDate();
}

const TQDate& MyMoneySchedule::nextDueDate(void) const
{
  return m_transaction.postDate();
}

TQDate MyMoneySchedule::adjustedNextDueDate(void) const
{
  if(isFinished())
    return TQDate();

  return adjustedDate(nextDueDate(), weekendOption());
}

TQDate MyMoneySchedule::adjustedDate(TQDate date, weekendOptionE option) const
{
  if (option == MyMoneySchedule::MoveNothing)
    return date;

  int step = 1;
  if (option == MyMoneySchedule::MoveFriday)
    step = -1;

  while (date.dayOfWeek() > 5)
    date = date.addDays(step);

  return date;
}

void MyMoneySchedule::setNextDueDate(const TQDate& date)
{
  if(date.isValid()) {
    m_transaction.setPostDate(date);
    m_startDate = date;
  }
}

void MyMoneySchedule::setLastPayment(const TQDate& date)
{
  // Delete all payments older than date
  TQValueList<TQDate>::Iterator it;
  TQValueList<TQDate> delList;

  for (it=m_recordedPayments.begin(); it!=m_recordedPayments.end(); ++it)
  {
    if (*it < date || !date.isValid())
      delList.append(*it);
  }

  for (it=delList.begin(); it!=delList.end(); ++it)
  {
    m_recordedPayments.remove(*it);
  }

  m_lastPayment = date;
  if(!m_startDate.isValid())
    m_startDate = date;
}

void MyMoneySchedule::setName(const TQString& nm)
{
  m_name = nm;
}

void MyMoneySchedule::setOccurence(occurenceE occ)
{
  MyMoneySchedule::occurenceE occ2 = occ;
  int mult = 1;
  simpleToCompoundOccurence(mult, occ2);
  setOccurencePeriod( occ2 );
  setOccurenceMultiplier( mult );
}

void MyMoneySchedule::setOccurencePeriod(occurenceE occ)
{
  m_occurence = occ;
}

void MyMoneySchedule::setOccurenceMultiplier(int occmultiplier)
{
  m_occurenceMultiplier = occmultiplier < 1 ? 1 : occmultiplier;
}

void MyMoneySchedule::setType(typeE type)
{
  m_type = type;
}

void MyMoneySchedule::validate(bool id_check) const
{
  /* Check the supplied instance is valid...
   *
   * To be valid it must not have the id set and have the following fields set:
   *
   * m_occurence
   * m_type
   * m_startDate
   * m_paymentType
   * m_transaction
   *   the transaction must contain at least one split (two is better ;-)  )
   */
  if (id_check && !m_id.isEmpty())
    throw new MYMONEYEXCEPTION("ID for schedule not empty when required");

  if(m_occurence == OCCUR_ANY)
    throw new MYMONEYEXCEPTION("Invalid occurence type for schedule");

  if(m_type == TYPE_ANY)
    throw new MYMONEYEXCEPTION("Invalid type for schedule");

  if(!nextDueDate().isValid())
    throw new MYMONEYEXCEPTION("Invalid next due date for schedule");

  if(m_paymentType == STYPE_ANY)
    throw new MYMONEYEXCEPTION("Invalid payment type for schedule");

  if(m_transaction.splitCount() == 0)
    throw new MYMONEYEXCEPTION("Scheduled transaction does not contain splits");

  // Check the payment types
  switch (m_type)
  {
    case TYPE_BILL:
      if (m_paymentType == STYPE_DIRECTDEPOSIT || m_paymentType == STYPE_MANUALDEPOSIT)
        throw new MYMONEYEXCEPTION("Invalid payment type for bills");
      break;

    case TYPE_DEPOSIT:
      if (m_paymentType == STYPE_DIRECTDEBIT || m_paymentType == STYPE_WRITECHEQUE)
        throw new MYMONEYEXCEPTION("Invalid payment type for deposits");
      break;

    case TYPE_ANY:
      throw new MYMONEYEXCEPTION("Invalid type ANY");
      break;

    case TYPE_TRANSFER:
//        if (m_paymentType == STYPE_DIRECTDEPOSIT || m_paymentType == STYPE_MANUALDEPOSIT)
//          return false;
      break;

    case TYPE_LOANPAYMENT:
      break;
  }
}

TQDate MyMoneySchedule::adjustedNextPayment(const TQDate& refDate) const
{
  TQDate date(nextPayment(refDate));
  return date.isValid() ? adjustedDate(date, weekendOption()) : date;
}

TQDate MyMoneySchedule::nextPayment(const TQDate& refDate) const
{
  // if the enddate is valid and it is before the reference date,
  // then there will be no more payments.
  if(m_endDate.isValid() && m_endDate < refDate) {
    return TQDate();
  }

  TQDate paymentDate(nextDueDate());

  if(refDate >= paymentDate) {
    switch (m_occurence)
    {
      case OCCUR_ONCE:
        // if the lastPayment is already set, then there will be no more payments
        // otherwise, the start date is the payment date
        if(m_lastPayment.isValid())
          return TQDate();
        // if the only payment should have been prior to the reference date,
        // then don't show it
        if(paymentDate < refDate)
          return TQDate();
        break;

      case OCCUR_DAILY:
        paymentDate = refDate.addDays(m_occurenceMultiplier);
        break;

      case OCCUR_WEEKLY:
        {
          int step = 7*m_occurenceMultiplier;
          do {
            paymentDate = paymentDate.addDays(step);
          }
          while (paymentDate <= refDate);
        }
        break;

      case OCCUR_EVERYHALFMONTH:
        do
        {
           paymentDate = addHalfMonths(paymentDate,m_occurenceMultiplier);
        }
        while (paymentDate <= refDate);
        break;

      case OCCUR_MONTHLY:
        do {
          paymentDate = paymentDate.addMonths(m_occurenceMultiplier);
          fixDate(paymentDate);
        }
        while (paymentDate <= refDate);
        break;

      case OCCUR_YEARLY:
        do {
          paymentDate = paymentDate.addYears(m_occurenceMultiplier);
          fixDate(paymentDate);
        }
        while (paymentDate <= refDate);
        break;

      case OCCUR_ANY:
      default:
        paymentDate = TQDate();
        break;
    }
  }
  if(paymentDate.isValid()) {
    if(m_endDate.isValid() && paymentDate > m_endDate)
      paymentDate = TQDate();
  }

  if (paymentDate.isValid() && m_recordedPayments.contains(paymentDate))
    paymentDate = nextPayment(paymentDate);

  return paymentDate;
}

TQValueList<TQDate> MyMoneySchedule::paymentDates(const TQDate& _startDate, const TQDate& _endDate) const
{
  TQDate paymentDate(nextDueDate());
  TQValueList<TQDate> theDates;

  TQDate endDate(_endDate);
  if ( willEnd() && m_endDate < endDate )
    endDate = m_endDate;

  weekendOptionE option(weekendOption());
  TQDate start_date(adjustedDate(startDate(), option));
  // if the period specified by the parameters and the period
  // defined for this schedule don't overlap, then the list remains empty
  if ((willEnd() && m_endDate < _startDate)
  || start_date > endDate)
    return theDates;

  TQDate date(adjustedDate(paymentDate, option));

  switch (m_occurence)
  {
    case OCCUR_ONCE:
      if (start_date >= _startDate && start_date <= endDate)
        theDates.append(start_date);
      break;

    case OCCUR_DAILY:
      while (date.isValid() && (date <= endDate))
      {
        if (date >= _startDate)
          theDates.append(date);
        paymentDate = paymentDate.addDays(m_occurenceMultiplier);
        date = adjustedDate(paymentDate, option);
      }
      break;

    case OCCUR_WEEKLY:
      {
        int step = 7*m_occurenceMultiplier;
        while (date.isValid() && (date <= endDate))
        {
          if (date >= _startDate)
            theDates.append(date);
          paymentDate = paymentDate.addDays(step);
          date = adjustedDate(paymentDate, option);
        }
      }
      break;

    case OCCUR_EVERYHALFMONTH:
      while (date.isValid() && (date <= endDate))
      {
        if (date >= _startDate)
          theDates.append(date);
        paymentDate = addHalfMonths(paymentDate,m_occurenceMultiplier);
        date = adjustedDate(paymentDate, option);
      }
      break;

    case OCCUR_MONTHLY:
      while (date.isValid() && (date <= endDate))
      {
        if (date >= _startDate)
          theDates.append(date);
        paymentDate = paymentDate.addMonths(m_occurenceMultiplier);
        fixDate(paymentDate);
        date = adjustedDate(paymentDate, option);
      }
      break;

    case OCCUR_YEARLY:
      while (date.isValid() && (date <= endDate))
      {
        if (date >= _startDate)
          theDates.append(date);
        paymentDate = paymentDate.addYears(m_occurenceMultiplier);
        fixDate(paymentDate);
        date = adjustedDate(paymentDate, option);
      }
      break;

    case OCCUR_ANY:
    default:
      break;
  }

  return theDates;
}

bool MyMoneySchedule::operator <(const MyMoneySchedule& right) const
{
  return adjustedNextDueDate() < right.adjustedNextDueDate();
}

bool MyMoneySchedule::operator ==(const MyMoneySchedule& right) const
{
  if (  MyMoneyObject::operator==(right) &&
        m_occurence == right.m_occurence &&
        m_occurenceMultiplier == right.m_occurenceMultiplier &&
        m_type == right.m_type &&
        m_startDate == right.m_startDate &&
        m_paymentType == right.m_paymentType &&
        m_fixed == right.m_fixed &&
        m_transaction == right.m_transaction &&
        m_endDate == right.m_endDate &&
        m_autoEnter == right.m_autoEnter &&
        m_lastPayment == right.m_lastPayment &&
        ((m_name.length() == 0 && right.m_name.length() == 0) || (m_name == right.m_name)))
    return true;
  return false;
}

int MyMoneySchedule::transactionsRemaining(void) const
{
  int counter=0;

  if (m_endDate.isValid())
  {
    TQValueList<TQDate> dates = paymentDates(m_lastPayment, m_endDate);
    // Dont include the last payment so -1
    counter = dates.count();
  }
  return counter;
}

MyMoneyAccount MyMoneySchedule::account(int cnt) const
{
  TQValueList<MyMoneySplit> splits = m_transaction.splits();
  TQValueList<MyMoneySplit>::ConstIterator it;
  MyMoneyFile* file = MyMoneyFile::instance();
  MyMoneyAccount acc;

  // search the first asset or liability account
  for(it = splits.begin(); it != splits.end() && (acc.id().isEmpty() || cnt); ++it) {
    try {
      acc = file->account((*it).accountId());
      if(acc.isAssetLiability())
          --cnt;

      if(!cnt)
        return acc;
    } catch(MyMoneyException *e) {
      tqWarning("Schedule '%s' references unknown account '%s'", id().data(),   (*it).accountId().data());
      delete e;
      return MyMoneyAccount();
    }
  }

  return MyMoneyAccount();
}

TQDate MyMoneySchedule::dateAfter(int transactions) const
{
  int counter=1;
  TQDate paymentDate(startDate());

  if (transactions<=0)
    return paymentDate;

  switch (m_occurence)
  {
    case OCCUR_ONCE:
      break;

    case OCCUR_DAILY:
      while (counter++ < transactions)
        paymentDate = paymentDate.addDays(m_occurenceMultiplier);
      break;

    case OCCUR_WEEKLY:
      {
        int step = 7 * m_occurenceMultiplier;
        while (counter++ < transactions)
          paymentDate = paymentDate.addDays(step);
      }
      break;

    case OCCUR_EVERYHALFMONTH:
      paymentDate = addHalfMonths(paymentDate,m_occurenceMultiplier*(transactions-1));
      break;

    case OCCUR_MONTHLY:
      while (counter++ < transactions)
        paymentDate = paymentDate.addMonths(m_occurenceMultiplier);
      break;

    case OCCUR_YEARLY:
      while (counter++ < transactions)
        paymentDate = paymentDate.addYears(m_occurenceMultiplier);
      break;

    case OCCUR_ANY:
    default:
      break;
  }

  return paymentDate;
}

bool MyMoneySchedule::isOverdue() const
{
  if (isFinished())
    return false;

  if(adjustedNextDueDate() >= TQDate::currentDate())
    return false;

  return true;
}

bool MyMoneySchedule::isFinished() const
{
  if(!m_lastPayment.isValid())
    return false;

  if (m_endDate.isValid()) {
    if(m_lastPayment >= m_endDate
    || !nextDueDate().isValid()
    || nextDueDate() > m_endDate)
      return true;
  }

  // Check to see if its a once off payment
  if (m_occurence == MyMoneySchedule::OCCUR_ONCE)
    return true;

  return false;
}

bool MyMoneySchedule::hasRecordedPayment(const TQDate& date) const
{
  // m_lastPayment should always be > recordedPayments()
  if (m_lastPayment.isValid() && m_lastPayment >= date)
    return true;

  if (m_recordedPayments.contains(date))
    return true;

  return false;
}

void MyMoneySchedule::recordPayment(const TQDate& date)
{
  m_recordedPayments.append(date);
}

void MyMoneySchedule::setWeekendOption(const weekendOptionE option)
{
  // make sure only valid values are used. Invalid defaults to MoveNothing.
  switch(option) {
    case MoveFriday:
    case MoveMonday:
      m_weekendOption = option;
      break;

    default:
      m_weekendOption = MoveNothing;
      break;
  }
}

void MyMoneySchedule::fixDate(TQDate& date) const
{
  TQDate fixDate(m_startDate);
  if(fixDate.isValid()
  && date.day() != fixDate.day()
  && TQDate::isValid(date.year(), date.month(), fixDate.day())) {
    date.setYMD(date.year(), date.month(), fixDate.day());
  }
}

void MyMoneySchedule::writeXML(TQDomDocument& document, TQDomElement& parent) const
{
  TQDomElement el = document.createElement("SCHEDULED_TX");

  writeBaseXML(document, el);

  el.setAttribute("name", m_name);
  el.setAttribute("type", m_type);
  el.setAttribute("occurence", m_occurence);
  el.setAttribute("occurenceMultiplier", m_occurenceMultiplier);
  el.setAttribute("paymentType", m_paymentType);
  el.setAttribute("startDate", dateToString(m_startDate));
  el.setAttribute("endDate", dateToString(m_endDate));
  el.setAttribute("fixed", m_fixed);
  el.setAttribute("autoEnter", m_autoEnter);
  el.setAttribute("lastPayment", dateToString(m_lastPayment));
  el.setAttribute("weekendOption", m_weekendOption);

  //store the payment history for this scheduled task.
  TQValueList<TQDate> payments = recordedPayments();
  TQValueList<TQDate>::ConstIterator it;
  TQDomElement paymentsElement = document.createElement("PAYMENTS");
  for (it=payments.begin(); it!=payments.end(); ++it) {
    TQDomElement paymentEntry = document.createElement("PAYMENT");
    paymentEntry.setAttribute("date", dateToString(*it));
    paymentsElement.appendChild(paymentEntry);
  }
  el.appendChild(paymentsElement);

  //store the transaction data for this task.
  m_transaction.writeXML(document, el);

  parent.appendChild(el);
}

bool MyMoneySchedule::hasReferenceTo(const TQString& id) const
{
  return m_transaction.hasReferenceTo(id);
}

TQString MyMoneySchedule::occurenceToString() const
{
  return occurenceToString( occurenceMultiplier(), occurencePeriod() );
}

TQString MyMoneySchedule::occurenceToString(occurenceE occurence)
{
  TQString occurenceString = I18N_NOOP("Any");

  if(occurence == MyMoneySchedule::OCCUR_ONCE)
    occurenceString = I18N_NOOP("Once");
  else if(occurence == MyMoneySchedule::OCCUR_DAILY)
    occurenceString = I18N_NOOP("Daily");
  else if(occurence == MyMoneySchedule::OCCUR_WEEKLY)
    occurenceString = I18N_NOOP("Weekly");
  else if(occurence == MyMoneySchedule::OCCUR_FORTNIGHTLY)
    occurenceString = I18N_NOOP("Fortnightly");
  else if(occurence == MyMoneySchedule::OCCUR_EVERYOTHERWEEK)
    occurenceString = I18N_NOOP("Every other week");
  else if(occurence == MyMoneySchedule::OCCUR_EVERYHALFMONTH)
    occurenceString = I18N_NOOP("Every half month");
  else if(occurence == MyMoneySchedule::OCCUR_EVERYTHREEWEEKS)
    occurenceString = I18N_NOOP("Every three weeks");
  else if(occurence == MyMoneySchedule::OCCUR_EVERYFOURWEEKS)
    occurenceString = I18N_NOOP("Every four weeks");
  else if(occurence == MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS)
    occurenceString = I18N_NOOP("Every thirty days");
  else if(occurence == MyMoneySchedule::OCCUR_MONTHLY)
    occurenceString = I18N_NOOP("Monthly");
  else if(occurence == MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS)
    occurenceString = I18N_NOOP("Every eight weeks");
  else if(occurence == MyMoneySchedule::OCCUR_EVERYOTHERMONTH)
    occurenceString = I18N_NOOP("Every two months");
  else if(occurence == MyMoneySchedule::OCCUR_EVERYTHREEMONTHS)
    occurenceString = I18N_NOOP("Every three months");
  else if(occurence == MyMoneySchedule::OCCUR_QUARTERLY)
    occurenceString = I18N_NOOP("Quarterly");
  else if(occurence == MyMoneySchedule::OCCUR_EVERYFOURMONTHS)
    occurenceString = I18N_NOOP("Every four months");
  else if(occurence == MyMoneySchedule::OCCUR_TWICEYEARLY)
    occurenceString = I18N_NOOP("Twice yearly");
  else if(occurence == MyMoneySchedule::OCCUR_YEARLY)
    occurenceString = I18N_NOOP("Yearly");
  else if(occurence == MyMoneySchedule::OCCUR_EVERYOTHERYEAR)
    occurenceString = I18N_NOOP("Every other year");
  return occurenceString;
}

TQString MyMoneySchedule::occurenceToString(int mult, occurenceE type)
{
  TQString occurenceString = I18N_NOOP("Any");

  if (type == MyMoneySchedule::OCCUR_ONCE)
    switch (mult)
    {
      case 1: occurenceString = I18N_NOOP("Once"); break;
      default: occurenceString = I18N_NOOP(static_cast<TQString>("%1 times").arg(mult));
    }
  else if(type == MyMoneySchedule::OCCUR_DAILY)
    switch (mult)
    {
      case 1: occurenceString = I18N_NOOP("Daily"); break;
      case 30: occurenceString = I18N_NOOP("Every thirty days"); break;
      default: occurenceString = I18N_NOOP(static_cast<TQString>("Every %1 days").arg(mult));
    }
  else if(type == MyMoneySchedule::OCCUR_WEEKLY)
    switch (mult)
    {
      case 1: occurenceString = I18N_NOOP("Weekly"); break;
      case 2: occurenceString = I18N_NOOP("Every other week"); break;
      case 3: occurenceString = I18N_NOOP("Every three weeks"); break;
      case 4: occurenceString = I18N_NOOP("Every four weeks"); break;
      case 8: occurenceString = I18N_NOOP("Every eight weeks"); break;
      default: occurenceString = I18N_NOOP(static_cast<TQString>("Every %1 weeks").arg(mult));
    }
  else if(type == MyMoneySchedule::OCCUR_EVERYHALFMONTH)
    switch (mult)
    {
      case 1: occurenceString = I18N_NOOP("Every half month"); break;
      default: occurenceString = I18N_NOOP(static_cast<TQString>("Every %1 half months").arg(mult));
    }
  else if(type == MyMoneySchedule::OCCUR_MONTHLY)
    switch (mult)
    {
      case 1: occurenceString = I18N_NOOP("Monthly"); break;
      case 2: occurenceString = I18N_NOOP("Every two months"); break;
      case 3: occurenceString = I18N_NOOP("Every three months"); break;
      case 4: occurenceString = I18N_NOOP("Every four months"); break;
      case 6: occurenceString = I18N_NOOP("Twice yearly"); break;
      default: occurenceString = I18N_NOOP(static_cast<TQString>("Every %1 months").arg(mult));
    }
  else if(type == MyMoneySchedule::OCCUR_YEARLY)
    switch (mult)
    {
      case 1: occurenceString = I18N_NOOP("Yearly"); break;
      case 2: occurenceString = I18N_NOOP("Every other year"); break;
      default: occurenceString = I18N_NOOP(static_cast<TQString>("Every %1 years").arg(mult));
    }
  return occurenceString;
}

TQString MyMoneySchedule::occurencePeriodToString(MyMoneySchedule::occurenceE type)
{
  TQString occurenceString = I18N_NOOP("Any");

  if(type == MyMoneySchedule::OCCUR_ONCE)
    occurenceString = I18N_NOOP("Once");
  else if(type == MyMoneySchedule::OCCUR_DAILY)
    occurenceString = I18N_NOOP("Day");
  else if(type == MyMoneySchedule::OCCUR_WEEKLY)
    occurenceString = I18N_NOOP("Week");
  else if(type == MyMoneySchedule::OCCUR_EVERYHALFMONTH)
    occurenceString = I18N_NOOP("Half-month");
  else if(type == MyMoneySchedule::OCCUR_MONTHLY)
    occurenceString = I18N_NOOP("Month");
  else if(type == MyMoneySchedule::OCCUR_YEARLY)
    occurenceString = I18N_NOOP("Year");
  return occurenceString;
}

TQString MyMoneySchedule::scheduleTypeToString(MyMoneySchedule::typeE type)
{
  TQString text;

  switch (type) {
    case MyMoneySchedule::TYPE_BILL:
      text = I18N_NOOP("Bill");
      break;
    case MyMoneySchedule::TYPE_DEPOSIT:
      text = I18N_NOOP("Deposit");
      break;
    case MyMoneySchedule::TYPE_TRANSFER:
      text = I18N_NOOP("Transfer");
      break;
    case MyMoneySchedule::TYPE_LOANPAYMENT:
      text = I18N_NOOP("Loan payment");
      break;
    case MyMoneySchedule::TYPE_ANY:
    default:
      text = I18N_NOOP("Unknown");
  }
  return text;
}


TQString MyMoneySchedule::paymentMethodToString(MyMoneySchedule::paymentTypeE paymentType)
{
  TQString text;

  switch (paymentType) {
    case MyMoneySchedule::STYPE_DIRECTDEBIT:
      text = I18N_NOOP("Direct debit");
      break;
    case MyMoneySchedule::STYPE_DIRECTDEPOSIT:
      text = I18N_NOOP("Direct deposit");
      break;
    case MyMoneySchedule::STYPE_MANUALDEPOSIT:
      text = I18N_NOOP("Manual deposit");
      break;
    case MyMoneySchedule::STYPE_OTHER:
      text = I18N_NOOP("Other");
      break;
    case MyMoneySchedule::STYPE_WRITECHEQUE:
      text = I18N_NOOP("Write check");
      break;
    case MyMoneySchedule::STYPE_STANDINGORDER:
      text = I18N_NOOP("Standing order");
      break;
    case MyMoneySchedule::STYPE_BANKTRANSFER:
      text = I18N_NOOP("Bank transfer");
      break;
    case MyMoneySchedule::STYPE_ANY:
      text = I18N_NOOP("Any (Error)");
      break;
  }
  return text;
}

TQString MyMoneySchedule::weekendOptionToString(MyMoneySchedule::weekendOptionE weekendOption)
{
  TQString text;

  switch (weekendOption) {
    case MyMoneySchedule::MoveFriday:
      text = I18N_NOOP("Change the date to the previous Friday");
      break;
    case MyMoneySchedule::MoveMonday:
      text = I18N_NOOP("Change the date to the next Monday");
      break;
    case MyMoneySchedule::MoveNothing:
      text = I18N_NOOP("Do Nothing");
      break;
  }
  return text;
}

// until we don't have the means to store the value
// of the variation, we default to 10% in case this
// scheduled transaction is marked 'not fixed'.
//
// ipwizard 2009-04-18

int MyMoneySchedule::variation(void) const
{
  int rc = 0;
  if(!isFixed()) {
    rc = 10;
#if 0
    TQString var = value("kmm-variation");
    if(!var.isEmpty())
      rc = var.toInt();
#endif
  }
  return rc;
}

void MyMoneySchedule::setVariation(int var)
{
#if 0
  deletePair("kmm-variation");
  if(var != 0)
    setValue("kmm-variation", TQString("%1").arg(var));
#endif
}

int MyMoneySchedule::eventsPerYear(MyMoneySchedule::occurenceE occurence)
{
  int rc = 0;

  switch(occurence) {
    case MyMoneySchedule::OCCUR_DAILY:
      rc = 365;
      break;
    case MyMoneySchedule::OCCUR_WEEKLY:
      rc = 52;
      break;
    case MyMoneySchedule::OCCUR_FORTNIGHTLY:
      rc = 26;
      break;
    case MyMoneySchedule::OCCUR_EVERYOTHERWEEK:
      rc = 26;
      break;
    case MyMoneySchedule::OCCUR_EVERYHALFMONTH:
      rc = 24;
      break;
    case MyMoneySchedule::OCCUR_EVERYTHREEWEEKS:
      rc = 17;
      break;
    case MyMoneySchedule::OCCUR_EVERYFOURWEEKS:
      rc = 13;
      break;
    case MyMoneySchedule::OCCUR_MONTHLY:
    case MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS:
      rc = 12;
      break;
    case MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS:
      rc = 6;
      break;
    case MyMoneySchedule::OCCUR_EVERYOTHERMONTH:
      rc = 6;
      break;
    case MyMoneySchedule::OCCUR_EVERYTHREEMONTHS:
    case MyMoneySchedule::OCCUR_QUARTERLY:
      rc = 4;
      break;
    case MyMoneySchedule::OCCUR_EVERYFOURMONTHS:
      rc = 3;
      break;
    case MyMoneySchedule::OCCUR_TWICEYEARLY:
      rc = 2;
      break;
    case MyMoneySchedule::OCCUR_YEARLY:
      rc = 1;
      break;
    default:
      tqWarning("Occurence not supported by financial calculator");
  }

  return rc;
}

int MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::occurenceE occurence)
{
  int rc = 0;

  switch(occurence) {
    case MyMoneySchedule::OCCUR_DAILY:
      rc = 1;
      break;
    case MyMoneySchedule::OCCUR_WEEKLY:
      rc = 7;
      break;
    case MyMoneySchedule::OCCUR_FORTNIGHTLY:
      rc = 14;
      break;
    case MyMoneySchedule::OCCUR_EVERYOTHERWEEK:
      rc = 14;
      break;
    case MyMoneySchedule::OCCUR_EVERYHALFMONTH:
      rc = 15;
      break;
    case MyMoneySchedule::OCCUR_EVERYTHREEWEEKS:
      rc = 21;
      break;
    case MyMoneySchedule::OCCUR_EVERYFOURWEEKS:
      rc = 28;
      break;
    case MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS:
      rc = 30;
      break;
    case MyMoneySchedule::OCCUR_MONTHLY:
      rc = 30;
      break;
    case MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS:
      rc = 56;
      break;
    case MyMoneySchedule::OCCUR_EVERYOTHERMONTH:
      rc = 60;
      break;
    case MyMoneySchedule::OCCUR_EVERYTHREEMONTHS:
    case MyMoneySchedule::OCCUR_QUARTERLY:
      rc = 90;
      break;
    case MyMoneySchedule::OCCUR_EVERYFOURMONTHS:
      rc = 120;
      break;
    case MyMoneySchedule::OCCUR_TWICEYEARLY:
      rc = 180;
      break;
    case MyMoneySchedule::OCCUR_YEARLY:
      rc = 360;
      break;
    default:
      tqWarning("Occurence not supported by financial calculator");
  }

 return rc;
}

TQDate MyMoneySchedule::addHalfMonths( TQDate date, int mult ) const
{
  TQDate newdate = date;
  int d, dm;
  if ( mult > 0 )
  {
    d = newdate.day();
    if ( d <= 12 )
    {
      if ( mult % 2 == 0 )
        newdate = newdate.addMonths(mult>>1);
      else
        newdate = newdate.addMonths(mult>>1).addDays(15);
    }
    else
      for ( int i = 0; i < mult; i++ )
      {
        if ( d <= 13 )
          newdate = newdate.addDays(15);
        else
        {
          dm = newdate.daysInMonth();
          if ( d == 14 )
            newdate = newdate.addDays(( dm < 30 ) ? dm - d : 15);
          else if ( d == 15 )
            newdate = newdate.addDays(dm - d);
          else if ( d == dm )
            newdate = newdate.addDays(15 - d).addMonths(1);
          else
            newdate = newdate.addDays(-15).addMonths(1);
        }
        d = newdate.day();
      }
  }
  else if ( mult < 0 ) // Go backwards
    for ( int i = 0; i > mult; i-- )
    {
      d = newdate.day();
      dm = newdate.daysInMonth();
      if ( d > 15 )
      {
        dm = newdate.daysInMonth();
        newdate = newdate.addDays( (d == dm) ? 15 - dm : -15);
      }
      else if ( d <= 13 )
        newdate = newdate.addMonths(-1).addDays(15);
      else if ( d == 15 )
        newdate = newdate.addDays(-15);
      else // 14
      {
        newdate = newdate.addMonths(-1);
        dm = newdate.daysInMonth();
        newdate = newdate.addDays(( dm < 30 ) ? dm - d : 15 );
      }
    }
  return newdate;
}

MyMoneySchedule::occurenceE MyMoneySchedule::stringToOccurence(const TQString& text)
{
  MyMoneySchedule::occurenceE occurence = MyMoneySchedule::OCCUR_ANY;
  TQString tmp = text.lower();

  if(tmp == i18n("Once").lower())
    occurence = MyMoneySchedule::OCCUR_ONCE;
  else if(tmp == i18n("Daily").lower())
    occurence = MyMoneySchedule::OCCUR_DAILY;
  else if(tmp == i18n("Weekly").lower())
    occurence = MyMoneySchedule::OCCUR_WEEKLY;
  else if(tmp == i18n("Fortnightly").lower())
    occurence = MyMoneySchedule::OCCUR_FORTNIGHTLY;
  else if(tmp == i18n("Every other week").lower())
    occurence = MyMoneySchedule::OCCUR_EVERYOTHERWEEK;
  else if(tmp == i18n("Every half month").lower())
    occurence = MyMoneySchedule::OCCUR_EVERYHALFMONTH;
  else if(tmp == i18n("Every three weeks").lower())
    occurence = MyMoneySchedule::OCCUR_EVERYTHREEWEEKS;
  else if(tmp == i18n("Every four weeks").lower())
    occurence = MyMoneySchedule::OCCUR_EVERYFOURWEEKS;
  else if(tmp == i18n("Every thirty days").lower())
    occurence = MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS;
  else if(tmp == i18n("Monthly").lower())
    occurence = MyMoneySchedule::OCCUR_MONTHLY;
  else if(tmp == i18n("Every eight weeks").lower())
    occurence = MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS;
  else if(tmp == i18n("Every two months").lower())
    occurence = MyMoneySchedule::OCCUR_EVERYOTHERMONTH;
  else if(tmp == i18n("Every three months").lower())
    occurence = MyMoneySchedule::OCCUR_EVERYTHREEMONTHS;
  else if(tmp == i18n("Quarterly").lower())
    occurence = MyMoneySchedule::OCCUR_QUARTERLY;
  else if(tmp == i18n("Every four months").lower())
    occurence = MyMoneySchedule::OCCUR_EVERYFOURMONTHS;
  else if(tmp == i18n("Twice yearly").lower())
    occurence = MyMoneySchedule::OCCUR_TWICEYEARLY;
  else if(tmp == i18n("Yearly").lower())
    occurence = MyMoneySchedule::OCCUR_YEARLY;
  else if(tmp == i18n("Every other year").lower())
    occurence = MyMoneySchedule::OCCUR_EVERYOTHERYEAR;

  return occurence;
}

/**
  * Helper method to convert simple occurence to compound occurence + multiplier
  *
  * @param multiplier Returned by reference.  Adjusted multiplier
  * @param occurence Returned by reference.  Occurence type
  */
void MyMoneySchedule::simpleToCompoundOccurence(int& multiplier,occurenceE& occurence)
{
  occurenceE newOcc = occurence;
  int newMulti = 1;
  if (occurence == MyMoneySchedule::OCCUR_ONCE ||
      occurence == MyMoneySchedule::OCCUR_DAILY ||
      occurence == MyMoneySchedule::OCCUR_WEEKLY ||
      occurence == MyMoneySchedule::OCCUR_EVERYHALFMONTH ||
      occurence == MyMoneySchedule::OCCUR_MONTHLY ||
      occurence == MyMoneySchedule::OCCUR_YEARLY )
  { // Already a base occurence and multiplier
  }
  else if(occurence == MyMoneySchedule::OCCUR_FORTNIGHTLY ||
          occurence == MyMoneySchedule::OCCUR_EVERYOTHERWEEK)
  {
    newOcc    = MyMoneySchedule::OCCUR_WEEKLY;
    newMulti  = 2;
  }
  else if(occurence == MyMoneySchedule::OCCUR_EVERYTHREEWEEKS)
  {
    newOcc    = MyMoneySchedule::OCCUR_WEEKLY;
    newMulti  = 3;
  }
  else if(occurence == MyMoneySchedule::OCCUR_EVERYFOURWEEKS)
  {
    newOcc    = MyMoneySchedule::OCCUR_WEEKLY;
    newMulti  = 4;
  }
  else if(occurence == MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS)
  {
    newOcc    = MyMoneySchedule::OCCUR_DAILY;
    newMulti  = 30;
  }
  else if(occurence == MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS)
  {
    newOcc    = MyMoneySchedule::OCCUR_WEEKLY;
    newMulti  = 8;
  }
  else if(occurence == MyMoneySchedule::OCCUR_EVERYOTHERMONTH)
  {
    newOcc    = MyMoneySchedule::OCCUR_MONTHLY;
    newMulti  = 2;
  }
  else if(occurence == MyMoneySchedule::OCCUR_EVERYTHREEMONTHS ||
          occurence == MyMoneySchedule::OCCUR_QUARTERLY )
  {
    newOcc    = MyMoneySchedule::OCCUR_MONTHLY;
    newMulti  = 3;
  }
  else if(occurence == MyMoneySchedule::OCCUR_EVERYFOURMONTHS)
  {
    newOcc    = MyMoneySchedule::OCCUR_MONTHLY;
    newMulti  = 4;
  }
  else if(occurence == MyMoneySchedule::OCCUR_TWICEYEARLY)
  {
    newOcc    = MyMoneySchedule::OCCUR_MONTHLY;
    newMulti  = 6;
  }
  else if(occurence == MyMoneySchedule::OCCUR_EVERYOTHERYEAR)
  {
    newOcc    = MyMoneySchedule::OCCUR_YEARLY;
    newMulti  = 2;
  }
  else  // Unknown
  {
    newOcc    = MyMoneySchedule::OCCUR_ANY;
    newMulti  = 1;
  }
  if (newOcc != occurence)
  {
    occurence   = newOcc;
    multiplier  = newMulti == 1 ? multiplier : newMulti * multiplier;
  }
}

/**
  * Helper method to convert compound occurence + multiplier to simple occurence
  *
  * @param multiplier Returned by reference.  Adjusted multiplier
  * @param occurence Returned by reference.  Occurence type
  */
void MyMoneySchedule::compoundToSimpleOccurence(int& multiplier,occurenceE& occurence)
{
  occurenceE newOcc = occurence;
  if(occurence == MyMoneySchedule::OCCUR_ONCE)
  { // Nothing to do
  }
  else if(occurence == MyMoneySchedule::OCCUR_DAILY)
  {
    switch (multiplier)
    {
      case 1: break;
      case 30: newOcc = MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS; break;
    }
  }
  else if(newOcc == MyMoneySchedule::OCCUR_WEEKLY)
  {
    switch (multiplier)
    {
      case 1: break;
      case 2: newOcc = MyMoneySchedule::OCCUR_EVERYOTHERWEEK; break;
      case 3: newOcc = MyMoneySchedule::OCCUR_EVERYTHREEWEEKS; break;
      case 4: newOcc = MyMoneySchedule::OCCUR_EVERYFOURWEEKS; break;
      case 8: newOcc = MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS; break;
    }
  }
  else if(occurence == MyMoneySchedule::OCCUR_MONTHLY)
    switch (multiplier)
    {
      case 1: break;
      case 2: newOcc = MyMoneySchedule::OCCUR_EVERYOTHERMONTH; break;
      case 3: newOcc = MyMoneySchedule::OCCUR_EVERYTHREEMONTHS; break;
      case 4: newOcc = MyMoneySchedule::OCCUR_EVERYFOURMONTHS; break;
      case 6: newOcc = MyMoneySchedule::OCCUR_TWICEYEARLY; break;
    }
  else if(occurence == MyMoneySchedule::OCCUR_EVERYHALFMONTH)
    switch (multiplier)
    {
      case 1: break;
    }
  else if(occurence == MyMoneySchedule::OCCUR_YEARLY)
  {
    switch (multiplier)
    {
      case 1: break;
      case 2: newOcc = MyMoneySchedule::OCCUR_EVERYOTHERYEAR; break;
    }
  }
  if (occurence != newOcc ) // Changed to derived type
  {
    occurence = newOcc;
    multiplier = 1;
  }
}
