/***************************************************************************
                          mymoneyqifprofile.cpp  -  description
                             -------------------
    begin                : Tue Dec 24 2002
    copyright            : (C) 2002 by Thomas Baumgart
    email                : thb@net-bembel.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

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

#include <tqregexp.h>
#include <tqvaluevector.h>

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

#include <tdeglobal.h>
#include <tdeconfig.h>
#include <tdelocale.h>
#include <kcalendarsystem.h>

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

#include "mymoneyqifprofile.h"
#include "../mymoney/mymoneyexception.h"
#include "../mymoney/mymoneymoney.h"

/*
 * CENTURY_BREAK is used to identfy the century for a two digit year
 *
 * if yr is < CENTURY_BREAK it is in 2000
 * if yr is >= CENTURY_BREAK it is in 1900
 *
 * so with CENTURY_BREAK being 70 the following will happen:
 *
 *  00..69  ->  2000..2069
 *  70..99  ->  1970..1999
 */
#define CENTURY_BREAK 70

class MyMoneyQifProfile::Private {
  public:
    Private() {
      m_changeCount.resize(3, 0);
      m_lastValue.resize(3, 0);
      m_largestValue.resize(3, 0);
    }

    void getThirdPosition(void);
    void dissectDate(TQValueVector<TQString>& parts, const TQString& txt) const;

    TQValueVector<int>    m_changeCount;
    TQValueVector<int>    m_lastValue;
    TQValueVector<int>    m_largestValue;
    TQMap<TQChar, int>     m_partPos;
};

void MyMoneyQifProfile::Private::dissectDate(TQValueVector<TQString>& parts, const TQString& txt) const
{
  TQRegExp nonDelimChars("[ 0-9a-zA-Z]");
  int part = 0;                 // the current part we scan
  unsigned int pos;             // the current scan position
  unsigned int maxPartSize = txt.length() > 6 ? 4 : 2;
                                // the maximum size of a part
  // some fu... up MS-Money versions write two delimiter in a row
  // so we need to keep track of them. Example: D14/12/'08
  bool lastWasDelim = false;

  // separate the parts of the date and keep the locations of the delimiters
  for(pos = 0; pos < txt.length() && part < 3; ++pos) {
    if(nonDelimChars.search(txt[pos]) == -1) {
      if(!lastWasDelim) {
        ++part;
        maxPartSize = 0;         // make sure to pick the right one depending if next char is numeric or not
        lastWasDelim = true;
      }
    } else {
      lastWasDelim = false;
      // check if the part is over and we did not see a delimiter
      if((maxPartSize != 0) && (parts[part].length() == maxPartSize)) {
        ++part;
        maxPartSize = 0;
      }
      if(maxPartSize == 0) {
        maxPartSize = txt[pos].isDigit() ? 2 : 3;
        if(part == 2)
          maxPartSize = 4;
      }
      if(part < 3)
        parts[part] += txt[pos];
    }
  }

  if(part == 3) { // invalid date
    for(int i = 0; i < 3; ++i) {
      parts[i] = "0";
    }
  }
}


void MyMoneyQifProfile::Private::getThirdPosition(void)
{
  // if we have detected two parts we can calculate the third and its position
  if(m_partPos.count() == 2) {
    TQValueList<TQChar> partsPresent = m_partPos.keys();
    TQStringList partsAvail = TQStringList::split(",", "d,m,y");
    int missingIndex = -1;
    int value = 0;
    for(int i = 0; i < 3; ++i) {
      if(!partsPresent.contains(partsAvail[i][0])) {
        missingIndex = i;
      } else {
        value += m_partPos[partsAvail[i][0]];
      }
    }
    m_partPos[partsAvail[missingIndex][0]] = 3 - value;
  }
}



MyMoneyQifProfile::MyMoneyQifProfile() :
  d(new Private),
  m_isDirty(false)
{
  clear();
}

