/***************************************************************************
                          mymoneyqifreader.cpp
                             -------------------
    begin                : Mon Jan 27 2003
    copyright            : (C) 2000-2003 by Michael Edwardes
    email                : mte@users.sourceforge.net
                           Javier Campos Morales <javi_c@users.sourceforge.net>
                           Felix Rodriguez <frodriguez@users.sourceforge.net>
                           John C <thetacoturtle@users.sourceforge.net>
                           Thomas Baumgart <ipwizard@users.sourceforge.net>
                           Kevin Tambascio <ktambascio@users.sourceforge.net>
                           Ace Jones <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 <iostream>

// ----------------------------------------------------------------------------
// QT Headers

#include <tqfile.h>
#include <tqstringlist.h>
#include <tqtimer.h>
#include <tqtextedit.h>
#include <tqregexp.h>
#include <tqbuffer.h>

// ----------------------------------------------------------------------------
// KDE Headers

#include <tdelocale.h>
#include <tdemessagebox.h>
#include <tdeconfig.h>
#include <kdebug.h>
#include <kprogress.h>
#include <kinputdialog.h>
#include <tdeio/netaccess.h>

// ----------------------------------------------------------------------------
// Project Headers

#include "mymoneyqifreader.h"
#include "../mymoney/mymoneyfile.h"
#include "../dialogs/kaccountselectdlg.h"
#include "../kmymoney2.h"
#include "kmymoneyglobalsettings.h"

#include "mymoneystatementreader.h"
#include <kmymoney/mymoneystatement.h>

// define this to debug the code. Using external filters
// while debugging did not work too good for me, so I added
// this code.
// #define DEBUG_IMPORT

#ifdef DEBUG_IMPORT
#warning "DEBUG_IMPORT defined --> external filter not available!!!!!!!"
#endif

class MyMoneyQifReader::Private {
  public:
    Private() :
      accountType(MyMoneyAccount::Checkings),
      mapCategories(true)
    {}

    TQString accountTypeToQif(MyMoneyAccount::accountTypeE type) const;

    /**
     * finalize the current statement and add it to the statement list
     */
    void finishStatement(void);

    bool isTransfer(TQString& name, const TQString& leftDelim, const TQString& rightDelim);

    /**
     * Converts the QIF specific N-record of investment transactions into
     * a category name
     */
    TQString typeToAccountName(const TQString& type) const;

    /**
     * Converts the QIF reconcile state to the KMyMoney reconcile state
     */
    MyMoneySplit::reconcileFlagE reconcileState(const TQString& state) const;

    /**
      */
    void fixMultiLineMemo(TQString& memo) const;

  public:
    /**
     * the statement that is currently collected/processed
     */
    MyMoneyStatement st;
    /**
     * the list of all statements to be sent to MyMoneyStatementReader
     */
    TQValueList<MyMoneyStatement> statements;

    /**
     * a list of already used hashes in this file
     */
    TQMap<TQString, bool> m_hashMap;

    TQString st_AccountName;
    TQString st_AccountId;
    MyMoneyAccount::accountTypeE accountType;
    bool     firstTransaction;
    bool     mapCategories;
    MyMoneyQifReader::QifEntryTypeE  transactionType;
};

void MyMoneyQifReader::Private::fixMultiLineMemo(TQString& memo) const
{
  memo.replace("\\n", "\n");
}

void MyMoneyQifReader::Private::finishStatement(void)
{
  // in case we have collected any data in the statement, we keep it
  if((st.m_listTransactions.count() + st.m_listPrices.count() + st.m_listSecurities.count()) > 0) {
    statements += st;
    tqDebug("Statement with %d transactions, %d prices and %d securities added to the statement list",
           st.m_listTransactions.count(), st.m_listPrices.count(), st.m_listSecurities.count());
  }
  // start with a fresh statement
  st = MyMoneyStatement();
  st.m_skipCategoryMatching = !mapCategories;
  st.m_eType = (transactionType == MyMoneyQifReader::EntryTransaction) ? MyMoneyStatement::etCheckings : MyMoneyStatement::etInvestment;
}

TQString MyMoneyQifReader::Private::accountTypeToQif(MyMoneyAccount::accountTypeE type) const
{
  TQString rc = "Bank";

  switch(type) {
    default:
      break;
    case MyMoneyAccount::Cash:
      rc = "Cash";
      break;
    case MyMoneyAccount::CreditCard:
      rc = "CCard";
      break;
    case MyMoneyAccount::Asset:
      rc = "Oth A";
      break;
    case MyMoneyAccount::Liability:
      rc = "Oth L";
      break;
    case MyMoneyAccount::Investment:
      rc = "Port";
      break;
  }
  return rc;
}

TQString MyMoneyQifReader::Private::typeToAccountName(const TQString& type) const
{
  if(type == "reinvdiv")
    return i18n("Category name", "Reinvested dividend");

  if(type == "reinvlg")
    return i18n("Category name", "Reinvested dividend (long term)");

  if(type == "reinvsh")
    return i18n("Category name", "Reinvested dividend (short term)");

  if (type == "div")
    return i18n("Category name", "Dividend");

  if(type == "intinc")
    return i18n("Category name", "Interest");

  if(type == "cgshort")
    return i18n("Category name", "Capital Gain (short term)");

  if( type == "cgmid")
    return i18n("Category name", "Capital Gain (mid term)");

  if(type == "cglong")
    return i18n("Category name", "Capital Gain (long term)");

  if(type == "rtrncap")
    return i18n("Category name", "Returned capital");

  if(type == "miscinc")
    return i18n("Category name", "Miscellaneous income");

  if(type == "miscexp")
    return i18n("Category name", "Miscellaneous expense");

  if(type == "sell" || type == "buy")
    return i18n("Category name", "Investment fees");

  return i18n("Unknown QIF type %1").arg(type);
}

bool MyMoneyQifReader::Private::isTransfer(TQString& tmp, const TQString& leftDelim, const TQString& rightDelim)
{
  // it's a transfer, extract the account name
  // I've seen entries like this
  //
  // S[Mehrwertsteuer]/_VATCode_N_I
  //
  // so extracting is a bit more complex and we use a regexp for it
  TQRegExp exp(TQString("\\%1(.*)\\%2(.*)").arg(leftDelim, rightDelim));

  bool rc;
  if((rc = (exp.search(tmp) != -1)) == true) {
    tmp = exp.cap(1)+exp.cap(2);
    tmp = tmp.stripWhiteSpace();
  }
  return rc;
}

MyMoneySplit::reconcileFlagE MyMoneyQifReader::Private::reconcileState(const TQString& state) const
{
  if(state == "X" || state == "R")        // Reconciled
    return MyMoneySplit::Reconciled;

  if(state == "*")                      // Cleared
    return MyMoneySplit::Cleared;

  return MyMoneySplit::NotReconciled;
}


MyMoneyQifReader::MyMoneyQifReader() :
  d(new Private)
{
  m_skipAccount = false;
  m_transactionsProcessed =
  m_transactionsSkipped = 0;
  m_progressCallback = 0;
  m_file = 0;
  m_entryType = EntryUnknown;
  m_processingData = false;
  m_userAbort = false;
  m_warnedInvestment = false;
  m_warnedSecurity = false;
  m_warnedPrice = false;

  connect(&m_filter, TQT_SIGNAL(wroteStdin(TDEProcess*)), this, TQT_SLOT(slotSendDataToFilter()));
  connect(&m_filter, TQT_SIGNAL(receivedStdout(TDEProcess*, char*, int)), this, TQT_SLOT(slotReceivedDataFromFilter(TDEProcess*, char*, int)));
  connect(&m_filter, TQT_SIGNAL(processExited(TDEProcess*)), this, TQT_SLOT(slotImportFinished()));
  connect(&m_filter, TQT_SIGNAL(receivedStderr(TDEProcess*, char*, int)), this, TQT_SLOT(slotReceivedErrorFromFilter(TDEProcess*, char*, int)));
}

MyMoneyQifReader::~MyMoneyQifReader()
{
  if(m_file)
    delete m_file;
  delete d;
}

void MyMoneyQifReader::setCategoryMapping(bool map)
{
  d->mapCategories = map;
}

void MyMoneyQifReader::setURL(const KURL& url)
{
  m_url = url;
}

void MyMoneyQifReader::setProfile(const TQString& profile)
{
  m_qifProfile.loadProfile("Profile-" + profile);
}

void MyMoneyQifReader::slotSendDataToFilter(void)
{
  TQ_LONG len;

  if(m_file->atEnd()) {
    // m_filter.flushStdin();
    m_filter.closeStdin();
  } else {
    len = m_file->readBlock(m_buffer, sizeof(m_buffer));
    if(len == -1) {
      tqWarning("Failed to read block from QIF import file");
      m_filter.closeStdin();
      m_filter.kill();
    } else {
      m_filter.writeStdin(m_buffer, len);
    }
  }
}

void MyMoneyQifReader::slotReceivedErrorFromFilter(TDEProcess* /* proc */, char *buff, int len)
{
  TQByteArray data;
  data.duplicate(buff, len);
  tqWarning("%s",static_cast<const char*>(data));
}

void MyMoneyQifReader::slotReceivedDataFromFilter(TDEProcess* /* proc */, char *buff, int len)
{
  m_pos += len;
  // signalProgress(m_pos, 0);

  while(len) {
    // process char
    if(*buff == '\n' || *buff == '\r') {
      // found EOL
      if(!m_lineBuffer.isEmpty()) {
        m_qifLines << TQString::fromUtf8(m_lineBuffer.stripWhiteSpace());
      }
      m_lineBuffer = TQCString();
    } else {
      // collect all others
      m_lineBuffer += (*buff);
    }
    ++buff;
    --len;
  }
}

