/***************************************************************************
                          mymoneyfile.cpp
                             -------------------
    copyright            : (C) 2000 by Michael Edwardes
                           (C) 2002, 2007-2008 by 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.                                   *
 *                                                                         *
 ***************************************************************************/

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

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

#include <tqstring.h>
#include <tqdatetime.h>
#include <tqvaluelist.h>

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

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

// ----------------------------------------------------------------------------
// Project Includes
#include "storage/mymoneyseqaccessmgr.h"
#include "mymoneyfile.h"
#include "mymoneyreport.h"
#include "mymoneybudget.h"
#include "mymoneyprice.h"
#include "mymoneyobjectcontainer.h"

#ifndef HAVE_CONFIG_H
#define VERSION "UNKNOWN"
#endif

const TQString MyMoneyFile::OpeningBalancesPrefix = I18N_NOOP("Opening Balances");
const TQString MyMoneyFile::AccountSeperator = ":";

// include the following line to get a 'cout' for debug purposes
// #include <iostream>
MyMoneyFile* MyMoneyFile::_instance = 0;

class MyMoneyFile::Private
{
public:
  Private() :
    m_inTransaction(false)
  {}

  bool                   m_inTransaction;
  MyMoneySecurity        m_baseCurrency;
  MyMoneyObjectContainer m_cache;
  MyMoneyPriceList       m_priceCache;

  /**
    * This member keeps a list of ids to notify after an
    * operation is completed. The boolean is used as follows
    * during processing of the list:
    *
    * false - don't reload the object immediately
    * true  - reload the object immediately
    */
  TQMap<TQString, bool>   m_notificationList;

};

MyMoneyFile MyMoneyFile::file;

MyMoneyFile::MyMoneyFile() :
  d(new Private)
{
  m_storage = 0;
}

MyMoneyFile::~MyMoneyFile()
{
  _instance = 0;
  delete m_storage;
  delete d;
}

MyMoneyFile::MyMoneyFile(IMyMoneyStorage *storage) :
  d(new Private)
{
  m_storage = 0;
  attachStorage(storage);
}

void MyMoneyFile::attachStorage(IMyMoneyStorage* const storage)
{
  if(m_storage != 0)
    throw new MYMONEYEXCEPTION("Storage already attached");

  if(storage == 0)
    throw new MYMONEYEXCEPTION("Storage must not be 0");

  m_storage = storage;

  // force reload of base currency
  d->m_baseCurrency = MyMoneySecurity();

  // and the whole cache
  d->m_cache.clear(storage);
  d->m_priceCache.clear();
  preloadCache();

  // notify application about new data availability
  emit dataChanged();
}

void MyMoneyFile::detachStorage(IMyMoneyStorage* const /* storage */)
{
  d->m_cache.clear();
  d->m_priceCache.clear();
  m_storage = 0;
}

void MyMoneyFile::startTransaction(void)
{
  checkStorage();
  if(d->m_inTransaction) {
    throw new MYMONEYEXCEPTION("Already started a transaction!");
  }

  m_storage->startTransaction();
  d->m_inTransaction = true;
}

bool MyMoneyFile::hasTransaction(void) const
{
  return d->m_inTransaction;
}

void MyMoneyFile::checkTransaction(const char* txt) const
{
  checkStorage();
  if(!d->m_inTransaction) {
    throw new MYMONEYEXCEPTION(TQString("No transaction started for %1").arg(txt));
  }
}

void MyMoneyFile::commitTransaction(void)
{
  checkTransaction(__PRETTY_FUNCTION__);

  bool changed = m_storage->commitTransaction();
  d->m_inTransaction = false;
  preloadCache();
  if(changed) {
    emit dataChanged();
  }
}

void MyMoneyFile::rollbackTransaction(void)
{
  checkTransaction(__PRETTY_FUNCTION__);

  m_storage->rollbackTransaction();
  d->m_inTransaction = false;
  preloadCache();
}

void MyMoneyFile::addInstitution(MyMoneyInstitution& institution)
{
  // perform some checks to see that the institution stuff is OK. For
  // now we assume that the institution must have a name, the ID is not set
  // and it does not have a parent (MyMoneyFile).

  if(institution.name().length() == 0
  || institution.id().length() != 0)
    throw new MYMONEYEXCEPTION("Not a new institution");

  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->addInstitution(institution);

  d->m_cache.preloadInstitution(institution);
}

void MyMoneyFile::modifyInstitution(const MyMoneyInstitution& institution)
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->modifyInstitution(institution);

  addNotification(institution.id());
}

void MyMoneyFile::modifyTransaction(const MyMoneyTransaction& transaction)
{
  checkTransaction(__PRETTY_FUNCTION__);

  const MyMoneyTransaction* t = &transaction;
  MyMoneyTransaction tCopy;

  // now check the splits
  bool loanAccountAffected = false;
  TQValueList<MyMoneySplit>::ConstIterator it_s;
  for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
    // the following line will throw an exception if the
    // account does not exist
    MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
    if(acc.id().isEmpty())
      throw new MYMONEYEXCEPTION("Cannot store split with no account assigned");
    if(isStandardAccount((*it_s).accountId()))
      throw new MYMONEYEXCEPTION("Cannot store split referencing standard account");
    if(acc.isLoan() && ((*it_s).action() == MyMoneySplit::ActionTransfer))
      loanAccountAffected = true;
  }

  // change transfer splits between asset/liability and loan accounts
  // into amortization splits
  if(loanAccountAffected) {
    tCopy = transaction;
    TQValueList<MyMoneySplit> list = transaction.splits();
    for(it_s = list.begin(); it_s != list.end(); ++it_s) {
      if((*it_s).action() == MyMoneySplit::ActionTransfer) {
        MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());

        if(acc.isAssetLiability()) {
          MyMoneySplit s = (*it_s);
          s.setAction(MyMoneySplit::ActionAmortization);
          tCopy.modifySplit(s);
          t = &tCopy;
        }
      }
    }
  }

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  // get the current setting of this transaction
  MyMoneyTransaction tr = MyMoneyFile::transaction(transaction.id());

  // scan the splits again to update notification list
  // and mark all accounts that are referenced
  for(it_s = tr.splits().begin(); it_s != tr.splits().end(); ++it_s) {
    addNotification((*it_s).accountId());
    addNotification((*it_s).payeeId());
  }

  // perform modification
  m_storage->modifyTransaction(*t);

  // and mark all accounts that are referenced
  for(it_s = t->splits().begin(); it_s != t->splits().end(); ++it_s) {
    addNotification((*it_s).accountId());
    addNotification((*it_s).payeeId());
  }
}

void MyMoneyFile::modifyAccount(const MyMoneyAccount& _account)
{
  checkTransaction(__PRETTY_FUNCTION__);

  MyMoneyAccount account(_account);

  MyMoneyAccount acc = MyMoneyFile::account(account.id());

  // check that for standard accounts only specific parameters are changed
  if(isStandardAccount(account.id())) {
    // make sure to use the stuff we found on file
    account = acc;

    // and only use the changes that are allowed
    account.setName(_account.name());
    account.setCurrencyId(_account.currencyId());

    // now check that it is the same
    if(!(account == _account))
      throw new MYMONEYEXCEPTION("Unable to modify the standard account groups");
  }

  if(account.accountType() != acc.accountType())
    throw new MYMONEYEXCEPTION("Unable to change account type");

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  // if the account was moved to another insitution, we notify
  // the old one as well as the new one and the structure change
  if(acc.institutionId() != account.institutionId()) {
    MyMoneyInstitution inst;
    if(!acc.institutionId().isEmpty()) {
      inst = institution(acc.institutionId());
      inst.removeAccountId(acc.id());
      modifyInstitution(inst);
    }
    if(!account.institutionId().isEmpty()) {
      inst = institution(account.institutionId());
      inst.addAccountId(acc.id());
      modifyInstitution(inst);
    }
    addNotification(acc.institutionId());
    addNotification(account.institutionId());
  }

  m_storage->modifyAccount(account);

  addNotification(account.id());
}

