/***************************************************************************
                         mymoneyofxconnector.cpp
                             -------------------
    begin                : Sat Nov 13 2004
    copyright            : (C) 2002 by Ace Jones
    email                : acejones@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.                                   *
 *                                                                         *
 ***************************************************************************/

#include <config-kmymoney.h>

#include "mymoneyofxconnector.h"

// ----------------------------------------------------------------------------
// System Includes

#include <libofx/libofx.h>

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

#include <QDateTime>
#include <QRegExp>
#include <QByteArray>
#include <QList>
#include <QApplication>

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

#include <klocale.h>
#include <kdebug.h>
#include <kcombobox.h>
#include <kpassworddialog.h>
#include <KWallet/Wallet>

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

#include <mymoneyfile.h>
#include <mymoneyaccount.h>
#include <mymoneyinstitution.h>
#include <mymoneykeyvaluecontainer.h>
#include <kdeversion.h>

using KWallet::Wallet;

OfxHeaderVersion::OfxHeaderVersion(KComboBox* combo, const QString& headerVersion) :
    m_combo(combo)
{
  combo->clear();
  combo->addItem("102");
  combo->addItem("103");

  if (!headerVersion.isEmpty()) {
    combo->setCurrentItem(headerVersion);
  } else {
    combo->setCurrentItem("102");
  }

#if ! LIBOFX_IS_VERSION(0,9,0)
  // This feature does not work with libOFX < 0.9 so
  // we just make disable the button in this case
  combo->setDisabled(true);
#endif
}

QString OfxHeaderVersion::headerVersion(void) const
{
  return m_combo->currentText();
}

OfxAppVersion::OfxAppVersion(KComboBox* combo, const QString& appId) :
    m_combo(combo)
{
// http://ofxblog.wordpress.com/2007/06/06/ofx-appid-and-appver-for-intuit-products/
// http://ofxblog.wordpress.com/2007/06/06/ofx-appid-and-appver-for-microsoft-money/

  // Quicken
  m_appMap[i18n("Quicken Windows 2003")] = "QWIN:1200";
  m_appMap[i18n("Quicken Windows 2004")] = "QWIN:1300";
  m_appMap[i18n("Quicken Windows 2005")] = "QWIN:1400";
  m_appMap[i18n("Quicken Windows 2006")] = "QWIN:1500";
  m_appMap[i18n("Quicken Windows 2007")] = "QWIN:1600";
  m_appMap[i18n("Quicken Windows 2008")] = "QWIN:1700";

  // MS-Money
  m_appMap[i18n("MS-Money 2003")] = "Money:1100";
  m_appMap[i18n("MS-Money 2004")] = "Money:1200";
  m_appMap[i18n("MS-Money 2005")] = "Money:1400";
  m_appMap[i18n("MS-Money 2006")] = "Money:1500";
  m_appMap[i18n("MS-Money 2007")] = "Money:1600";
  m_appMap[i18n("MS-Money Plus")] = "Money:1700";

  // KMyMoney
  m_appMap["KMyMoney"] = "KMyMoney:1000";

  combo->clear();
  combo->addItems(m_appMap.keys());

  QMap<QString, QString>::const_iterator it_a;
  for (it_a = m_appMap.constBegin(); it_a != m_appMap.constEnd(); ++it_a) {
    if (*it_a == appId)
      break;
  }

  if (it_a != m_appMap.constEnd()) {
    combo->setCurrentItem(it_a.key());
  } else {
    combo->setCurrentItem(i18n("Quicken Windows 2008"));
  }

#if ! LIBOFX_IS_VERSION(0,9,0)
  // This feature does not work with libOFX < 0.9 so
  // we just make disable the button in this case
  combo->setDisabled(true);
#endif
}

const QString OfxAppVersion::appId(void) const
{
  static QString defaultAppId("QWIN:1700");

  QString app = m_combo->currentText();
  if (m_appMap[app] != defaultAppId)
    return m_appMap[app];
  return QString();
}