void MyMoneyQifReader::slotImportFinished(void)
{
  // check if the last EOL char was missing and add the trailing line
  if(!m_lineBuffer.isEmpty()) {
    m_qifLines << TQString::fromUtf8(m_lineBuffer.stripWhiteSpace());
  }
  tqDebug("Read %d bytes", m_pos);
  TQTimer::singleShot(0, this, TQT_SLOT(slotProcessData()));
}

void MyMoneyQifReader::slotProcessData(void)
{
  signalProgress(-1, -1);

  // scan the file and try to determine numeric and date formats
  m_qifProfile.autoDetect(m_qifLines);

  // the detection is accurate for numeric values, but it could be
  // that the dates were too ambiguous so that we have to let the user
  // decide which one to pick.
  TQStringList dateFormats;
  m_qifProfile.possibleDateFormats(dateFormats);
  TQStringList list;
  if(dateFormats.count() > 1) {
    list << dateFormats.first();
    bool ok;
    list = KInputDialog::getItemList(i18n("Date format selection"), i18n("Pick the date format that suits your input file"), dateFormats, list, false, &ok);
    if(!ok) {
      m_userAbort = true;
    }
  } else
    list = dateFormats;

  m_qifProfile.setInputDateFormat(list.first());

  tqDebug("Selected date format: '%s'", list.first().data());

  signalProgress(0, m_qifLines.count(), i18n("Importing QIF ..."));
  TQStringList::iterator it;
  for(it = m_qifLines.begin(); m_userAbort == false && it != m_qifLines.end(); ++it) {
    ++m_linenumber;
    // tqDebug("Proc: '%s'", (*it).data());
    if((*it).startsWith("!")) {
      processQifSpecial(*it);
      m_qifEntry.clear();
    } else if(*it == "^") {
      if(m_qifEntry.count() > 0) {
        signalProgress(m_linenumber, 0);
        processQifEntry();
        m_qifEntry.clear();
      }
    } else {
      m_qifEntry += *it;
    }
  }
  d->finishStatement();

  tqDebug("%d lines processed", m_linenumber);
  signalProgress(-1, -1);

  emit importFinished();
}

bool MyMoneyQifReader::startImport(void)
{
  bool rc = false;
  d->st = MyMoneyStatement();
  d->st.m_skipCategoryMatching = !d->mapCategories;
  m_dontAskAgain.clear();
  m_accountTranslation.clear();
  m_userAbort = false;
  m_pos = 0;
  m_linenumber = 0;
  m_filename = TQString();
  m_data.clear();

  if(!TDEIO::NetAccess::download(m_url, m_filename, NULL)) {
    KMessageBox::detailedError(0,
                               i18n("Error while loading file '%1'!").arg(m_url.prettyURL()),
                               TDEIO::NetAccess::lastErrorString(),
                               i18n("File access error"));
    return false;
  }

  m_file = new TQFile(m_filename);
  if(m_file->open(IO_ReadOnly)) {

#ifdef DEBUG_IMPORT
    TQ_LONG len;

    while(!m_file->atEnd()) {
      len = m_file->readBlock(m_buffer, sizeof(m_buffer));
      if(len == -1) {
        tqWarning("Failed to read block from QIF import file");
      } else {
        slotReceivedDataFromFilter(0, m_buffer, len);
      }
    }
    slotImportFinished();

#else
    // start filter process, use 'cat -' as the default filter
    m_filter.clearArguments();
    if(m_qifProfile.filterScriptImport().isEmpty()) {
      m_filter << "cat";
      m_filter << "-";
    } else {
      m_filter << TQStringList::split(" ", m_qifProfile.filterScriptImport(), true);
    }
    m_entryType = EntryUnknown;

    if(m_filter.start(TDEProcess::NotifyOnExit, TDEProcess::All)) {
      m_filter.resume();
      signalProgress(0, m_file->size(), i18n("Reading QIF ..."));
      slotSendDataToFilter();
      rc = true;
    } else {
      tqDebug("starting filter failed :-(");
    }
#endif
  }
  return rc;
}

bool MyMoneyQifReader::finishImport(void)
{
  bool  rc = false;

#ifdef DEBUG_IMPORT
  delete m_file;
  m_file = 0;

  // remove the Don't ask again entries
  TDEConfig* config = TDEGlobal::config();
  config->setGroup(TQString::fromLatin1("Notification Messages"));
  TQStringList::ConstIterator it;

  for(it = m_dontAskAgain.begin(); it != m_dontAskAgain.end(); ++it) {
    config->deleteEntry(*it);
  }
  config->sync();
  m_dontAskAgain.clear();
  m_accountTranslation.clear();

  signalProgress(-1, -1);
  rc = !m_userAbort;

#else
  if(!m_filter.isRunning()) {
    delete m_file;
    m_file = 0;

    // remove the Don't ask again entries
    TDEConfig* config = TDEGlobal::config();
    config->setGroup(TQString::fromLatin1("Notification Messages"));
    TQStringList::ConstIterator it;

    for(it = m_dontAskAgain.begin(); it != m_dontAskAgain.end(); ++it) {
      config->deleteEntry(*it);
    }
    config->sync();
    m_dontAskAgain.clear();
    m_accountTranslation.clear();

    signalProgress(-1, -1);
    rc = !m_userAbort && m_filter.normalExit();
  } else {
    tqWarning("MyMoneyQifReader::finishImport() must not be called while the filter\n\tprocess is still running.");
  }
#endif

  // if a temporary file was constructed by NetAccess::download,
  // then it will be removed with the next call. Otherwise, it
  // stays untouched on the local filesystem
  TDEIO::NetAccess::removeTempFile(m_filename);

#if 0
  // Add the transaction entries
  KProgressDialog dlg(0,"transactionaddprogress",i18n("Adding transactions"),i18n("Now adding the transactions to your ledger..."));
  dlg.progressBar()->setTotalSteps(m_transactionCache.count());
  dlg.progressBar()->setTextEnabled(true);
  dlg.setAllowCancel(true);
  dlg.show();
  kapp->processEvents();
  MyMoneyFile* file = MyMoneyFile::instance();
  TQValueList<MyMoneyTransaction>::iterator it = m_transactionCache.begin();
  MyMoneyFileTransaction ft;
  try
  {
    while( it != m_transactionCache.end() )
    {
      if ( dlg.wasCancelled() )
      {
        m_userAbort = true;
        rc = false;
        break;
      }
      file->addTransaction(*it);
      dlg.progressBar()->advance(1);
      ++it;
    }
    if(rc)
      ft.commit();
  } catch(MyMoneyException *e) {
    KMessageBox::detailedSorry(0, i18n("Unable to add transactions"),
    (e->what() + " " + i18n("thrown in") + " " + e->file()+ ":%1").arg(e->line()));
    delete e;
    rc = false;
  }
#endif
  // Now to import the statements
  TQValueList<MyMoneyStatement>::const_iterator it_st;
  for(it_st = d->statements.begin(); it_st != d->statements.end(); ++it_st)
    kmymoney2->slotStatementImport(*it_st);
  return rc;
}

void MyMoneyQifReader::processQifSpecial(const TQString& _line)
{
  TQString line = _line.mid(1);   // get rid of exclamation mark
  // TQString test = line.left(5).lower();
  if(line.left(5).lower() == TQString("type:")) {
    line = line.mid(5);

    // exportable accounts
    if(line.lower() == "ccard" || KMyMoneyGlobalSettings::qifCreditCard().lower().contains(line.lower())) {
      d->accountType = MyMoneyAccount::CreditCard;
      d->firstTransaction = true;
      d->transactionType = m_entryType = EntryTransaction;

    } else if(line.lower() == "bank" || KMyMoneyGlobalSettings::qifBank().lower().contains(line.lower())) {
      d->accountType = MyMoneyAccount::Checkings;
      d->firstTransaction = true;
      d->transactionType = m_entryType = EntryTransaction;

    } else if(line.lower() == "cash" || KMyMoneyGlobalSettings::qifCash().lower().contains(line.lower())) {
      d->accountType = MyMoneyAccount::Cash;
      d->firstTransaction = true;
      d->transactionType = m_entryType = EntryTransaction;

    } else if(line.lower() == "oth a" || KMyMoneyGlobalSettings::qifAsset().lower().contains(line.lower())) {
      d->accountType = MyMoneyAccount::Asset;
      d->firstTransaction = true;
      d->transactionType = m_entryType = EntryTransaction;

    } else if(line.lower() == "oth l" || line.lower() == i18n("QIF tag for liability account", "Oth L").lower()) {
      d->accountType = MyMoneyAccount::Liability;
      d->firstTransaction = true;
      d->transactionType = m_entryType = EntryTransaction;

    } else if(line.lower() == "invst" || line.lower() == i18n("QIF tag for investment account", "Invst").lower()) {
      d->transactionType = m_entryType = EntryInvestmentTransaction;

    } else if(line.lower() == "invoice" || KMyMoneyGlobalSettings::qifInvoice().lower().contains(line.lower())) {
      m_entryType = EntrySkip;

    } else if(line.lower() == "tax") {
      m_entryType = EntrySkip;

    } else if(line.lower() == "bill") {
      m_entryType = EntrySkip;

    // exportable lists
    } else if(line.lower() == "cat" || line.lower() == i18n("QIF tag for category", "Cat").lower()) {
      m_entryType = EntryCategory;

    } else if(line.lower() == "security" || line.lower() == i18n("QIF tag for security", "Security").lower()) {
      m_entryType = EntrySecurity;

    } else if(line.lower() == "prices" || line.lower() == i18n("QIF tag for prices", "Prices").lower()) {
      m_entryType = EntryPrice;

    } else if(line.lower() == "payee") {
      m_entryType = EntryPayee;

    } else if(line.lower() == "class" || line.lower() == i18n("QIF tag for a class", "Class").lower()) {
      m_entryType = EntryClass;

    } else if(line.lower() == "memorized") {
      m_entryType = EntryMemorizedTransaction;

    } else if(line.lower() == "budget") {
      m_entryType = EntrySkip;

    } else if(line.lower() == "invitem") {
      m_entryType = EntrySkip;

    } else if(line.lower() == "template") {
      m_entryType = EntrySkip;

    } else {
      tqWarning("Unknown export header '!Type:%s' in QIF file on line %d: Skipping section.", line.data(), m_linenumber);
      m_entryType = EntrySkip;
    }

  // account headers
  } else if(line.lower() == "account") {
    m_entryType = EntryAccount;

  } else if(line.lower() == "option:autoswitch") {
    m_entryType = EntryAccount;

  } else if(line.lower() == "clear:autoswitch") {
    m_entryType = d->transactionType;
  }
}