MyMoneyQifProfile::MyMoneyQifProfile(const TQString& name) :
  d(new Private),
  m_isDirty(false)
{
  loadProfile(name);
}

MyMoneyQifProfile::~MyMoneyQifProfile()
{
  delete d;
}

void MyMoneyQifProfile::clear(void)
{
  m_dateFormat = "%d.%m.%yyyy";
  m_apostropheFormat = "2000-2099";
  m_valueMode = "";
  m_filterScriptImport = "";
  m_filterScriptExport = "";
  m_filterFileType = "*.qif";

  m_decimal.clear();
  m_decimal['$'] =
  m_decimal['Q'] =
  m_decimal['T'] =
  m_decimal['O'] =
  m_decimal['I'] = TDEGlobal::locale()->monetaryDecimalSymbol()[0];

  m_thousands.clear();
  m_thousands['$'] =
  m_thousands['Q'] =
  m_thousands['T'] =
  m_thousands['O'] =
  m_thousands['I'] = TDEGlobal::locale()->monetaryThousandsSeparator()[0];

  m_openingBalanceText = "Opening Balance";
  m_voidMark = "VOID ";
  m_accountDelimiter = "[";

  m_profileName = "";
  m_profileDescription = "";
  m_profileType = "Bank";

  m_attemptMatchDuplicates = true;
}

void MyMoneyQifProfile::loadProfile(const TQString& name)
{
  TDEConfig* config = TDEGlobal::config();
  config->setGroup(name);

  clear();

  m_profileName = name;
  m_profileDescription = config->readEntry("Description", m_profileDescription);
  m_profileType = config->readEntry("Type", m_profileType);
  m_dateFormat = config->readEntry("DateFormat", m_dateFormat);
  m_apostropheFormat = config->readEntry("ApostropheFormat", m_apostropheFormat);
  m_accountDelimiter = config->readEntry("AccountDelimiter", m_accountDelimiter);
  m_openingBalanceText = config->readEntry("OpeningBalance", m_openingBalanceText);
  m_voidMark = config->readEntry("VoidMark", m_voidMark);
  m_filterScriptImport = config->readEntry("FilterScriptImport", m_filterScriptImport);
  m_filterScriptExport = config->readEntry("FilterScriptExport", m_filterScriptExport);
  m_filterFileType = config->readEntry("FilterFileType",m_filterFileType);

  m_attemptMatchDuplicates = config->readBoolEntry("AttemptMatchDuplicates", m_attemptMatchDuplicates);

  // make sure, we remove any old stuff for now
  config->deleteEntry("FilterScript");

  TQString tmp = TQString(m_decimal['Q']) + m_decimal['T'] + m_decimal['I'] +
                m_decimal['$'] + m_decimal['O'];
  tmp = config->readEntry("Decimal", tmp);
  m_decimal['Q'] = tmp[0];
  m_decimal['T'] = tmp[1];
  m_decimal['I'] = tmp[2];
  m_decimal['$'] = tmp[3];
  m_decimal['O'] = tmp[4];

  tmp = TQString(m_thousands['Q']) + m_thousands['T'] + m_thousands['I'] +
                m_thousands['$'] + m_thousands['O'];
  tmp = config->readEntry("Thousand", tmp);
  m_thousands['Q'] = tmp[0];
  m_thousands['T'] = tmp[1];
  m_thousands['I'] = tmp[2];
  m_thousands['$'] = tmp[3];
  m_thousands['O'] = tmp[4];

  m_isDirty = false;
}

