/***************************************************************************
                          pivottable.cpp
                             -------------------
    begin                : Mon May 17 2004
    copyright            : (C) 2004-2005 by Ace Jones
    email                : <ace.j@hotpop.com>
                           Thomas Baumgart <ipwizard@users.sourceforge.net>
                           Alvaro Soliverez <asoliverez@gmail.com>
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 <tqlayout.h>
#include <tqdatetime.h>
#include <tqregexp.h>
#include <tqdragobject.h>
#include <tqclipboard.h>
#include <tqapplication.h>
#include <tqprinter.h>
#include <tqpainter.h>
#include <tqfile.h>
#include <tqdom.h>

// ----------------------------------------------------------------------------
// KDE Includes
// This is just needed for i18n() and weekStartDay().
// Once I figure out how to handle i18n
// without using this macro directly, I'll be freed of KDE dependency.  This
// is a minor problem because we use these terms when rendering to HTML,
// and a more major problem because we need it to translate account types
// (e.g. MyMoneyAccount::Checkings) into their text representation.  We also
// use that text representation in the core data structure of the report. (Ace)

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

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

#include "pivottable.h"
#include "pivotgrid.h"
#include "reportdebug.h"
#include "kreportchartview.h"
#include "../kmymoneyglobalsettings.h"
#include "../kmymoneyutils.h"
#include "../mymoney/mymoneyforecast.h"


#include <kmymoney/kmymoneyutils.h>

namespace reports {

TQString Debug::m_sTabs;
bool Debug::m_sEnabled = DEBUG_ENABLED_BY_DEFAULT;
TQString Debug::m_sEnableKey;

Debug::Debug( const TQString& _name ): m_methodName( _name ), m_enabled( m_sEnabled )
{
  if (!m_enabled && _name == m_sEnableKey)
    m_enabled = true;

  if (m_enabled)
  {
    tqDebug( "%s%s(): ENTER", m_sTabs.latin1(), m_methodName.latin1() );
    m_sTabs.append("--");
  }
}

Debug::~Debug()
{
  if ( m_enabled )
  {
    m_sTabs.remove(0,2);
    tqDebug( "%s%s(): EXIT", m_sTabs.latin1(), m_methodName.latin1() );

    if (m_methodName == m_sEnableKey)
      m_enabled = false;
  }
}

void Debug::output( const TQString& _text )
{
  if ( m_enabled )
    tqDebug( "%s%s(): %s", m_sTabs.latin1(), m_methodName.latin1(), _text.latin1() );
}

PivotTable::PivotTable( const MyMoneyReport& _config_f ):
  m_runningSumsCalculated(false),
  m_config_f( _config_f )
{
  init();
}

void PivotTable::init(void)
{
  DEBUG_ENTER(__PRETTY_FUNCTION__);

  //
  // Initialize locals
  //

  MyMoneyFile* file = MyMoneyFile::instance();

  //
  // Initialize member variables
  //

  //make sure we have all subaccounts of investment accounts
  includeInvestmentSubAccounts();

  m_config_f.validDateRange( m_beginDate, m_endDate );

  // If we need to calculate running sums, it does not make sense
  // to show a row total column
  if ( m_config_f.isRunningSum() )
    m_config_f.setShowingRowTotals(false);

  // if this is a months-based report
  if (! m_config_f.isColumnsAreDays())
  {
    // strip out the 'days' component of the begin and end dates.
    // we're only using these variables to contain year and month.
    m_beginDate =  TQDate( m_beginDate.year(), m_beginDate.month(), 1 );
    m_endDate = TQDate( m_endDate.year(), m_endDate.month(), 1 );
  }

  m_numColumns = columnValue(m_endDate) - columnValue(m_beginDate) + 2;

  //Load what types of row the report is going to show
  loadRowTypeList();

  //
  // Initialize outer groups of the grid
  //
  if ( m_config_f.rowType() == MyMoneyReport::eAssetLiability )
  {
    m_grid.insert(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Asset),PivotOuterGroup(m_numColumns));
    m_grid.insert(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Liability),PivotOuterGroup(m_numColumns,PivotOuterGroup::m_kDefaultSortOrder,true /* inverted */));
  }
  else
  {
    m_grid.insert(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Income),PivotOuterGroup(m_numColumns,PivotOuterGroup::m_kDefaultSortOrder-2));
    m_grid.insert(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Expense),PivotOuterGroup(m_numColumns,PivotOuterGroup::m_kDefaultSortOrder-1,true /* inverted */));
    //
    // Create rows for income/expense reports with all accounts included
    //
    if(m_config_f.isIncludingUnusedAccounts())
      createAccountRows();
  }

  //
  // Initialize grid totals
  //

  m_grid.m_total = PivotGridRowSet(m_numColumns);

  //
  // Get opening balances
  // (for running sum reports only)
  //

  if ( m_config_f.isRunningSum() )
    calculateOpeningBalances();

  //
  // Calculate budget mapping
  // (for budget-vs-actual reports only)
  //
  if ( m_config_f.hasBudget())
    calculateBudgetMapping();

  //
  // Populate all transactions into the row/column pivot grid
  //

  TQValueList<MyMoneyTransaction> transactions;
  m_config_f.setReportAllSplits(false);
  m_config_f.setConsiderCategory(true);
  try {
    transactions = file->transactionList(m_config_f);
  } catch(MyMoneyException *e) {
    tqDebug("ERR: %s thrown in %s(%ld)", e->what().data(), e->file().data(), e->line());
    throw e;
  }
  DEBUG_OUTPUT(TQString("Found %1 matching transactions").arg(transactions.count()));


  // Include scheduled transactions if required
  if ( m_config_f.isIncludingSchedules() )
  {
    // Create a custom version of the report filter, excluding date
    // We'll use this to compare the transaction against
    MyMoneyTransactionFilter schedulefilter(m_config_f);
    schedulefilter.setDateFilter(TQDate(),TQDate());

    // Get the real dates from the config filter
    TQDate configbegin, configend;
    m_config_f.validDateRange(configbegin, configend);

    TQValueList<MyMoneySchedule> schedules = file->scheduleList();
    TQValueList<MyMoneySchedule>::const_iterator it_schedule = schedules.begin();
    while ( it_schedule != schedules.end() )
    {
      // If the transaction meets the filter
      MyMoneyTransaction tx = (*it_schedule).transaction();
      if (!(*it_schedule).isFinished() && schedulefilter.match(tx) )
      {
        // Keep the id of the schedule with the transaction so that
        // we can do the autocalc later on in case of a loan payment
        tx.setValue("kmm-schedule-id", (*it_schedule).id());

        // Get the dates when a payment will be made within the report window
        TQDate nextpayment = (*it_schedule).adjustedNextPayment(configbegin);
        if ( nextpayment.isValid() )
        {
          // Add one transaction for each date
          TQValueList<TQDate> paymentDates = (*it_schedule).paymentDates(nextpayment,configend);
          TQValueList<TQDate>::const_iterator it_date = paymentDates.begin();
          while ( it_date != paymentDates.end() )
          {
            //if the payment occurs in the past, enter it tomorrow
            if(TQDate::currentDate() >= *it_date) {
              tx.setPostDate(TQDate::currentDate().addDays(1));
            } else {
              tx.setPostDate(*it_date);
            }
            if ( tx.postDate() <= configend
               && tx.postDate() >= configbegin ) {
              transactions += tx;
            }

            DEBUG_OUTPUT(TQString("Added transaction for schedule %1 on %2").arg((*it_schedule).id()).arg((*it_date).toString()));

            ++it_date;
          }
        }
      }

      ++it_schedule;
    }
  }

  // whether asset & liability transactions are actually to be considered
  // transfers
  bool al_transfers = ( m_config_f.rowType() == MyMoneyReport::eExpenseIncome ) && ( m_config_f.isIncludingTransfers() );

  //this is to store balance for loan accounts when not included in the report
  TQMap<TQString, MyMoneyMoney> loanBalances;

  TQValueList<MyMoneyTransaction>::const_iterator it_transaction = transactions.begin();
  unsigned colofs = columnValue(m_beginDate) - 1;
  while ( it_transaction != transactions.end() )
  {
    TQDate postdate = (*it_transaction).postDate();
    unsigned column = columnValue(postdate) - colofs;

    MyMoneyTransaction tx = (*it_transaction);

    // check if we need to call the autocalculation routine
    if(tx.isLoanPayment() && tx.hasAutoCalcSplit() && (tx.value("kmm-schedule-id").length() > 0)) {
      // make sure to consider any autocalculation for loan payments
      MyMoneySchedule sched = file->schedule(tx.value("kmm-schedule-id"));
      const MyMoneySplit& split = tx.amortizationSplit();
      if(!split.id().isEmpty()) {
        ReportAccount splitAccount = file->account(split.accountId());
        MyMoneyAccount::accountTypeE type = splitAccount.accountGroup();
        TQString outergroup = KMyMoneyUtils::accountTypeToString(type);

        //if the account is included in the report, calculate the balance from the cells
        if(m_config_f.includes( splitAccount )) {
          loanBalances[splitAccount.id()] = cellBalance(outergroup, splitAccount, column, false);
        } else {
          //if it is not in the report and also not in loanBalances, get the balance from the file
          if(!loanBalances.contains(splitAccount.id())) {
            TQDate dueDate = sched.nextDueDate();

            //if the payment is overdue, use current date
            if(dueDate < TQDate::currentDate())
              dueDate = TQDate::currentDate();

            //get the balance from the file for the date
            loanBalances[splitAccount.id()] = file->balance(splitAccount.id(), dueDate.addDays(-1));
          }
        }

        KMyMoneyUtils::calculateAutoLoan(sched, tx, loanBalances);

        //if the loan split is not included in the report, update the balance for the next occurrence
        if(!m_config_f.includes( splitAccount )) {
          TQValueList<MyMoneySplit>::ConstIterator it_loanSplits;
          for(it_loanSplits = tx.splits().begin(); it_loanSplits != tx.splits().end(); ++it_loanSplits) {
            if((*it_loanSplits).isAmortizationSplit() && (*it_loanSplits).accountId() == splitAccount.id() )
              loanBalances[splitAccount.id()] = loanBalances[splitAccount.id()] + (*it_loanSplits).shares();
          }
        }
      }
    }

    TQValueList<MyMoneySplit> splits = tx.splits();
    TQValueList<MyMoneySplit>::const_iterator it_split = splits.begin();
    while ( it_split != splits.end() )
    {
      ReportAccount splitAccount = (*it_split).accountId();

      // Each split must be further filtered, because if even one split matches,
      // the ENTIRE transaction is returned with all splits (even non-matching ones)
      if ( m_config_f.includes( splitAccount ) && m_config_f.match(&(*it_split)))
      {
        // reverse sign to match common notation for cash flow direction, only for expense/income splits
        MyMoneyMoney reverse(splitAccount.isIncomeExpense() ? -1 : 1, 1);

        MyMoneyMoney value;
        // the outer group is the account class (major account type)
        MyMoneyAccount::accountTypeE type = splitAccount.accountGroup();
        TQString outergroup = KMyMoneyUtils::accountTypeToString(type);

        value = (*it_split).shares();
        bool stockSplit = tx.isStockSplit();
        if(!stockSplit) {
          // retrieve the value in the account's underlying currency
          if(value != MyMoneyMoney::autoCalc) {
            value = value * reverse;
          } else {
            tqDebug("PivotTable::PivotTable(): This must not happen");
            value = MyMoneyMoney();  // keep it 0 so far
          }

          // Except in the case of transfers on an income/expense report
          if ( al_transfers && ( type == MyMoneyAccount::Asset || type == MyMoneyAccount::Liability ) )
          {
            outergroup = i18n("Transfers");
            value = -value;
          }
        }
        // add the value to its correct position in the pivot table
        assignCell( outergroup, splitAccount, column, value, false, stockSplit );
      }
      ++it_split;
    }

    ++it_transaction;
  }

  //
  // Get forecast data
  //
  if(m_config_f.isIncludingForecast())
    calculateForecast();

  //
  //Insert Price data
  //
  if(m_config_f.isIncludingPrice())
    fillBasePriceUnit(ePrice);

  //
  //Insert Average Price data
  //
  if(m_config_f.isIncludingAveragePrice()) {
    fillBasePriceUnit(eActual);
    calculateMovingAverage();
  }

  //
  // Collapse columns to match column type
  //


  if ( m_config_f.columnPitch() > 1 )
    collapseColumns();

  //
  // Calculate the running sums
  // (for running sum reports only)
  //

  if ( m_config_f.isRunningSum() )
    calculateRunningSums();

  //
  // Calculate Moving Average
  //
  if ( m_config_f.isIncludingMovingAverage() )
    calculateMovingAverage();

  //
  // Calculate Budget Difference
  //

  if ( m_config_f.isIncludingBudgetActuals() )
    calculateBudgetDiff();

  //
  // Convert all values to the deep currency
  //

  convertToDeepCurrency();

  //
  // Convert all values to the base currency
  //

  if ( m_config_f.isConvertCurrency() )
    convertToBaseCurrency();

  //
  // Determine column headings
  //

  calculateColumnHeadings();

  //
  // Calculate row and column totals
  //

  calculateTotals();
}