void MyMoneyQifReader::processQifEntry(void)
{
  // This method processes a 'QIF Entry' which is everything between two caret
  // signs
  //
  try {
    switch(m_entryType) {
      case EntryCategory:
        processCategoryEntry();
        break;

      case EntryUnknown:
        kdDebug(2) << "Line " << m_linenumber << ": Warning: Found an entry without a type being specified. Checking assumed." << endl;
        processTransactionEntry();
        break;

      case EntryTransaction:
        processTransactionEntry();
        break;

      case EntryInvestmentTransaction:
        processInvestmentTransactionEntry();
        break;

      case EntryAccount:
        processAccountEntry();
        break;

      case EntrySecurity:
        processSecurityEntry();
        break;

      case EntryPrice:
        processPriceEntry();
        break;

      case EntryPayee:
        processPayeeEntry();
        break;

      case EntryClass:
        kdDebug(2) << "Line " << m_linenumber << ": Classes are not yet supported!" << endl;
        break;

      case EntryMemorizedTransaction:
        kdDebug(2) << "Line " << m_linenumber << ": Memorized transactions are not yet implemented!" << endl;
        break;

      case EntrySkip:
        break;

      default:
        kdDebug(2) << "Line " << m_linenumber<< ": EntryType " << m_entryType <<" not yet implemented!" << endl;
        break;
    }
  } catch(MyMoneyException *e) {
    if(e->what() != "USERABORT") {
      kdDebug(2) << "Line " << m_linenumber << ": Unhandled error: " << e->what() << endl;
    } else {
      m_userAbort = true;
    }
    delete e;
  }
}

const TQString MyMoneyQifReader::extractLine(const TQChar id, int cnt)
{
  TQStringList::ConstIterator it;

  m_extractedLine = -1;
  for(it = m_qifEntry.begin(); it != m_qifEntry.end(); ++it) {
    m_extractedLine++;
    if((*it)[0] == id) {
      if(cnt-- == 1) {
        if((*it).mid(1).isEmpty())
          return TQString(" ");
        return (*it).mid(1);
      }
    }
  }
  m_extractedLine = -1;
  return TQString::null;
}

void MyMoneyQifReader::extractSplits(TQValueList<qSplit>& listqSplits) const
{
//     *** With apologies to TQString MyMoneyQifReader::extractLine ***

  TQStringList::ConstIterator it;

  for(it = m_qifEntry.begin(); it != m_qifEntry.end(); ++it) {
    if((*it)[0] == "S") {
      qSplit q;
      q.m_strCategoryName = (*it++).mid(1);       //   'S'
      if((*it)[0] == "E") {
        q.m_strMemo =  (*it++).mid(1);    //           'E'
        d->fixMultiLineMemo(q.m_strMemo);
      }
      if((*it)[0] == "$") {
        q.m_amount =  (*it).mid(1);    //              '$'
      }
      listqSplits += q;
    }
  }
}
#if 0
void MyMoneyQifReader::processMSAccountEntry(const MyMoneyAccount::accountTypeE accountType)
{
  if(extractLine('P').lower() == m_qifProfile.openingBalanceText().lower()) {
    m_account = MyMoneyAccount();
    m_account.setAccountType(accountType);
    TQString txt = extractLine('T');
    MyMoneyMoney balance = m_qifProfile.value('T', txt);

    TQDate date = m_qifProfile.date(extractLine('D'));
    m_account.setOpeningDate(date);

    TQString name = extractLine('L');
    if(name.left(1) == m_qifProfile.accountDelimiter().left(1)) {
      name = name.mid(1, name.length()-2);
    }
    d->st_AccountName = name;
    m_account.setName(name);
    selectOrCreateAccount(Select, m_account, balance);
    d->st.m_accountId = m_account.id();
    if ( ! balance.isZero() )
    {
      MyMoneyFile* file = MyMoneyFile::instance();
      TQString openingtxid = file->openingBalanceTransaction(m_account);
      MyMoneyFileTransaction ft;
      if ( ! openingtxid.isEmpty() )
      {
        MyMoneyTransaction openingtx = file->transaction(openingtxid);
        MyMoneySplit split = openingtx.splitByAccount(m_account.id());

        if ( split.shares() != balance )
        {
          const MyMoneySecurity& sec = file->security(m_account.currencyId());
          if ( KMessageBox::questionYesNo(
            tqApp->mainWidget(),
            i18n("The %1 account currently has an opening balance of %2. This QIF file reports an opening balance of %3. Would you like to overwrite the current balance with the one from the QIF file?").arg(m_account.name(), split.shares().formatMoney(m_account, sec),balance.formatMoney(m_account, sec)),
            i18n("Overwrite opening balance"),
            KStdGuiItem::yes(),
            KStdGuiItem::no(),
            "OverwriteOpeningBalance" )
            == KMessageBox::Yes )
          {
            file->removeTransaction( openingtx );
            m_account.setOpeningDate( date );
            file->createOpeningBalanceTransaction( m_account, balance );
          }
        }

      }
      else
      {
        // Add an opening balance
        m_account.setOpeningDate( date );
        file->createOpeningBalanceTransaction( m_account, balance );
      }
      ft.commit();
    }

  } else {
    // for some unknown reason, Quicken 2001 generates the following (somewhat
    // misleading) sequence of lines:
    //
    //  1: !Account
    //  2: NAT&T Universal
    //  3: DAT&T Univers(...xxxx) [CLOSED]
    //  4: TCCard
    //  5: ^
    //  6: !Type:CCard
    //  7: !Account
    //  8: NCFCU Visa
    //  9: DRick's CFCU Visa card (...xxxx)
    // 10: TCCard
    // 11: ^
    // 12: !Type:CCard
    // 13: D1/ 4' 1
    //
    // Lines 1-5 are processed via processQifEntry() and processAccountEntry()
    // Then Quicken issues line 6 but since the account does not carry any
    // transaction does not write an end delimiter. Arrrgh! So we end up with
    // a QIF entry comprising of lines 6-11 and end up in this routine. Actually,
    // lines 7-11 are the leadin for the next account. So we check here if
    // the !Type:xxx record also contains an !Account line and process the
    // entry as required.
    //
    // (Ace) I think a better solution here is to handle exclamation point
    // lines separately from entries.  In the above case:
    // Line 1 would set the mode to "account entries".
    // Lines 2-5 would be interpreted as an account entry.  This would set m_account.
    // Line 6 would set the mode to "cc transaction entries".
    // Line 7 would immediately set the mode to "account entries" again
    // Lines 8-11 would be interpreted as an account entry.  This would set m_account.
    // Line 12 would set the mode to "cc transaction entries"
    // Lines 13+ would be interpreted as cc transaction entries, and life is good
    int exclamationCnt = 1;
    TQString category;
    do {
      category = extractLine('!', exclamationCnt++);
    } while(!category.isEmpty() && category != "Account");

    // we have such a weird empty account
    if(category == "Account") {
      processAccountEntry();
    } else
    {
      selectOrCreateAccount(Select, m_account);

      d->st_AccountName = m_account.name();
      d->st.m_strAccountName = m_account.name();
      d->st.m_accountId = m_account.id();
      d->st.m_strAccountNumber = m_account.id();
      m_account.setNumber(m_account.id());
      if ( m_entryType == EntryInvestmentTransaction )
        processInvestmentTransactionEntry();
      else
        processTransactionEntry();
    }
  }
}
#endif

void MyMoneyQifReader::processPayeeEntry(void)
{
  // TODO
}

void MyMoneyQifReader::processCategoryEntry(void)
{
  MyMoneyFile* file = MyMoneyFile::instance();
  MyMoneyAccount account = MyMoneyAccount();
  account.setName(extractLine('N'));
  account.setDescription(extractLine('D'));

  MyMoneyAccount parentAccount;
  if(!extractLine('I').isEmpty()) {
    account.setAccountType(MyMoneyAccount::Income);
    parentAccount = file->income();
  } else if(!extractLine('E').isEmpty()) {
    account.setAccountType(MyMoneyAccount::Expense);
    parentAccount = file->expense();
  }

  // check if we can find the account already in the file
  MyMoneyAccount acc = kmymoney2->findAccount(account, MyMoneyAccount());

  // if not, we just create it
  if(acc.id().isEmpty()) {
    MyMoneyAccount brokerage;
    MyMoneyMoney balance;
    kmymoney2->createAccount(account, parentAccount, brokerage, balance);
  }
}