void MyMoneyFile::reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent)
{
  checkTransaction(__PRETTY_FUNCTION__);

  // check that it's not one of the standard account groups
  if(isStandardAccount(account.id()))
    throw new MYMONEYEXCEPTION("Unable to reparent the standard account groups");

  if(account.accountGroup() == parent.accountGroup()
  || (account.accountType() == MyMoneyAccount::Income && parent.accountType() == MyMoneyAccount::Expense)
  || (account.accountType() == MyMoneyAccount::Expense && parent.accountType() == MyMoneyAccount::Income)) {

    if(account.isInvest() && parent.accountType() != MyMoneyAccount::Investment)
      throw new MYMONEYEXCEPTION("Unable to reparent Stock to non-investment account");

    if(parent.accountType() == MyMoneyAccount::Investment && !account.isInvest())
      throw new MYMONEYEXCEPTION("Unable to reparent non-stock to investment account");

    // clear all changed objects from cache
    MyMoneyNotifier notifier(this);

    // keep a notification of the current parent
    addNotification(account.parentAccountId());

    m_storage->reparentAccount(account, parent);

    // and also keep one for the account itself and the new parent
    addNotification(account.id());
    addNotification(parent.id());

  } else
    throw new MYMONEYEXCEPTION("Unable to reparent to different account type");
}

const MyMoneyInstitution& MyMoneyFile::institution(const TQString& id) const
{
  return d->m_cache.institution(id);
}

const MyMoneyAccount& MyMoneyFile::account(const TQString& id) const
{
  return d->m_cache.account(id);
}

const MyMoneyAccount& MyMoneyFile::subAccountByName(const MyMoneyAccount& acc, const TQString& name) const
{
  static MyMoneyAccount nullAccount;

  TQValueList<TQString>::const_iterator it_a;
  for(it_a = acc.accountList().begin(); it_a != acc.accountList().end(); ++it_a) {
    const MyMoneyAccount& sacc = account(*it_a);
    if(sacc.name() == name)
      return sacc;
  }
  return nullAccount;
}

const MyMoneyAccount& MyMoneyFile::accountByName(const TQString& name) const
{
  return d->m_cache.accountByName(name);
}

void MyMoneyFile::removeTransaction(const MyMoneyTransaction& transaction)
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  // get the engine's idea about this transaction
  MyMoneyTransaction tr = MyMoneyFile::transaction(transaction.id());
  TQValueList<MyMoneySplit>::ConstIterator it_s;

  // scan the splits again to update notification list
  for(it_s = tr.splits().begin(); it_s != tr.splits().end(); ++it_s) {
    MyMoneyAccount acc = account((*it_s).accountId());
    if(acc.isClosed())
      throw new MYMONEYEXCEPTION(i18n("Cannot remove transaction that references a closed account."));
    addNotification((*it_s).accountId());
    addNotification((*it_s).payeeId());
  }

  m_storage->removeTransaction(transaction);
}


bool MyMoneyFile::hasActiveSplits(const TQString& id) const
{
  checkStorage();

  return m_storage->hasActiveSplits(id);
}

bool MyMoneyFile::isStandardAccount(const TQString& id) const
{
  checkStorage();

  return m_storage->isStandardAccount(id);
}

void MyMoneyFile::setAccountName(const TQString& id, const TQString& name) const
{
  checkTransaction(__PRETTY_FUNCTION__);

  m_storage->setAccountName(id, name);
}

void MyMoneyFile::removeAccount(const MyMoneyAccount& account)
{
  checkTransaction(__PRETTY_FUNCTION__);

  MyMoneyAccount parent;
  MyMoneyAccount acc;
  MyMoneyInstitution institution;

  // check that the account and its parent exist
  // this will throw an exception if the id is unknown
  acc = MyMoneyFile::account(account.id());
  parent = MyMoneyFile::account(account.parentAccountId());
  if(!acc.institutionId().isEmpty())
    institution = MyMoneyFile::institution(acc.institutionId());

  // check that it's not one of the standard account groups
  if(isStandardAccount(account.id()))
    throw new MYMONEYEXCEPTION("Unable to remove the standard account groups");

  if(hasActiveSplits(account.id())) {
    throw new MYMONEYEXCEPTION("Unable to remove account with active splits");
  }

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  // collect all sub-ordinate accounts for notification
  TQStringList::ConstIterator it;
  for(it = acc.accountList().begin(); it != acc.accountList().end(); ++it)
    addNotification(*it);
  // don't forget the parent and a possible institution
  addNotification(parent.id());
  addNotification(account.institutionId());

  if(!institution.id().isEmpty()) {
    institution.removeAccountId(account.id());
    m_storage->modifyInstitution(institution);
  }
  acc.setInstitutionId(TQString());

  m_storage->removeAccount(acc);
  addNotification(acc.id(), false);
  d->m_cache.clear(acc.id());
}

void MyMoneyFile::removeAccountList(const TQStringList& account_list, unsigned int level) {
 if (level > 100)
   throw new MYMONEYEXCEPTION("Too deep recursion in [MyMoneyFile::removeAccountList]!");

  checkTransaction(__PRETTY_FUNCTION__);

  // upon entry, we check that we could proceed with the operation
  if(!level) {
    if(!hasOnlyUnusedAccounts(account_list, 0))
      throw new MYMONEYEXCEPTION("One or more accounts cannot be removed");

    // NOTE: We don't use a MyMoneyNotifier here, but rather clear the whole cache
    d->m_cache.clear();
  }

  // process all accounts in the list and test if they have transactions assigned
  for (TQStringList::const_iterator it = account_list.begin(); it != account_list.end(); ++it) {
    MyMoneyAccount a = m_storage->account(*it);
    //kdDebug() << "Deleting account '"<< a.name() << "'" << endl;

    // first remove all sub-accounts
    if (!a.accountList().isEmpty())
      removeAccountList(a.accountList(), level+1);

    // then remove account itself, but we first have to get
    // rid of the account list that is still stored in
    // the MyMoneyAccount object. Easiest way is to get a fresh copy.
    a = m_storage->account(*it);
    //kdDebug() << "Deleting account '"<< a2.name() << "' itself" << endl;
    m_storage->removeAccount(a);
  }
}

bool MyMoneyFile::hasOnlyUnusedAccounts(const TQStringList& account_list, unsigned int level)
{
  if (level > 100)
    throw new MYMONEYEXCEPTION("Too deep recursion in [MyMoneyFile::hasOnlyUnusedAccounts]!");
  // process all accounts in the list and test if they have transactions assigned
  for (TQStringList::const_iterator it = account_list.begin(); it != account_list.end(); ++it) {
    if (transactionCount(*it) != 0)
      return false; // the current account has a transaction assigned
    if (!hasOnlyUnusedAccounts(account(*it).accountList(), level+1))
      return false; // some sub-account has a transaction assigned
  }
  return true; // all subaccounts unused
}


void MyMoneyFile::removeInstitution(const MyMoneyInstitution& institution)
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  TQValueList<TQString>::ConstIterator it_a;
  MyMoneyInstitution inst = MyMoneyFile::institution(institution.id());

  bool blocked = signalsBlocked();
  blockSignals(true);
  for(it_a = inst.accountList().begin(); it_a != inst.accountList().end(); ++it_a) {
    MyMoneyAccount acc = account(*it_a);
    acc.setInstitutionId(TQString());
    modifyAccount(acc);
  }
  blockSignals(blocked);

  m_storage->removeInstitution(institution);

  addNotification(institution.id(), false);
}