void PivotTable::collapseColumns(void)
{
  DEBUG_ENTER(__PRETTY_FUNCTION__);

  unsigned columnpitch = m_config_f.columnPitch();
  if ( columnpitch != 1 )
  {
    unsigned sourcemonth = (m_config_f.isColumnsAreDays())
      // use the user's locale to determine the week's start
      ? (m_beginDate.dayOfWeek() + 8 - TDEGlobal::locale()->weekStartDay()) % 7
      : m_beginDate.month();
    unsigned sourcecolumn = 1;
    unsigned destcolumn = 1;
    while ( sourcecolumn < m_numColumns )
    {
      if ( sourcecolumn != destcolumn )
      {
#if 0
        // TODO: Clean up this rather inefficient kludge. We really should jump by an entire
        // destcolumn at a time on RS reports, and calculate the proper sourcecolumn to use,
        // allowing us to clear and accumulate only ONCE per destcolumn
        if ( m_config_f.isRunningSum() )
          clearColumn(destcolumn);
#endif
        accumulateColumn(destcolumn,sourcecolumn);
      }

      if (++sourcecolumn < m_numColumns) {
        if ((sourcemonth++ % columnpitch) == 0) {
          if (sourcecolumn != ++destcolumn)
            clearColumn (destcolumn);
        }
      }
    }
    m_numColumns = destcolumn + 1;
  }
}

void PivotTable::accumulateColumn(unsigned destcolumn, unsigned sourcecolumn)
{
  DEBUG_ENTER(__PRETTY_FUNCTION__);
  DEBUG_OUTPUT(TQString("From Column %1 to %2").arg(sourcecolumn).arg(destcolumn));

  // iterate over outer groups
  PivotGrid::iterator it_outergroup = m_grid.begin();
  while ( it_outergroup != m_grid.end() )
  {
    // iterate over inner groups
    PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
    while ( it_innergroup != (*it_outergroup).end() )
    {
      // iterator over rows
      PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
      while ( it_row != (*it_innergroup).end() )
      {
        if ( (*it_row)[eActual].count() <= sourcecolumn )
          throw new MYMONEYEXCEPTION(TQString("Sourcecolumn %1 out of grid range (%2) in PivotTable::accumulateColumn").arg(sourcecolumn).arg((*it_row)[eActual].count()));
        if ( (*it_row)[eActual].count() <= destcolumn )
          throw new MYMONEYEXCEPTION(TQString("Destcolumn %1 out of grid range (%2) in PivotTable::accumulateColumn").arg(sourcecolumn).arg((*it_row)[eActual].count()));

        (*it_row)[eActual][destcolumn] += (*it_row)[eActual][sourcecolumn];
        ++it_row;
      }

      ++it_innergroup;
    }
    ++it_outergroup;
  }
}

void PivotTable::clearColumn(unsigned column)
{
  DEBUG_ENTER(__PRETTY_FUNCTION__);
  DEBUG_OUTPUT(TQString("Column %1").arg(column));

  // iterate over outer groups
  PivotGrid::iterator it_outergroup = m_grid.begin();
  while ( it_outergroup != m_grid.end() )
  {
    // iterate over inner groups
    PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
    while ( it_innergroup != (*it_outergroup).end() )
    {
      // iterator over rows
      PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
      while ( it_row != (*it_innergroup).end() )
      {
        if ( (*it_row)[eActual].count() <= column )
          throw new MYMONEYEXCEPTION(TQString("Column %1 out of grid range (%2) in PivotTable::accumulateColumn").arg(column).arg((*it_row)[eActual].count()));

        (*it_row++)[eActual][column] = PivotCell();
      }

      ++it_innergroup;
    }
    ++it_outergroup;
  }
}

void PivotTable::calculateColumnHeadings(void)
{
  DEBUG_ENTER(__PRETTY_FUNCTION__);

  // one column for the opening balance
  m_columnHeadings.append( "Opening" );

  unsigned columnpitch = m_config_f.columnPitch();

  // if this is a days-based report
  if (m_config_f.isColumnsAreDays())
  {
    if ( columnpitch == 1 )
    {
      TQDate columnDate = m_beginDate;
      unsigned column = 1;
      while ( column++ < m_numColumns )
      {
        TQString heading = TDEGlobal::locale()->calendar()->monthName(columnDate.month(), columnDate.year(), true) + " " + TQString::number(columnDate.day());
        columnDate = columnDate.addDays(1);
        m_columnHeadings.append( heading);
      }
    }
    else
    {
      TQDate day = m_beginDate;
      TQDate prv = m_beginDate;

      // use the user's locale to determine the week's start
      unsigned dow = (day.dayOfWeek() +8 -TDEGlobal::locale()->weekStartDay())%7;

      while (day <= m_endDate)
      {
        if (((dow % columnpitch) == 0) || (day == m_endDate))
        {
          m_columnHeadings.append(TQString("%1&nbsp;%2 - %3&nbsp;%4")
            .arg(TDEGlobal::locale()->calendar()->monthName(prv.month(), prv.year(), true))
            .arg(prv.day())
            .arg(TDEGlobal::locale()->calendar()->monthName(day.month(), day.year(), true))
            .arg(day.day()));
          prv = day.addDays(1);
        }
        day = day.addDays(1);
        dow++;
      }
    }
  }

  // else it's a months-based report
  else
  {
    if ( columnpitch == 12 )
    {
      unsigned year = m_beginDate.year();
      unsigned column = 1;
      while ( column++ < m_numColumns )
        m_columnHeadings.append(TQString::number(year++));
    }
    else
    {
      unsigned year = m_beginDate.year();
      bool includeyear = ( m_beginDate.year() != m_endDate.year() );
      unsigned segment = ( m_beginDate.month() - 1 ) / columnpitch;
      unsigned column = 1;
      while ( column++ < m_numColumns )
      {
        TQString heading = TDEGlobal::locale()->calendar()->monthName(1+segment*columnpitch, 2000, true);
        if ( columnpitch != 1 )
          heading += "-" + TDEGlobal::locale()->calendar()->monthName((1+segment)*columnpitch, 2000, true);
        if ( includeyear )
          heading += " " + TQString::number(year);
        m_columnHeadings.append( heading);
        if ( ++segment >= 12/columnpitch )
        {
          segment -= 12/columnpitch;
          ++year;
        }
      }
    }
  }
}

void PivotTable::createAccountRows(void)
{
  DEBUG_ENTER(__PRETTY_FUNCTION__);
  MyMoneyFile* file = MyMoneyFile::instance();

  TQValueList<MyMoneyAccount> accounts;
  file->accountList(accounts);

  TQValueList<MyMoneyAccount>::const_iterator it_account = accounts.begin();

  while ( it_account != accounts.end() )
  {
    ReportAccount account = *it_account;

    // only include this item if its account group is included in this report
    // and if the report includes this account
    if ( m_config_f.includes( *it_account ) )
    {
      DEBUG_OUTPUT(TQString("Includes account %1").arg(account.name()));

      // the row group is the account class (major account type)
      TQString outergroup = KMyMoneyUtils::accountTypeToString(account.accountGroup());
      // place into the 'opening' column...
      assignCell( outergroup, account, 0, MyMoneyMoney() );
    }
    ++it_account;
  }
}

void PivotTable::calculateOpeningBalances( void )
{
  DEBUG_ENTER(__PRETTY_FUNCTION__);

  // First, determine the inclusive dates of the report.  Normally, that's just
  // the begin & end dates of m_config_f.  However, if either of those dates are
  // blank, we need to use m_beginDate and/or m_endDate instead.
  TQDate from = m_config_f.fromDate();
  TQDate to = m_config_f.toDate();
  if ( ! from.isValid() )
    from = m_beginDate;
  if ( ! to.isValid() )
    to = m_endDate;

  MyMoneyFile* file = MyMoneyFile::instance();

  TQValueList<MyMoneyAccount> accounts;
  file->accountList(accounts);

  TQValueList<MyMoneyAccount>::const_iterator it_account = accounts.begin();

  while ( it_account != accounts.end() )
  {
    ReportAccount account = *it_account;

    // only include this item if its account group is included in this report
    // and if the report includes this account
    if ( m_config_f.includes( *it_account ) )
    {

      //do not include account if it is closed and it has no transactions in the report period
      if(account.isClosed()) {
        //check if the account has transactions for the report timeframe
        MyMoneyTransactionFilter filter;
        filter.addAccount(account.id());
        filter.setDateFilter(m_beginDate, m_endDate);
        filter.setReportAllSplits(false);
        TQValueList<MyMoneyTransaction> transactions = file->transactionList(filter);
        //if a closed account has no transactions in that timeframe, do not include it
        if(transactions.size() == 0 ) {
          DEBUG_OUTPUT(TQString("DOES NOT INCLUDE account %1").arg(account.name()));
          ++it_account;
          continue;
        }
      }

      DEBUG_OUTPUT(TQString("Includes account %1").arg(account.name()));
      // the row group is the account class (major account type)
      TQString outergroup = KMyMoneyUtils::accountTypeToString(account.accountGroup());

      // extract the balance of the account for the given begin date, which is
      // the opening balance plus the sum of all transactions prior to the begin
      // date

      // this is in the underlying currency
      MyMoneyMoney value = file->balance(account.id(), from.addDays(-1));

      // place into the 'opening' column...
      assignCell( outergroup, account, 0, value );
    }
    else
    {
      DEBUG_OUTPUT(TQString("DOES NOT INCLUDE account %1").arg(account.name()));
    }

    ++it_account;
  }
}