TQString MyMoneyQifReader::transferAccount(TQString name, bool useBrokerage)
{
  TQString accountId;
  TQStringList tmpEntry = m_qifEntry;   // keep temp copies
  MyMoneyAccount tmpAccount = m_account;

  m_qifEntry.clear();               // and construct a temp entry to create/search the account
  m_qifEntry << TQString("N%1").arg(name);
  m_qifEntry << TQString("Tunknown");
  m_qifEntry << TQString("D%1").arg(i18n("Autogenerated by QIF importer"));
  accountId = processAccountEntry(false);

  // in case we found a reference to an investment account, we need
  // to switch to the brokerage account instead.
  MyMoneyAccount acc = MyMoneyFile::instance()->account(accountId);
  if(useBrokerage && (acc.accountType() == MyMoneyAccount::Investment)) {
    name = acc.brokerageName();
    m_qifEntry.clear();               // and construct a temp entry to create/search the account
    m_qifEntry << TQString("N%1").arg(name);
    m_qifEntry << TQString("Tunknown");
    m_qifEntry << TQString("D%1").arg(i18n("Autogenerated by QIF importer"));
    accountId = processAccountEntry(false);
  }
  m_qifEntry = tmpEntry;               // restore local copies
  m_account = tmpAccount;

  return accountId;
}

void MyMoneyQifReader::createOpeningBalance(MyMoneyAccount::_accountTypeE accType)
{
  MyMoneyFile* file = MyMoneyFile::instance();

  // if we don't have a name for the current account we need to extract the name from the L-record
  if(m_account.name().isEmpty()) {
    TQString name = extractLine('L');
    if(name.isEmpty()) {
      name = i18n("QIF imported, no account name supplied");
    }
    d->isTransfer(name, m_qifProfile.accountDelimiter().left(1), m_qifProfile.accountDelimiter().mid(1,1));
    TQStringList entry = m_qifEntry;   // keep a temp copy
    m_qifEntry.clear();               // and construct a temp entry to create/search the account
    m_qifEntry << TQString("N%1").arg(name);
    m_qifEntry << TQString("T%1").arg(d->accountTypeToQif(accType));
    m_qifEntry << TQString("D%1").arg(i18n("Autogenerated by QIF importer"));
    processAccountEntry();
    m_qifEntry = entry;               // restore local copy
  }

  MyMoneyFileTransaction ft;
  try {
    bool needCreate = true;

    MyMoneyAccount acc = m_account;
    // in case we're dealing with an investment account, we better use
    // the accompanying brokerage account for the opening balance
    acc = file->accountByName(m_account.brokerageName());

    // check if we already have an opening balance transaction
    TQString tid = file->openingBalanceTransaction(acc);
    MyMoneyTransaction ot;
    if(!tid.isEmpty()) {
      ot = file->transaction(tid);
      MyMoneySplit s0 = ot.splitByAccount(acc.id());
          // if the value is the same, we can silently skip this transaction
      if(s0.shares() == m_qifProfile.value('T', extractLine('T'))) {
        needCreate = false;
      }
      if(needCreate) {
            // in case we create it anyway, we issue a warning to the user to check it manually
        KMessageBox::sorry(0, TQString("<qt>%1</qt>").arg(i18n("KMyMoney has imported a second opening balance transaction into account <b>%1</b> which differs from the one found already on file. Please correct this manually once the import is done.").arg(acc.name())), i18n("Opening balance problem"));
      }
    }

    if(needCreate) {
      acc.setOpeningDate(m_qifProfile.date(extractLine('D')));
      file->modifyAccount(acc);
      MyMoneyTransaction t = file->createOpeningBalanceTransaction(acc, m_qifProfile.value('T', extractLine('T')));
      if(!t.id().isEmpty()) {
        t.setImported();
        file->modifyTransaction(t);
      }
      ft.commit();
    }

    // make sure to use the updated version of the account
    if(m_account.id() == acc.id())
      m_account = acc;

    // remember which account we created
    d->st.m_accountId = m_account.id();
  } catch(MyMoneyException* e) {
    KMessageBox::detailedError(0,
                               i18n("Error while creating opening balance transaction"),
                                    TQString("%1(%2):%3").arg(e->file()).arg(e->line()).arg(e->what()),
                                            i18n("File access error"));
    delete e;
  }
}

void MyMoneyQifReader::processTransactionEntry(void)
{
  ++m_transactionsProcessed;
  // in case the user selected to skip the account or the account
  // was not found we skip this transaction
/*
  if(m_account.id().isEmpty()) {
    m_transactionsSkipped++;
    return;
  }
*/
  MyMoneyFile* file = MyMoneyFile::instance();
  MyMoneyStatement::Split s1;
  MyMoneyStatement::Transaction tr;
  TQString tmp;
  TQString accountId;
  int pos;
  TQString payee = extractLine('P');
  unsigned long h;

  h = MyMoneyTransaction::hash(m_qifEntry.join(";"));

  TQString hashBase;
  hashBase.sprintf("%s-%07lx", m_qifProfile.date(extractLine('D')).toString(Qt::ISODate).data(), h);
  int idx = 1;
  TQString hash;
  for(;;) {
    hash = TQString("%1-%2").arg(hashBase).arg(idx);
    TQMap<TQString, bool>::const_iterator it;
    it = d->m_hashMap.find(hash);
    if(it == d->m_hashMap.end()) {
      d->m_hashMap[hash] = true;
      break;
    }
    ++idx;
  }
  tr.m_strBankID = hash;

  if(d->firstTransaction) {
    // check if this is an opening balance transaction and process it out of the statement
    if(!payee.isEmpty() && ((payee.lower() == "opening balance") || KMyMoneyGlobalSettings::qifOpeningBalance().lower().contains(payee.lower()))) {
      createOpeningBalance();
      d->firstTransaction = false;
      return;
    }
  }

  // Process general transaction data

  if(d->st.m_accountId.isEmpty())
    d->st.m_accountId = m_account.id();

  s1.m_accountId = d->st.m_accountId;

  d->st.m_eType = MyMoneyStatement::etCheckings;
  tr.m_datePosted = (m_qifProfile.date(extractLine('D')));
  if(!tr.m_datePosted.isValid())
  {
    int rc = KMessageBox::warningContinueCancel(0,
         i18n("The date entry \"%1\" read from the file cannot be interpreted through the current "
              "date profile setting of \"%2\".\n\nPressing \"Continue\" will "
              "assign todays date to the transaction. Pressing \"Cancel\" will abort "
              "the import operation. You can then restart the import and select a different "
              "QIF profile or create a new one.")
           .arg(extractLine('D')).arg(m_qifProfile.inputDateFormat()),
         i18n("Invalid date format"));
    switch(rc) {
      case KMessageBox::Continue:
        tr.m_datePosted = (TQDate::currentDate());
        break;

      case KMessageBox::Cancel:
        throw new MYMONEYEXCEPTION("USERABORT");
        break;
    }
  }

  tmp = extractLine('L');
  pos = tmp.findRev("--");
  if(tmp.left(1) == m_qifProfile.accountDelimiter().left(1)) {
    // it's a transfer, so we wipe the memo
//   tmp = "";         why??
//    st.m_strAccountName = tmp;
  } else if(pos != -1) {
//    what's this?
//    t.setValue("Dialog", tmp.mid(pos+2));
    tmp = tmp.left(pos);
  }
//  t.setMemo(tmp);

  // Assign the "#" field to the transaction's bank id
  // This is the custom KMM extension to QIF for a unique ID
  tmp = extractLine('#');
  if(!tmp.isEmpty())
  {
     tr.m_strBankID = TQString("ID %1").arg(tmp);
  }

#if 0
  // Collect data for the account's split
  s1.m_accountId = m_account.id();
  tmp = extractLine('S');
  pos = tmp.findRev("--");
  if(pos != -1) {
    tmp = tmp.left(pos);
  }
  if(tmp.left(1) == m_qifProfile.accountDelimiter().left(1))
      // it's a transfer, extract the account name
      tmp = tmp.mid(1, tmp.length()-2);
  s1.m_strCategoryName = tmp;
#endif
  // TODO (Ace) Deal with currencies more gracefully.  QIF cannot deal with multiple
  // currencies, so we should assume that transactions imported into a given
  // account are in THAT ACCOUNT's currency.  If one of those involves a transfer
  // to an account with a different currency, value and shares should be
  // different.  (Shares is in the target account's currency, value is in the
  // transaction's)


  s1.m_amount = m_qifProfile.value('T', extractLine('T'));
  tr.m_amount = m_qifProfile.value('T', extractLine('T'));
  tr.m_shares = m_qifProfile.value('T', extractLine('T'));
  tmp = extractLine('N');
  if (!tmp.isEmpty())
    tr.m_strNumber = tmp;

  if(!payee.isEmpty()) {
      tr.m_strPayee = payee;
  }

  tr.m_reconcile = d->reconcileState(extractLine('C'));
  tr.m_strMemo = extractLine('M');
  d->fixMultiLineMemo(tr.m_strMemo);
  s1.m_strMemo = tr.m_strMemo;
  // tr.m_listSplits.append(s1);

  if(extractLine('$').isEmpty()) {
    MyMoneyAccount account;
    // use the same values for the second split, but clear the ID and reverse the value
    MyMoneyStatement::Split s2 = s1;
    s2.m_reconcile = tr.m_reconcile;
    s2.m_amount = (-s1.m_amount);
//    s2.clearId();

    // standard transaction
    tmp = extractLine('L');
    if(d->isTransfer(tmp, m_qifProfile.accountDelimiter().left(1), m_qifProfile.accountDelimiter().mid(1, 1))) {
      accountId = transferAccount(tmp, false);

    } else {
/*      pos = tmp.findRev("--");
      if(pos != -1) {
        t.setValue("Dialog", tmp.mid(pos+2));
        tmp = tmp.left(pos);
      }*/

      // it's an expense / income
      tmp = tmp.stripWhiteSpace();
      accountId = checkCategory(tmp, s1.m_amount, s2.m_amount);
    }

    if(!accountId.isEmpty()) {
      try {
        MyMoneyAccount account = file->account(accountId);
        // FIXME: check that the type matches and ask if not

        if ( account.accountType() == MyMoneyAccount::Investment )
        {
          kdDebug(0) << "Line " << m_linenumber << ": Cannot transfer to/from an investment account. Transaction ignored." << endl;
          return;
        }
        if ( account.id() == m_account.id() )
        {
          kdDebug(0) << "Line " << m_linenumber << ": Cannot transfer to the same account. Transfer ignored." << endl;
          accountId = TQString();
        }

      } catch (MyMoneyException *e) {
        kdDebug(0) << "Line " << m_linenumber << ": Account with id " << accountId.data() << " not found" << endl;
        accountId = TQString();
        delete e;
      }
    }

    if(!accountId.isEmpty()) {
      s2.m_accountId = accountId;
      s2.m_strCategoryName = tmp;
      tr.m_listSplits.append(s2);
    }

  } else {
    // split transaction
    TQValueList<qSplit> listqSplits;

    extractSplits(listqSplits);   //      ****** ensure each field is ******
                                  //      *   attached to correct split    *
    int   count;

    for(count = 1; !extractLine('$', count).isEmpty(); ++count)
    {
      MyMoneyStatement::Split s2 = s1;
      s2.m_amount = (-m_qifProfile.value('$', listqSplits[count-1].m_amount));   // Amount of split
      s2.m_strMemo = listqSplits[count-1].m_strMemo;                             // Memo in split
      tmp = listqSplits[count-1].m_strCategoryName;                              // Category in split

      if(d->isTransfer(tmp, m_qifProfile.accountDelimiter().left(1), m_qifProfile.accountDelimiter().mid(1, 1)))
      {
        accountId = transferAccount(tmp, false);

      } else {
        pos = tmp.findRev("--");
        if(pos != -1) {
///          t.setValue("Dialog", tmp.mid(pos+2));
          tmp = tmp.left(pos);
        }
        tmp = tmp.stripWhiteSpace();
        accountId = checkCategory(tmp, s1.m_amount, s2.m_amount);
      }

      if(!accountId.isEmpty()) {
        try {
          MyMoneyAccount account = file->account(accountId);
          // FIXME: check that the type matches and ask if not

          if ( account.accountType() == MyMoneyAccount::Investment )
          {
            kdDebug(0) << "Line " << m_linenumber << ": Cannot convert a split transfer to/from an investment account. Split removed. Total amount adjusted from " << tr.m_amount.formatMoney("", 2) << " to " << (tr.m_amount + s2.m_amount).formatMoney("", 2) << "\n";
            tr.m_amount += s2.m_amount;
            continue;
          }
          if ( account.id() == m_account.id() )
          {
            kdDebug(0) << "Line " << m_linenumber << ": Cannot transfer to the same account. Transfer ignored." << endl;
            accountId = TQString();
          }

        } catch (MyMoneyException *e) {
          kdDebug(0) << "Line " << m_linenumber << ": Account with id " << accountId.data() << " not found" << endl;
          accountId = TQString();
          delete e;
        }
      }
      if(!accountId.isEmpty())
      {
        s2.m_accountId = accountId;
        s2.m_strCategoryName = tmp;
        tr.m_listSplits += s2;
        // in case the transaction does not have a memo and we
        // process the first split just copy the memo over
        if(tr.m_listSplits.count() == 1 && tr.m_strMemo.isEmpty())
          tr.m_strMemo = s2.m_strMemo;
      }
      else
      {
        // TODO add an option to create a "Unassigned" category
        // for now, we just drop the split which will show up as unbalanced
        // transaction in the KMyMoney ledger view
      }
    }
  }

  // Add the transaction to the statement
  d->st.m_listTransactions +=tr;
}