void MyMoneyFile::addAccount(MyMoneyAccount& account, MyMoneyAccount& parent)
{
  checkTransaction(__PRETTY_FUNCTION__);

  MyMoneyInstitution institution;

  // perform some checks to see that the account stuff is OK. For
  // now we assume that the account must have a name, has no
  // transaction and sub-accounts and parent account
  // it's own ID is not set and it does not have a pointer to (MyMoneyFile)

  if(account.name().length() == 0)
    throw new MYMONEYEXCEPTION("Account has no name");

  if(account.id().length() != 0)
    throw new MYMONEYEXCEPTION("New account must have no id");

  if(account.accountList().count() != 0)
    throw new MYMONEYEXCEPTION("New account must have no sub-accounts");

  if(!account.parentAccountId().isEmpty())
    throw new MYMONEYEXCEPTION("New account must have no parent-id");

  if(account.accountType() == MyMoneyAccount::UnknownAccountType)
    throw new MYMONEYEXCEPTION("Account has invalid type");

  // make sure, that the parent account exists
  // if not, an exception is thrown. If it exists,
  // get a copy of the current data
  MyMoneyAccount acc = MyMoneyFile::account(parent.id());

#if 0
  // TODO: remove the following code as we now can have multiple accounts
  // with the same name even in the same hierarchy position of the account tree
  //
  // check if the selected name is currently not among the child accounts
  // if we find one, then return it as the new account
  TQStringList::const_iterator it_a;
  for(it_a = acc.accountList().begin(); it_a != acc.accountList().end(); ++it_a) {
    MyMoneyAccount a = MyMoneyFile::account(*it_a);
    if(account.name() == a.name()) {
      account = a;
      return;
    }
  }
#endif

  // FIXME: make sure, that the parent has the same type
  // I left it out here because I don't know, if there is
  // a tight coupling between e.g. checking accounts and the
  // class asset. It certainly does not make sense to create an
  // expense account under an income account. Maybe it does, I don't know.

  // We enforce, that a stock account can never be a parent and
  // that the parent for a stock account must be an investment. Also,
  // an investment cannot have another investment account as it's parent
  if(parent.isInvest())
    throw new MYMONEYEXCEPTION("Stock account cannot be parent account");

  if(account.isInvest() && parent.accountType() != MyMoneyAccount::Investment)
    throw new MYMONEYEXCEPTION("Stock account must have investment account as parent ");

  if(!account.isInvest() && parent.accountType() == MyMoneyAccount::Investment)
    throw new MYMONEYEXCEPTION("Investment account can only have stock accounts as children");

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  // if an institution is set, verify that it exists
  if(account.institutionId().length() != 0) {
    // check the presence of the institution. if it
    // does not exist, an exception is thrown
    institution = MyMoneyFile::institution(account.institutionId());
  }


  if(!account.openingDate().isValid()) {
    account.setOpeningDate(TQDate::currentDate());
  }

  account.setParentAccountId(parent.id());

  m_storage->addAccount(account);
  m_storage->addAccount(parent, account);

  if(account.institutionId().length() != 0) {
    institution.addAccountId(account.id());
    m_storage->modifyInstitution(institution);
    addNotification(institution.id());
  }

  d->m_cache.preloadAccount(account);
  addNotification(parent.id());
}

MyMoneyTransaction MyMoneyFile::createOpeningBalanceTransaction(const MyMoneyAccount& acc, const MyMoneyMoney& balance)
{
  MyMoneyTransaction t;
  // if the opening balance is not zero, we need
  // to create the respective transaction
  if(!balance.isZero()) {
    checkTransaction(__PRETTY_FUNCTION__);

    MyMoneySecurity currency = security(acc.currencyId());
    MyMoneyAccount openAcc = openingBalanceAccount(currency);

    if(openAcc.openingDate() > acc.openingDate()) {
      openAcc.setOpeningDate(acc.openingDate());
      modifyAccount(openAcc);
    }

    MyMoneySplit s;

    t.setPostDate(acc.openingDate());
    t.setCommodity(acc.currencyId());

    s.setAccountId(acc.id());
    s.setShares(balance);
    s.setValue(balance);
    t.addSplit(s);

    s.clearId();
    s.setAccountId(openAcc.id());
    s.setShares(-balance);
    s.setValue(-balance);
    t.addSplit(s);

    addTransaction(t);
  }
  return t;
}

TQString MyMoneyFile::openingBalanceTransaction(const MyMoneyAccount& acc) const
{
  TQString result;

  MyMoneySecurity currency = security(acc.currencyId());
  MyMoneyAccount openAcc;

  try
  {
    openAcc = openingBalanceAccount(currency);
  }
  catch(MyMoneyException *e)
  {
    delete e;
    return result;
  }

  // Iterate over all the opening balance transactions for this currency
  MyMoneyTransactionFilter filter;
  filter.addAccount(openAcc.id());
  TQValueList<MyMoneyTransaction> transactions = transactionList(filter);
  TQValueList<MyMoneyTransaction>::const_iterator it_t = transactions.begin();
  while ( it_t != transactions.end() )
  {
    try
    {
      // Test whether the transaction also includes a split into
      // this account
      (*it_t).splitByAccount(acc.id(), true /*match*/);

      // If so, we have a winner!
      result = (*it_t).id();
      break;
    }
    catch(MyMoneyException *e)
    {
      // If not, keep searching
      ++it_t;
      delete e;
    }
  }

  return result;
}

const MyMoneyAccount MyMoneyFile::openingBalanceAccount(const MyMoneySecurity& security)
{
  if(!security.isCurrency())
    throw new MYMONEYEXCEPTION("Opening balance for non currencies not supported");

  try
  {
    return openingBalanceAccount_internal(security);
  }
  catch(MyMoneyException *e)
  {
    delete e;
    MyMoneyFileTransaction ft;
    MyMoneyAccount acc;

    try {
      acc = createOpeningBalanceAccount(security);
      ft.commit();

    } catch(MyMoneyException* e) {
      tqDebug(TQString("Unable to create opening balance account for security %1").arg(security.id()));
      delete e;
    }
    return acc;
  }
}

const MyMoneyAccount MyMoneyFile::openingBalanceAccount(const MyMoneySecurity& security) const
{
  return openingBalanceAccount_internal(security);
}

const MyMoneyAccount MyMoneyFile::openingBalanceAccount_internal(const MyMoneySecurity& security) const
{
  if(!security.isCurrency())
    throw new MYMONEYEXCEPTION("Opening balance for non currencies not supported");

  MyMoneyAccount acc;
  TQRegExp match(TQString("^%1").arg(i18n(MyMoneyFile::OpeningBalancesPrefix.utf8())));

  TQValueList<MyMoneyAccount> accounts;
  TQValueList<MyMoneyAccount>::Iterator it;

  accountList(accounts, equity().accountList(), true);

  for(it = accounts.begin(); it != accounts.end(); ++it) {
    if(match.search((*it).name()) != -1) {
      if((*it).currencyId() == security.id()) {
        acc = *it;
        break;
      }
    }
  }

  if(acc.id().isEmpty()) {
    throw new MYMONEYEXCEPTION(TQString("No opening balance account for %1").arg(security.tradingSymbol()));
  }

  return acc;
}

const MyMoneyAccount MyMoneyFile::createOpeningBalanceAccount(const MyMoneySecurity& security)
{
  checkTransaction(__PRETTY_FUNCTION__);

  MyMoneyAccount acc;
  TQString name(i18n(MyMoneyFile::OpeningBalancesPrefix.utf8()));
  if(security.id() != baseCurrency().id()) {
    name += TQString(" (%1)").arg(security.id());
  }
  acc.setName(name);
  acc.setAccountType(MyMoneyAccount::Equity);
  acc.setCurrencyId(security.id());

  MyMoneyAccount parent = equity();
  this->addAccount(acc, parent);
  return acc;
}

void MyMoneyFile::addTransaction(MyMoneyTransaction& transaction)
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  // perform some checks to see that the transaction stuff is OK. For
  // now we assume that
  // * no ids are assigned
  // * the date valid (must not be empty)
  // * the referenced accounts in the splits exist

  // first perform all the checks
  if(!transaction.id().isEmpty())
    throw new MYMONEYEXCEPTION("Unable to add transaction with id set");
  if(!transaction.postDate().isValid())
    throw new MYMONEYEXCEPTION("Unable to add transaction with invalid postdate");

  // now check the splits
  bool loanAccountAffected = false;
  TQValueList<MyMoneySplit>::ConstIterator it_s;
  for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
    // the following line will throw an exception if the
    // account does not exist or is one of the standard accounts
    MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
    if(acc.id().isEmpty())
      throw new MYMONEYEXCEPTION("Cannot add split with no account assigned");
    if(acc.isLoan())
      loanAccountAffected = true;
    if(isStandardAccount((*it_s).accountId()))
      throw new MYMONEYEXCEPTION("Cannot add split referencing standard account");
  }

  // change transfer splits between asset/liability and loan accounts
  // into amortization splits
  if(loanAccountAffected) {
    TQValueList<MyMoneySplit> list = transaction.splits();
    for(it_s = list.begin(); it_s != list.end(); ++it_s) {
      if((*it_s).action() == MyMoneySplit::ActionTransfer) {
        MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());

        if(acc.isAssetLiability()) {
          MyMoneySplit s = (*it_s);
          s.setAction(MyMoneySplit::ActionAmortization);
          transaction.modifySplit(s);
        }
      }
    }
  }

  // check that we have a commodity
  if(transaction.commodity().isEmpty()) {
    transaction.setCommodity(baseCurrency().id());
  }

  // then add the transaction to the file global pool
  m_storage->addTransaction(transaction);

  // scan the splits again to update notification list
  for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
    addNotification((*it_s).accountId());
    addNotification((*it_s).payeeId());
  }
}