void PivotTable::calculateRunningSums( PivotInnerGroup::iterator& it_row)
{
  MyMoneyMoney runningsum = it_row.data()[eActual][0].calculateRunningSum(MyMoneyMoney(0,1));
  unsigned column = 1;
  while ( column < m_numColumns )
  {
    if ( it_row.data()[eActual].count() <= column )
      throw new MYMONEYEXCEPTION(TQString("Column %1 out of grid range (%2) in PivotTable::calculateRunningSums").arg(column).arg(it_row.data()[eActual].count()));

    runningsum = it_row.data()[eActual][column].calculateRunningSum(runningsum);

    ++column;
  }
}

void PivotTable::calculateRunningSums( void )
{
  DEBUG_ENTER(__PRETTY_FUNCTION__);

  m_runningSumsCalculated = true;

  PivotGrid::iterator it_outergroup = m_grid.begin();
  while ( it_outergroup != m_grid.end() )
  {
    PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
    while ( it_innergroup != (*it_outergroup).end() )
    {
      PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
      while ( it_row != (*it_innergroup).end() )
      {
#if 0
        MyMoneyMoney runningsum = it_row.data()[0];
        unsigned column = 1;
        while ( column < m_numColumns )
        {
        if ( it_row.data()[eActual].count() <= column )
        throw new MYMONEYEXCEPTION(TQString("Column %1 out of grid range (%2) in PivotTable::calculateRunningSums").arg(column).arg(it_row.data()[eActual].count()));

          runningsum = ( it_row.data()[eActual][column] += runningsum );

          ++column;
        }
#endif
        calculateRunningSums( it_row );
        ++it_row;
      }
      ++it_innergroup;
    }
    ++it_outergroup;
  }
}

MyMoneyMoney PivotTable::cellBalance(const TQString& outergroup, const ReportAccount& _row, unsigned _column, bool budget)
{
  if(m_runningSumsCalculated) {
    tqDebug("You must not call PivotTable::cellBalance() after calling PivotTable::calculateRunningSums()");
    throw new MYMONEYEXCEPTION(TQString("You must not call PivotTable::cellBalance() after calling PivotTable::calculateRunningSums()"));
  }

  // for budget reports, if this is the actual value, map it to the account which
  // holds its budget
  ReportAccount row = _row;
  if ( !budget && m_config_f.hasBudget() )
  {
    TQString newrow = m_budgetMap[row.id()];

    // if there was no mapping found, then the budget report is not interested
    // in this account.
    if ( newrow.isEmpty() )
      return MyMoneyMoney();

    row = newrow;
  }

  // ensure the row already exists (and its parental hierarchy)
  createRow( outergroup, row, true );

  // Determine the inner group from the top-most parent account
  TQString innergroup( row.topParentName() );

  if ( m_numColumns <= _column )
    throw new MYMONEYEXCEPTION(TQString("Column %1 out of m_numColumns range (%2) in PivotTable::cellBalance").arg(_column).arg(m_numColumns));
  if ( m_grid[outergroup][innergroup][row][eActual].count() <= _column )
    throw new MYMONEYEXCEPTION(TQString("Column %1 out of grid range (%2) in PivotTable::cellBalance").arg(_column).arg(m_grid[outergroup][innergroup][row][eActual].count()));

  MyMoneyMoney balance;
  if ( budget )
    balance = m_grid[outergroup][innergroup][row][eBudget][0].cellBalance(MyMoneyMoney());
  else
    balance = m_grid[outergroup][innergroup][row][eActual][0].cellBalance(MyMoneyMoney());

  unsigned column = 1;
  while ( column < _column)
  {
    if ( m_grid[outergroup][innergroup][row][eActual].count() <= column )
      throw new MYMONEYEXCEPTION(TQString("Column %1 out of grid range (%2) in PivotTable::cellBalance").arg(column).arg(m_grid[outergroup][innergroup][row][eActual].count()));

    balance = m_grid[outergroup][innergroup][row][eActual][column].cellBalance(balance);

    ++column;
  }

  return balance;
}


void PivotTable::calculateBudgetMapping( void )
{
  DEBUG_ENTER(__PRETTY_FUNCTION__);

  MyMoneyFile* file = MyMoneyFile::instance();

  // Only do this if there is at least one budget in the file
  if ( file->countBudgets() )
  {
    // Select a budget
    //
    // It will choose the first budget in the list for the start year of the report if no budget is select
    MyMoneyBudget budget = MyMoneyBudget();
    
    TQValueList<MyMoneyBudget> budgets = file->budgetList();
    bool validBudget = false;
    
    //check that the selected budget is valid
    if (m_config_f.budget() != "Any") {
      TQValueList<MyMoneyBudget>::const_iterator budgets_it = budgets.begin();
      while( budgets_it != budgets.end() ) {
        //pick the budget by id
        if ((*budgets_it).id() == m_config_f.budget()) {
          budget = file->budget((*budgets_it).id());
          validBudget = true;
          break;
        }
        ++budgets_it;
      }
    }
    
    //if no budget has been selected
    if (!validBudget ) {
      
      TQValueList<MyMoneyBudget>::const_iterator budgets_it = budgets.begin();
      while( budgets_it != budgets.end() ) {
        //pick the first budget that matches the report start year
        if( (*budgets_it).budgetStart().year() == TQDate::currentDate().year() ) {
          budget = file->budget( (*budgets_it).id());
          break;
        }
        ++budgets_it;
      }
      //if we can't find a matching budget, take the first of the list
      if( budget.id() == "" )
        budget = budgets[0];

      //assign the budget to the report
      m_config_f.setBudget(budget.id(), m_config_f.isIncludingBudgetActuals());
    }

    // Dump the budget
    //kdDebug(2) << "Budget " << budget.name() << ": " << endl;

    // Go through all accounts in the system to build the mapping
    TQValueList<MyMoneyAccount> accounts;
    file->accountList(accounts);
    TQValueList<MyMoneyAccount>::const_iterator it_account = accounts.begin();
    while ( it_account != accounts.end() )
    {
      //include only the accounts selected for the report
      if ( m_config_f.includes ( *it_account ) ) {
        TQString id = ( *it_account ).id();
        TQString acid = id;

        // If the budget contains this account outright
        if ( budget.contains ( id ) )
        {
          // Add it to the mapping
          m_budgetMap[acid] = id;
          // kdDebug(2) << ReportAccount(acid).debugName() << " self-maps / type =" << budget.account(id).budgetLevel() << endl;
        }
        // Otherwise, search for a parent account which includes sub-accounts
        else
        {
          //if includeBudgetActuals, include all accounts regardless of whether in budget or not
          if ( m_config_f.isIncludingBudgetActuals() ) {
            m_budgetMap[acid] = id;
            // kdDebug(2) << ReportAccount(acid).debugName() << " maps to " << ReportAccount(id).debugName() << endl;
          }
          do
          {
            id = file->account ( id ).parentAccountId();
            if ( budget.contains ( id ) )
            {
              if ( budget.account ( id ).budgetSubaccounts() )
              {
                m_budgetMap[acid] = id;
                // kdDebug(2) << ReportAccount(acid).debugName() << " maps to " << ReportAccount(id).debugName() << endl;
                break;
              }
            }
          }
          while ( ! id.isEmpty() );
        }
      }
      ++it_account;
    } // end while looping through the accounts in the file

    // Place the budget values into the budget grid
    TQValueList<MyMoneyBudget::AccountGroup> baccounts = budget.getaccounts();
    TQValueList<MyMoneyBudget::AccountGroup>::const_iterator it_bacc = baccounts.begin();
    while ( it_bacc != baccounts.end() )
    {
      ReportAccount splitAccount = (*it_bacc).id();

      //include the budget account only if it is included in the report
      if ( m_config_f.includes ( splitAccount ) ) {
        MyMoneyAccount::accountTypeE type = splitAccount.accountGroup();
        TQString outergroup = KMyMoneyUtils::accountTypeToString(type);

        // reverse sign to match common notation for cash flow direction, only for expense/income splits
        MyMoneyMoney reverse((splitAccount.accountType() == MyMoneyAccount::Expense) ? -1 : 1, 1);

        const TQMap<TQDate, MyMoneyBudget::PeriodGroup>& periods = (*it_bacc).getPeriods();
        MyMoneyMoney value = (*periods.begin()).amount() * reverse;
        MyMoneyMoney price = MyMoneyMoney(1,1);
        unsigned column = 1;

        // based on the kind of budget it is, deal accordingly
        switch ( (*it_bacc).budgetLevel() )
        {
          case MyMoneyBudget::AccountGroup::eYearly:
            // divide the single yearly value by 12 and place it in each column
            value /= MyMoneyMoney(12,1);
          case MyMoneyBudget::AccountGroup::eNone:
          case MyMoneyBudget::AccountGroup::eMax:
          case MyMoneyBudget::AccountGroup::eMonthly:
            // place the single monthly value in each column of the report
            // only add the value if columns are monthly or longer
            if(m_config_f.columnType() == MyMoneyReport::eBiMonths
               || m_config_f.columnType() == MyMoneyReport::eMonths
               || m_config_f.columnType() == MyMoneyReport::eYears
               || m_config_f.columnType() == MyMoneyReport::eQuarters) {
              //value = value * MyMoneyMoney(m_config_f.columnType(), 1);

              TQDate budgetDate = budget.budgetStart();
              while ( column < m_numColumns && budget.budgetStart().addYears(1) > budgetDate ) {
                //only show budget values if the budget year and the column date match
                //no currency conversion is done here because that is done for all columns later
                if(budgetDate > columnDate(column) ) {
                  ++column;
                } else {
                  if(budgetDate >= m_beginDate.addDays(-m_beginDate.day() + 1)
                    && budgetDate <= m_endDate.addDays(m_endDate.daysInMonth() - m_endDate.day() )
                    && budgetDate > (columnDate(column).addMonths(-m_config_f.columnType()))) {
                    assignCell( outergroup, splitAccount, column, value, true /*budget*/ );
                  }
                  budgetDate = budgetDate.addMonths(1);
                }
              }
            }
            break;
          case MyMoneyBudget::AccountGroup::eMonthByMonth:
          // place each value in the appropriate column
          // budget periods are supposed to come in order just like columns
          {
            TQMap<TQDate, MyMoneyBudget::PeriodGroup>::const_iterator it_period = periods.begin();
            while ( it_period != periods.end() && column < m_numColumns)
            {
              if((*it_period).startDate() > columnDate(column) ) {
                ++column;
              } else {
                switch(m_config_f.columnType()) {
                  case MyMoneyReport::eYears:
                  case MyMoneyReport::eBiMonths:
                  case MyMoneyReport::eQuarters:
                  case MyMoneyReport::eMonths:
                  {
                    if((*it_period).startDate() >= m_beginDate.addDays(-m_beginDate.day() + 1)
                        && (*it_period).startDate() <= m_endDate.addDays(m_endDate.daysInMonth() - m_endDate.day() )
                        && (*it_period).startDate() > (columnDate(column).addMonths(-m_config_f.columnType()))) {
                      //no currency conversion is done here because that is done for all columns later
                      value = (*it_period).amount() * reverse;
                      assignCell( outergroup, splitAccount, column, value, true /*budget*/ );
                    }
                    ++it_period;
                    break;
                  }
                  default:
                    break;
                }
              }
            }
            break;
          }
        }
      }
      ++it_bacc;
    }
  } // end if there was a budget
}