void MyMoneyQifReader::processInvestmentTransactionEntry(void)
{
//   kdDebug(2) << "Investment Transaction:" << m_qifEntry.count() << " lines" << endl;
  /*
  Items for Investment Accounts
  Field   Indicator Explanation
  D   Date
  N   Action
  Y   Security (NAME, not symbol)
  I   Price
  Q   Quantity (number of shares or split ratio)
  T   Transaction amount
  C   Cleared status
  P   Text in the first line for transfers and reminders (Payee)
  M   Memo
  O   Commission
  L   Account for the transfer
  $   Amount transferred
  ^   End of the entry

  It will be presumed all transactions are to the associated cash account, if
  one exists, unless otherwise noted by the 'L' field.

  Expense/Income categories will be automatically generated, "_Dividend",
  "_InterestIncome", etc.

  */

  MyMoneyFile* file = MyMoneyFile::instance();

  MyMoneyStatement::Transaction tr;
  d->st.m_eType = MyMoneyStatement::etInvestment;

//  t.setCommodity(m_account.currencyId());
  // 'D' field: Date
  TQDate date = m_qifProfile.date(extractLine('D'));
  if(date.isValid())
    tr.m_datePosted = date;
  else
  {
    int rc = KMessageBox::warningContinueCancel(0,
         i18n("The date entry \"%1\" read from the file cannot be interpreted through the current "
              "date profile setting of \"%2\".\n\nPressing \"Continue\" will "
              "assign todays date to the transaction. Pressing \"Cancel\" will abort "
              "the import operation. You can then restart the import and select a different "
              "QIF profile or create a new one.")
           .arg(extractLine('D')).arg(m_qifProfile.inputDateFormat()),
         i18n("Invalid date format"));
    switch(rc) {
      case KMessageBox::Continue:
        tr.m_datePosted = TQDate::currentDate();
        break;

      case KMessageBox::Cancel:
        throw new MYMONEYEXCEPTION("USERABORT");
        break;
    }
  }

  // 'M' field: Memo
  TQString memo = extractLine('M');
  d->fixMultiLineMemo(memo);
  tr.m_strMemo = memo;
  unsigned long h;

  h = MyMoneyTransaction::hash(m_qifEntry.join(";"));

  TQString hashBase;
  hashBase.sprintf("%s-%07lx", m_qifProfile.date(extractLine('D')).toString(Qt::ISODate).data(), h);
  int idx = 1;
  TQString hash;
  for(;;) {
    hash = TQString("%1-%2").arg(hashBase).arg(idx);
    TQMap<TQString, bool>::const_iterator it;
    it = d->m_hashMap.find(hash);
    if(it == d->m_hashMap.end()) {
      d->m_hashMap[hash] = true;
      break;
    }
    ++idx;
  }
  tr.m_strBankID = hash;

  // '#' field: BankID
  TQString tmp = extractLine('#');
  if ( ! tmp.isEmpty() )
    tr.m_strBankID = TQString("ID %1").arg(tmp);

  // Reconciliation flag
  tr.m_reconcile = d->reconcileState(extractLine('C'));

  // 'O' field: Fees
  tr.m_fees = m_qifProfile.value('T', extractLine('O'));
  // 'T' field: Amount
  MyMoneyMoney amount = m_qifProfile.value('T', extractLine('T'));
  tr.m_amount = amount;

  MyMoneyStatement::Price price;

  price.m_date = date;
  price.m_strSecurity = extractLine('Y');
  price.m_amount = m_qifProfile.value('T', extractLine('I'));

#if 0 // we must check for that later, because certain activities don't need a security
  // 'Y' field: Security name

  TQString securityname = extractLine('Y').lower();
  if ( securityname.isEmpty() )
  {
    kdDebug(2) << "Line " << m_linenumber << ": Investment transaction without a security is not supported." << endl;
    return;
  }
  tr.m_strSecurity = securityname;
#endif

#if 0

  // For now, we let the statement reader take care of that.

  // The big problem here is that the Y field is not the SYMBOL, it's the NAME.
  // The name is not very unique, because people could have used slightly different
  // abbreviations or ordered words differently, etc.
  //
  // If there is a perfect name match with a subordinate stock account, great.
  // More likely, we have to rely on the QIF file containing !Type:Security
  // records, which tell us the mapping from name to symbol.
  //
  // Therefore, generally it is not recommended to import a QIF file containing
  // investment transactions but NOT containing security records.

  TQString securitysymbol = m_investmentMap[securityname];

  // the correct account is the stock account which matches two criteria:
  // (1) it is a sub-account of the selected investment account, and either
  // (2a) the security name of the transaction matches the name of the security, OR
  // (2b) the security name of the transaction maps to a symbol which matches the symbol of the security

  // search through each subordinate account
  bool found = false;
  MyMoneyAccount thisaccount = m_account;
  TQStringList accounts = thisaccount.accountList();
  TQStringList::const_iterator it_account = accounts.begin();
  while( !found && it_account != accounts.end() )
  {
    TQString currencyid = file->account(*it_account).currencyId();
    MyMoneySecurity security = file->security( currencyid );
    TQString symbol = security.tradingSymbol().lower();
    TQString name = security.name().lower();

    if ( securityname == name || securitysymbol == symbol )
    {
      d->st_AccountId = *it_account;
      s1.m_accountId = *it_account;
      thisaccount = file->account(*it_account);
      found = true;

#if 0
      // update the price, while we're here.  in the future, this should be
      // an option
      TQString basecurrencyid = file->baseCurrency().id();
      MyMoneyPrice price = file->price( currencyid, basecurrencyid, t_in.m_datePosted, true );
      if ( !price.isValid() )
      {
        MyMoneyPrice newprice( currencyid, basecurrencyid, t_in.m_datePosted, t_in.m_moneyAmount / t_in.m_dShares, i18n("Statement Importer") );
        file->addPrice(newprice);
      }
#endif
    }

    ++it_account;
  }

  if (!found)
  {
    kdDebug(2) << "Line " << m_linenumber << ": Security " << securityname << " not found in this account.  Transaction ignored." << endl;

    // If the security is not known, notify the user
    // TODO (Ace) A "SelectOrCreateAccount" interface for investments
    KMessageBox::information(0, i18n("This investment account does not contain the \"%1\" security.  "
                                      "Transactions involving this security will be ignored.").arg(securityname),
                                i18n("Security not found"),
                                TQString("MissingSecurity%1").arg(securityname.stripWhiteSpace()));
    return;
  }
#endif

  // 'Y' field: Security
  tr.m_strSecurity = extractLine('Y');

  // 'Q' field: Quantity
  MyMoneyMoney quantity = m_qifProfile.value('T', extractLine('Q'));

  // 'N' field: Action
  TQString action = extractLine('N').lower();

  // remove trailing X, which seems to have no purpose (?!)
  bool xAction = false;
  if ( action.endsWith("x") ) {
    action = action.left( action.length() - 1 );
    xAction = true;
  }

  // Whether to create a cash split for the other side of the value
  TQString accountname ;//= extractLine('L');
  if ( action == "reinvdiv" || action == "reinvlg" || action == "reinvsh" )
  {
    d->st.m_listPrices += price;
    tr.m_shares = quantity;
    tr.m_eAction = (MyMoneyStatement::Transaction::eaReinvestDividend);
    tr.m_price = m_qifProfile.value('I', extractLine('I'));

    tr.m_strInterestCategory = extractLine('L');
    if(tr.m_strInterestCategory.isEmpty()) {
      tr.m_strInterestCategory = d->typeToAccountName(action);
    }
  }
  else if ( action == "div" || action == "cgshort" || action == "cgmid" || action == "cglong" || action == "rtrncap")
  {
    tr.m_eAction = (MyMoneyStatement::Transaction::eaCashDividend);

    TQString tmp = extractLine('L');
    // if the action ends in an X, the L-Record contains the asset account
    // to which the dividend should be transferred. In the other cases, it
    // may contain a category that identifies the income category for the
    // dividend payment
    if((xAction == true)
    && (d->isTransfer(tmp, m_qifProfile.accountDelimiter().left(1), m_qifProfile.accountDelimiter().mid(1, 1)) == true)) {
      tr.m_strBrokerageAccount = tmp;
      transferAccount(tmp);           // make sure the account exists
    } else {
      tr.m_strInterestCategory = tmp;
    }

    // make sure, we have valid category. Either taken from the L-Record above,
    // or derived from the action code
    if(tr.m_strInterestCategory.isEmpty()) {
      tr.m_strInterestCategory = d->typeToAccountName(action);
    }

    // For historic reasons (coming from the OFX importer) the statement
    // reader expects the dividend with a reverse sign. So we just do that.
    tr.m_amount = -(amount - tr.m_fees);

    // We need an extra split which will be the zero-amount investment split
    // that serves to mark this transaction as a cash dividend and note which
    // stock account it belongs to.
    MyMoneyStatement::Split s2;
    s2.m_amount = MyMoneyMoney();
    s2.m_strCategoryName = extractLine('Y');
    tr.m_listSplits.append(s2);
  }
  else if ( action == "intinc" || action == "miscinc" || action == "miscexp")
  {
    tr.m_eAction = (MyMoneyStatement::Transaction::eaInterest);
    if(action == "miscexp")
      tr.m_eAction = (MyMoneyStatement::Transaction::eaFees);

    TQString tmp = extractLine('L');
    // if the action ends in an X, the L-Record contains the asset account
    // to which the dividend should be transferred. In the other cases, it
    // may contain a category that identifies the income category for the
    // payment
    if((xAction == true)
        && (d->isTransfer(tmp, m_qifProfile.accountDelimiter().left(1), m_qifProfile.accountDelimiter().mid(1, 1)) == true)) {
      tr.m_strBrokerageAccount = tmp;
      transferAccount(tmp);           // make sure the account exists
    } else {
      tr.m_strInterestCategory = tmp;
    }

    // make sure, we have a valid category. Either taken from the L-Record above,
    // or derived from the action code
    if(tr.m_strInterestCategory.isEmpty()) {
      tr.m_strInterestCategory = d->typeToAccountName(action);
    }


    // For historic reasons (coming from the OFX importer) the statement
    // reader expects the dividend with a reverse sign. So we just do that.
    if(action != "miscexp")
      tr.m_amount = -(amount - tr.m_fees);

    if(tr.m_strMemo.isEmpty())
      tr.m_strMemo = (TQString("%1 %2").arg(extractLine('Y')).arg(d->typeToAccountName(action))).stripWhiteSpace();
  }
  else if (action == "xin" || action == "xout")
  {
    TQString payee = extractLine('P');
    if(!payee.isEmpty() && ((payee.lower() == "opening balance") || KMyMoneyGlobalSettings::qifOpeningBalance().lower().contains(payee.lower()))) {
      createOpeningBalance(MyMoneyAccount::Investment);
      return;
    }

    tr.m_eAction = (MyMoneyStatement::Transaction::eaNone);
    MyMoneyStatement::Split s2;
    TQString tmp = extractLine('L');
    if(d->isTransfer(tmp, m_qifProfile.accountDelimiter().left(1), m_qifProfile.accountDelimiter().mid(1, 1))) {
      s2.m_accountId = transferAccount(tmp);
      s2.m_strCategoryName = tmp;
    } else {
      s2.m_strCategoryName = extractLine('L');
      if(tr.m_strInterestCategory.isEmpty()) {
        s2.m_strCategoryName = d->typeToAccountName(action);
      }
    }

    if(action == "xout")
      tr.m_amount = -tr.m_amount;

    s2.m_amount = -tr.m_amount;
    tr.m_listSplits.append(s2);
  }
  else if (action == "buy")
  {
    TQString tmp = extractLine('L');
    if(d->isTransfer(tmp, m_qifProfile.accountDelimiter().left(1), m_qifProfile.accountDelimiter().mid(1, 1)) == true) {
      tr.m_strBrokerageAccount = tmp;
      transferAccount(tmp);           // make sure the account exists
    }

    d->st.m_listPrices += price;
    tr.m_shares = quantity;
    tr.m_eAction = (MyMoneyStatement::Transaction::eaBuy);
  }
  else if (action == "sell")
  {
    TQString tmp = extractLine('L');
    if(d->isTransfer(tmp, m_qifProfile.accountDelimiter().left(1), m_qifProfile.accountDelimiter().mid(1, 1)) == true) {
      tr.m_strBrokerageAccount = tmp;
      transferAccount(tmp);           // make sure the account exists
    }

    d->st.m_listPrices += price;
    tr.m_shares = -quantity;
    tr.m_amount = -amount;
    tr.m_eAction = (MyMoneyStatement::Transaction::eaSell);
  }
  else if ( action == "shrsin" )
  {
    tr.m_shares = quantity;
    tr.m_eAction = (MyMoneyStatement::Transaction::eaShrsin);
  }
  else if ( action == "shrsout" )
  {
    tr.m_shares = -quantity;
    tr.m_eAction = (MyMoneyStatement::Transaction::eaShrsout);
  }
  else if ( action == "stksplit" )
  {
    MyMoneyMoney splitfactor = (quantity / MyMoneyMoney(10,1)).reduce();

    // Stock splits not supported
//     kdDebug(2) << "Line " << m_linenumber << ": Stock split not supported (date=" << date << " security=" << securityname << " factor=" << splitfactor.toString() << ")" << endl;

//    s1.setShares(splitfactor);
//    s1.setValue(0);
//   s1.setAction(MyMoneySplit::ActionSplitShares);

//     return;
  }
  else
  {
    // Unsupported action type
    kdDebug(0) << "Line " << m_linenumber << ": Unsupported transaction action (" << action << ")" << endl;
    return;
  }
  d->st.m_strAccountName = accountname;
  d->st.m_listTransactions +=tr;

  /*************************************************************************
   *
   * These transactions are natively supported by KMyMoney
   *
   *************************************************************************/
  /*
  D1/ 3' 5
  NShrsIn
  YGENERAL MOTORS CORP 52BR1
  I20
  Q200
  U4,000.00
  T4,000.00
  M200 shares added to account @ $20/share
  ^
  */
  /*
  ^
  D1/14' 5
  NShrsOut
  YTEMPLETON GROWTH 97GJ0
  Q50
90  ^
  */
  /*
  D1/28' 5
  NBuy
  YGENERAL MOTORS CORP 52BR1
  I24.35
  Q100
  U2,435.00
  T2,435.00
  ^
  */
  /*
  D1/ 5' 5
  NSell
  YUnited Vanguard
  I8.41
  Q50
  U420.50
  T420.50
  ^
  */
  /*
  D1/ 7' 5
  NReinvDiv
  YFRANKLIN INCOME 97GM2
  I38
  Q1
  U38.00
  T38.00
  ^
  */
  /*************************************************************************
   *
   * These transactions are all different kinds of income.  (Anything that
   * follows the DNYUT pattern).  They are all handled the same, the only
   * difference is which income account the income is placed into.  By
   * default, it's placed into _xxx where xxx is the right side of the
   * N field.  e.g. NDiv transaction goes into the _Div account
   *
   *************************************************************************/
  /*
  D1/10' 5
  NDiv
  YTEMPLETON GROWTH 97GJ0
  U10.00
  T10.00
  ^
  */
  /*
  D1/10' 5
  NIntInc
  YTEMPLETON GROWTH 97GJ0
  U20.00
  T20.00
  ^
  */
  /*
  D1/10' 5
  NCGShort
  YTEMPLETON GROWTH 97GJ0
  U111.00
  T111.00
  ^
  */
  /*
  D1/10' 5
  NCGLong
  YTEMPLETON GROWTH 97GJ0
  U333.00
  T333.00
  ^
  */
  /*
  D1/10' 5
  NCGMid
  YTEMPLETON GROWTH 97GJ0
  U222.00
  T222.00
  ^
  */
  /*
  D2/ 2' 5
  NRtrnCap
  YFRANKLIN INCOME 97GM2
  U1,234.00
  T1,234.00
  ^
  */
  /*************************************************************************
   *
   * These transactions deal with miscellaneous activity that KMyMoney
   * does not support, but may support in the future.
   *
   *************************************************************************/
  /*   Note the Q field is the split ratio per 10 shares, so Q12.5 is a
        12.5:10 split, otherwise known as 5:4.
  D1/14' 5
  NStkSplit
  YIBM
  Q12.5
  ^
  */
  /*************************************************************************
   *
   * These transactions deal with short positions and options, which are
   * not supported at all by KMyMoney.  They will be ignored for now.
   * There may be a way to hack around this, by creating a new security
   * "IBM_Short".
   *
   *************************************************************************/
  /*
  D1/21' 5
  NShtSell
  YIBM
  I92.38
  Q100
  U9,238.00
  T9,238.00
  ^
  */
  /*
  D1/28' 5
  NCvrShrt
  YIBM
  I92.89
  Q100
  U9,339.00
  T9,339.00
  O50.00
  ^
  */
  /*
  D6/ 1' 5
  NVest
  YIBM Option
  Q20
  ^
  */
  /*
  D6/ 8' 5
  NExercise
  YIBM Option
  I60.952381
  Q20
  MFrom IBM Option Grant 6/1/2004
  ^
  */
  /*
  D6/ 1'14
  NExpire
  YIBM Option
  Q5
  ^
  */
  /*************************************************************************
   *
   * These transactions do not have an associated investment ("Y" field)
   * so presumably they are only valid for the cash account.  Once I
   * understand how these are really implemented, they can probably be
   * handled without much trouble.
   *
   *************************************************************************/
  /*
  D1/14' 5
  NCash
  U-100.00
  T-100.00
  LBank Chrg
  ^
  */
  /*
  D1/15' 5
  NXOut
  U500.00
  T500.00
  L[CU Savings]
  $500.00
  ^
  */
  /*
  D1/28' 5
  NXIn
  U1,000.00
  T1,000.00
  L[CU Checking]
  $1,000.00
  ^
  */
  /*
  D1/25' 5
  NMargInt
  U25.00
  T25.00
  ^
  */
}

