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

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

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

#include <libofx/libofx.h>

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

#include <tqdatetime.h>
#include <tqregexp.h>

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

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

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

#include <kmymoney/mymoneyfile.h>
#include <kmymoney/mymoneyaccount.h>
#include <kmymoney/mymoneyinstitution.h>
#include <kmymoney/mymoneykeyvaluecontainer.h>
#include "mymoneyofxconnector.h"

OfxHeaderVersion::OfxHeaderVersion(KComboBox* combo, const TQString& headerVersion) :
  m_combo(combo)
{
  combo->clear();
  combo->insertItem("102");
  combo->insertItem("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
}

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

OfxAppVersion::OfxAppVersion(KComboBox* combo, const TQString& 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->insertStringList(m_appMap.keys());

  TQMap<TQString, TQString>::const_iterator it_a;
  for(it_a = m_appMap.begin(); it_a != m_appMap.end(); ++it_a) {
    if(*it_a == appId)
      break;
  }

  if(it_a != m_appMap.end()) {
    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 TQString& OfxAppVersion::appId(void) const
{
  static TQString defaultAppId("QWIN:1700");

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

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

TQString MyMoneyOfxConnector::iban(void) const { return m_fiSettings.value("bankid"); }
TQString MyMoneyOfxConnector::fiorg(void) const { return m_fiSettings.value("org"); }
TQString MyMoneyOfxConnector::fiid(void) const { return m_fiSettings.value("fid"); }
TQString MyMoneyOfxConnector::username(void) const { return m_fiSettings.value("username"); }
TQString MyMoneyOfxConnector::password(void) const { return m_fiSettings.value("password"); }
TQString MyMoneyOfxConnector::accountnum(void) const { return m_fiSettings.value("accountid"); }
TQString MyMoneyOfxConnector::url(void) const { return m_fiSettings.value("url"); }

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

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

  TQString 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!

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

    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!

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

    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().latin1(), OFX_FID_LENGTH-1);
  strncpy(fi->org, fiorg().latin1(), OFX_ORG_LENGTH-1);
  strncpy(fi->userid, username().latin1(), OFX_USERID_LENGTH-1);
  strncpy(fi->userpass, password().latin1(), 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/
  TQString appId = m_account.onlineBankingSettings().value("appId");
  TQRegExp exp("(.*):(.*)");
  if(exp.search(appId) != -1) {
    strncpy(fi->appid, exp.cap(1).latin1(), OFX_APPID_LENGTH-1);
    strncpy(fi->appver, exp.cap(2).latin1(), OFX_APPVER_LENGTH-1);
  } else {
    strncpy(fi->appid, "QWIN", OFX_APPID_LENGTH-1);
    strncpy(fi->appver, "1700", OFX_APPVER_LENGTH-1);
  }

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

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

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

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

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

  char* szrequest = libofx_request_statement( &fi, &account, TQDateTime(statementStartDate()).toTime_t() );
  TQString request = szrequest;
  // remove the trailing zero
  TQByteArray result = request.utf8();
  result.truncate(result.size()-1);
  free(szrequest);

  TQString msg(result);
  return result;
}

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

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

  return result;
}
#endif

#if 0

MyMoneyOfxConnector::Tag MyMoneyOfxConnector::message(const TQString& _msgType, const TQString& _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
{
  TQString dtnow_string = TQDateTime::currentDateTime().toString(TQt::ISODate).remove(TQRegExp("[^0-9]"));
  TQString dtstart_string = _dtstart.toString(TQt::ISODate).remove(TQRegExp("[^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 TQDate& _dtstart) const
{
  TQString dtstart_string = _dtstart.toString(TQt::ISODate).remove(TQRegExp("[^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 TQDate& _dtstart) const
{
  TQString dtstart_string = _dtstart.toString(TQt::ISODate).remove(TQRegExp("[^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
{
  TQString dtnow_string = TQDateTime::currentDateTime().toString(TQt::ISODate).remove(TQRegExp("[^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"));
}

TQString MyMoneyOfxConnector::header(void)
{
  return TQString("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());
}

TQString MyMoneyOfxConnector::uuid(void)
{
  static int id = 1;
  return TQDateTime::currentDateTime().toString("yyyyMMdd-hhmmsszzz-") + TQString::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 TQByteArray MyMoneyOfxConnector::statementResponse(const TQDate& _dtstart) const
{
  TQString 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
  TQByteArray result = request.utf8();
  result.truncate(result.size()-1);

  return result;
}

MyMoneyOfxConnector::Tag MyMoneyOfxConnector::signOnResponse(void) const
{
  TQString dtnow_string = TQDateTime::currentDateTime().toString(TQt::ISODate).remove(TQRegExp("[^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 TQString& _msgType, const TQString& _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 TQDate& _dtstart) const
{
  MyMoneyFile* file = MyMoneyFile::instance();

  TQString dtstart_string = _dtstart.toString(TQt::ISODate).remove(TQRegExp("[^0-9]"));
  TQString dtnow_string = TQDateTime::currentDateTime().toString(TQt::ISODate).remove(TQRegExp("[^0-9]"));

  TQString transactionlist;

  MyMoneyTransactionFilter filter;
  filter.setDateFilter(_dtstart,TQDate::currentDate());
  filter.addAccount(m_account.id());
  TQValueList<MyMoneyTransaction> transactions = file->transactionList(filter);
  TQValueList<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(TQString(),2)).element("DTASOF",dtnow_string )));
}

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

  TQString dtstart_string = _dtstart.toString(TQt::ISODate).remove(TQRegExp("[^0-9]"));
  TQString dtnow_string = TQDateTime::currentDateTime().toString(TQt::ISODate).remove(TQRegExp("[^0-9]"));

  TQString transactionlist;

  MyMoneyTransactionFilter filter;
  filter.setDateFilter(_dtstart,TQDate::currentDate());
  filter.addAccount(m_account.id());
  TQValueList<MyMoneyTransaction> transactions = file->transactionList(filter);
  TQValueList<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(TQString(),2)).element("DTASOF",dtnow_string )));
}

TQString MyMoneyOfxConnector::investmentStatementResponse(const TQDate& _dtstart) const
{
  MyMoneyFile* file = MyMoneyFile::instance();

  TQString dtstart_string = _dtstart.toString(TQt::ISODate).remove(TQRegExp("[^0-9]"));
  TQString dtnow_string = TQDateTime::currentDateTime().toString(TQt::ISODate).remove(TQRegExp("[^0-9]"));

  TQString transactionlist;

  MyMoneyTransactionFilter filter;
  filter.setDateFilter(_dtstart,TQDate::currentDate());
  filter.addAccount(m_account.id());
  filter.addAccount(m_account.accountList());
  TQValueList<MyMoneyTransaction> transactions = file->transactionList(filter);
  TQValueList<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(TQt::ISODate).remove(TQRegExp("[^0-9]")))
    .element("TRNAMT",s.value().formatMoney(TQString(),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 );

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

  Tag invtran("INVTRAN");
  invtran.element("FITID",_t.id()).element("DTTRADE",_t.postDate().toString(TQt::ISODate).remove(TQRegExp("[^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",TQString(((s.shares())).formatMoney(TQString(),2)).remove(TQRegExp("[^0-9.\\-]")))
          .element("UNITPRICE",TQString((s.value()/s.shares()).formatMoney(TQString(),2)).remove(TQRegExp("[^0-9.]")))
          .element("TOTAL",TQString((-s.value()).formatMoney(TQString(),2)).remove(TQRegExp("[^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",TQString((s.shares()).formatMoney(TQString(),2)).remove(TQRegExp("[^0-9.\\-]")))
          .element("UNITPRICE",TQString((s.value()/s.shares()).formatMoney(TQString(),2)).remove(TQRegExp("[^0-9.]")))
          .element("TOTAL",TQString((-(s.value())).formatMoney(TQString(),2)).remove(TQRegExp("[^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",TQString((-s.value()).formatMoney(TQString(),2)).remove(TQRegExp("[^0-9.\\-]")))
      .element("SUBACCTSEC","CASH")
      .element("UNITS",TQString((s.shares()).formatMoney(TQString(),2)).remove(TQRegExp("[^0-9.\\-]")))
      .element("UNITPRICE",TQString((s.value()/s.shares()).formatMoney(TQString(),2)).remove(TQRegExp("[^0-9.]")));
  }
  else if ( s.action() == MyMoneySplit::ActionDividend )
  {
    // find the split with the category, which has the actual amount of the dividend
    TQValueList<MyMoneySplit> splits = _t.splits();
    TQValueList<MyMoneySplit>::const_iterator it_split = splits.begin();
    bool found = false;
    while( it_split != splits.end() )
    {
      TQCString 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",TQString((-(*it_split).value()).formatMoney(TQString(),2)).remove(TQRegExp("[^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