void PivotTable::convertToBaseCurrency( void )
{
  DEBUG_ENTER(__PRETTY_FUNCTION__);

  int fraction = MyMoneyFile::instance()->baseCurrency().smallestAccountFraction();

  PivotGrid::iterator it_outergroup = m_grid.begin();
  while ( it_outergroup != m_grid.end() )
  {
    PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
    while ( it_innergroup != (*it_outergroup).end() )
    {
      PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
      while ( it_row != (*it_innergroup).end() )
      {
        unsigned column = 1;
        while ( column < m_numColumns )
        {
          if ( it_row.data()[eActual].count() <= column )
            throw new MYMONEYEXCEPTION(TQString("Column %1 out of grid range (%2) in PivotTable::convertToBaseCurrency").arg(column).arg(it_row.data()[eActual].count()));

          TQDate valuedate = columnDate(column);

          //get base price for that date
          MyMoneyMoney conversionfactor = it_row.key().baseCurrencyPrice(valuedate);

          for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
            if( m_rowTypeList[i] != eAverage ) {
              //calculate base value
              MyMoneyMoney oldval = it_row.data()[ m_rowTypeList[i] ][column];
              MyMoneyMoney value = (oldval * conversionfactor).reduce();

              //convert to lowest fraction
              it_row.data()[ m_rowTypeList[i] ][column] = PivotCell(value.convert(fraction));

              DEBUG_OUTPUT_IF(conversionfactor != MyMoneyMoney(1,1) ,TQString("Factor of %1, value was %2, now %3").arg(conversionfactor).arg(DEBUG_SENSITIVE(oldval)).arg(DEBUG_SENSITIVE(it_row.data()[m_rowTypeList[i]][column].toDouble())));
            }
          }


          ++column;
        }
        ++it_row;
      }
      ++it_innergroup;
    }
    ++it_outergroup;
  }
}

void PivotTable::convertToDeepCurrency( void )
{
  DEBUG_ENTER(__PRETTY_FUNCTION__);
  MyMoneyFile* file = MyMoneyFile::instance();
  
  PivotGrid::iterator it_outergroup = m_grid.begin();
  while ( it_outergroup != m_grid.end() )
  {
    PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
    while ( it_innergroup != (*it_outergroup).end() )
    {
      PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
      while ( it_row != (*it_innergroup).end() )
      {
        unsigned column = 1;
        while ( column < m_numColumns )
        {
          if ( it_row.data()[eActual].count() <= column )
            throw new MYMONEYEXCEPTION(TQString("Column %1 out of grid range (%2) in PivotTable::convertToDeepCurrency").arg(column).arg(it_row.data()[eActual].count()));

          TQDate valuedate = columnDate(column);

          //get conversion factor for the account and date
          MyMoneyMoney conversionfactor = it_row.key().deepCurrencyPrice(valuedate);

          //use the fraction relevant to the account at hand
          int fraction = it_row.key().currency().smallestAccountFraction();

          //use base currency fraction if not initialized
          if(fraction == -1)
            fraction = file->baseCurrency().smallestAccountFraction();

          //convert to deep currency
          MyMoneyMoney oldval = it_row.data()[eActual][column];
          MyMoneyMoney value = (oldval * conversionfactor).reduce();
          //reduce to lowest fraction
          it_row.data()[eActual][column] = PivotCell(value.convert(fraction));

          //convert price data
          if(m_config_f.isIncludingPrice()) {
            MyMoneyMoney oldPriceVal = it_row.data()[ePrice][column];
            MyMoneyMoney priceValue = (oldPriceVal * conversionfactor).reduce();
            it_row.data()[ePrice][column] = PivotCell(priceValue.convert(10000));
          }

          DEBUG_OUTPUT_IF(conversionfactor != MyMoneyMoney(1,1) ,TQString("Factor of %1, value was %2, now %3").arg(conversionfactor).arg(DEBUG_SENSITIVE(oldval)).arg(DEBUG_SENSITIVE(it_row.data()[eActual][column].toDouble())));

          ++column;
        }
        ++it_row;
      }
      ++it_innergroup;
    }
    ++it_outergroup;
  }
}

void PivotTable::calculateTotals( void )
{
  //insert the row type that is going to be used
  for(unsigned i = 0; i < m_rowTypeList.size(); ++i)
    m_grid.m_total[ m_rowTypeList[i] ].insert( m_grid.m_total[ m_rowTypeList[i] ].end(), m_numColumns, PivotCell() );

  //
  // Outer groups
  //

  // iterate over outer groups
  PivotGrid::iterator it_outergroup = m_grid.begin();
  while ( it_outergroup != m_grid.end() )
  {
    for(unsigned i = 0; i < m_rowTypeList.size(); ++i)
      (*it_outergroup).m_total[ m_rowTypeList[i] ].insert( (*it_outergroup).m_total[ m_rowTypeList[i] ].end(), m_numColumns, PivotCell() );

    //
    // Inner Groups
    //

    PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
    while ( it_innergroup != (*it_outergroup).end() )
    {
      for(unsigned i = 0; i < m_rowTypeList.size(); ++i)
        (*it_innergroup).m_total[ m_rowTypeList[i] ].insert( (*it_innergroup).m_total[ m_rowTypeList[i] ].end(), m_numColumns, PivotCell() );
      //
      // Rows
      //

      PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
      while ( it_row != (*it_innergroup).end() )
      {
        //
        // Columns
        //

        unsigned column = 1;
        while ( column < m_numColumns )
        {
          for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
            if ( it_row.data()[ m_rowTypeList[i] ].count() <= column )
              throw new MYMONEYEXCEPTION(TQString("Column %1 out of grid range (%2) in PivotTable::calculateTotals, row columns").arg(column).arg(it_row.data()[ m_rowTypeList[i] ].count()));
            if ( (*it_innergroup).m_total[ m_rowTypeList[i] ].count() <= column )
              throw new MYMONEYEXCEPTION(TQString("Column %1 out of grid range (%2) in PivotTable::calculateTotals, inner group totals").arg(column).arg((*it_innergroup).m_total[ m_rowTypeList[i] ].count()));

            //calculate total
            MyMoneyMoney value = it_row.data()[ m_rowTypeList[i] ][column];
            (*it_innergroup).m_total[ m_rowTypeList[i] ][column] += value;
            (*it_row)[ m_rowTypeList[i] ].m_total += value;
          }
          ++column;
        }
        ++it_row;
      }

      //
      // Inner Row Group Totals
      //

      unsigned column = 1;
      while ( column < m_numColumns )
      {
        for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
          if ( (*it_innergroup).m_total[ m_rowTypeList[i] ].count() <= column )
            throw new MYMONEYEXCEPTION(TQString("Column %1 out of grid range (%2) in PivotTable::calculateTotals, inner group totals").arg(column).arg((*it_innergroup).m_total[ m_rowTypeList[i] ].count()));
          if ( (*it_outergroup).m_total[ m_rowTypeList[i] ].count() <= column )
            throw new MYMONEYEXCEPTION(TQString("Column %1 out of grid range (%2) in PivotTable::calculateTotals, outer group totals").arg(column).arg((*it_innergroup).m_total[ m_rowTypeList[i] ].count()));

          //calculate totals
          MyMoneyMoney value = (*it_innergroup).m_total[ m_rowTypeList[i] ][column];
          (*it_outergroup).m_total[ m_rowTypeList[i] ][column] += value;
          (*it_innergroup).m_total[ m_rowTypeList[i] ].m_total += value;
        }
        ++column;
      }

      ++it_innergroup;
    }

    //
    // Outer Row Group Totals
    //

    bool invert_total = (*it_outergroup).m_inverted;
    unsigned column = 1;
    while ( column < m_numColumns )
    {
      for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
        if ( m_grid.m_total[ m_rowTypeList[i] ].count() <= column )
          throw new MYMONEYEXCEPTION(TQString("Column %1 out of grid range (%2) in PivotTable::calculateTotals, grid totals").arg(column).arg((*it_innergroup).m_total[ m_rowTypeList[i] ].count()));

      //calculate actual totals
        MyMoneyMoney value = (*it_outergroup).m_total[ m_rowTypeList[i] ][column];
        (*it_outergroup).m_total[ m_rowTypeList[i] ].m_total += value;

        //so far the invert only applies to actual and budget
        if ( invert_total
             && m_rowTypeList[i] != eBudgetDiff
             &&  m_rowTypeList[i] != eForecast)
          value = -value;

        m_grid.m_total[ m_rowTypeList[i] ][column] += value;
      }
      ++column;
    }
    ++it_outergroup;
  }

  //
  // Report Totals
  //

  unsigned totalcolumn = 1;
  while ( totalcolumn < m_numColumns )
  {
    for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
      if ( m_grid.m_total[ m_rowTypeList[i] ].count() <= totalcolumn )
        throw new MYMONEYEXCEPTION(TQString("Total column %1 out of grid range (%2) in PivotTable::calculateTotals, grid totals").arg(totalcolumn).arg(m_grid.m_total[ m_rowTypeList[i] ].count()));

    //calculate actual totals
      MyMoneyMoney value = m_grid.m_total[ m_rowTypeList[i] ][totalcolumn];
      m_grid.m_total[ m_rowTypeList[i] ].m_total += value;
    }
    ++totalcolumn;
  }
}

void PivotTable::assignCell( const TQString& outergroup, const ReportAccount& _row, unsigned column, MyMoneyMoney value, bool budget, bool stockSplit )
{
  DEBUG_ENTER(__PRETTY_FUNCTION__);
  DEBUG_OUTPUT(TQString("Parameters: %1,%2,%3,%4,%5").arg(outergroup).arg(_row.debugName()).arg(column).arg(DEBUG_SENSITIVE(value.toDouble())).arg(budget));

  // for budget reports, if this is the actual value, map it to the account which
  // holds its budget
  ReportAccount row = _row;
  if ( !budget && m_config_f.hasBudget() )
  {
    TQString newrow = m_budgetMap[row.id()];

    // if there was no mapping found, then the budget report is not interested
    // in this account.
    if ( newrow.isEmpty() )
      return;

    row = newrow;
  }

  // ensure the row already exists (and its parental hierarchy)
  createRow( outergroup, row, true );

  // Determine the inner group from the top-most parent account
  TQString innergroup( row.topParentName() );

  if ( m_numColumns <= column )
    throw new MYMONEYEXCEPTION(TQString("Column %1 out of m_numColumns range (%2) in PivotTable::assignCell").arg(column).arg(m_numColumns));
  if ( m_grid[outergroup][innergroup][row][eActual].count() <= column )
    throw new MYMONEYEXCEPTION(TQString("Column %1 out of grid range (%2) in PivotTable::assignCell").arg(column).arg(m_grid[outergroup][innergroup][row][eActual].count()));

  if(!stockSplit) {
    // Determine whether the value should be inverted before being placed in the row
    if ( m_grid[outergroup].m_inverted )
      value = -value;

    // Add the value to the grid cell
    if ( budget )
      m_grid[outergroup][innergroup][row][eBudget][column] += value;
    else
      m_grid[outergroup][innergroup][row][eActual][column] += value;
  } else {
    m_grid[outergroup][innergroup][row][eActual][column] += PivotCell::stockSplit(value);
  }

}