const TQString MyMoneyQifReader::findOrCreateIncomeAccount(const TQString& searchname)
{
  TQString result;

  MyMoneyFile *file = MyMoneyFile::instance();

  // First, try to find this account as an income account
  MyMoneyAccount acc = file->income();
  TQStringList list = acc.accountList();
  TQStringList::ConstIterator it_accid = list.begin();
  while ( it_accid != list.end() )
  {
    acc = file->account(*it_accid);
    if ( acc.name() == searchname )
    {
      result = *it_accid;
      break;
    }
    ++it_accid;
  }

  // If we did not find the account, now we must create one.
  if ( result.isEmpty() )
  {
    MyMoneyAccount acc;
    acc.setName( searchname );
    acc.setAccountType( MyMoneyAccount::Income );
    MyMoneyAccount income = file->income();
    MyMoneyFileTransaction ft;
    file->addAccount( acc, income );
    ft.commit();
    result = acc.id();
  }

  return result;
}

// TODO (Ace) Combine this and the previous function

const TQString MyMoneyQifReader::findOrCreateExpenseAccount(const TQString& searchname)
{
  TQString result;

  MyMoneyFile *file = MyMoneyFile::instance();

  // First, try to find this account as an income account
  MyMoneyAccount acc = file->expense();
  TQStringList list = acc.accountList();
  TQStringList::ConstIterator it_accid = list.begin();
  while ( it_accid != list.end() )
  {
    acc = file->account(*it_accid);
    if ( acc.name() == searchname )
    {
      result = *it_accid;
      break;
    }
    ++it_accid;
  }

  // If we did not find the account, now we must create one.
  if ( result.isEmpty() )
  {
    MyMoneyAccount acc;
    acc.setName( searchname );
    acc.setAccountType( MyMoneyAccount::Expense );
    MyMoneyFileTransaction ft;
    MyMoneyAccount expense = file->expense();
    file->addAccount( acc, expense );
    ft.commit();
    result = acc.id();
  }

  return result;
}