MyMoneyOfxConnector::MyMoneyOfxConnector(const MyMoneyAccount& _account):
    m_account(_account)
{
  m_fiSettings = m_account.onlineBankingSettings();
}

QString MyMoneyOfxConnector::iban(void) const
{
  return m_fiSettings.value("bankid");
}
QString MyMoneyOfxConnector::fiorg(void) const
{
  return m_fiSettings.value("org");
}
QString MyMoneyOfxConnector::fiid(void) const
{
  return m_fiSettings.value("fid");
}
QString MyMoneyOfxConnector::username(void) const
{
  return m_fiSettings.value("username");
}
QString MyMoneyOfxConnector::password(void) const
{
  // if we don't find a password in the wallet, we use the old method
  // and retrieve it from the settings stored in the KMyMoney data storage.
  // in case we don't have a password on file, we ask the user
  QString key = OFX_PASSWORD_KEY(m_fiSettings.value("url"), m_fiSettings.value("uniqueId"));
  QString pwd = m_fiSettings.value("password");

  // make sure, we have a reference to an active window, otherwise use 0 as ID
  WId winId = 0;
  if (qApp->activeWindow())
    winId = qApp->activeWindow()->winId();

  // now check for the wallet
  Wallet *wallet = Wallet::openWallet(Wallet::NetworkWallet(), winId, Wallet::Synchronous);
  if (wallet
      && !Wallet::keyDoesNotExist(Wallet::NetworkWallet(), Wallet::PasswordFolder(), key)) {
    wallet->setFolder(Wallet::PasswordFolder());
    wallet->readPassword(key, pwd);
  }

  if (pwd.isEmpty()) {
    KPasswordDialog dlg(0);
    dlg.setPrompt(i18n("Enter your password"));
    if (dlg.exec())
      pwd = dlg.password();
  }
  return pwd;
}
QString MyMoneyOfxConnector::accountnum(void) const
{
  return m_fiSettings.value("accountid");
}
QString MyMoneyOfxConnector::url(void) const
{
  return m_fiSettings.value("url");
}

QDate MyMoneyOfxConnector::statementStartDate(void) const
{
  if ((m_fiSettings.value("kmmofx-todayMinus").toInt() != 0) && !m_fiSettings.value("kmmofx-numRequestDays").isEmpty()) {
    return QDate::currentDate().addDays(-m_fiSettings.value("kmmofx-numRequestDays").toInt());
  } else if ((m_fiSettings.value("kmmofx-lastUpdate").toInt() != 0) && !m_account.value("lastImportedTransactionDate").isEmpty()) {
    return QDate::fromString(m_account.value("lastImportedTransactionDate"), Qt::ISODate);
  } else if ((m_fiSettings.value("kmmofx-pickDate").toInt() != 0) && !m_fiSettings.value("kmmofx-specificDate").isEmpty()) {
    return QDate::fromString(m_fiSettings.value("kmmofx-specificDate"));
  }
  return QDate::currentDate().addMonths(-2);
}