const MyMoneyTransaction MyMoneyFile::transaction(const TQString& id) const
{
  checkStorage();

  return m_storage->transaction(id);
}

const MyMoneyTransaction MyMoneyFile::transaction(const TQString& account, const int idx) const
{
  checkStorage();

  return m_storage->transaction(account, idx);
}

void MyMoneyFile::addPayee(MyMoneyPayee& payee)
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->addPayee(payee);

  d->m_cache.preloadPayee(payee);
}

const MyMoneyPayee& MyMoneyFile::payee(const TQString& id) const
{
  return d->m_cache.payee(id);
}

const MyMoneyPayee& MyMoneyFile::payeeByName(const TQString& name) const
{
  checkStorage();

  return d->m_cache.payee(m_storage->payeeByName(name).id());
}

void MyMoneyFile::modifyPayee(const MyMoneyPayee& payee)
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  addNotification(payee.id());

  m_storage->modifyPayee(payee);
}

void MyMoneyFile::removePayee(const MyMoneyPayee& payee)
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->removePayee(payee);

  addNotification(payee.id(), false);
}

void MyMoneyFile::accountList(TQValueList<MyMoneyAccount>& list, const TQStringList& idlist, const bool recursive) const
{
  if(idlist.isEmpty()) {
    d->m_cache.account(list);

#if 0
    // TODO: I have no idea what this was good for, but it caused the networth report
    //       to show double the numbers so I commented it out (ipwizard, 2008-05-24)
    if (m_storage && (list.isEmpty() || list.size() != m_storage->accountCount())) {
      m_storage->accountList(list);
      d->m_cache.preloadAccount(list);
    }
#endif

    TQValueList<MyMoneyAccount>::Iterator it;
    TQValueList<MyMoneyAccount>::Iterator next;
    for(it = list.begin(); it != list.end(); ) {
      if(isStandardAccount( (*it).id() )) {
        it = list.erase(it);
      } else {
        ++it;
      }
    }
  } else {
    TQValueList<MyMoneyAccount>::ConstIterator it;
    TQValueList<MyMoneyAccount> list_a;
    d->m_cache.account(list_a);

    for(it = list_a.begin(); it != list_a.end(); ++it) {
      if(!isStandardAccount((*it).id())) {
        if(idlist.findIndex((*it).id()) != -1) {
          list.append(*it);
          if(recursive == true) {
            accountList(list, (*it).accountList(), true);
          }
        }
      }
    }
  }
}

void MyMoneyFile::institutionList(TQValueList<MyMoneyInstitution>& list) const
{
  d->m_cache.institution(list);
}

const TQValueList<MyMoneyInstitution> MyMoneyFile::institutionList(void) const
{
  TQValueList<MyMoneyInstitution> list;
  institutionList(list);
  return list;
}

// general get functions
const MyMoneyPayee MyMoneyFile::user(void) const { checkStorage(); return m_storage->user(); }

// general set functions
void MyMoneyFile::setUser(const MyMoneyPayee& user)
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->setUser(user);
}

bool MyMoneyFile::dirty(void) const
{
  if(!m_storage)
    return false;

  return m_storage->dirty();
}

void MyMoneyFile::setDirty(void) const
{
  checkStorage();

  m_storage->setDirty();
}

unsigned int MyMoneyFile::accountCount(void) const
{
  checkStorage();

  return m_storage->accountCount();
}

void MyMoneyFile::ensureDefaultCurrency(MyMoneyAccount& acc) const
{
  if(acc.currencyId().isEmpty()) {
    if(!baseCurrency().id().isEmpty())
      acc.setCurrencyId(baseCurrency().id());
  }
}

const MyMoneyAccount& MyMoneyFile::liability(void) const
{
  checkStorage();

  return d->m_cache.account (STD_ACC_LIABILITY);
}

const MyMoneyAccount& MyMoneyFile::asset(void) const
{
  checkStorage();

  return d->m_cache.account (STD_ACC_ASSET);
}

const MyMoneyAccount& MyMoneyFile::expense(void) const
{
  checkStorage();

  return d->m_cache.account (STD_ACC_EXPENSE);
}

const MyMoneyAccount& MyMoneyFile::income(void) const
{
  checkStorage();

  return d->m_cache.account (STD_ACC_INCOME);
}

const MyMoneyAccount& MyMoneyFile::equity(void) const
{
  checkStorage();

  return d->m_cache.account (STD_ACC_EQUITY);
}

unsigned int MyMoneyFile::transactionCount(const TQString& account) const
{
  checkStorage();

  return m_storage->transactionCount(account);
}

const TQMap<TQString, unsigned long> MyMoneyFile::transactionCountMap(void) const
{
  checkStorage();

  return m_storage->transactionCountMap();
}

unsigned int MyMoneyFile::institutionCount(void) const
{
  checkStorage();

  return m_storage->institutionCount();
}

const MyMoneyMoney MyMoneyFile::balance(const TQString& id, const TQDate& date) const
{
  checkStorage();

  return m_storage->balance(id, date);
}

const MyMoneyMoney MyMoneyFile::totalBalance(const TQString& id, const TQDate& date) const
{
  checkStorage();

  return m_storage->totalBalance(id, date);
}

void MyMoneyFile::warningMissingRate(const TQString& fromId, const TQString& toId) const
{
  MyMoneySecurity from, to;
  try {
    from = security(fromId);
    to = security(toId);
    tqWarning(TQString("Missing price info for conversion from %1 to %2").arg(from.name()).arg(to.name()));

  } catch(MyMoneyException *e) {
    tqFatal(TQString("Missing security caught in MyMoneyFile::warningMissingRate(): %1(%2) %3").arg(e->file()).arg(e->line()).arg(e->what()));
    delete e;
  }
}

void MyMoneyFile::notify(void)
{
  TQMap<TQString, bool>::ConstIterator it;
  for(it = d->m_notificationList.begin(); it != d->m_notificationList.end(); ++it) {
    if(*it)
      d->m_cache.refresh(it.key());
    else
      d->m_cache.clear(it.key());
  }
  clearNotification();
}

void MyMoneyFile::addNotification(const TQString& id, bool reload)
{
  if(!id.isEmpty())
    d->m_notificationList[id] = reload;
}

void MyMoneyFile::clearNotification()
{
  // reset list to be empty
  d->m_notificationList.clear();
}

void MyMoneyFile::transactionList(TQValueList<TQPair<MyMoneyTransaction, MyMoneySplit> >& list, MyMoneyTransactionFilter& filter) const
{
  checkStorage();
  m_storage->transactionList(list, filter);
}

void MyMoneyFile::transactionList(TQValueList<MyMoneyTransaction>& list, MyMoneyTransactionFilter& filter) const
{
  checkStorage();
  m_storage->transactionList(list, filter);
}

const TQValueList<MyMoneyTransaction> MyMoneyFile::transactionList(MyMoneyTransactionFilter& filter) const
{
  TQValueList<MyMoneyTransaction> list;
  transactionList(list, filter);
  return list;
}

const TQValueList<MyMoneyPayee> MyMoneyFile::payeeList(void) const
{
  TQValueList<MyMoneyPayee> list;
  d->m_cache.payee(list);
  return list;
}

TQString MyMoneyFile::accountToCategory(const TQString& accountId, bool includeStandardAccounts) const
{
  MyMoneyAccount acc;
  TQString rc;

  if(!accountId.isEmpty()) {
    acc = account(accountId);
    do {
      if(!rc.isEmpty())
        rc = AccountSeperator + rc;
      rc = acc.name() + rc;
      acc = account(acc.parentAccountId());
    } while(!acc.id().isEmpty() && (includeStandardAccounts || !isStandardAccount(acc.id())));
  }
  return rc;
}