void MyMoneyQifProfile::saveProfile(void)
{
  if(m_isDirty == true) {
    TDEConfig* config = TDEGlobal::config();
    config->setGroup(m_profileName);

    config->writeEntry("Description", m_profileDescription);
    config->writeEntry("Type", m_profileType);
    config->writeEntry("DateFormat", m_dateFormat);
    config->writeEntry("ApostropheFormat", m_apostropheFormat);
    config->writeEntry("AccountDelimiter", m_accountDelimiter);
    config->writeEntry("OpeningBalance", m_openingBalanceText);
    config->writeEntry("VoidMark", m_voidMark);
    config->writeEntry("FilterScriptImport", m_filterScriptImport);
    config->writeEntry("FilterScriptExport", m_filterScriptExport);
    config->writeEntry("FilterFileType", m_filterFileType);
    config->writeEntry("AttemptMatchDuplicates", m_attemptMatchDuplicates);

    TQString tmp;

    tmp = TQString(m_decimal['Q']) + m_decimal['T'] + m_decimal['I'] +
                  m_decimal['$'] + m_decimal['O'];
    config->writeEntry("Decimal", tmp);
    tmp = TQString(m_thousands['Q']) + m_thousands['T'] + m_thousands['I'] +
                m_thousands['$'] + m_thousands['O'];
    config->writeEntry("Thousand", tmp);
  }
  m_isDirty = false;
}

void MyMoneyQifProfile::setProfileName(const TQString& name)
{
  if(m_profileName != name)
    m_isDirty = true;

  m_profileName = name;
}

void MyMoneyQifProfile::setProfileDescription(const TQString& desc)
{
  if(m_profileDescription != desc)
    m_isDirty = true;

  m_profileDescription = desc;
}

void MyMoneyQifProfile::setProfileType(const TQString& type)
{
  if(m_profileType != type)
    m_isDirty = true;
  m_profileType = type;
}

void MyMoneyQifProfile::setOutputDateFormat(const TQString& dateFormat)
{
  if(m_dateFormat != dateFormat)
    m_isDirty = true;

  m_dateFormat = dateFormat;
}

void MyMoneyQifProfile::setInputDateFormat(const TQString& dateFormat)
{
  int j = -1;
  if(dateFormat.length() > 0) {
    for(unsigned int i = 0; i < dateFormat.length()-1; ++i) {
      if(dateFormat[i] == '%') {
        d->m_partPos[dateFormat[++i]] = ++j;
      }
    }
  }
}

void MyMoneyQifProfile::setApostropheFormat(const TQString& apostropheFormat)
{
  if(m_apostropheFormat != apostropheFormat)
    m_isDirty = true;

  m_apostropheFormat = apostropheFormat;
}

void MyMoneyQifProfile::setAmountDecimal(const TQChar& def, const TQChar& chr)
{
  TQChar ch(chr);
  if(ch == TQChar())
    ch = ' ';

  if(m_decimal[def] != ch)
    m_isDirty = true;

  m_decimal[def] = ch;
}

void MyMoneyQifProfile::setAmountThousands(const TQChar& def, const TQChar& chr)
{
  TQChar ch(chr);
  if(ch == TQChar())
    ch = ' ';

  if(m_thousands[def] != ch)
    m_isDirty = true;

  m_thousands[def] = ch;
}

TQChar MyMoneyQifProfile::amountDecimal(const TQChar& def) const
{
  TQChar chr = m_decimal[def];
  return chr;
}

TQChar MyMoneyQifProfile::amountThousands(const TQChar& def) const
{
  TQChar chr = m_thousands[def];
  return chr;
}

void MyMoneyQifProfile::setAccountDelimiter(const TQString& delim)
{
  TQString txt(delim);

  if(txt.isEmpty())
     txt = " ";
  else if(txt[0] != '[')
    txt = "[";

  if(m_accountDelimiter[0] != txt[0])
    m_isDirty = true;
  m_accountDelimiter = txt[0];
}

void MyMoneyQifProfile::setOpeningBalanceText(const TQString& txt)
{
  if(m_openingBalanceText != txt)
    m_isDirty = true;
  m_openingBalanceText = txt;
}