#if LIBOFX_IS_VERSION(0,9,0)
OfxAccountData::AccountType MyMoneyOfxConnector::accounttype(void) const
{
  OfxAccountData::AccountType result = OfxAccountData::OFX_CHECKING;

  QString type = m_account.onlineBankingSettings()["type"];
  if (type == "CHECKING")
    result = OfxAccountData::OFX_CHECKING;
  else if (type == "SAVINGS")
    result = OfxAccountData::OFX_SAVINGS;
  else if (type == "MONEY MARKET")
    result = OfxAccountData::OFX_MONEYMRKT;
  else if (type == "CREDIT LINE")
    result = OfxAccountData::OFX_CREDITLINE;
  else if (type == "CMA")
    result = OfxAccountData::OFX_CMA;
  else if (type == "CREDIT CARD")
    result = OfxAccountData::OFX_CREDITCARD;
  else if (type == "INVESTMENT")
    result = OfxAccountData::OFX_INVESTMENT;
  else {
    switch (m_account.accountType()) {
      case MyMoneyAccount::Investment:
        result = OfxAccountData::OFX_INVESTMENT;
        break;
      case MyMoneyAccount::CreditCard:
        result = OfxAccountData::OFX_CREDITCARD;
        break;
      case MyMoneyAccount::Savings:
        result = OfxAccountData::OFX_SAVINGS;
        break;
      default:
        break;
    }
  }

  // This is a bit of a personalized hack.  Sometimes we may want to override the
  // ofx type for an account.  For now, I will stash it in the notes!

  QRegExp rexp("OFXTYPE:([A-Z]*)");
  if (rexp.indexIn(m_account.description()) != -1) {
    QString override = rexp.cap(1);
    kDebug(2) << "MyMoneyOfxConnector::accounttype() overriding to " << result;

    if (override == "BANK")
      result = OfxAccountData::OFX_CHECKING;
    else if (override == "CC")
      result = OfxAccountData::OFX_CREDITCARD;
    else if (override == "INV")
      result = OfxAccountData::OFX_INVESTMENT;
    else if (override == "MONEYMARKET")
      result = OfxAccountData::OFX_MONEYMRKT;
  }

  return result;
}
#else
AccountType MyMoneyOfxConnector::accounttype(void) const
{
  AccountType result = OFX_BANK_ACCOUNT;

  switch (m_account.accountType()) {
    case MyMoneyAccount::Investment:
      result = OFX_INVEST_ACCOUNT;
      break;
    case MyMoneyAccount::CreditCard:
      result = OFX_CREDITCARD_ACCOUNT;
      break;
    default:
      break;
  }

  // This is a bit of a personalized hack.  Sometimes we may want to override the
  // ofx type for an account.  For now, I will stash it in the notes!

  QRegExp rexp("OFXTYPE:([A-Z]*)");
  if (rexp.search(m_account.description()) != -1) {
    QString override = rexp.cap(1);
    kDebug(2) << "MyMoneyOfxConnector::accounttype() overriding to " << result;

    if (override == "BANK")
      result = OFX_BANK_ACCOUNT;
    else if (override == "CC")
      result = OFX_CREDITCARD_ACCOUNT;
    else if (override == "INV")
      result = OFX_INVEST_ACCOUNT;
#if 0  // money market is not supported by 0.8.x
    else if (override == "MONEYMARKET")
      result = OFX_MONEYMRKT;
#endif
  }

  return result;
}
#endif

void MyMoneyOfxConnector::initRequest(OfxFiLogin* fi) const
{
  memset(fi, 0, sizeof(OfxFiLogin));
  strncpy(fi->fid, fiid().toLatin1(), OFX_FID_LENGTH - 1);
  strncpy(fi->org, fiorg().toLatin1(), OFX_ORG_LENGTH - 1);
  strncpy(fi->userid, username().toLatin1(), OFX_USERID_LENGTH - 1);
  strncpy(fi->userpass, password().toLatin1(), OFX_USERPASS_LENGTH - 1);

#if LIBOFX_IS_VERSION(0,9,0)
  // If we don't know better, we pretend to be Quicken 2008
  // http://ofxblog.wordpress.com/2007/06/06/ofx-appid-and-appver-for-intuit-products/
  // http://ofxblog.wordpress.com/2007/06/06/ofx-appid-and-appver-for-microsoft-money/
  QString appId = m_account.onlineBankingSettings().value("appId");
  QRegExp exp("(.*):(.*)");
  if (exp.indexIn(appId) != -1) {
    strncpy(fi->appid, exp.cap(1).toLatin1(), OFX_APPID_LENGTH - 1);
    strncpy(fi->appver, exp.cap(2).toLatin1(), OFX_APPVER_LENGTH - 1);
  } else {
    strncpy(fi->appid, "QWIN", OFX_APPID_LENGTH - 1);
    strncpy(fi->appver, "1700", OFX_APPVER_LENGTH - 1);
  }

  QString headerVersion = m_account.onlineBankingSettings().value("kmmofx-headerVersion");
  if (!headerVersion.isEmpty()) {
    strncpy(fi->header_version, headerVersion.toLatin1(), OFX_HEADERVERSION_LENGTH - 1);
  }
#endif
}