TQString MyMoneyFile::categoryToAccount(const TQString& category, MyMoneyAccount::accountTypeE type) const
{
  TQString id;

  // search the category in the expense accounts and if it is not found, try
  // to locate it in the income accounts
  if(type == MyMoneyAccount::UnknownAccountType
  || type == MyMoneyAccount::Expense) {
    id = locateSubAccount(MyMoneyFile::instance()->expense(), category);
  }

  if((id.isEmpty() && type == MyMoneyAccount::UnknownAccountType)
  || type == MyMoneyAccount::Income) {
    id = locateSubAccount(MyMoneyFile::instance()->income(), category);
  }

  return id;
}

TQString MyMoneyFile::nameToAccount(const TQString& name) const
{
  TQString id;

  // search the category in the asset accounts and if it is not found, try
  // to locate it in the liability accounts
  id = locateSubAccount(MyMoneyFile::instance()->asset(), name);
  if(id.isEmpty())
    id = locateSubAccount(MyMoneyFile::instance()->liability(), name);

  return id;
}

TQString MyMoneyFile::parentName(const TQString& name) const
{
  return name.section(AccountSeperator, 0, -2);
}

TQString MyMoneyFile::locateSubAccount(const MyMoneyAccount& base, const TQString& category) const
{
  MyMoneyAccount nextBase;
  TQString level, remainder;
  level = category.section(AccountSeperator, 0, 0);
  remainder = category.section(AccountSeperator, 1);

  TQStringList list = base.accountList();
  TQStringList::ConstIterator it_a;

  for(it_a = list.begin(); it_a != list.end(); ++it_a) {
    nextBase = account(*it_a);
    if(nextBase.name() == level) {
      if(remainder.isEmpty()) {
        return nextBase.id();
      }
      return locateSubAccount(nextBase, remainder);
    }
  }
  return TQString::null;
}

TQString MyMoneyFile::value(const TQString& key) const
{
  checkStorage();

  return m_storage->value(key);
}

void MyMoneyFile::setValue(const TQString& key, const TQString& val)
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->setValue(key, val);
}

void MyMoneyFile::deletePair(const TQString& key)
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->deletePair(key);
}

void MyMoneyFile::addSchedule(MyMoneySchedule& sched)
{
  checkTransaction(__PRETTY_FUNCTION__);

  MyMoneyTransaction transaction = sched.transaction();
  TQValueList<MyMoneySplit>::const_iterator it_s;
  for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
    // the following line will throw an exception if the
    // account does not exist or is one of the standard accounts
    MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
    if(acc.id().isEmpty())
      throw new MYMONEYEXCEPTION("Cannot add split with no account assigned");
    if(isStandardAccount((*it_s).accountId()))
      throw new MYMONEYEXCEPTION("Cannot add split referencing standard account");
  }

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->addSchedule(sched);
}

void MyMoneyFile::modifySchedule(const MyMoneySchedule& sched)
{
  checkTransaction(__PRETTY_FUNCTION__);

  MyMoneyTransaction transaction = sched.transaction();
  TQValueList<MyMoneySplit>::const_iterator it_s;
  for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
    // the following line will throw an exception if the
    // account does not exist or is one of the standard accounts
    MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
    if(acc.id().isEmpty())
      throw new MYMONEYEXCEPTION("Cannot store split with no account assigned");
    if(isStandardAccount((*it_s).accountId()))
      throw new MYMONEYEXCEPTION("Cannot store split referencing standard account");
  }

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->modifySchedule(sched);

  addNotification(sched.id());
}

void MyMoneyFile::removeSchedule(const MyMoneySchedule& sched)
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->removeSchedule(sched);

  addNotification(sched.id(), false);
}

const MyMoneySchedule MyMoneyFile::schedule(const TQString& id) const
{
  return d->m_cache.schedule(id);
}

const TQValueList<MyMoneySchedule> MyMoneyFile::scheduleList(
                          const TQString& accountId,
                          const MyMoneySchedule::typeE type,
                          const MyMoneySchedule::occurenceE occurence,
                          const MyMoneySchedule::paymentTypeE paymentType,
                          const TQDate& startDate,
                          const TQDate& endDate,
                          const bool overdue) const
{
  checkStorage();

  return m_storage->scheduleList(accountId, type, occurence, paymentType, startDate, endDate, overdue);
}