TQString MyMoneyQifReader::checkCategory(const TQString& name, const MyMoneyMoney value, const MyMoneyMoney value2)
{
  TQString accountId;
  MyMoneyFile *file = MyMoneyFile::instance();
  MyMoneyAccount account;
  bool found = true;

  if(!name.isEmpty()) {
    // The category might be constructed with an arbitraty depth (number of
    // colon delimited fields). We try to find a parent account within this
    // hierarchy by searching the following sequence:
    //
    //    aaaa:bbbb:cccc:ddddd
    //
    // 1. search aaaa:bbbb:cccc:dddd, create nothing
    // 2. search aaaa:bbbb:cccc     , create dddd
    // 3. search aaaa:bbbb          , create cccc:dddd
    // 4. search aaaa               , create bbbb:cccc:dddd
    // 5. don't search              , create aaaa:bbbb:cccc:dddd

    account.setName(name);
    TQString accName;      // part to be created (right side in above list)
    TQString parent(name); // a possible parent part (left side in above list)
    do {
      accountId = file->categoryToAccount(parent);
      if(accountId.isEmpty()) {
        found = false;
        // prepare next step
        if(!accName.isEmpty())
          accName.prepend(':');
        accName.prepend(parent.section(':', -1));
        account.setName(accName);
        parent = parent.section(':', 0, -2);
      } else if(!accName.isEmpty()) {
        account.setParentAccountId(accountId);
      }
    }
    while(!parent.isEmpty() && accountId.isEmpty());

    // if we did not find the category, we create it
    if(!found) {
      MyMoneyAccount parent;
      if(account.parentAccountId().isEmpty()) {
        if(!value.isNegative() && value2.isNegative())
          parent = file->income();
        else
          parent = file->expense();
      } else {
        parent = file->account(account.parentAccountId());
      }
      account.setAccountType((!value.isNegative() && value2.isNegative()) ? MyMoneyAccount::Income : MyMoneyAccount::Expense);
      MyMoneyAccount brokerage;
      // clear out the parent id, because createAccount() does not like that
      account.setParentAccountId(TQString());
      kmymoney2->createAccount(account, parent, brokerage, MyMoneyMoney());
      accountId = account.id();
    }
  }

  return accountId;
}

TQString MyMoneyQifReader::processAccountEntry(bool resetAccountId)
{
  MyMoneyFile* file = MyMoneyFile::instance();

  MyMoneyAccount account;
  TQString tmp;

  account.setName(extractLine('N'));
  // tqDebug("Process account '%s'", account.name().data());

  account.setDescription(extractLine('D'));

  tmp = extractLine('$');
  if(tmp.length() > 0)
    account.setValue("lastStatementBalance", tmp);

  tmp = extractLine('/');
  if(tmp.length() > 0)
    account.setValue("lastStatementDate", m_qifProfile.date(tmp).toString("yyyy-MM-dd"));

  QifEntryTypeE transactionType = EntryTransaction;
  TQString type = extractLine('T').lower().remove(TQRegExp("\\s+"));
  if(type == m_qifProfile.profileType().lower().remove(TQRegExp("\\s+"))) {
    account.setAccountType(MyMoneyAccount::Checkings);
  } else if(type == "ccard" || type == "creditcard") {
    account.setAccountType(MyMoneyAccount::CreditCard);
  } else if(type == "cash") {
    account.setAccountType(MyMoneyAccount::Cash);
  } else if(type == "otha") {
    account.setAccountType(MyMoneyAccount::Asset);
  } else if(type == "othl") {
    account.setAccountType(MyMoneyAccount::Liability);
  } else if(type == "invst" || type == "port") {
    account.setAccountType(MyMoneyAccount::Investment);
    transactionType = EntryInvestmentTransaction;
  } else if(type == "mutual") { // stock account w/o umbrella investment account
    account.setAccountType(MyMoneyAccount::Stock);
    transactionType = EntryInvestmentTransaction;
  } else if(type == "unknown") {
    // don't do anything with the type, leave it unknown
  } else {
    account.setAccountType(MyMoneyAccount::Checkings);
    kdDebug(2) << "Line " << m_linenumber << ": Unknown account type '" << type << "', checkings assumed" << endl;
  }

  // check if we can find the account already in the file
  MyMoneyAccount acc = kmymoney2->findAccount(account, MyMoneyAccount());
  if(acc.id().isEmpty()) {
    // in case the account is not found by name and the type is
    // unknown, we have to assume something and create a checking account.
    // this might be wrong, but we have no choice at this point.
    if(account.accountType() == MyMoneyAccount::UnknownAccountType)
      account.setAccountType(MyMoneyAccount::Checkings);

    MyMoneyAccount parentAccount;
    MyMoneyAccount brokerage;
    MyMoneyMoney balance;
    // in case it's a stock account, we need to setup a fix investment account
    if(account.isInvest()) {
      acc.setName(i18n("%1 (Investment)").arg(account.name()));    // use the same name for the investment account
      acc.setDescription(i18n("Autogenerated by QIF importer from type Mutual account entry"));
      acc.setAccountType(MyMoneyAccount::Investment);
      parentAccount = file->asset();
      kmymoney2->createAccount(acc, parentAccount, brokerage, MyMoneyMoney());
      parentAccount = acc;
      tqDebug("We still need to create the stock account in MyMoneyQifReader::processAccountEntry()");
    } else {
      // setup parent according the type of the account
      switch(account.accountGroup()) {
        case MyMoneyAccount::Asset:
        default:
          parentAccount = file->asset();
          break;
        case MyMoneyAccount::Liability:
          parentAccount = file->liability();
          break;
        case MyMoneyAccount::Equity:
          parentAccount = file->equity();
          break;
      }
    }

    // investment accounts will receive a brokerage account, as KMyMoney
    // currently does not allow to store funds in the investment account directly
    if(account.accountType() == MyMoneyAccount::Investment) {
      brokerage.setName(account.brokerageName());
      brokerage.setAccountType(MyMoneyAccount::Checkings);
      brokerage.setCurrencyId(MyMoneyFile::instance()->baseCurrency().id());
    }
    kmymoney2->createAccount(account, parentAccount, brokerage, balance);
    acc = account;
    // tqDebug("Account created");
  } else {
    // tqDebug("Existing account found");
  }

  if(resetAccountId) {
    // possibly start a new statement
    d->finishStatement();
    m_account = acc;
    d->st.m_accountId = m_account.id();
    d->transactionType = transactionType;
  }
  return acc.id();
}