const QByteArray MyMoneyOfxConnector::statementRequest(void) const
{
  OfxFiLogin fi;
  initRequest(&fi);

#if LIBOFX_IS_VERSION(0,9,0)
  OfxAccountData account;
  memset(&account, 0, sizeof(OfxAccountData));

  if (!iban().toLatin1().isEmpty()) {
    strncpy(account.bank_id, iban().toLatin1(), OFX_BANKID_LENGTH - 1);
    strncpy(account.broker_id, iban().toLatin1(), OFX_BROKERID_LENGTH - 1);
  }
  strncpy(account.account_number, accountnum().toLatin1(), OFX_ACCTID_LENGTH - 1);
  account.account_type = accounttype();
#else
  OfxAccountInfo account;
  memset(&account, 0, sizeof(OfxAccountInfo));

  if (iban().toLatin1() != 0) {
    strncpy(account.bankid, iban().toLatin1(), OFX_BANKID_LENGTH - 1);
    strncpy(account.brokerid, iban().toLatin1(), OFX_BROKERID_LENGTH - 1);
  }
  strncpy(account.accountid, accountnum().toLatin1(), OFX_ACCOUNT_ID_LENGTH - 1);
  account.type = accounttype();
#endif

  QByteArray result;
  if (fi.userpass[0]) {
    char *szrequest = libofx_request_statement(&fi, &account, QDateTime(statementStartDate()).toTime_t());
    QString request = szrequest;
    // remove the trailing zero
    result = request.toUtf8();
    result.truncate(result.size() - 1);
    free(szrequest);
  }

  return result;
}

#if 0
// this code is not used anymore. The logic is now
// contained in KOnlineBankingSetupWizard::finishLoginPage(void)
const QByteArray MyMoneyOfxConnector::accountInfoRequest(void) const
{
  OfxFiLogin fi;
  initRequest(&fi);

  char* szrequest = libofx_request_accountinfo(&fi);
  QString request = szrequest;
  // remove the trailing zero
  QByteArray result = request.utf8();
  result.truncate(result.size() - 1);
  free(szrequest);

  return result;
}
#endif

#if 0

MyMoneyOfxConnector::Tag MyMoneyOfxConnector::message(const QString& _msgType, const QString& _trnType, const Tag& _request)
{
  return Tag(_msgType + "MSGSRQV1")
         .subtag(Tag(_trnType + "TRNRQ")
                 .element("TRNUID", uuid())
                 .element("CLTCOOKIE", "1")
                 .subtag(_request));
}

MyMoneyOfxConnector::Tag MyMoneyOfxConnector::investmentRequest(void) const
{
  QString dtnow_string = QDateTime::currentDateTime().toString(Qt::ISODate).remove(QRegExp("[^0-9]"));
  QString dtstart_string = _dtstart.toString(Qt::ISODate).remove(QRegExp("[^0-9]"));

  return message("INVSTMT", "INVSTMT", Tag("INVSTMTRQ")
                 .subtag(Tag("INVACCTFROM").element("BROKERID", fiorg()).element("ACCTID", accountnum()))
                 .subtag(Tag("INCTRAN").element("DTSTART", dtstart_string).element("INCLUDE", "Y"))
                 .element("INCOO", "Y")
                 .subtag(Tag("INCPOS").element("DTASOF", dtnow_string).element("INCLUDE", "Y"))
                 .element("INCBAL", "Y"));
}

MyMoneyOfxConnector::Tag MyMoneyOfxConnector::bankStatementRequest(const QDate& _dtstart) const
{
  QString dtstart_string = _dtstart.toString(Qt::ISODate).remove(QRegExp("[^0-9]"));

  return message("BANK", "STMT", Tag("STMTRQ")
                 .subtag(Tag("BANKACCTFROM").element("BANKID", iban()).element("ACCTID", accountnum()).element("ACCTTYPE", "CHECKING"))
                 .subtag(Tag("INCTRAN").element("DTSTART", dtstart_string).element("INCLUDE", "Y")));
}