void PivotTable::createRow( const TQString& outergroup, const ReportAccount& row, bool recursive )
{
  DEBUG_ENTER(__PRETTY_FUNCTION__);

  // Determine the inner group from the top-most parent account
  TQString innergroup( row.topParentName() );

  if ( ! m_grid.contains(outergroup) )
  {
    DEBUG_OUTPUT(TQString("Adding group [%1]").arg(outergroup));
    m_grid[outergroup] = PivotOuterGroup(m_numColumns);
  }

  if ( ! m_grid[outergroup].contains(innergroup) )
  {
    DEBUG_OUTPUT(TQString("Adding group [%1][%2]").arg(outergroup).arg(innergroup));
    m_grid[outergroup][innergroup] = PivotInnerGroup(m_numColumns);
  }

  if ( ! m_grid[outergroup][innergroup].contains(row) )
  {
    DEBUG_OUTPUT(TQString("Adding row [%1][%2][%3]").arg(outergroup).arg(innergroup).arg(row.debugName()));
    m_grid[outergroup][innergroup][row] = PivotGridRowSet(m_numColumns);

    if ( recursive && !row.isTopLevel() )
        createRow( outergroup, row.parent(), recursive );
  }
}

unsigned PivotTable::columnValue(const TQDate& _date) const
{
  if (m_config_f.isColumnsAreDays())
    return (TQDate().daysTo(_date));
  else
    return (_date.year() * 12 + _date.month());
}

TQDate PivotTable::columnDate(int column) const
{
  if (m_config_f.isColumnsAreDays())
    return m_beginDate.addDays( m_config_f.columnPitch() * column - 1 );
  else
    return m_beginDate.addMonths( m_config_f.columnPitch() * column ).addDays(-1);
}

TQString PivotTable::renderCSV( void ) const
{
  DEBUG_ENTER(__PRETTY_FUNCTION__);

  //
  // Report Title
  //

  TQString result = TQString("\"Report: %1\"\n").arg(m_config_f.name());
  if ( m_config_f.isConvertCurrency() )
    result += i18n("All currencies converted to %1\n").arg(MyMoneyFile::instance()->baseCurrency().name());
  else
    result += i18n("All values shown in %1 unless otherwise noted\n").arg(MyMoneyFile::instance()->baseCurrency().name());

  //
  // Table Header
  //

  result += i18n("Account");

  unsigned column = 1;
  while ( column < m_numColumns )
    result += TQString(",%1").arg(TQString(m_columnHeadings[column++]));

  if ( m_config_f.isShowingRowTotals() )
    result += TQString(",%1").arg(i18n("Total"));

  result += "\n";

  int fraction = MyMoneyFile::instance()->baseCurrency().smallestAccountFraction();

  //
  // Outer groups
  //

  // iterate over outer groups
  PivotGrid::const_iterator it_outergroup = m_grid.begin();
  while ( it_outergroup != m_grid.end() )
  {
    //
    // Outer Group Header
    //

    result += it_outergroup.key() + "\n";

    //
    // Inner Groups
    //

    PivotOuterGroup::const_iterator it_innergroup = (*it_outergroup).begin();
    unsigned rownum = 0;
    while ( it_innergroup != (*it_outergroup).end() )
    {
      //
      // Rows
      //

      TQString innergroupdata;
      PivotInnerGroup::const_iterator it_row = (*it_innergroup).begin();
      while ( it_row != (*it_innergroup).end() )
      {
        ReportAccount rowname = it_row.key();
        int fraction = rowname.currency().smallestAccountFraction();

        //
        // Columns
        //

        TQString rowdata;
        unsigned column = 1;

        bool isUsed = false;
        for(unsigned i = 0; i < m_rowTypeList.size(); ++i)
          isUsed |= it_row.data()[ m_rowTypeList[i] ][0].isUsed();

        while ( column < m_numColumns ) {
          //show columns
          for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
            isUsed |= it_row.data()[ m_rowTypeList[i] ][column].isUsed();
            rowdata += TQString(",\"%1\"").arg(it_row.data()[ m_rowTypeList[i] ][column].formatMoney(fraction, false));
          }
          column++;
        }

        if ( m_config_f.isShowingRowTotals() ) {
          for(unsigned i = 0; i < m_rowTypeList.size(); ++i)
            rowdata += TQString(",\"%1\"").arg((*it_row)[ m_rowTypeList[i] ].m_total.formatMoney(fraction, false));
        }

        //
        // Row Header
        //

        if(!rowname.isClosed() || isUsed) {
          innergroupdata += "\"" + TQString().fill(' ',rowname.hierarchyDepth() - 1) + rowname.name();

          // if we don't convert the currencies to the base currency and the
          // current row contains a foreign currency, then we append the currency
          // to the name of the account
          if (!m_config_f.isConvertCurrency() && rowname.isForeignCurrency() )
            innergroupdata += TQString(" (%1)").arg(rowname.currencyId());

          innergroupdata += "\"";

          if ( isUsed )
            innergroupdata += rowdata;

          innergroupdata += "\n";
        }
        ++it_row;
      }

      //
      // Inner Row Group Totals
      //

      bool finishrow = true;
      TQString finalRow;
      bool isUsed = false;
      if ( m_config_f.detailLevel() == MyMoneyReport::eDetailAll && ((*it_innergroup).size() > 1 ))
      {
        // Print the individual rows
        result += innergroupdata;

        if ( m_config_f.isShowingColumnTotals() )
        {
          // Start the TOTALS row
          finalRow = i18n("Total");
          isUsed = true;
        }
        else
        {
          ++rownum;
          finishrow = false;
        }
      }
      else
      {
        // Start the single INDIVIDUAL ACCOUNT row
        ReportAccount rowname = (*it_innergroup).begin().key();
        isUsed |= !rowname.isClosed();

        finalRow = "\"" + TQString().fill(' ',rowname.hierarchyDepth() - 1) + rowname.name();
        if (!m_config_f.isConvertCurrency() && rowname.isForeignCurrency() )
          finalRow += TQString(" (%1)").arg(rowname.currencyId());
        finalRow += "\"";
      }

      // Finish the row started above, unless told not to
      if ( finishrow )
      {
        unsigned column = 1;

        for(unsigned i = 0; i < m_rowTypeList.size(); ++i)
          isUsed |= (*it_innergroup).m_total[ m_rowTypeList[i] ][0].isUsed();

        while ( column < m_numColumns )
        {
          for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
            isUsed |= (*it_innergroup).m_total[ m_rowTypeList[i] ][column].isUsed();
            finalRow += TQString(",\"%1\"").arg((*it_innergroup).m_total[ m_rowTypeList[i] ][column].formatMoney(fraction, false));
          }
          column++;
        }

        if (  m_config_f.isShowingRowTotals() ) {
          for(unsigned i = 0; i < m_rowTypeList.size(); ++i)
            finalRow += TQString(",\"%1\"").arg((*it_innergroup).m_total[ m_rowTypeList[i] ].m_total.formatMoney(fraction, false));
        }

        finalRow += "\n";
      }

      if(isUsed)
      {
        result += finalRow;
        ++rownum;
      }
      ++it_innergroup;
    }

    //
    // Outer Row Group Totals
    //

    if ( m_config_f.isShowingColumnTotals() )
    {
      result += TQString("%1 %2").arg(i18n("Total")).arg(it_outergroup.key());
      unsigned column = 1;
      while ( column < m_numColumns ) {
        for(unsigned i = 0; i < m_rowTypeList.size(); ++i)
          result += TQString(",\"%1\"").arg((*it_outergroup).m_total[ m_rowTypeList[i] ][column].formatMoney(fraction, false));

        column++;
      }

      if (  m_config_f.isShowingRowTotals() ) {
        for(unsigned i = 0; i < m_rowTypeList.size(); ++i)
          result += TQString(",\"%1\"").arg((*it_outergroup).m_total[ m_rowTypeList[i] ].m_total.formatMoney(fraction, false));
      }

      result += "\n";
    }
    ++it_outergroup;
  }

  //
  // Report Totals
  //

  if ( m_config_f.isShowingColumnTotals() )
  {
    result += i18n("Grand Total");
    unsigned totalcolumn = 1;
    while ( totalcolumn < m_numColumns ) {
      for(unsigned i = 0; i < m_rowTypeList.size(); ++i)
        result += TQString(",\"%1\"").arg(m_grid.m_total[ m_rowTypeList[i] ][totalcolumn].formatMoney(fraction, false));

      totalcolumn++;
    }

    if (  m_config_f.isShowingRowTotals() ) {
      for(unsigned i = 0; i < m_rowTypeList.size(); ++i)
        result += TQString(",\"%1\"").arg(m_grid.m_total[ m_rowTypeList[i] ].m_total.formatMoney(fraction, false));
    }

    result += "\n";
  }

  return result;
}