void MyMoneyQifProfile::setVoidMark(const TQString& txt)
{
  if(m_voidMark != txt)
    m_isDirty = true;
  m_voidMark = txt;
}

TQString MyMoneyQifProfile::accountDelimiter(void) const
{
  TQString rc;

  switch(m_accountDelimiter[0]) {
    case ' ':
      rc = "  ";
      break;
    default:
      rc = "[]";
      break;
  }
  return rc;
}

TQString MyMoneyQifProfile::date(const TQDate& datein) const
{
  const char* format = m_dateFormat.latin1();
  TQString buffer;
  TQChar delim;
  int maskLen;
  char maskChar;

  while(*format) {
    switch(*format) {
      case '%':
        maskLen = 0;
        maskChar = *++format;
        while(*format && *format == maskChar) {
          ++maskLen;
          ++format;
        }

        switch(maskChar) {
          case 'd':
            if(delim)
              buffer += delim;
            buffer += TQString::number(datein.day()).rightJustify(2, '0');
            break;

          case 'm':
            if(delim)
              buffer += delim;
            if(maskLen == 3)
              buffer += TDEGlobal::locale()->calendar()->monthName(datein.month(), datein.year(), true);
            else
              buffer += TQString::number(datein.month()).rightJustify(2, '0');
            break;

          case 'y':
            if(maskLen == 2) {
              buffer += twoDigitYear(delim, datein.year());
            } else {
              if(delim)
                buffer += delim;
              buffer += TQString::number(datein.year());
            }
            break;
          default:
            throw new MYMONEYEXCEPTION("Invalid char in QifProfile date field");
            break;
        }
        delim = 0;
        break;

      default:
        if(delim)
          buffer += delim;
        delim = *format++;
        break;
    }
  }
  return buffer;
}