MyMoneyOfxConnector::Tag MyMoneyOfxConnector::creditCardRequest(const QDate& _dtstart) const
{
  QString dtstart_string = _dtstart.toString(Qt::ISODate).remove(QRegExp("[^0-9]"));

  return message("CREDITCARD", "CCSTMT", Tag("CCSTMTRQ")
                 .subtag(Tag("CCACCTFROM").element("ACCTID", accountnum()))
                 .subtag(Tag("INCTRAN").element("DTSTART", dtstart_string).element("INCLUDE", "Y")));
}

MyMoneyOfxConnector::Tag MyMoneyOfxConnector::signOn(void) const
{
  QString dtnow_string = QDateTime::currentDateTime().toString(Qt::ISODate).remove(QRegExp("[^0-9]"));

  Tag fi("FI");
  fi.element("ORG", fiorg());
  if (!fiid().isEmpty())
    fi.element("FID", fiid());

  return Tag("SIGNONMSGSRQV1")
         .subtag(Tag("SONRQ")
                 .element("DTCLIENT", dtnow_string)
                 .element("USERID", username())
                 .element("USERPASS", password())
                 .element("LANGUAGE", "ENG")
                 .subtag(fi)
                 .element("APPID", "QWIN")
                 .element("APPVER", "1100"));
}

QString MyMoneyOfxConnector::header(void)
{
  return QString("OFXHEADER:100\r\n"
                 "DATA:OFXSGML\r\n"
                 "VERSION:102\r\n"
                 "SECURITY:NONE\r\n"
                 "ENCODING:USASCII\r\n"
                 "CHARSET:1252\r\n"
                 "COMPRESSION:NONE\r\n"
                 "OLDFILEUID:NONE\r\n"
                 "NEWFILEUID:%1\r\n"
                 "\r\n").arg(uuid());
}

QString MyMoneyOfxConnector::uuid(void)
{
  static int id = 1;
  return QDateTime::currentDateTime().toString("yyyyMMdd-hhmmsszzz-") + QString::number(id++);
}

//
// Methods to provide RESPONSES to OFX requests.  This has no real use in
// KMyMoney, but it's included for the purposes of unit testing.  This way, I
// can create a MyMoneyAccount, write it to an OFX file, import that OFX file,
// and check that everything made it through the importer.
//
// It's also a far-off dream to write an OFX server using KMyMoney as a
// backend.  It really should not be that hard, and it would fill a void in
// the open source software community.
//

const QByteArray MyMoneyOfxConnector::statementResponse(const QDate& _dtstart) const
{
  QString request;

  if (accounttype() == "CC")
    request = header() + Tag("OFX").subtag(signOnResponse()).subtag(creditCardStatementResponse(_dtstart));
  else if (accounttype() == "INV")
    request = header() + Tag("OFX").subtag(signOnResponse()).data(investmentStatementResponse(_dtstart));
  else
    request = header() + Tag("OFX").subtag(signOnResponse()).subtag(bankStatementResponse(_dtstart));

  // remove the trailing zero
  QByteArray result = request.utf8();
  result.truncate(result.size() - 1);

  return result;
}

MyMoneyOfxConnector::Tag MyMoneyOfxConnector::signOnResponse(void) const
{
  QString dtnow_string = QDateTime::currentDateTime().toString(Qt::ISODate).remove(QRegExp("[^0-9]"));

  Tag sonrs("SONRS");
  sonrs
  .subtag(Tag("STATUS")
          .element("CODE", "0")
          .element("SEVERITY", "INFO")
          .element("MESSAGE", "The operation succeeded.")
         )
  .element("DTSERVER", dtnow_string)
  .element("LANGUAGE", "ENG");

  Tag fi("FI");
  if (!fiorg().isEmpty())
    fi.element("ORG", fiorg());
  if (!fiid().isEmpty())
    fi.element("FID", fiid());

  if (!fi.isEmpty())
    sonrs.subtag(fi);

  return Tag("SIGNONMSGSRSV1").subtag(sonrs);
}