void MyMoneyQifReader::selectOrCreateAccount(const SelectCreateMode mode, MyMoneyAccount& account, const MyMoneyMoney& balance)
{
  MyMoneyFile* file = MyMoneyFile::instance();

  TQString accountId;
  TQString msg;
  TQString typeStr;
  TQString leadIn;
  KMyMoneyUtils::categoryTypeE type;

  TQMap<TQString, TQString>::ConstIterator it;

  type = KMyMoneyUtils::none;
  switch(account.accountGroup()) {
    default:
      type = KMyMoneyUtils::asset;
      type = (KMyMoneyUtils::categoryTypeE) (type | KMyMoneyUtils::liability);
      typeStr = i18n("account");
      leadIn = i18n("al");
      break;

    case MyMoneyAccount::Income:
    case MyMoneyAccount::Expense:
      type = KMyMoneyUtils::income;
      type = (KMyMoneyUtils::categoryTypeE) (type | KMyMoneyUtils::expense);
      typeStr = i18n("category");
      leadIn = i18n("ei");
      msg = i18n("Category selection");
      break;
  }

  KAccountSelectDlg accountSelect(type, "QifImport", kmymoney2);
  if(!msg.isEmpty())
    accountSelect.setCaption(msg);

  it = m_accountTranslation.find((leadIn + MyMoneyFile::AccountSeperator + account.name()).lower());
  if(it != m_accountTranslation.end()) {
    try {
      account = file->account(*it);
      return;

    } catch (MyMoneyException *e) {
      TQString message(i18n("Account \"%1\" disappeared: ").arg(account.name()));
      message += e->what();
      KMessageBox::error(0, message);
      delete e;
    }
  }

  if(!account.name().isEmpty()) {
    if(type & (KMyMoneyUtils::income | KMyMoneyUtils::expense)) {
      accountId = file->categoryToAccount(account.name());
    } else {
      accountId = file->nameToAccount(account.name());
    }

    if(mode == Create) {
      if(!accountId.isEmpty()) {
        account = file->account(accountId);
        return;

      } else {
        switch(KMessageBox::questionYesNo(0,
                  i18n("The %1 '%2' does not exist. Do you "
                       "want to create it?").arg(typeStr).arg(account.name()))) {
          case KMessageBox::Yes:
            break;
          case KMessageBox::No:
            return;
        }
      }
    } else {
      accountSelect.setHeader(i18n("Select %1").arg(typeStr));
      if(!accountId.isEmpty()) {
        msg = i18n("The %1 <b>%2</b> currently exists. Do you want "
                   "to import transactions to this account?")
                    .arg(typeStr).arg(account.name());

      } else {
        msg = i18n("The %1 <b>%2</b> currently does not exist. You can "
                   "create a new %3 by pressing the <b>Create</b> button "
                   "or select another %4 manually from the selection box.")
                  .arg(typeStr).arg(account.name()).arg(typeStr).arg(typeStr);
      }
    }
  } else {
    accountSelect.setHeader(i18n("Import transactions to %1").arg(typeStr));
    msg = i18n("No %1 information has been found in the selected QIF file. "
               "Please select an account using the selection box in the dialog or "
               "create a new %2 by pressing the <b>Create</b> button.")
               .arg(typeStr).arg(typeStr);
  }

  accountSelect.setDescription(msg);
  accountSelect.setAccount(account, accountId);
  accountSelect.setMode(mode == Create);
  accountSelect.showAbortButton(true);

  // display current entry in widget, the offending line (if any) will be shown in red
  TQStringList::Iterator it_e;
  int i = 0;
  for(it_e = m_qifEntry.begin(); it_e != m_qifEntry.end(); ++it_e) {
    if(m_extractedLine == i)
      accountSelect.m_qifEntry->setColor(TQColor("red"));
    accountSelect.m_qifEntry->append(*it_e);
    accountSelect.m_qifEntry->setColor(TQColor("black"));
    ++i;
  }

  for(;;) {
    if(accountSelect.exec() == TQDialog::Accepted) {
      if(!accountSelect.selectedAccount().isEmpty()) {
        accountId = accountSelect.selectedAccount();

        m_accountTranslation[(leadIn + MyMoneyFile::AccountSeperator + account.name()).lower()] = accountId;

        // MMAccount::openingBalance() is where the accountSelect dialog has
        // stashed the opening balance that the user chose.
        MyMoneyAccount importedAccountData(account);
        // MyMoneyMoney balance = importedAccountData.openingBalance();
        account = file->account(accountId);
        if ( ! balance.isZero() )
        {
          TQString openingtxid = file->openingBalanceTransaction(account);
          MyMoneyFileTransaction ft;
          if ( ! openingtxid.isEmpty() )
          {
            MyMoneyTransaction openingtx = file->transaction(openingtxid);
            MyMoneySplit split = openingtx.splitByAccount(account.id());

            if ( split.shares() != balance )
            {
              const MyMoneySecurity&  sec = file->security(account.currencyId());
              if ( KMessageBox::questionYesNo(
                tqApp->mainWidget(),
                i18n("The %1 account currently has an opening balance of %2. This QIF file reports an opening balance of %3. Would you like to overwrite the current balance with the one from the QIF file?").arg(account.name(), split.shares().formatMoney(account, sec), balance.formatMoney(account, sec)),
                i18n("Overwrite opening balance"),
                KStdGuiItem::yes(),
                KStdGuiItem::no(),
                "OverwriteOpeningBalance" )
                == KMessageBox::Yes )
              {
                file->removeTransaction( openingtx );
                file->createOpeningBalanceTransaction( account, balance );
              }
            }
          }
          else
          {
            // Add an opening balance
            file->createOpeningBalanceTransaction( account, balance );
          }
          ft.commit();
        }
        break;
      }

    } else if(accountSelect.aborted())
      throw new MYMONEYEXCEPTION("USERABORT");

    if(typeStr == i18n("account")) {
      KMessageBox::error(0, i18n("You must select or create an account."));
    } else {
      KMessageBox::error(0, i18n("You must select or create a category."));
    }
  }
}

void MyMoneyQifReader::setProgressCallback(void(*callback)(int, int, const TQString&))
{
  m_progressCallback = callback;
}

void MyMoneyQifReader::signalProgress(int current, int total, const TQString& msg)
{
  if(m_progressCallback != 0)
    (*m_progressCallback)(current, total, msg);
}

void MyMoneyQifReader::processPriceEntry(void)
{
/*
  !Type:Prices
  "IBM",141 9/16,"10/23/98"
  ^
  !Type:Prices
  "GMW",21.28," 3/17' 5"
  ^
  !Type:Prices
  "GMW",71652181.001,"67/128/ 0"
  ^

  Note that Quicken will often put in a price with a bogus date and number.  We will ignore
  prices with bogus dates.  Hopefully that will catch all of these.

  Also note that prices can be in fractional units, e.g. 141 9/16.

*/

  TQStringList::const_iterator it_line = m_qifEntry.begin();

  // Make a price for each line
  TQRegExp priceExp("\"(.*)\",(.*),\"(.*)\"");
  while ( it_line != m_qifEntry.end() )
  {
    if(priceExp.search(*it_line) != -1) {
      MyMoneyStatement::Price price;
      price.m_strSecurity = priceExp.cap(1);
      TQString pricestr = priceExp.cap(2);
      TQString datestr = priceExp.cap(3);
      kdDebug(0) << "Price:" << price.m_strSecurity << " / " << pricestr << " / " << datestr << endl;

      // Only add the price if the date is valid.  If invalid, fail silently.  See note above.
      // Also require the price value to not have any slashes.  Old prices will be something like
      // "25 9/16", which we do not support.  So we'll skip the price for now.
      TQDate date = m_qifProfile.date(datestr);
      MyMoneyMoney rate(m_qifProfile.value('P', pricestr));
      if(date.isValid() && !rate.isZero())
      {
        price.m_amount = rate;
        price.m_date = date;
        d->st.m_listPrices += price;
      }
    }
    ++it_line;
  }
}

void MyMoneyQifReader::processSecurityEntry(void)
{
  /*
  !Type:Security
  NVANGUARD 500 INDEX
  SVFINX
  TMutual Fund
  ^
  */

  MyMoneyStatement::Security security;
  security.m_strName = extractLine('N');
  security.m_strSymbol = extractLine('S');

  d->st.m_listSecurities += security;
}

#include "mymoneyqifreader.moc"