const TQDate MyMoneyQifProfile::date(const TQString& datein) const
{
  // in case we don't know the format, we return an invalid date
  if(d->m_partPos.count() != 3)
    return TQDate();

  TQValueVector<TQString> scannedParts(3);
  d->dissectDate(scannedParts, datein);

  int yr, mon, day;
  bool ok;
  yr = scannedParts[d->m_partPos['y']].toInt();
  mon = scannedParts[d->m_partPos['m']].toInt(&ok);
  if(!ok) {
    TQStringList monthNames = TQStringList::split(",", "jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec");
    int j;
    for(j = 1; j <= 12; ++j) {
      if((TDEGlobal::locale()->calendar()->monthName(j, 2000, true).lower() == scannedParts[d->m_partPos['m']].lower())
      || (monthNames[j-1] == scannedParts[d->m_partPos['m']].lower())) {
        mon = j;
        break;
      }
    }
    if(j == 13) {
      tqWarning("Unknown month '%s'", scannedParts[d->m_partPos['m']].data());
      return TQDate();
    }
  }

  day = scannedParts[d->m_partPos['d']].toInt();
  if(yr < 100) {  // two digit year information?
    if(yr < CENTURY_BREAK)   // less than the CENTURY_BREAK we assume this century
      yr += 2000;
    else
      yr += 1900;
  }
  return TQDate(yr, mon, day);

#if 0
  TQString scannedDelim[2];
  TQString formatParts[3];
  TQString formatDelim[2];
  int part;
  int delim;
  unsigned int i,j;

  part = -1;
  delim = 0;
  for(i = 0; i < m_dateFormat.length(); ++i) {
    if(m_dateFormat[i] == '%') {
      ++part;
      if(part == 3) {
        tqWarning("MyMoneyQifProfile::date(const TQString& datein) Too many parts in date format");
        return TQDate();
      }
      ++i;
    }
    switch(m_dateFormat[i].latin1()) {
      case 'm':
      case 'd':
      case 'y':
        formatParts[part] += m_dateFormat[i];
        break;
      case '/':
      case '-':
      case '.':
      case '\'':
        if(delim == 2) {
          tqWarning("MyMoneyQifProfile::date(const TQString& datein) Too many delimiters in date format");
          return TQDate();
        }
        formatDelim[delim] = m_dateFormat[i];
        ++delim;
        break;
      default:
        tqWarning("MyMoneyQifProfile::date(const TQString& datein) Invalid char in date format");
        return TQDate();
    }
  }


  part = 0;
  delim = 0;
  bool prevWasChar = false;
  for(i = 0; i < datein.length(); ++i) {
   switch(datein[i].latin1()) {
      case '/':
      case '.':
      case '-':
      case '\'':
        if(delim == 2) {
          tqWarning("MyMoneyQifProfile::date(const TQString& datein) Too many delimiters in date field");
          return TQDate();
        }
        scannedDelim[delim] = datein[i];
        ++delim;
        ++part;
        prevWasChar = false;
        break;

      default:
        if(prevWasChar && datein[i].isDigit()) {
          ++part;
          prevWasChar = false;
        }
        if(datein[i].isLetter())
          prevWasChar = true;
        // replace blank with 0
        scannedParts[part] += (datein[i] == ' ') ? TQChar('0') : datein[i];
        break;
    }
  }

  int day = 1,
      mon = 1,
      yr = 1900;
  bool ok = false;
  for(i = 0; i < 2; ++i) {
    if(scannedDelim[i] != formatDelim[i]
    && scannedDelim[i] != TQChar('\'')) {
      tqWarning("MyMoneyQifProfile::date(const TQString& datein) Invalid delimiter '%s' when '%s' was expected",
        scannedDelim[i].latin1(), formatDelim[i].latin1());
      return TQDate();
    }
  }

  TQString msg;
  for(i = 0; i < 3; ++i) {
    switch(formatParts[i][0].latin1()) {
      case 'd':
        day = scannedParts[i].toUInt(&ok);
        if (!ok)
          msg = "Invalid numeric character in day string";
        break;
      case 'm':
        if(formatParts[i].length() != 3) {
          mon = scannedParts[i].toUInt(&ok);
          if (!ok)
            msg = "Invalid numeric character in month string";
        } else {
          for(j = 1; j <= 12; ++j) {
            if(TDEGlobal::locale()->calendar()->monthName(j, 2000, true).lower() == formatParts[i].lower()) {
              mon = j;
              ok = true;
              break;
            }
          }
          if(j == 13) {
            msg = "Unknown month '" + scannedParts[i] + "'";
          }
        }
        break;
      case 'y':
        ok = false;
        if(scannedParts[i].length() == formatParts[i].length()) {
          yr = scannedParts[i].toUInt(&ok);
          if (!ok)
            msg = "Invalid numeric character in month string";
          if(yr < 100) {      // two digit year info
            if(i > 1) {
              ok = true;
              if(scannedDelim[i-1] == TQChar('\'')) {
                if(m_apostropheFormat == "1900-1949") {
                  if(yr < 50)
                    yr += 1900;
                  else
                    yr += 2000;
                } else if(m_apostropheFormat == "1900-1999") {
                  yr += 1900;
                } else if(m_apostropheFormat == "2000-2099") {
                  yr += 2000;
                } else {
                  msg = "Unsupported apostropheFormat!";
                  ok = false;
                }
              } else {
                if(m_apostropheFormat == "1900-1949") {
                  if(yr < 50)
                    yr += 2000;
                  else
                    yr += 1900;
                } else if(m_apostropheFormat == "1900-1999") {
                  yr += 2000;
                } else if(m_apostropheFormat == "2000-2099") {
                  yr += 1900;
                } else {
                  msg = "Unsupported apostropheFormat!";
                  ok = false;
                }
              }
            } else {
              msg = "Year as first parameter is not supported!";
            }
          } else if(yr < 1900) {
              msg = "Year not in range < 100 or >= 1900!";
          } else {
            ok = true;
          }
        } else {
          msg = TQString("Length of year (%1) does not match expected length (%2).")
                .arg(scannedParts[i].length()).arg(formatParts[i].length());
        }
        break;
    }
    if(!msg.isEmpty()) {
      tqWarning("MyMoneyQifProfile::date(const TQString& datein) %s",msg.latin1());
      return TQDate();
    }
  }
  return TQDate(yr, mon, day);
#endif
}