MyMoneyOfxConnector::Tag MyMoneyOfxConnector::messageResponse(const QString& _msgType, const QString& _trnType, const Tag& _response)
{
  return Tag(_msgType + "MSGSRSV1")
         .subtag(Tag(_trnType + "TRNRS")
                 .element("TRNUID", uuid())
                 .subtag(Tag("STATUS").element("CODE", "0").element("SEVERITY", "INFO"))
                 .element("CLTCOOKIE", "1")
                 .subtag(_response));
}

MyMoneyOfxConnector::Tag MyMoneyOfxConnector::bankStatementResponse(const QDate& _dtstart) const
{
  MyMoneyFile* file = MyMoneyFile::instance();

  QString dtstart_string = _dtstart.toString(Qt::ISODate).remove(QRegExp("[^0-9]"));
  QString dtnow_string = QDateTime::currentDateTime().toString(Qt::ISODate).remove(QRegExp("[^0-9]"));

  QString transactionlist;

  MyMoneyTransactionFilter filter;
  filter.setDateFilter(_dtstart, QDate::currentDate());
  filter.addAccount(m_account.id());
  QList<MyMoneyTransaction> transactions = file->transactionList(filter);
  QList<MyMoneyTransaction>::const_iterator it_transaction = transactions.begin();
  while (it_transaction != transactions.end()) {
    transactionlist += transaction(*it_transaction);
    ++it_transaction;
  }

  return messageResponse("BANK", "STMT", Tag("STMTRS")
                         .element("CURDEF", "USD")
                         .subtag(Tag("BANKACCTFROM").element("BANKID", iban()).element("ACCTID", accountnum()).element("ACCTTYPE", "CHECKING"))
                         .subtag(Tag("BANKTRANLIST").element("DTSTART", dtstart_string).element("DTEND", dtnow_string).data(transactionlist))
                         .subtag(Tag("LEDGERBAL").element("BALAMT", file->balance(m_account.id()).formatMoney(QString(), 2)).element("DTASOF", dtnow_string)));
}

MyMoneyOfxConnector::Tag MyMoneyOfxConnector::creditCardStatementResponse(const QDate& _dtstart) const
{
  MyMoneyFile* file = MyMoneyFile::instance();

  QString dtstart_string = _dtstart.toString(Qt::ISODate).remove(QRegExp("[^0-9]"));
  QString dtnow_string = QDateTime::currentDateTime().toString(Qt::ISODate).remove(QRegExp("[^0-9]"));

  QString transactionlist;

  MyMoneyTransactionFilter filter;
  filter.setDateFilter(_dtstart, QDate::currentDate());
  filter.addAccount(m_account.id());
  QList<MyMoneyTransaction> transactions = file->transactionList(filter);
  QList<MyMoneyTransaction>::const_iterator it_transaction = transactions.begin();
  while (it_transaction != transactions.end()) {
    transactionlist += transaction(*it_transaction);
    ++it_transaction;
  }

  return messageResponse("CREDITCARD", "CCSTMT", Tag("CCSTMTRS")
                         .element("CURDEF", "USD")
                         .subtag(Tag("CCACCTFROM").element("ACCTID", accountnum()))
                         .subtag(Tag("BANKTRANLIST").element("DTSTART", dtstart_string).element("DTEND", dtnow_string).data(transactionlist))
                         .subtag(Tag("LEDGERBAL").element("BALAMT", file->balance(m_account.id()).formatMoney(QString(), 2)).element("DTASOF", dtnow_string)));
}