const TQStringList MyMoneyFile::consistencyCheck(void)
{
  TQValueList<MyMoneyAccount> list;
  TQValueList<MyMoneyAccount>::Iterator it_a;
  TQValueList<MyMoneySchedule>::Iterator it_sch;
  TQValueList<MyMoneyPayee>::Iterator it_p;
  TQValueList<MyMoneyTransaction>::Iterator it_t;
  TQValueList<MyMoneyReport>::Iterator it_r;
  TQStringList accountRebuild;
  TQStringList::ConstIterator it_c;

  TQMap<TQString, bool> interestAccounts;

  MyMoneyAccount parent;
  MyMoneyAccount child;
  MyMoneyAccount toplevel;

  TQString parentId;
  TQStringList rc;

  int problemCount = 0;
  int unfixedCount = 0;
  TQString problemAccount;

  // check that we have a storage object
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  // get the current list of accounts
  accountList(list);
  // add the standard accounts
  list << MyMoneyFile::instance()->asset();
  list << MyMoneyFile::instance()->liability();
  list << MyMoneyFile::instance()->income();
  list << MyMoneyFile::instance()->expense();

  for(it_a = list.begin(); it_a != list.end(); ++it_a) {
    // no more checks for standard accounts
    if(isStandardAccount((*it_a).id())) {
      continue;
    }

    switch((*it_a).accountGroup()) {
      case MyMoneyAccount::Asset:
        toplevel = asset();
        break;
      case MyMoneyAccount::Liability:
        toplevel = liability();
        break;
      case MyMoneyAccount::Expense:
        toplevel = expense();
        break;
      case MyMoneyAccount::Income:
        toplevel = income();
        break;
      case MyMoneyAccount::Equity:
        toplevel = equity();
        break;
      default:
        tqWarning("%s:%d This should never happen!", __FILE__ , __LINE__);
        break;
    }

    // check for loops in the hierarchy
    parentId = (*it_a).parentAccountId();
    try {
      bool dropOut = false;
      while(!isStandardAccount(parentId) && !dropOut) {
        parent = account(parentId);
        if(parent.id() == (*it_a).id()) {
          // parent loops, so we need to re-parent to toplevel account
          // find parent account in our list
          problemCount++;
          TQValueList<MyMoneyAccount>::Iterator it_b;
          for(it_b = list.begin(); it_b != list.end(); ++it_b) {
            if((*it_b).id() == parent.id()) {
              if(problemAccount != (*it_a).name()) {
                problemAccount = (*it_a).name();
                rc << i18n("* Problem with account '%1'").arg(problemAccount);
                rc << i18n("  * Loop detected between this account and account '%2'.").arg((*it_b).name());
                rc << i18n("    Reparenting account '%2' to top level account '%1'.").arg(toplevel.name(), (*it_a).name());
                (*it_a).setParentAccountId(toplevel.id());
                if(accountRebuild.contains(toplevel.id()) == 0)
                  accountRebuild << toplevel.id();
                if(accountRebuild.contains((*it_a).id()) == 0)
                  accountRebuild << (*it_a).id();
                dropOut = true;
                break;
              }
            }
          }
        }
        parentId = parent.parentAccountId();
      }

    } catch(MyMoneyException *e) {
      // if we don't know about a parent, we catch it later
      delete e;
    }

    // check that the parent exists
    parentId = (*it_a).parentAccountId();
    try {
      parent = account(parentId);
      if((*it_a).accountGroup() != parent.accountGroup()) {
        problemCount++;
        if(problemAccount != (*it_a).name()) {
          problemAccount = (*it_a).name();
          rc << i18n("* Problem with account '%1'").arg(problemAccount);
        }
        // the parent belongs to a different group, so we reconnect to the
        // master group account (asset, liability, etc) to which this account
        // should belong and update it in the engine.
        rc << i18n("  * Parent account '%1' belongs to a different group.").arg(parent.name());
        rc << i18n("    New parent account is the top level account '%1'.").arg(toplevel.name());
        (*it_a).setParentAccountId(toplevel.id());

        // make sure to rebuild the sub-accounts of the top account
        // and the one we removed this account from
        if(accountRebuild.contains(toplevel.id()) == 0)
          accountRebuild << toplevel.id();
        if(accountRebuild.contains(parent.id()) == 0)
          accountRebuild << parent.id();
      } else if(!parent.accountList().contains((*it_a).id())) {
        problemCount++;
        if(problemAccount != (*it_a).name()) {
          problemAccount = (*it_a).name();
          rc << i18n("* Problem with account '%1'").arg(problemAccount);
        }
        // parent exists, but does not have a reference to the account
        rc << i18n("  * Parent account '%1' does not contain '%2' as sub-account.").arg(parent.name(), problemAccount);
        if(accountRebuild.contains(parent.id()) == 0)
          accountRebuild << parent.id();
      }
    } catch(MyMoneyException *e) {
      delete e;
      // apparently, the parent does not exist anymore. we reconnect to the
      // master group account (asset, liability, etc) to which this account
      // should belong and update it in the engine.
      problemCount++;
      if(problemAccount != (*it_a).name()) {
        problemAccount = (*it_a).name();
        rc << i18n("* Problem with account '%1'").arg(problemAccount);
      }
      rc << i18n("  * The parent with id %1 does not exist anymore.").arg(parentId);
      rc << i18n("    New parent account is the top level account '%1'.").arg(toplevel.name());
      (*it_a).setParentAccountId(toplevel.id());

      addNotification((*it_a).id());

      // make sure to rebuild the sub-accounts of the top account
      if(accountRebuild.contains(toplevel.id()) == 0)
        accountRebuild << toplevel.id();
    }

    // now check that all the children exist and have the correct type
    for(it_c = (*it_a).accountList().begin(); it_c != (*it_a).accountList().end(); ++it_c) {
      // check that the child exists
      try {
        child = account(*it_c);
      } catch(MyMoneyException *e) {
        problemCount++;
        if(problemAccount != (*it_a).name()) {
          problemAccount = (*it_a).name();
          rc << i18n("* Problem with account '%1'").arg(problemAccount);
        }
        rc << i18n("  * Child account with id %1 does not exist anymore.").arg(*it_c);
        rc << i18n("    The child account list will be reconstructed.");
        if(accountRebuild.contains((*it_a).id()) == 0)
          accountRebuild << (*it_a).id();
      }
    }

    // see if it is a loan account. if so, remember the assigned interest account
    if((*it_a).isLoan()) {
      const MyMoneyAccountLoan* loan = dynamic_cast<MyMoneyAccountLoan*>(&(*it_a));
      if(!loan->interestAccountId().isEmpty())
        interestAccounts[loan->interestAccountId()] = true;
    }

    // if the account was modified, we need to update it in the engine
    if(!(m_storage->account((*it_a).id()) == (*it_a))) {
      try {
        m_storage->modifyAccount(*it_a, true);
        addNotification((*it_a).id());
      } catch (MyMoneyException *e) {
        delete e;
        rc << i18n("  * Unable to update account data in engine.");
        return rc;
      }
    }
  }

  if(accountRebuild.count() != 0) {
    rc << i18n("* Reconstructing the child lists for");
  }

  // clear the affected lists
  for(it_a = list.begin(); it_a != list.end(); ++it_a) {
    if(accountRebuild.contains((*it_a).id())) {
      rc << TQString("  %1").arg((*it_a).name());
      // clear the account list
      for(it_c = (*it_a).accountList().begin(); it_c != (*it_a).accountList().end();) {
        (*it_a).removeAccountId(*it_c);
        it_c = (*it_a).accountList().begin();
      }
    }
  }

  // reconstruct the lists
  for(it_a = list.begin(); it_a != list.end(); ++it_a) {
    TQValueList<MyMoneyAccount>::Iterator it;
    parentId = (*it_a).parentAccountId();
    if(accountRebuild.contains(parentId)) {
      for(it = list.begin(); it != list.end(); ++it) {
        if((*it).id() == parentId) {
          (*it).addAccountId((*it_a).id());
          break;
        }
      }
    }
  }

  // update the engine objects
  for(it_a = list.begin(); it_a != list.end(); ++it_a) {
    if(accountRebuild.contains((*it_a).id())) {
      try {
        m_storage->modifyAccount(*it_a, true);
        addNotification((*it_a).id());
      } catch (MyMoneyException *e) {
        delete e;
        rc << i18n("  * Unable to update account data for account %1 in engine").arg((*it_a).name());
      }
    }
  }

  // For some reason, files exist with invalid ids. This has been found in the payee id
  // so we fix them here
  TQValueList<MyMoneyPayee> pList = payeeList();
  TQMap<TQString, TQString>payeeConversionMap;

  for(it_p = pList.begin(); it_p != pList.end(); ++it_p) {
    if((*it_p).id().length() > 7) {
      // found one of those with an invalid ids
      // create a new one and store it in the map.
      MyMoneyPayee payee = (*it_p);
      payee.clearId();
      m_storage->addPayee(payee);
      payeeConversionMap[(*it_p).id()] = payee.id();
      rc << i18n("  * Payee %1 recreated with fixed id").arg(payee.name());
      ++problemCount;
    }
  }

  // Fix the transactions
  TQValueList<MyMoneyTransaction> tList;
  MyMoneyTransactionFilter filter;
  filter.setReportAllSplits(false);
  m_storage->transactionList(tList, filter);
  // Generate the list of interest accounts
  for(it_t = tList.begin(); it_t != tList.end(); ++it_t) {
    const MyMoneyTransaction& t = (*it_t);
    TQValueList<MyMoneySplit>::const_iterator it_s;
    for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
      if((*it_s).action() == MyMoneySplit::ActionInterest)
        interestAccounts[(*it_s).accountId()] = true;
    }
  }
  for(it_t = tList.begin(); it_t != tList.end(); ++it_t) {
    MyMoneyTransaction t = (*it_t);
    TQValueList<MyMoneySplit>::const_iterator it_s;
    bool tChanged = false;
    for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
      bool sChanged = false;
      MyMoneySplit s = (*it_s);
      if(payeeConversionMap.find((*it_s).payeeId()) != payeeConversionMap.end()) {
        s.setPayeeId(payeeConversionMap[s.payeeId()]);
        sChanged = true;
        rc << i18n("  * Payee id updated in split of transaction '%1'.").arg(t.id());
        ++problemCount;
      }

      // make sure, that shares and value have the same number if they
      // represent the same currency.
      try {
        const MyMoneyAccount& acc = this->account(s.accountId());
        if(t.commodity() == acc.currencyId()
        && s.shares().reduce() != s.value().reduce()) {
          // use the value as master if the transaction is balanced
          if(t.splitSum().isZero()) {
            s.setShares(s.value());
            rc << i18n("  * shares set to value in split of transaction '%1'.").arg(t.id());
          } else {
            s.setValue(s.shares());
            rc << i18n("  * value set to shares in split of transaction '%1'.").arg(t.id());
          }
          sChanged = true;
          ++problemCount;
        }
      } catch(MyMoneyException *e) {
        rc << i18n("  * Split %2 in transaction '%1' contains a reference to invalid account %3. Please fix manually.").arg(t.id(), (*it_s).id(), (*it_s).accountId());
        ++problemCount;
        ++unfixedCount;
        delete e;
      }

      // make sure the interest splits are marked correct as such
      if(interestAccounts.find(s.accountId()) != interestAccounts.end()
      && s.action() != MyMoneySplit::ActionInterest) {
        s.setAction(MyMoneySplit::ActionInterest);
        sChanged = true;
        rc << i18n("  * action marked as interest in split of transaction '%1'.").arg(t.id());
        ++problemCount;
      }

      if(sChanged) {
        tChanged = true;
        t.modifySplit(s);
      }
    }
    if(tChanged) {
      m_storage->modifyTransaction(t);
    }
  }

  // Fix the schedules
  TQValueList<MyMoneySchedule> schList = scheduleList();
  for(it_sch = schList.begin(); it_sch != schList.end(); ++it_sch) {
    MyMoneySchedule sch = (*it_sch);
    MyMoneyTransaction t = sch.transaction();
    bool tChanged = false;
    TQValueList<MyMoneySplit>::const_iterator it_s;
    for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
      MyMoneySplit s = (*it_s);
      bool sChanged = false;
      if(payeeConversionMap.find((*it_s).payeeId()) != payeeConversionMap.end()) {
        s.setPayeeId(payeeConversionMap[s.payeeId()]);
        sChanged = true;
        rc << i18n("  * Payee id updated in split of schedule '%1'.").arg((*it_sch).name());
        ++problemCount;
      }
      if(!(*it_s).value().isZero() && (*it_s).shares().isZero()) {
        s.setShares(s.value());
        sChanged = true;
        rc << i18n("  * Split in scheduled transaction '%1' contained value != 0 and shares == 0.").arg((*it_sch).name());
        rc << i18n("    Shares set to value.");
        ++problemCount;
      }

      // make sure, we don't have a bankid stored with a split in a schedule
      if(!(*it_s).bankID().isEmpty()) {
        s.setBankID(TQString());
        sChanged = true;
        rc << i18n("  * Removed bankid from split in scheduled transaction '%1'.").arg((*it_sch).name());
        ++problemCount;
      }

      // make sure, that shares and value have the same number if they
      // represent the same currency.
      try {
        const MyMoneyAccount& acc = this->account(s.accountId());
        if(t.commodity() == acc.currencyId()
        && s.shares().reduce() != s.value().reduce()) {
          // use the value as master if the transaction is balanced
          if(t.splitSum().isZero()) {
            s.setShares(s.value());
            rc << i18n("  * shares set to value in split in schedule '%1'.").arg((*it_sch).name());
          } else {
            s.setValue(s.shares());
            rc << i18n("  * value set to shares in split in schedule '%1'.").arg((*it_sch).name());
          }
          sChanged = true;
          ++problemCount;
        }
      } catch(MyMoneyException *e) {
        rc << i18n("  * Split %2 in schedule '%1' contains a reference to invalid account %3. Please fix manually.").arg((*it_sch).name(), (*it_s).id(), (*it_s).accountId());
        ++problemCount;
        ++unfixedCount;
        delete e;
      }
      if(sChanged) {
        t.modifySplit(s);
        tChanged = true;
      }
    }
    if(tChanged) {
      sch.setTransaction(t);
      m_storage->modifySchedule(sch);
    }
  }

  // Fix the reports
  TQValueList<MyMoneyReport> rList = reportList();
  for(it_r = rList.begin(); it_r != rList.end(); ++it_r) {
    MyMoneyReport r = *it_r;
    TQStringList pList;
    TQStringList::Iterator it_p;
    (*it_r).payees(pList);
    bool rChanged = false;
    for(it_p = pList.begin(); it_p != pList.end(); ++it_p) {
      if(payeeConversionMap.find(*it_p) != payeeConversionMap.end()) {
        rc << i18n("  * Payee id updated in report '%1'.").arg((*it_r).name());
        ++problemCount;
        r.removeReference(*it_p);
        r.addPayee(payeeConversionMap[*it_p]);
        rChanged = true;
      }
    }
    if(rChanged) {
      m_storage->modifyReport(r);
    }
  }

  // erase old payee ids
  TQMap<TQString, TQString>::Iterator it_m;
  for(it_m = payeeConversionMap.begin(); it_m != payeeConversionMap.end(); ++it_m) {
    MyMoneyPayee payee = this->payee(it_m.key());
    removePayee(payee);
    rc << i18n("  * Payee '%1' removed.").arg(payee.id());
    ++problemCount;
  }

  // add more checks here

  if(problemCount == 0)
    rc << i18n("Finish! Data is consistent.");
  else
    rc << i18n("Finish! %1 problem(s) corrected. %2 problem(s) still present.")
            .arg(problemCount).arg(unfixedCount);

  return rc;
}