TQString MyMoneyQifProfile::twoDigitYear(const TQChar delim, int yr) const
{
  TQChar realDelim = delim;
  TQString buffer;

  if(delim) {
    if((m_apostropheFormat == "1900-1949" && yr <= 1949)
    || (m_apostropheFormat == "1900-1999" && yr <= 1999)
    || (m_apostropheFormat == "2000-2099" && yr >= 2000))
      realDelim = '\'';
    buffer += realDelim;
  }
  yr -= 1900;
  if(yr > 100)
    yr -= 100;

  if(yr < 10)
    buffer += "0";

  buffer += TQString::number(yr);
  return buffer;
}

TQString MyMoneyQifProfile::value(const TQChar& def, const MyMoneyMoney& valuein) const
{
  unsigned char _decimalSeparator;
  unsigned char _thousandsSeparator;
  TQString res;

  _decimalSeparator = MyMoneyMoney::decimalSeparator();
  _thousandsSeparator = MyMoneyMoney::thousandSeparator();
  MyMoneyMoney::signPosition _signPosition = MyMoneyMoney::negativeMonetarySignPosition();

  MyMoneyMoney::setDecimalSeparator(amountDecimal(def));
  MyMoneyMoney::setThousandSeparator(amountThousands(def));
  MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::BeforeQuantityMoney);

  res = valuein.formatMoney("", 2);

  MyMoneyMoney::setDecimalSeparator(_decimalSeparator);
  MyMoneyMoney::setThousandSeparator(_thousandsSeparator);
  MyMoneyMoney::setNegativeMonetarySignPosition(_signPosition);

  return res;
}

MyMoneyMoney MyMoneyQifProfile::value(const TQChar& def, const TQString& valuein) const
{
  unsigned char _decimalSeparator;
  unsigned char _thousandsSeparator;
  MyMoneyMoney res;

  _decimalSeparator = MyMoneyMoney::decimalSeparator();
  _thousandsSeparator = MyMoneyMoney::thousandSeparator();
  MyMoneyMoney::signPosition _signPosition = MyMoneyMoney::negativeMonetarySignPosition();

  MyMoneyMoney::setDecimalSeparator(amountDecimal(def));
  MyMoneyMoney::setThousandSeparator(amountThousands(def));
  MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::BeforeQuantityMoney);

  res = MyMoneyMoney(valuein);

  MyMoneyMoney::setDecimalSeparator(_decimalSeparator);
  MyMoneyMoney::setThousandSeparator(_thousandsSeparator);
  MyMoneyMoney::setNegativeMonetarySignPosition(_signPosition);

  return res;
}

void MyMoneyQifProfile::setFilterScriptImport(const TQString& script)
{
  if(m_filterScriptImport != script)
    m_isDirty = true;

  m_filterScriptImport = script;
}

void MyMoneyQifProfile::setFilterScriptExport(const TQString& script)
{
  if(m_filterScriptExport != script)
    m_isDirty = true;

  m_filterScriptExport = script;
}

void MyMoneyQifProfile::setFilterFileType(const TQString& txt)
{
  if(m_filterFileType != txt)
    m_isDirty = true;

  m_filterFileType = txt;
}

void MyMoneyQifProfile::setAttemptMatchDuplicates(bool f)
{
  if ( m_attemptMatchDuplicates != f )
    m_isDirty = true;

  m_attemptMatchDuplicates = f;
}