QString MyMoneyOfxConnector::investmentStatementResponse(const QDate& _dtstart) const
{
  MyMoneyFile* file = MyMoneyFile::instance();

  QString dtstart_string = _dtstart.toString(Qt::ISODate).remove(QRegExp("[^0-9]"));
  QString dtnow_string = QDateTime::currentDateTime().toString(Qt::ISODate).remove(QRegExp("[^0-9]"));

  QString transactionlist;

  MyMoneyTransactionFilter filter;
  filter.setDateFilter(_dtstart, QDate::currentDate());
  filter.addAccount(m_account.id());
  filter.addAccount(m_account.accountList());
  QList<MyMoneyTransaction> transactions = file->transactionList(filter);
  QList<MyMoneyTransaction>::const_iterator it_transaction = transactions.begin();
  while (it_transaction != transactions.end()) {
    transactionlist += investmentTransaction(*it_transaction);
    ++it_transaction;
  }

  Tag securitylist("SECLIST");
  QCStringList accountids = m_account.accountList();
  QCStringList::const_iterator it_accountid = accountids.begin();
  while (it_accountid != accountids.end()) {
    MyMoneySecurity equity = file->security(file->account(*it_accountid).currencyId());

    securitylist.subtag(Tag("STOCKINFO")
                        .subtag(Tag("SECINFO")
                                .subtag(Tag("SECID")
                                        .element("UNIQUEID", equity.id())
                                        .element("UNIQUEIDTYPE", "KMYMONEY"))
                                .element("SECNAME", equity.name())
                                .element("TICKER", equity.tradingSymbol())
                                .element("FIID", equity.id())));

    ++it_accountid;
  }

  return messageResponse("INVSTMT", "INVSTMT", Tag("INVSTMTRS")
                         .element("DTASOF", dtstart_string)
                         .element("CURDEF", "USD")
                         .subtag(Tag("INVACCTFROM").element("BROKERID", fiorg()).element("ACCTID", accountnum()))
                         .subtag(Tag("INVTRANLIST").element("DTSTART", dtstart_string).element("DTEND", dtnow_string).data(transactionlist))
                        )
         + Tag("SECLISTMSGSRSV1").subtag(securitylist);
}

MyMoneyOfxConnector::Tag MyMoneyOfxConnector::transaction(const MyMoneyTransaction& _t) const
{
  // This method creates a transaction tag using ONLY the elements that importer uses

  MyMoneyFile* file = MyMoneyFile::instance();

  //Use this version for bank/cc transactions
  MyMoneySplit s = _t.splitByAccount(m_account.id(), true);

  //TODO (Ace) Write "investmentTransaction()"...
  //Use this version for inv transactions
  //MyMoneySplit s = _t.splitByAccount( m_account.accountList(), true );

  Tag result("STMTTRN");

  result
  // This is a temporary hack.  I don't use the trntype field in importing at all,
  // but libofx requires it to be there in order to import the file.
  .element("TRNTYPE", "DEBIT")
  .element("DTPOSTED", _t.postDate().toString(Qt::ISODate).remove(QRegExp("[^0-9]")))
  .element("TRNAMT", s.value().formatMoney(QString(), 2));

  if (! _t.bankID().isEmpty())
    result.element("FITID", _t.bankID());
  else
    result.element("FITID", _t.id());

  if (! s.number().isEmpty())
    result.element("CHECKNUM", s.number());

  if (! s.payeeId().isEmpty())
    result.element("NAME", file->payee(s.payeeId()).name());

  if (! _t.memo().isEmpty())
    result.element("MEMO", _t.memo());

  return result;
}