TQString PivotTable::renderHTML( void ) const
{
  DEBUG_ENTER(__PRETTY_FUNCTION__);

  TQString colspan = TQString(" colspan=\"%1\"").arg(m_numColumns + 1 + (m_config_f.isShowingRowTotals() ? 1 : 0) );

  //
  // Report Title
  //

  TQString result = TQString("<h2 class=\"report\">%1</h2>\n").arg(m_config_f.name());

  //actual dates of the report
  result += TQString("<div class=\"subtitle\">");
  result += i18n("Report date range", "%1 through %2").arg(TDEGlobal::locale()->formatDate(m_config_f.fromDate(), true)).arg(TDEGlobal::locale()->formatDate(m_config_f.toDate(), true));
  result += TQString("</div>\n");
  result += TQString("<div class=\"gap\">&nbsp;</div>\n");

  //currency conversion message
  result += TQString("<div class=\"subtitle\">");
  if ( m_config_f.isConvertCurrency() )
    result += i18n("All currencies converted to %1").arg(MyMoneyFile::instance()->baseCurrency().name());
  else
    result += i18n("All values shown in %1 unless otherwise noted").arg(MyMoneyFile::instance()->baseCurrency().name());
  result += TQString("</div>\n");
  result += TQString("<div class=\"gap\">&nbsp;</div>\n");

  // setup a leftborder for better readability of budget vs actual reports
  TQString leftborder;
  if (m_rowTypeList.size() > 1)
    leftborder = " class=\"leftborder\"";

  //
  // Table Header
  //
  result += TQString("\n\n<table class=\"report\" cellspacing=\"0\">\n"
       "<thead><tr class=\"itemheader\">\n<th>%1</th>").arg(i18n("Account"));

  TQString headerspan;
  int span = m_rowTypeList.size();

  headerspan = TQString(" colspan=\"%1\"").arg(span);

  unsigned column = 1;
  while ( column < m_numColumns )
    result += TQString("<th%1>%2</th>").arg(headerspan,TQString(m_columnHeadings[column++]).replace(TQRegExp(" "),"<br>"));

  if ( m_config_f.isShowingRowTotals() )
    result += TQString("<th%1>%2</th>").arg(headerspan).arg(i18n("Total"));

  result += "</tr></thead>\n";

  //
  // Header for multiple columns
  //
  if ( span > 1 )
  {
    result += "<tr><td></td>";

    unsigned column = 1;
    while ( column < m_numColumns )
    {
      TQString lb;
      if(column != 1)
        lb = leftborder;

      for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
        result += TQString("<td%2>%1</td>")
            .arg(i18n( m_columnTypeHeaderList[i] ))
            .arg(i == 0 ? lb : TQString() );
      }
      column++;
    }
    if ( m_config_f.isShowingRowTotals() ) {
      for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
        result += TQString("<td%2>%1</td>")
            .arg(i18n( m_columnTypeHeaderList[i] ))
            .arg(i == 0 ? leftborder : TQString() );
      }
    }
    result += "</tr>";
  }


  // Skip the body of the report if the report only calls for totals to be shown
  if ( m_config_f.detailLevel() != MyMoneyReport::eDetailTotal )
  {
    //
    // Outer groups
    //

    // Need to sort the outergroups.  They can't always be sorted by name.  So we create a list of
    // map iterators, and sort that.  Then we'll iterate through the map iterators and use those as
    // before.
    //
    // I hope this doesn't bog the performance of reports, given that we're copying the entire report
    // data.  If this is a perf hit, we could change to storing outergroup pointers, I think.
    TQValueList<PivotOuterGroup> outergroups;
    PivotGrid::const_iterator it_outergroup_map = m_grid.begin();
    while ( it_outergroup_map != m_grid.end() )
    {
      outergroups.push_back(it_outergroup_map.data());

      // copy the name into the outergroup, because we will now lose any association with
      // the map iterator
      outergroups.back().m_displayName = it_outergroup_map.key();

      ++it_outergroup_map;
    }
    qHeapSort(outergroups);

    TQValueList<PivotOuterGroup>::const_iterator it_outergroup = outergroups.begin();
    while ( it_outergroup != outergroups.end() )
    {
      //
      // Outer Group Header
      //

      result += TQString("<tr class=\"sectionheader\"><td class=\"left\"%1>%2</td></tr>\n").arg(colspan).arg((*it_outergroup).m_displayName);

      // Skip the inner groups if the report only calls for outer group totals to be shown
      if ( m_config_f.detailLevel() != MyMoneyReport::eDetailGroup )
      {

        //
        // Inner Groups
        //

        PivotOuterGroup::const_iterator it_innergroup = (*it_outergroup).begin();
        unsigned rownum = 0;
        while ( it_innergroup != (*it_outergroup).end() )
        {
          //
          // Rows
          //

          TQString innergroupdata;
          PivotInnerGroup::const_iterator it_row = (*it_innergroup).begin();
          while ( it_row != (*it_innergroup).end() )
          {
            //
            // Columns
            //

            TQString rowdata;
            unsigned column = 1;
            bool isUsed = it_row.data()[eActual][0].isUsed();
            while ( column < m_numColumns )
            {
              TQString lb;
              if(column != 1)
                lb = leftborder;

              for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
                rowdata += TQString("<td%2>%1</td>")
                    .arg(coloredAmount(it_row.data()[ m_rowTypeList[i] ][column]))
                    .arg(i == 0 ? lb : TQString());

                isUsed |= it_row.data()[ m_rowTypeList[i] ][column].isUsed();
              }

              column++;
            }

            if ( m_config_f.isShowingRowTotals() )
            {
              for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
                rowdata += TQString("<td%2>%1</td>")
                    .arg(coloredAmount(it_row.data()[ m_rowTypeList[i] ].m_total))
                    .arg(i == 0 ? leftborder : TQString());
              }
            }

            //
            // Row Header
            //

            ReportAccount rowname = it_row.key();

            // don't show closed accounts if they have not been used
            if(!rowname.isClosed() || isUsed) {
              innergroupdata += TQString("<tr class=\"row-%1\"%2><td%3 class=\"left\" style=\"text-indent: %4.0em\">%5%6</td>")
                .arg(rownum & 0x01 ? "even" : "odd")
                .arg(rowname.isTopLevel() ? " id=\"topparent\"" : "")
                .arg("") //.arg((*it_row).m_total.isZero() ? colspan : "")  // colspan the distance if this row will be blank
                .arg(rowname.hierarchyDepth() - 1)
                .arg(rowname.name().replace(TQRegExp(" "), "&nbsp;"))
                .arg((m_config_f.isConvertCurrency() || !rowname.isForeignCurrency() )?TQString():TQString(" (%1)").arg(rowname.currency().id()));

              // Don't print this row if it's going to be all zeros
              // TODO: Uncomment this, and deal with the case where the data
              // is zero, but the budget is non-zero
              //if ( !(*it_row).m_total.isZero() )
              innergroupdata += rowdata;

              innergroupdata += "</tr>\n";
            }

            ++it_row;
          }

          //
          // Inner Row Group Totals
          //

          bool finishrow = true;
          TQString finalRow;
          bool isUsed = false;
          if ( m_config_f.detailLevel() == MyMoneyReport::eDetailAll && ((*it_innergroup).size() > 1 ))
          {
            // Print the individual rows
            result += innergroupdata;

            if ( m_config_f.isShowingColumnTotals() )
            {
              // Start the TOTALS row
              finalRow = TQString("<tr class=\"row-%1\" id=\"subtotal\"><td class=\"left\">&nbsp;&nbsp;%2</td>")
                .arg(rownum & 0x01 ? "even" : "odd")
                .arg(i18n("Total"));
              // don't suppress display of totals
              isUsed = true;
            }
            else {
              finishrow = false;
              ++rownum;
            }
          }
          else
          {
            // Start the single INDIVIDUAL ACCOUNT row
            // FIXME: There is a bit of a bug here with class=leftX.  There's only a finite number
            // of classes I can define in the .CSS file, and the user can theoretically nest deeper.
            // The right solution is to use style=Xem, and calculate X.  Let's see if anyone complains
            // first :)  Also applies to the row header case above.
            // FIXED: I found it in one of my reports and changed it to the proposed method.
            // This works for me (ipwizard)
            ReportAccount rowname = (*it_innergroup).begin().key();
            isUsed |= !rowname.isClosed();
            finalRow = TQString("<tr class=\"row-%1\"%2><td class=\"left\" style=\"text-indent: %3.0em;\">%5%6</td>")
              .arg(rownum & 0x01 ? "even" : "odd")
                .arg( m_config_f.detailLevel() == MyMoneyReport::eDetailAll ? "id=\"solo\"" : "" )
              .arg(rowname.hierarchyDepth() - 1)
              .arg(rowname.name().replace(TQRegExp(" "), "&nbsp;"))
              .arg((m_config_f.isConvertCurrency() || !rowname.isForeignCurrency() )?TQString():TQString(" (%1)").arg(rowname.currency().id()));
          }

          // Finish the row started above, unless told not to
          if ( finishrow )
          {
            unsigned column = 1;
            isUsed |= (*it_innergroup).m_total[eActual][0].isUsed();
            while ( column < m_numColumns )
            {
              TQString lb;
              if(column != 1)
                lb = leftborder;

              for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
                finalRow += TQString("<td%2>%1</td>")
                    .arg(coloredAmount((*it_innergroup).m_total[ m_rowTypeList[i] ][column]))
                    .arg(i == 0 ? lb : TQString());
                isUsed |= (*it_innergroup).m_total[ m_rowTypeList[i] ][column].isUsed();
              }

              column++;
            }

            if (  m_config_f.isShowingRowTotals() )
            {
              for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
                finalRow += TQString("<td%2>%1</td>")
                    .arg(coloredAmount((*it_innergroup).m_total[ m_rowTypeList[i] ].m_total))
                    .arg(i == 0 ? leftborder : TQString());
              }
            }

            finalRow += "</tr>\n";
            if(isUsed) {
              result += finalRow;
              ++rownum;
            }
          }

          ++it_innergroup;

        } // end while iterating on the inner groups

      } // end if detail level is not "group"

      //
      // Outer Row Group Totals
      //

      if ( m_config_f.isShowingColumnTotals() )
      {
        result += TQString("<tr class=\"sectionfooter\"><td class=\"left\">%1&nbsp;%2</td>").arg(i18n("Total")).arg((*it_outergroup).m_displayName);
        unsigned column = 1;
        while ( column < m_numColumns )
        {
          TQString lb;
          if(column != 1)
            lb = leftborder;

          for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
            result += TQString("<td%2>%1</td>")
                .arg(coloredAmount((*it_outergroup).m_total[ m_rowTypeList[i] ][column]))
                .arg(i == 0 ? lb : TQString());
          }

          column++;
        }

        if (  m_config_f.isShowingRowTotals() )
        {
          for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
            result += TQString("<td%2>%1</td>")
                .arg(coloredAmount((*it_outergroup).m_total[ m_rowTypeList[i] ].m_total))
                .arg(i == 0 ? leftborder : TQString());
          }
        }
        result += "</tr>\n";
      }

      ++it_outergroup;

    } // end while iterating on the outergroups

  } // end if detail level is not "total"

  //
  // Report Totals
  //

  if ( m_config_f.isShowingColumnTotals() )
  {
    result += TQString("<tr class=\"spacer\"><td>&nbsp;</td></tr>\n");
    result += TQString("<tr class=\"reportfooter\"><td class=\"left\">%1</td>").arg(i18n("Grand Total"));
    unsigned totalcolumn = 1;
    while ( totalcolumn < m_numColumns )
    {
      TQString lb;
      if(totalcolumn != 1)
        lb = leftborder;

      for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
        result += TQString("<td%2>%1</td>")
            .arg(coloredAmount(m_grid.m_total[ m_rowTypeList[i] ][totalcolumn]))
            .arg(i == 0 ? lb : TQString());
      }

      totalcolumn++;
    }

    if (  m_config_f.isShowingRowTotals() )
    {
      for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
        result += TQString("<td%2>%1</td>")
            .arg(coloredAmount(m_grid.m_total[ m_rowTypeList[i] ].m_total))
            .arg(i == 0 ? leftborder : TQString());
      }
    }

    result += "</tr>\n";
  }

  result += TQString("<tr class=\"spacer\"><td>&nbsp;</td></tr>\n");
  result += TQString("<tr class=\"spacer\"><td>&nbsp;</td></tr>\n");
  result += "</table>\n";

  return result;
}

void PivotTable::dump( const TQString& file, const TQString& /* context */) const
{
  TQFile g( file );
  g.open( IO_WriteOnly );
  TQTextStream(&g) << renderHTML();
  g.close();
}