TQString MyMoneyQifProfile::inputDateFormat(void) const
{
  TQStringList list;
  possibleDateFormats(list);
  if(list.count() == 1)
    return list.first();
  return TQString::null;
}

void MyMoneyQifProfile::possibleDateFormats(TQStringList& list) const
{
  TQStringList defaultList = TQStringList::split(":", "y,m,d:y,d,m:m,d,y:m,y,d:d,m,y:d,y,m");
  list.clear();
  TQStringList::const_iterator it_d;
  for(it_d = defaultList.begin(); it_d != defaultList.end(); ++it_d) {
    TQStringList parts = TQStringList::split(",", *it_d);
    int i;
    for(i = 0; i < 3; ++i) {
      if(d->m_partPos.contains(parts[i][0])) {
        if(d->m_partPos[parts[i][0]] != i)
          break;
      }
      // months can't be larger than 12
      if(parts[i] == "m" && d->m_largestValue[i] > 12)
        break;
      // days can't be larger than 31
      if(parts[i] == "d" && d->m_largestValue[i] > 31)
        break;
    }
    // matches all tests
    if(i == 3) {
      TQString format = *it_d;
      format.replace('y', "%y");
      format.replace('m', "%m");
      format.replace('d', "%d");
      format.replace(',', " ");
      list << format;
    }
  }
  // if we haven't found any, then there's something wrong.
  // in this case, we present the full list and let the user decide
  if(list.count() == 0) {
    for(it_d = defaultList.begin(); it_d != defaultList.end(); ++it_d) {
      TQString format = *it_d;
      format.replace('y', "%y");
      format.replace('m', "%m");
      format.replace('d', "%d");
      format.replace(',', " ");
      list << format;
    }
  }
}