MyMoneyOfxConnector::Tag MyMoneyOfxConnector::investmentTransaction(const MyMoneyTransaction& _t) const
{
  MyMoneyFile* file = MyMoneyFile::instance();

  //Use this version for inv transactions
  MyMoneySplit s = _t.splitByAccount(m_account.accountList(), true);

  QByteArray stockid = file->account(s.accountId()).currencyId();

  Tag invtran("INVTRAN");
  invtran.element("FITID", _t.id()).element("DTTRADE", _t.postDate().toString(Qt::ISODate).remove(QRegExp("[^0-9]")));
  if (!_t.memo().isEmpty())
    invtran.element("MEMO", _t.memo());

  if (s.action() == MyMoneySplit::ActionBuyShares) {
    if (s.shares().isNegative()) {
      return Tag("SELLSTOCK")
             .subtag(Tag("INVSELL")
                     .subtag(invtran)
                     .subtag(Tag("SECID").element("UNIQUEID", stockid).element("UNIQUEIDTYPE", "KMYMONEY"))
                     .element("UNITS", QString(((s.shares())).formatMoney(QString(), 2)).remove(QRegExp("[^0-9.\\-]")))
                     .element("UNITPRICE", QString((s.value() / s.shares()).formatMoney(QString(), 2)).remove(QRegExp("[^0-9.]")))
                     .element("TOTAL", QString((-s.value()).formatMoney(QString(), 2)).remove(QRegExp("[^0-9.\\-]")))
                     .element("SUBACCTSEC", "CASH")
                     .element("SUBACCTFUND", "CASH"))
             .element("SELLTYPE", "SELL");
    } else {
      return Tag("BUYSTOCK")
             .subtag(Tag("INVBUY")
                     .subtag(invtran)
                     .subtag(Tag("SECID").element("UNIQUEID", stockid).element("UNIQUEIDTYPE", "KMYMONEY"))
                     .element("UNITS", QString((s.shares()).formatMoney(QString(), 2)).remove(QRegExp("[^0-9.\\-]")))
                     .element("UNITPRICE", QString((s.value() / s.shares()).formatMoney(QString(), 2)).remove(QRegExp("[^0-9.]")))
                     .element("TOTAL", QString((-(s.value())).formatMoney(QString(), 2)).remove(QRegExp("[^0-9.\\-]")))
                     .element("SUBACCTSEC", "CASH")
                     .element("SUBACCTFUND", "CASH"))
             .element("BUYTYPE", "BUY");
    }
  } else if (s.action() == MyMoneySplit::ActionReinvestDividend) {
    // Should the TOTAL tag really be negative for a REINVEST?  That's very strange, but
    // it's what they look like coming from my bank, and I can't find any information to refute it.

    return Tag("REINVEST")
           .subtag(invtran)
           .subtag(Tag("SECID").element("UNIQUEID", stockid).element("UNIQUEIDTYPE", "KMYMONEY"))
           .element("INCOMETYPE", "DIV")
           .element("TOTAL", QString((-s.value()).formatMoney(QString(), 2)).remove(QRegExp("[^0-9.\\-]")))
           .element("SUBACCTSEC", "CASH")
           .element("UNITS", QString((s.shares()).formatMoney(QString(), 2)).remove(QRegExp("[^0-9.\\-]")))
           .element("UNITPRICE", QString((s.value() / s.shares()).formatMoney(QString(), 2)).remove(QRegExp("[^0-9.]")));
  } else if (s.action() == MyMoneySplit::ActionDividend) {
    // find the split with the category, which has the actual amount of the dividend
    QList<MyMoneySplit> splits = _t.splits();
    QList<MyMoneySplit>::const_iterator it_split = splits.begin();
    bool found = false;
    while (it_split != splits.end()) {
      QByteArray accid = (*it_split).accountId();
      MyMoneyAccount acc = file->account(accid);
      if (acc.accountType() == MyMoneyAccount::Income || acc.accountType() == MyMoneyAccount::Expense) {
        found = true;
        break;
      }
      ++it_split;
    }

    if (found)
      return Tag("INCOME")
             .subtag(invtran)
             .subtag(Tag("SECID").element("UNIQUEID", stockid).element("UNIQUEIDTYPE", "KMYMONEY"))
             .element("INCOMETYPE", "DIV")
             .element("TOTAL", QString((-(*it_split).value()).formatMoney(QString(), 2)).remove(QRegExp("[^0-9\\.\\-]")))
             .element("SUBACCTSEC", "CASH")
             .element("SUBACCTFUND", "CASH");
    else
      return Tag("ERROR").element("DETAILS", "Unable to determine the amount of this income transaction.");
  }

  //FIXME: Do something useful with these errors
  return Tag("ERROR").element("DETAILS", "This transaction contains an unsupported action type");
}
#endif