TQString MyMoneyFile::createCategory(const MyMoneyAccount& base, const TQString& name)
{
  checkTransaction(__PRETTY_FUNCTION__);

  MyMoneyAccount parent = base;
  TQString categoryText;

  if(base.id() != expense().id() && base.id() != income().id())
    throw new MYMONEYEXCEPTION("Invalid base category");

  TQStringList subAccounts = TQStringList::split(AccountSeperator, name);
  TQStringList::Iterator it;
  for (it = subAccounts.begin(); it != subAccounts.end(); ++it)
  {
    MyMoneyAccount categoryAccount;

    categoryAccount.setName(*it);
    categoryAccount.setAccountType(base.accountType());

    if (it == subAccounts.begin())
      categoryText += *it;
    else
      categoryText += (AccountSeperator + *it);

    // Only create the account if it doesn't exist
    try
    {
      TQString categoryId = categoryToAccount(categoryText);
      if (categoryId.isEmpty())
        addAccount(categoryAccount, parent);
      else
      {
        categoryAccount = account(categoryId);
      }
    }
    catch (MyMoneyException *e)
    {
      tqDebug(TQString("Unable to add account %1, %2, %3: %4").
        arg(categoryAccount.name()).
        arg(parent.name()).
        arg(categoryText).
        arg(e->what()));
      delete e;
    }

    parent = categoryAccount;
  }

  return categoryToAccount(name);
}

const TQValueList<MyMoneySchedule> MyMoneyFile::scheduleListEx( int scheduleTypes,
                                              int scheduleOcurrences,
                                              int schedulePaymentTypes,
                                              TQDate startDate,
                                              const TQStringList& accounts) const
{
  checkStorage();

  return m_storage->scheduleListEx(scheduleTypes, scheduleOcurrences, schedulePaymentTypes, startDate, accounts);
}

void MyMoneyFile::addSecurity(MyMoneySecurity& security)
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->addSecurity(security);

  d->m_cache.preloadSecurity(security);
}

void MyMoneyFile::modifySecurity(const MyMoneySecurity& security)
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->modifySecurity(security);

  addNotification(security.id());
}

void MyMoneyFile::removeSecurity(const MyMoneySecurity& security)
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->removeSecurity(security);

  addNotification(security.id(), false);
}

const MyMoneySecurity& MyMoneyFile::security(const TQString& id) const
{
  if(id.isEmpty())
    return baseCurrency();

  return d->m_cache.security(id);
}

const TQValueList<MyMoneySecurity> MyMoneyFile::securityList(void) const
{
  checkStorage();

  return m_storage->securityList();
}

void MyMoneyFile::addCurrency(const MyMoneySecurity& currency)
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->addCurrency(currency);

  // we can't really use addNotification here, because there is
  // a difference in currency and security handling. So we just
  // preload the object right here.
  d->m_cache.preloadSecurity(currency);
}

void MyMoneyFile::modifyCurrency(const MyMoneySecurity& currency)
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  // force reload of base currency object
  if(currency.id() == d->m_baseCurrency.id())
    d->m_baseCurrency.clearId();

  m_storage->modifyCurrency(currency);

  addNotification(currency.id());
}

void MyMoneyFile::removeCurrency(const MyMoneySecurity& currency)
{
  checkTransaction(__PRETTY_FUNCTION__);

  if(currency.id() == d->m_baseCurrency.id()) {
    throw new MYMONEYEXCEPTION("Cannot delete base currency.");
  }

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->removeCurrency(currency);

  addNotification(currency.id(), false);
}

const MyMoneySecurity& MyMoneyFile::currency(const TQString& id) const
{
  if(id.isEmpty())
    return baseCurrency();

  const MyMoneySecurity& curr = d->m_cache.security(id);
  if(curr.id().isEmpty())
    throw new MYMONEYEXCEPTION("Currency not found.");
  return curr;
}

const TQValueList<MyMoneySecurity> MyMoneyFile::currencyList(void) const
{
  checkStorage();

  return m_storage->currencyList();
}

const TQString& MyMoneyFile::foreignCurrency(const TQString& first, const TQString& second) const
{
  if(baseCurrency().id() == second)
    return first;
  return second;
}

const MyMoneySecurity& MyMoneyFile::baseCurrency(void) const
{
  if(d->m_baseCurrency.id().isEmpty()) {
    TQString id = TQString(value("kmm-baseCurrency"));
    if(!id.isEmpty())
      d->m_baseCurrency = currency(id);
  }

  return d->m_baseCurrency;
}

void MyMoneyFile::setBaseCurrency(const MyMoneySecurity& curr)
{
  // make sure the currency exists
  MyMoneySecurity c = currency(curr.id());

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  if(c.id() != d->m_baseCurrency.id()) {
    setValue("kmm-baseCurrency", curr.id());
    // force reload of base currency cache
    d->m_baseCurrency = MyMoneySecurity();
  }
}

void MyMoneyFile::addPrice(const MyMoneyPrice& price)
{
  if(price.rate(TQString()).isZero())
    return;

  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->addPrice(price);
}

void MyMoneyFile::removePrice(const MyMoneyPrice& price)
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->removePrice(price);
}