#ifdef HAVE_KDCHART
void PivotTable::drawChart( KReportChartView& _view ) const
{
#if 1 // make this "#if 1" if you want to play with the axis settings
  // not sure if 0 is X and 1 is Y.
  KDChartAxisParams xAxisParams, yAxisParams;
  KDChartAxisParams::deepCopy(xAxisParams, _view.params()->axisParams(0));
  KDChartAxisParams::deepCopy(yAxisParams, _view.params()->axisParams(1));

  // modify axis settings here
  xAxisParams.setAxisLabelsFontMinSize(12);
  xAxisParams.setAxisLabelsFontRelSize(20);
  yAxisParams.setAxisLabelsFontMinSize(12);
  yAxisParams.setAxisLabelsFontRelSize(20);

  _view.params()->setAxisParams( 0, xAxisParams );
  _view.params()->setAxisParams( 1, yAxisParams );

#endif
  _view.params()->setLegendFontRelSize(20);
  _view.params()->setLegendTitleFontRelSize(24);
  _view.params()->setLegendTitleText(i18n("Legend"));

  _view.params()->setAxisShowGrid(0,m_config_f.isChartGridLines());
  _view.params()->setAxisShowGrid(1,m_config_f.isChartGridLines());
  _view.params()->setPrintDataValues(m_config_f.isChartDataLabels());

  // whether to limit the chart to use series totals only.  Used for reports which only
  // show one dimension (pie).
  bool seriesTotals = false;

  // whether series (rows) are accounts (true) or months (false). This causes a lot
  // of complexity in the charts.  The problem is that circular reports work best with
  // an account in a COLUMN, while line/bar prefer it in a ROW.
  bool accountSeries = true;

  //what values should be shown
  bool showBudget = m_config_f.hasBudget();
  bool showForecast = m_config_f.isIncludingForecast();
  bool showActual = false;
  if( (m_config_f.isIncludingBudgetActuals()) || ( !showBudget && !showForecast) )
    showActual = true;

  _view.params()->setLineWidth( m_config_f.chartLineWidth() );

  switch( m_config_f.chartType() )
  {
  case MyMoneyReport::eChartNone:
  case MyMoneyReport::eChartEnd:
  case MyMoneyReport::eChartLine:
    _view.params()->setChartType( KDChartParams::Line );
    _view.params()->setAxisDatasets( 0,0 );
    break;
  case MyMoneyReport::eChartBar:
    _view.params()->setChartType( KDChartParams::Bar );
    _view.params()->setBarChartSubType( KDChartParams::BarNormal );
    break;
  case MyMoneyReport::eChartStackedBar:
    _view.params()->setChartType( KDChartParams::Bar );
    _view.params()->setBarChartSubType( KDChartParams::BarStacked );
    break;
  case MyMoneyReport::eChartPie:
    _view.params()->setChartType( KDChartParams::Pie );
    // Charts should only be 3D if this adds any information
    _view.params()->setThreeDPies( false );
    accountSeries = false;
    seriesTotals = true;
    break;
  case MyMoneyReport::eChartRing:
    _view.params()->setChartType( KDChartParams::Ring );
    _view.params()->setRelativeRingThickness( true );
    accountSeries = false;
    break;
  }

  // For onMouseOver events, we want to activate mouse tracking
  _view.setMouseTracking( true );

  //
  // In KDChart parlance, a 'series' (or row) is an account (or accountgroup, etc)
  // and an 'item' (or column) is a month
  //
  unsigned r;
  unsigned c;
  if ( accountSeries )
  {
    r = 1;
    c = m_numColumns - 1;
  }
  else
  {
    c = 1;
    r = m_numColumns - 1;
  }
  KDChartTableData data( r,c );

  // The KReportChartView widget needs to know whether the legend
  // corresponds to rows or columns
  _view.setAccountSeries( accountSeries );

  // Set up X axis labels (ie "abscissa" to use the technical term)
  TQStringList& abscissaNames = _view.abscissaNames();
  abscissaNames.clear();
  if ( accountSeries )
  {
    unsigned column = 1;
    while ( column < m_numColumns ) {
      abscissaNames += TQString(m_columnHeadings[column++]).replace("&nbsp;", " ");
    }
  }
  else
  {
    // we will set these up while putting in the chart values.
  }

  switch ( m_config_f.detailLevel() )
  {
    case MyMoneyReport::eDetailNone:
    case MyMoneyReport::eDetailEnd:
    case MyMoneyReport::eDetailAll:
    {
      unsigned rowNum = 0;

      // iterate over outer groups
      PivotGrid::const_iterator it_outergroup = m_grid.begin();
      while ( it_outergroup != m_grid.end() )
      {

        // iterate over inner groups
        PivotOuterGroup::const_iterator it_innergroup = (*it_outergroup).begin();
        while ( it_innergroup != (*it_outergroup).end() )
        {
          //
          // Rows
          //
          TQString innergroupdata;
          PivotInnerGroup::const_iterator it_row = (*it_innergroup).begin();
          while ( it_row != (*it_innergroup).end() )
          {
            //Do not include investments accounts in the chart because they are merely container of stock and other accounts
            if( it_row.key().accountType() != MyMoneyAccount::Investment) {
              //iterate row types
              for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
                //skip the budget difference rowset
                if(m_rowTypeList[i] != eBudgetDiff ) {
                  rowNum = drawChartRowSet(rowNum, seriesTotals, accountSeries, data, it_row.data(), m_rowTypeList[i]);

                  //only show the column type in the header if there is more than one type
                  if(m_rowTypeList.size() > 1) {
                    _view.params()->setLegendText( rowNum-1, m_columnTypeHeaderList[i] + " - " + it_row.key().name() );
                  } else {
                    _view.params()->setLegendText( rowNum-1, it_row.key().name() );
                  }
                }
              }
            }
            ++it_row;
          }
          ++it_innergroup;
        }
        ++it_outergroup;
      }
    }
    break;

    case MyMoneyReport::eDetailTop:
    {
      unsigned rowNum = 0;

      // iterate over outer groups
      PivotGrid::const_iterator it_outergroup = m_grid.begin();
      while ( it_outergroup != m_grid.end() )
      {

        // iterate over inner groups
        PivotOuterGroup::const_iterator it_innergroup = (*it_outergroup).begin();
        while ( it_innergroup != (*it_outergroup).end() )
        {
          //iterate row types
          for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
            //skip the budget difference rowset
            if(m_rowTypeList[i] != eBudgetDiff ) {
              rowNum = drawChartRowSet(rowNum, seriesTotals, accountSeries, data, (*it_innergroup).m_total, m_rowTypeList[i]);

              //only show the column type in the header if there is more than one type
              if(m_rowTypeList.size() > 1) {
                _view.params()->setLegendText( rowNum-1, m_columnTypeHeaderList[i] + " - " + it_innergroup.key() );
              } else {
                _view.params()->setLegendText( rowNum-1, it_innergroup.key() );
              }
            }
          }
          ++it_innergroup;
        }
        ++it_outergroup;
      }
    }
    break;

    case MyMoneyReport::eDetailGroup:
    {
      unsigned rowNum = 0;

      // iterate over outer groups
      PivotGrid::const_iterator it_outergroup = m_grid.begin();
      while ( it_outergroup != m_grid.end() )
      {
        //iterate row types
        for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
          //skip the budget difference rowset
          if(m_rowTypeList[i] != eBudgetDiff ) {
            rowNum = drawChartRowSet(rowNum, seriesTotals, accountSeries, data, (*it_outergroup).m_total, m_rowTypeList[i]);

            //only show the column type in the header if there is more than one type
            if(m_rowTypeList.size() > 1) {
              _view.params()->setLegendText( rowNum-1, m_columnTypeHeaderList[i] + " - " + it_outergroup.key() );
            } else {
              _view.params()->setLegendText( rowNum-1, it_outergroup.key() );
            }
          }
        }
        ++it_outergroup;
      }

      //if selected, show totals too
      if (m_config_f.isShowingRowTotals())
      {
        //iterate row types
        for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
          //skip the budget difference rowset
          if(m_rowTypeList[i] != eBudgetDiff ) {
            rowNum = drawChartRowSet(rowNum, seriesTotals, accountSeries, data, m_grid.m_total, m_rowTypeList[i]);

            //only show the column type in the header if there is more than one type
            if(m_rowTypeList.size() > 1) {
              _view.params()->setLegendText( rowNum-1, m_columnTypeHeaderList[i] + " - " + i18n("Total") );
            } else {
              _view.params()->setLegendText( rowNum-1, i18n("Total") );
            }
          }
        }
      }
    }
    break;

    case MyMoneyReport::eDetailTotal:
    {
      unsigned rowNum = 0;

      //iterate row types
      for(unsigned i = 0; i < m_rowTypeList.size(); ++i) {
        //skip the budget difference rowset
        if(m_rowTypeList[i] != eBudgetDiff ) {
          rowNum = drawChartRowSet(rowNum, seriesTotals, accountSeries, data, m_grid.m_total, m_rowTypeList[i]);

          //only show the column type in the header if there is more than one type
          if(m_rowTypeList.size() > 1) {
            _view.params()->setLegendText( rowNum-1, m_columnTypeHeaderList[i] + " - " + i18n("Total") );
          } else {
            _view.params()->setLegendText( rowNum-1, i18n("Total") );
          }
        }
      }
    }
    break;
  }

  _view.setNewData(data);

  // make sure to show only the required number of fractional digits on the labels of the graph
  _view.params()->setDataValuesCalc(0, MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction()));
  _view.refreshLabels();

#if 0
  // I have not been able to get this to work (ace)

  //
  // Set line to dashed for the future
  //

  if ( accountSeries )
  {
    // the first column of report which represents a date in the future, or one past the
    // last column if all columns are in the present day. Only relevant when accountSeries==true
    unsigned futurecolumn = columnValue(TQDate::currentDate()) - columnValue(m_beginDate) + 1;

    // kdDebug(2) << "futurecolumn: " << futurecolumn << endl;
    // kdDebug(2) << "m_numColumns: " << m_numColumns << endl;

    // Properties for line charts whose values are in the future.
    KDChartPropertySet propSetFutureValue("future value", KDChartParams::KDCHART_PROPSET_NORMAL_DATA);
    propSetFutureValue.setLineStyle(KDChartPropertySet::OwnID, TQt::DotLine);
    const int idPropFutureValue = _view.params()->registerProperties(propSetFutureValue);

    for(int col = futurecolumn; col < m_numColumns; ++col) {
      _view.setProperty(0, col, idPropFutureValue);
    }

  }
#endif
}
#else
void PivotTable::drawChart( KReportChartView& ) const { }
#endif

unsigned PivotTable::drawChartRowSet(unsigned rowNum, const bool seriesTotals, const bool accountSeries, KDChartTableData& data, const PivotGridRowSet& rowSet, const ERowType rowType ) const
{
  //only add a row if one has been added before
  // TODO: This is inefficient. Really we should total up how many rows
  // there will be and allocate it all at once.
  if(rowNum > 0) {
    if ( accountSeries )
      data.expand( rowNum+1, m_numColumns-1 );
    else
      data.expand( m_numColumns-1, rowNum+1 );
  }

  // Columns
  if ( seriesTotals )
  {
    if ( accountSeries )
      data.setCell( rowNum, 0, rowSet[rowType].m_total.toDouble() );
    else
      data.setCell( 0, rowNum, rowSet[rowType].m_total.toDouble() );
  }
  else
  {
    unsigned column = 1;
    while ( column < m_numColumns )
    {
      if ( accountSeries )
        data.setCell( rowNum, column-1, rowSet[rowType][column].toDouble() );
      else
        data.setCell( column-1, rowNum, rowSet[rowType][column].toDouble() );
      ++column;
    }
  }

  return ++rowNum;
}