void MyMoneyQifProfile::autoDetect(const TQStringList& lines)
{
  m_dateFormat = TQString();
  m_decimal.clear();
  m_thousands.clear();

  TQString numericRecords = "BT$OIQ";
  TQStringList::const_iterator it;
  int datesScanned = 0;
  // section: used to switch between different QIF sections,
  // because the Record identifiers are ambigous between sections
  // eg. in transaction records, T identifies a total amount, in
  // account sections it's the type.
  //
  // 0 - unknown
  // 1 - account
  // 2 - transactions
  // 3 - prices
  int section = 0;
  TQRegExp price("\"(.*)\",(.*),\"(.*)\"");
  for(it = lines.begin(); it != lines.end(); ++it) {
    TQChar c((*it)[0]);
    if(c == '!') {
      TQString sname = (*it).lower();
      section = 0;
      if(sname.startsWith("!account"))
        section = 1;
      else if(sname.startsWith("!type")) {
        if(sname.startsWith("!type:cat")
           || sname.startsWith("!type:payee")
           || sname.startsWith("!type:security")
           || sname.startsWith("!type:class")) {
          section = 0;
        } else if(sname.startsWith("!type:price")) {
          section = 3;
        } else
          section = 2;
      }
    }

    switch(section) {
      case 1:
        if(c == 'B') {
          scanNumeric((*it).mid(1), m_decimal[c], m_thousands[c]);
        }
        break;
      case 2:
        if(numericRecords.contains(c)) {
          scanNumeric((*it).mid(1), m_decimal[c], m_thousands[c]);
        } else if((c == 'D') && (m_dateFormat.isEmpty())) {
          if(d->m_partPos.count() != 3) {
            scanDate((*it).mid(1));
            ++datesScanned;
            if(d->m_partPos.count() == 2) {
              // if we have detected two parts we can calculate the third and its position
              d->getThirdPosition();
            }
          }
        }
        break;
      case 3:
        if(price.search(*it) != -1) {
          scanNumeric(price.cap(2), m_decimal['P'], m_thousands['P']);
          scanDate(price.cap(3));
          ++datesScanned;
        }
        break;
    }
  }

  // the following algorithm is only applied if we have more
  // than 20 dates found. Smaller numbers have shown that the
  // results are inaccurate which leads to a reduced number of
  // date formats presented to choose from.
  if(d->m_partPos.count() != 3 && datesScanned > 20) {
    TQMap<int, int> sortedPos;
    // make sure to reset the known parts for the following algorithm
    if(d->m_partPos.contains('y')) {
      d->m_changeCount[d->m_partPos['y']] = -1;
      for(int i = 0; i < 3; ++i) {
        if(d->m_partPos['y'] == i)
          continue;
        // can we say for sure that we hit the day field?
        if(d->m_largestValue[i] > 12) {
          d->m_partPos['d'] = i;
        }
      }
    }
    if(d->m_partPos.contains('d'))
      d->m_changeCount[d->m_partPos['d']] = -1;
    if(d->m_partPos.contains('m'))
      d->m_changeCount[d->m_partPos['m']] = -1;

    for(int i = 0; i < 3; ++i) {
      if(d->m_changeCount[i] != -1) {
        sortedPos[d->m_changeCount[i]] = i;
      }
    }

    TQMap<int, int>::const_iterator it_a;
    TQMap<int, int>::const_iterator it_b;
    switch(sortedPos.count()) {
      case 1: // all the same
        // let the user decide, we can't figure it out
        break;

      case 2: // two are the same, we treat the largest as the day
              // if it's 20% larger than the other one and let the
              // user pick the other two
        {
          it_b = sortedPos.begin();
          it_a = it_b;
          ++it_b;
          double a = d->m_changeCount[*it_a];
          double b = d->m_changeCount[*it_b];
          if(b > (a * 1.2)) {
            d->m_partPos['d'] = *it_b;
          }
        }
        break;

      case 3: // three different, we check if they are 20% apart each
        it_b = sortedPos.begin();
        for(int i = 0; i < 2; ++i) {
          it_a = it_b;
          ++it_b;
          double a = d->m_changeCount[*it_a];
          double b = d->m_changeCount[*it_b];
          if(b > (a * 1.2)) {
            switch(i) {
              case 0:
                d->m_partPos['y'] = *it_a;
                break;
              case 1:
                d->m_partPos['d'] = *it_b;
                break;
            }
          }
        }
        break;
    }
    // extract the last if necessary and possible date position
    d->getThirdPosition();
  }
}

void MyMoneyQifProfile::scanNumeric(const TQString& txt, TQChar& decimal, TQChar& thousands) const
{
  TQChar first, second;
  TQRegExp numericChars("[0-9-()]");
  for(unsigned int i = 0; i < txt.length(); ++i) {
    if(numericChars.search(txt[i]) == -1) {
      first = second;
      second = txt[i];
    }
  }
  if(!second.isNull())
    decimal = second;
  if(!first.isNull())
    thousands = first;
}

void MyMoneyQifProfile::scanDate(const TQString& txt) const
{
  // extract the parts from the txt
  TQValueVector<TQString> parts(3);             // the various parts of the date
  d->dissectDate(parts, txt);

  // now analyse the parts
  for(int i = 0; i < 3; ++i) {
    bool ok;
    int value = parts[i].toInt(&ok);
    if(!ok) {  // this should happen only if the part is non-numeric -> month
      d->m_partPos['m'] = i;
    } else if(value != 0) {
      if(value != d->m_lastValue[i]) {
        d->m_changeCount[i]++;
        d->m_lastValue[i] = value;
        if(value > d->m_largestValue[i])
          d->m_largestValue[i] = value;
      }
      // if it's > 31 it can only be years
      if(value > 31) {
        d->m_partPos['y'] = i;
      }
      // and if it's in between 12 and 32 and we already identified the
      // position for the year it must be days
      if((value > 12) && (value < 32) && d->m_partPos.contains('y')) {
        d->m_partPos['d'] = i;
      }
    }
  }
}

#include "mymoneyqifprofile.moc"