const MyMoneyPrice MyMoneyFile::price(const TQString& fromId, const TQString& toId, const TQDate& date, const bool exactDate) const
{
  checkStorage();

  TQString to(toId);
  if(to.isEmpty())
    to = value("kmm-baseCurrency");
  // if some id is missing, we can return an empty price object
  if(fromId.isEmpty() || to.isEmpty())
    return MyMoneyPrice();

  // we don't search our tables if someone asks stupid stuff
  if(fromId == toId) {
    return MyMoneyPrice(fromId, toId, date, MyMoneyMoney(1,1), "KMyMoney");
  }

  // search 'from-to' rate
  MyMoneyPrice rc = m_storage->price(fromId, to, date, exactDate);
  if(!rc.isValid()) {
    // not found, search 'to-fron' rate and use reciprocal value
    rc = m_storage->price(to, fromId, date, exactDate);
  }
  return rc;
}

const MyMoneyPriceList MyMoneyFile::priceList(void) const
{
  checkStorage();

  return m_storage->priceList();
}

bool MyMoneyFile::hasAccount(const TQString& id, const TQString& name) const
{
  MyMoneyAccount acc = d->m_cache.account(id);
  TQStringList list = acc.accountList();
  TQStringList::ConstIterator it;
  bool rc = false;
  for(it = list.begin(); rc == false && it != list.end(); ++it) {
    MyMoneyAccount a = d->m_cache.account(*it);
    if(a.name() == name)
      rc = true;
  }
  return rc;
}

const TQValueList<MyMoneyReport> MyMoneyFile::reportList( void ) const
{
  checkStorage();

  return m_storage->reportList();
}

void MyMoneyFile::addReport( MyMoneyReport& report )
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->addReport( report );
}

void MyMoneyFile::modifyReport( const MyMoneyReport& report )
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->modifyReport( report );

  addNotification(report.id());
}

unsigned MyMoneyFile::countReports(void) const
{
  checkStorage();

  return m_storage->countReports();
}

const MyMoneyReport MyMoneyFile::report( const TQString& id ) const
{
  checkStorage();

  return m_storage->report(id);
}

void MyMoneyFile::removeReport(const MyMoneyReport& report)
{
  checkTransaction(__PRETTY_FUNCTION__);
  MyMoneyNotifier notifier(this);

  m_storage->removeReport(report);

  addNotification(report.id(), false);
}


const TQValueList<MyMoneyBudget> MyMoneyFile::budgetList( void ) const
{
  checkStorage();

  return m_storage->budgetList();
}

void MyMoneyFile::addBudget( MyMoneyBudget& budget )
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->addBudget( budget );
}

const MyMoneyBudget MyMoneyFile::budgetByName(const TQString& name) const
{
  checkStorage();

  return m_storage->budgetByName(name);
}

void MyMoneyFile::modifyBudget( const MyMoneyBudget& budget )
{
  checkTransaction(__PRETTY_FUNCTION__);

  // clear all changed objects from cache
  MyMoneyNotifier notifier(this);

  m_storage->modifyBudget( budget );

  addNotification(budget.id());
}

unsigned MyMoneyFile::countBudgets(void) const
{
  checkStorage();

  return m_storage->countBudgets();
}

const MyMoneyBudget MyMoneyFile::budget( const TQString& id ) const
{
  checkStorage();

  return m_storage->budget(id);
}

void MyMoneyFile::removeBudget(const MyMoneyBudget& budget)
{
  checkTransaction(__PRETTY_FUNCTION__);
  MyMoneyNotifier notifier(this);

  m_storage->removeBudget(budget);

  addNotification(budget.id(), false);
}



bool MyMoneyFile::isReferenced(const MyMoneyObject& obj, const MyMoneyFileBitArray& skipChecks) const
{
  checkStorage();
  return m_storage->isReferenced(obj, skipChecks);
}

bool MyMoneyFile::checkNoUsed(const TQString& accId, const TQString& no) const
{
  // by definition, an empty string or a non-numeric string is not used
  TQRegExp exp(TQString("(.*\\D)?(\\d+)(\\D.*)?"));
  if(no.isEmpty() || exp.search(no) == -1)
    return false;

  MyMoneyTransactionFilter filter;
  filter.addAccount(accId);
  TQValueList<MyMoneyTransaction> transactions = transactionList(filter);
  TQValueList<MyMoneyTransaction>::const_iterator it_t = transactions.begin();
  while ( it_t != transactions.end() ) {
    try {
      MyMoneySplit split;
      // Test whether the transaction also includes a split into
      // this account
      split = (*it_t).splitByAccount(accId, true /*match*/);
      if(!split.number().isEmpty() && split.number() == no)
        return true;
    } catch(MyMoneyException *e) {
      delete e;
    }
    ++it_t;
  }
  return false;
}

TQString MyMoneyFile::highestCheckNo(const TQString& accId) const
{
  unsigned64 lno = 0, cno;
  TQString no;
  MyMoneyTransactionFilter filter;
  filter.addAccount(accId);
  TQValueList<MyMoneyTransaction> transactions = transactionList(filter);
  TQValueList<MyMoneyTransaction>::const_iterator it_t = transactions.begin();
  while ( it_t != transactions.end() ) {
    try {
      // Test whether the transaction also includes a split into
      // this account
      MyMoneySplit split = (*it_t).splitByAccount(accId, true /*match*/);
      if(!split.number().isEmpty()) {
        // non-numerical values stored in number will return 0 in the next line
        cno = split.number().toULongLong();
        if(cno > lno) {
          lno = cno;
          no = split.number();
        }
      }
    } catch(MyMoneyException *e) {
      delete e;
    }
    ++it_t;
  }
  return no;
}

void MyMoneyFile::clearCache(void)
{
  checkStorage();
  m_storage->clearCache();
  d->m_cache.clear();
}

void MyMoneyFile::preloadCache(void)
{
  checkStorage();

  d->m_cache.clear();
  TQValueList<MyMoneyAccount> a_list;
  m_storage->accountList(a_list);
  d->m_cache.preloadAccount(a_list);
  d->m_cache.preloadPayee(m_storage->payeeList());
  d->m_cache.preloadInstitution(m_storage->institutionList());
  d->m_cache.preloadSecurity(m_storage->securityList() + m_storage->currencyList());
  d->m_cache.preloadSchedule(m_storage->scheduleList());
}

bool MyMoneyFile::isTransfer(const MyMoneyTransaction& t) const
{
  bool rc = false;
  if(t.splitCount() == 2) {
    TQValueList<MyMoneySplit>::const_iterator it_s;
    for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
      MyMoneyAccount acc = account((*it_s).accountId());
      if(acc.isIncomeExpense())
        break;
    }
    if(it_s == t.splits().end())
      rc = true;
  }
  return rc;
}

bool MyMoneyFile::referencesClosedAccount(const MyMoneyTransaction& t) const
{
  TQValueList<MyMoneySplit>::const_iterator it_s;
  const TQValueList<MyMoneySplit>& list = t.splits();
  for(it_s = list.begin(); it_s != list.end(); ++it_s) {
    if(referencesClosedAccount(*it_s))
      break;
  }
  return it_s != list.end();
}

bool MyMoneyFile::referencesClosedAccount(const MyMoneySplit& s) const
{
  if(s.accountId().isEmpty())
    return false;

  try {
    return account(s.accountId()).isClosed();
  } catch(MyMoneyException *e) {
    delete e;
  }
  return false;
}

MyMoneyFileTransaction::MyMoneyFileTransaction() :
  m_isNested(MyMoneyFile::instance()->hasTransaction()),
  m_needRollback(!m_isNested)
{
  if(!m_isNested)
    MyMoneyFile::instance()->startTransaction();
}

MyMoneyFileTransaction::~MyMoneyFileTransaction()
{
  rollback();
}

void MyMoneyFileTransaction::restart(void)
{
  rollback();

  m_needRollback = !m_isNested;
  if(!m_isNested)
    MyMoneyFile::instance()->startTransaction();
}

void MyMoneyFileTransaction::commit(void)
{
  if(!m_isNested)
    MyMoneyFile::instance()->commitTransaction();
  m_needRollback = false;
}

void MyMoneyFileTransaction::rollback(void)
{
  if(m_needRollback)
    MyMoneyFile::instance()->rollbackTransaction();
  m_needRollback = false;
}


#include "mymoneyfile.moc"