TQString PivotTable::coloredAmount(const MyMoneyMoney& amount, const TQString& currencySymbol, int prec) const
{
  TQString result;
  if( amount.isNegative() )
    result += TQString("<font color=\"rgb(%1,%2,%3)\">")
        .arg(KMyMoneyGlobalSettings::listNegativeValueColor().red())
        .arg(KMyMoneyGlobalSettings::listNegativeValueColor().green())
        .arg(KMyMoneyGlobalSettings::listNegativeValueColor().blue());
  result += amount.formatMoney(currencySymbol, prec);
  if( amount.isNegative() )
    result += TQString("</font>");
  return result;
}

void PivotTable::calculateBudgetDiff(void)
{
  PivotGrid::iterator it_outergroup = m_grid.begin();
  while ( it_outergroup != m_grid.end() )
  {
    PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
    while ( it_innergroup != (*it_outergroup).end() )
    {
      PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
      while ( it_row != (*it_innergroup).end() )
      {
        unsigned column = 1;
        switch( it_row.key().accountGroup() )
        {
          case MyMoneyAccount::Income:
          case MyMoneyAccount::Asset:
            while ( column < m_numColumns ) {
              it_row.data()[eBudgetDiff][column] = it_row.data()[eActual][column] - it_row.data()[eBudget][column];
              ++column;
            }
            break;
          case MyMoneyAccount::Expense:
          case MyMoneyAccount::Liability:
            while ( column < m_numColumns ) {
              it_row.data()[eBudgetDiff][column] = it_row.data()[eBudget][column] - it_row.data()[eActual][column];
              ++column;
            }
            break;
          default:
            break;
        }
        ++it_row;
      }
      ++it_innergroup;
    }
    ++it_outergroup;
  }

}

void PivotTable::calculateForecast(void)
{
  //setup forecast
  MyMoneyForecast forecast;

  //setup forecast settings

  //since this is a net worth forecast we want to include all account even those that are not in use
  forecast.setIncludeUnusedAccounts(true);

  //setup forecast dates
  if(m_endDate > TQDate::currentDate()) {
    forecast.setForecastEndDate(m_endDate);
    forecast.setForecastStartDate(TQDate::currentDate());
    forecast.setForecastDays(TQDate::currentDate().daysTo(m_endDate));
  } else {
    forecast.setForecastStartDate(m_beginDate);
    forecast.setForecastEndDate(m_endDate);
    forecast.setForecastDays(m_beginDate.daysTo(m_endDate) + 1);
  }

  //adjust history dates if beginning date is before today
  if(m_beginDate < TQDate::currentDate()) {
    forecast.setHistoryEndDate(m_beginDate.addDays(-1));
    forecast.setHistoryStartDate(forecast.historyEndDate().addDays(-forecast.accountsCycle()*forecast.forecastCycles()));
  }

  //run forecast
  forecast.doForecast();

  //go through the data and add forecast
  PivotGrid::iterator it_outergroup = m_grid.begin();
  while ( it_outergroup != m_grid.end() )
  {
    PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
    while ( it_innergroup != (*it_outergroup).end() )
    {
      PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
      while ( it_row != (*it_innergroup).end() )
      {
        unsigned column = 1;
        TQDate forecastDate = m_beginDate;
        //check whether columns are days or months
        if(m_config_f.isColumnsAreDays())
        {
          while(column < m_numColumns) {
            it_row.data()[eForecast][column] = forecast.forecastBalance(it_row.key(), forecastDate);

            forecastDate = forecastDate.addDays(1);
            ++column;
          }
        } else {
          //if columns are months
          while(column < m_numColumns) {
            //set forecastDate to last day of each month
            //TODO we really need a date manipulation util
            forecastDate = TQDate(forecastDate.year(), forecastDate.month(), forecastDate.daysInMonth());
            //check that forecastDate is not over ending date
            if(forecastDate > m_endDate)
              forecastDate = m_endDate;

            //get forecast balance and set the corresponding column
            it_row.data()[eForecast][column] = forecast.forecastBalance(it_row.key(), forecastDate);

            forecastDate = forecastDate.addDays(1);
            ++column;
          }
        }
        ++it_row;
      }
      ++it_innergroup;
    }
    ++it_outergroup;
  }
}

void PivotTable::loadRowTypeList()
{
  if( (m_config_f.isIncludingBudgetActuals()) ||
       ( !m_config_f.hasBudget()
       && !m_config_f.isIncludingForecast()
       && !m_config_f.isIncludingMovingAverage()
       && !m_config_f.isIncludingPrice()
       && !m_config_f.isIncludingAveragePrice())
     ) {
    m_rowTypeList.append(eActual);
    m_columnTypeHeaderList.append(i18n("Actual"));
  }

  if (m_config_f.hasBudget()) {
    m_rowTypeList.append(eBudget);
    m_columnTypeHeaderList.append(i18n("Budget"));
  }

  if(m_config_f.isIncludingBudgetActuals()) {
    m_rowTypeList.append(eBudgetDiff);
    m_columnTypeHeaderList.append(i18n("Difference"));
  }

  if(m_config_f.isIncludingForecast()) {
    m_rowTypeList.append(eForecast);
    m_columnTypeHeaderList.append(i18n("Forecast"));
  }

  if(m_config_f.isIncludingMovingAverage()) {
    m_rowTypeList.append(eAverage);
    m_columnTypeHeaderList.append(i18n("Moving Average"));
  }

  if(m_config_f.isIncludingAveragePrice()) {
    m_rowTypeList.append(eAverage);
    m_columnTypeHeaderList.append(i18n("Moving Average Price"));
  }

  if(m_config_f.isIncludingPrice()) {
    m_rowTypeList.append(ePrice);
    m_columnTypeHeaderList.append(i18n("Price"));
  }
}


void PivotTable::calculateMovingAverage (void)
{
  int delta = m_config_f.movingAverageDays()/2;

  //go through the data and add the moving average
  PivotGrid::iterator it_outergroup = m_grid.begin();
  while ( it_outergroup != m_grid.end() )
  {
    PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
    while ( it_innergroup != (*it_outergroup).end() )
    {
      PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
      while ( it_row != (*it_innergroup).end() )
      {
        unsigned column = 1;

        //check whether columns are days or months
        if(m_config_f.columnType() == MyMoneyReport::eDays) {
          while(column < m_numColumns) {
            MyMoneyMoney totalPrice = MyMoneyMoney( 0, 1 );

            TQDate averageStart = columnDate(column).addDays(-delta);
            TQDate averageEnd = columnDate(column).addDays(delta);
            for(TQDate averageDate = averageStart; averageDate <= averageEnd; averageDate = averageDate.addDays(1)) {
              if(m_config_f.isConvertCurrency()) {
                totalPrice += it_row.key().deepCurrencyPrice(averageDate) * it_row.key().baseCurrencyPrice(averageDate);
              } else {
                totalPrice += it_row.key().deepCurrencyPrice(averageDate);
              }
              totalPrice = totalPrice.convert(10000);
            }

            //calculate the average price
            MyMoneyMoney averagePrice = totalPrice / MyMoneyMoney ((averageStart.daysTo(averageEnd) + 1), 1);

            //get the actual value, multiply by the average price and save that value
            MyMoneyMoney averageValue = it_row.data()[eActual][column] * averagePrice;
            it_row.data()[eAverage][column] = averageValue.convert(10000);

            ++column;
          }
        } else {
          //if columns are months
          while(column < m_numColumns) {
            TQDate averageStart = columnDate(column);

            //set the right start date depending on the column type
            switch(m_config_f.columnType()) {
              case MyMoneyReport::eYears:
              {
                averageStart = TQDate(columnDate(column).year(), 1, 1);
                break;
              }
              case MyMoneyReport::eBiMonths:
              {
                averageStart = TQDate(columnDate(column).year(), columnDate(column).month(), 1).addMonths(-1);
                break;
              }
              case MyMoneyReport::eQuarters:
              {
                averageStart = TQDate(columnDate(column).year(), columnDate(column).month(), 1).addMonths(-1);
                break;
              }
              case MyMoneyReport::eMonths:
              {
                averageStart = TQDate(columnDate(column).year(), columnDate(column).month(), 1);
                break;
              }
              case MyMoneyReport::eWeeks:
              {
                averageStart = columnDate(column).addDays(-columnDate(column).dayOfWeek() + 1);
                break;
              }
              default:
                break;
            }

            //gather the actual data and calculate the average
            MyMoneyMoney totalPrice = MyMoneyMoney(0, 1);
            TQDate averageEnd = columnDate(column);
            for(TQDate averageDate = averageStart; averageDate <= averageEnd; averageDate = averageDate.addDays(1)) {
              if(m_config_f.isConvertCurrency()) {
                totalPrice += it_row.key().deepCurrencyPrice(averageDate) * it_row.key().baseCurrencyPrice(averageDate);
              } else {
                totalPrice += it_row.key().deepCurrencyPrice(averageDate);
              }
              totalPrice = totalPrice.convert(10000);
            }

            MyMoneyMoney averagePrice = totalPrice / MyMoneyMoney ((averageStart.daysTo(averageEnd) + 1), 1);
            MyMoneyMoney averageValue = it_row.data()[eActual][column] * averagePrice;

            //fill in the average
            it_row.data()[eAverage][column] = averageValue.convert(10000);

            ++column;
          }
        }
        ++it_row;
      }
      ++it_innergroup;
    }
    ++it_outergroup;
  }
}

void PivotTable::fillBasePriceUnit(ERowType rowType)
{
  //go through the data and add forecast
  PivotGrid::iterator it_outergroup = m_grid.begin();
  while ( it_outergroup != m_grid.end() )
  {
    PivotOuterGroup::iterator it_innergroup = ( *it_outergroup ).begin();
    while ( it_innergroup != ( *it_outergroup ).end() )
    {
      PivotInnerGroup::iterator it_row = ( *it_innergroup ).begin();
      while ( it_row != ( *it_innergroup ).end() )
      {
        unsigned column = 1;
        while ( column < m_numColumns ) {
          //insert a unit of currency for each account
          it_row.data() [rowType][column] = MyMoneyMoney ( 1, 1 );
          ++column;
        }
        ++it_row;
      }
      ++it_innergroup;
    }
    ++it_outergroup;
  }
}

void PivotTable::includeInvestmentSubAccounts()
{
  // if we're not in expert mode, we need to make sure
  // that all stock accounts for the selected investment
  // account are also selected
  TQStringList accountList;
  if(m_config_f.accounts(accountList)) {
    if(!KMyMoneyGlobalSettings::expertMode()) {
      TQStringList::const_iterator it_a, it_b;
      for(it_a = accountList.begin(); it_a != accountList.end(); ++it_a) {
        MyMoneyAccount acc = MyMoneyFile::instance()->account(*it_a);
        if(acc.accountType() == MyMoneyAccount::Investment) {
          for(it_b = acc.accountList().begin(); it_b != acc.accountList().end(); ++it_b) {
            if(!accountList.contains(*it_b)) {
              m_config_f.addAccount(*it_b);
            }
          }
        }
      }
    }
  }
}

} // namespace
// vim:cin:si:ai:et:ts=2:sw=2:
