/***************************************************************************
                          objectinfotable.cpp
                         -------------------
    begin                : Sat 28 jun 2008
    copyright            : (C) 2004-2005 by Ace Jones
                               2008 by Alvaro Soliverez
    email                :  acejones@users.sourceforge.net
                            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.                                   *
 *                                                                         *
 ***************************************************************************/

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

// ----------------------------------------------------------------------------
// TQt Includes
#include <tqvaluelist.h>
#include <tqfile.h>
#include <tqtextstream.h>

// ----------------------------------------------------------------------------
// TDE Includes
// This is just needed for i18n().  Once I figure out how to handle i18n
// without using this macro directly, I'll be freed of KDE dependency.

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

// ----------------------------------------------------------------------------
// Project Includes
#include "../mymoney/mymoneyfile.h"
#include "../mymoney/mymoneyreport.h"
#include "../mymoney/mymoneyexception.h"
#include "../kmymoneyutils.h"
#include "reportaccount.h"
#include "reportdebug.h"
#include "objectinfotable.h"

namespace reports {

// ****************************************************************************
//
// ObjectInfoTable implementation
//
// ****************************************************************************

/**
  * TODO
  *
  * - Collapse 2- & 3- groups when they are identical
  * - Way more test cases (especially splits & transfers)
  * - Option to collapse splits
  * - Option to exclude transfers
  *
  */

ObjectInfoTable::ObjectInfoTable(const MyMoneyReport& _report): ListTable(_report)
{
  // seperated into its own method to allow debugging (setting breakpoints
  // directly in ctors somehow does not work for me (ipwizard))
  // TODO: remove the init() method and move the code back to the ctor
  init();
}

void ObjectInfoTable::init ( void )
{
  switch ( m_config.rowType() )
  {
    case MyMoneyReport::eSchedule:
      constructScheduleTable();
      m_columns = "nextduedate,name";
      break;
    case MyMoneyReport::eAccountInfo:
      constructAccountTable();
      m_columns = "institution,type,name";
      break;
    case MyMoneyReport::eAccountLoanInfo:
      constructAccountLoanTable();
      m_columns = "institution,type,name";
      break;
    default:
      break;
  }

  // Sort the data to match the report definition
  m_subtotal="value";

  switch ( m_config.rowType() )
  {
    case MyMoneyReport::eSchedule:
      m_group = "type";
      m_subtotal="value";
      break;
    case MyMoneyReport::eAccountInfo:
    case MyMoneyReport::eAccountLoanInfo:
      m_group = "topcategory,institution";
      m_subtotal="currentbalance";
      break;
    default:
      throw new MYMONEYEXCEPTION ( "ObjectInfoTable::ObjectInfoTable(): unhandled row type" );
  }

  TQString sort = m_group + "," + m_columns + ",id,rank";

  switch ( m_config.rowType() ) {
    case MyMoneyReport::eSchedule:
      if ( m_config.detailLevel() == MyMoneyReport::eDetailAll ) {
          m_columns="name,payee,paymenttype,occurence,nextduedate,category";
      } else {
          m_columns="name,payee,paymenttype,occurence,nextduedate";
      }
      break;
    case MyMoneyReport::eAccountInfo:
      m_columns="type,name,number,description,openingdate,currencyname,balancewarning,maxbalancelimit,creditwarning,maxcreditlimit,tax,favorite";
      break;
    case MyMoneyReport::eAccountLoanInfo:
      m_columns="type,name,number,description,openingdate,currencyname,payee,loanamount,interestrate,nextinterestchange,periodicpayment,finalpayment,favorite";
      break;
    default:
      m_columns = "";
  }

  TableRow::setSortCriteria ( sort );
  qHeapSort ( m_rows );
}

void ObjectInfoTable::constructScheduleTable ( void )
{
  MyMoneyFile* file = MyMoneyFile::instance();

  TQValueList<MyMoneySchedule> schedules;

  schedules = file->scheduleList ( "", MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY, m_config.fromDate(), m_config.toDate() );

  TQValueList<MyMoneySchedule>::const_iterator it_schedule = schedules.begin();
  while ( it_schedule != schedules.end() )
  {
    MyMoneySchedule schedule = *it_schedule;

    ReportAccount account = schedule.account();

    if ( m_config.includes ( account ) )  {
      //get fraction for account
      int fraction = account.fraction();

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

      TableRow scheduleRow;

      //convert to base currency if needed
      MyMoneyMoney xr = MyMoneyMoney(1,1);
      if (m_config.isConvertCurrency() && account.isForeignCurrency()) {
        xr = account.baseCurrencyPrice(TQDate::currentDate()).reduce();
      }

      // help for sort and render functions
      scheduleRow["rank"] = "0";

      //schedule data
      scheduleRow["id"] = schedule.id();
      scheduleRow["name"] = schedule.name();
      scheduleRow["nextduedate"] = schedule.nextDueDate().toString ( TQt::ISODate );
      scheduleRow["type"] = KMyMoneyUtils::scheduleTypeToString ( schedule.type() );
      scheduleRow["occurence"] = i18n(schedule.occurenceToString().utf8());
      scheduleRow["paymenttype"] = KMyMoneyUtils::paymentMethodToString ( schedule.paymentType() );

      //scheduleRow["category"] = account.name();

      //to get the payee we must look into the splits of the transaction
      MyMoneyTransaction transaction = schedule.transaction();
      MyMoneySplit split = transaction.splitByAccount ( account.id(), true );
      scheduleRow["value"] = (split.value() * xr).toString();
      MyMoneyPayee payee = file->payee ( split.payeeId() );
      scheduleRow["payee"] = payee.name();
      m_rows += scheduleRow;

      //the text matches the main split
      bool transaction_text = m_config.match(&split);

      if ( m_config.detailLevel() == MyMoneyReport::eDetailAll )
      {
        //get the information for all splits
        TQValueList<MyMoneySplit> splits = transaction.splits();
        TQValueList<MyMoneySplit>::const_iterator split_it = splits.begin();
        for ( ;split_it != splits.end(); split_it++ )
        {
          TableRow splitRow;
          ReportAccount splitAcc = ( *split_it ).accountId();

          splitRow["rank"] = "1";
          splitRow["id"] = schedule.id();
          splitRow["name"] = schedule.name();
          splitRow["type"] = KMyMoneyUtils::scheduleTypeToString ( schedule.type() );
          splitRow["nextduedate"] = schedule.nextDueDate().toString ( TQt::ISODate );

          if ( ( *split_it ).value() == MyMoneyMoney::autoCalc ) {
            splitRow["split"] = MyMoneyMoney::autoCalc.toString();
          } else if ( ! splitAcc.isIncomeExpense() ) {
            splitRow["split"] = ( *split_it ).value().toString();
          } else {
            splitRow["split"] = ( - ( *split_it ).value() ).toString();
          }

          //if it is an assett account, mark it as a transfer
          if ( ! splitAcc.isIncomeExpense() ) {
            splitRow["category"] = ( ( * split_it ).value().isNegative() )
                                   ? i18n ( "Transfer from %1" ).arg ( splitAcc.fullName() )
                                   : i18n ( "Transfer to %1" ).arg ( splitAcc.fullName() );
          } else {
            splitRow ["category"] = splitAcc.fullName();
          }

          //add the split only if it matches the text or it matches the main split
          if(m_config.match( &(*split_it) )
             || transaction_text )
            m_rows += splitRow;
        }
      }
    }
    ++it_schedule;
  }
}

void ObjectInfoTable::constructAccountTable ( void )
{
  MyMoneyFile* file = MyMoneyFile::instance();

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

  TQValueList<MyMoneyAccount> accounts;
  file->accountList(accounts);
  TQValueList<MyMoneyAccount>::const_iterator it_account = accounts.begin();
  while ( it_account != accounts.end() )
  {
    TableRow accountRow;
    ReportAccount account = *it_account;

    if(m_config.includes(account)
       && account.accountType() != MyMoneyAccount::Stock
       && !account.isClosed())
    {
      MyMoneyMoney value;
      accountRow["rank"] = "0";
      accountRow["topcategory"] = KMyMoneyUtils::accountTypeToString(account.accountGroup());
      accountRow["institution"] = (file->institution(account.institutionId())).name();
      accountRow["type"] = KMyMoneyUtils::accountTypeToString(account.accountType());
      accountRow["name"] = account.name();
      accountRow["number"] = account.number();
      accountRow["description"] = account.description();
      accountRow["openingdate"] = account.openingDate().toString( TQt::ISODate );
      //accountRow["currency"] = (file->currency(account.currencyId())).tradingSymbol();
      accountRow["currencyname"] = (file->currency(account.currencyId())).name();
      accountRow["balancewarning"] = account.value("minBalanceEarly");
      accountRow["maxbalancelimit"] = account.value("minBalanceAbsolute");
      accountRow["creditwarning"] = account.value("maxCreditEarly");
      accountRow["maxcreditlimit"] = account.value("maxCreditAbsolute");
      accountRow["tax"] = account.value("Tax");
      accountRow["favorite"] = account.value("PreferredAccount");

      //investment accounts show the balances of all its subaccounts
      if(account.accountType() == MyMoneyAccount::Investment) {
        value = investmentBalance(account);
      } else {
        value = file->balance(account.id());
      }

      //convert to base currency if needed
      if (m_config.isConvertCurrency() && account.isForeignCurrency()) {
        MyMoneyMoney xr = account.baseCurrencyPrice(TQDate::currentDate()).reduce();
        value = value * xr;
      }
      accountRow["currentbalance"] = value.toString();

      m_rows += accountRow;
    }
    ++it_account;
  }
}

void ObjectInfoTable::constructAccountLoanTable ( void )
{
  MyMoneyFile* file = MyMoneyFile::instance();

  TQValueList<MyMoneyAccount> accounts;
  file->accountList(accounts);
  TQValueList<MyMoneyAccount>::const_iterator it_account = accounts.begin();
  while ( it_account != accounts.end() )
  {
    TableRow accountRow;
    ReportAccount account = *it_account;
    MyMoneyAccountLoan loan = *it_account;

    if(m_config.includes(account) &&
       ( account.accountType() == MyMoneyAccount::Loan
       || account.accountType() == MyMoneyAccount::AssetLoan )
       && !account.isClosed())
    {
      //convert to base currency if needed
      MyMoneyMoney xr = MyMoneyMoney(1,1);
      if (m_config.isConvertCurrency() && account.isForeignCurrency()) {
         xr = account.baseCurrencyPrice(TQDate::currentDate()).reduce();
      }

      accountRow["rank"] = "0";
      accountRow["topcategory"] = KMyMoneyUtils::accountTypeToString(account.accountGroup());
      accountRow["institution"] = (file->institution(account.institutionId())).name();
      accountRow["type"] = KMyMoneyUtils::accountTypeToString(account.accountType());
      accountRow["name"] = account.name();
      accountRow["number"] = account.number();
      accountRow["description"] = account.description();
      accountRow["openingdate"] = account.openingDate().toString( TQt::ISODate );
      //accountRow["currency"] = (file->currency(account.currencyId())).tradingSymbol();
      accountRow["currencyname"] = (file->currency(account.currencyId())).name();
      accountRow["payee"] = file->payee(loan.payee()).name();
      accountRow["loanamount"] = (loan.loanAmount() * xr).toString();
      accountRow["interestrate"] = (loan.interestRate(TQDate::currentDate())/MyMoneyMoney(100,1)*xr).toString();
      accountRow["nextinterestchange"] = loan.nextInterestChange().toString( TQt::ISODate );
      accountRow["periodicpayment"] = (loan.periodicPayment() * xr).toString();
      accountRow["finalpayment"] = (loan.finalPayment() * xr).toString();
      accountRow["favorite"] = account.value("PreferredAccount");

      MyMoneyMoney value = file->balance(account.id());
      value = value * xr;
      accountRow["currentbalance"] = value.toString();
      m_rows += accountRow;
    }
    ++it_account;
  }
}

MyMoneyMoney ObjectInfoTable::investmentBalance(const MyMoneyAccount& acc)
{
  MyMoneyFile* file = MyMoneyFile::instance();
  MyMoneyMoney value;

  value = file->balance(acc.id());
  TQValueList<TQString>::const_iterator it_a;
  for(it_a = acc.accountList().begin(); it_a != acc.accountList().end(); ++it_a) {
    MyMoneyAccount stock = file->account(*it_a);
    try {
      MyMoneyMoney val;
      MyMoneyMoney balance = file->balance(stock.id());
      MyMoneySecurity security = file->security(stock.currencyId());
      MyMoneyPrice price = file->price(stock.currencyId(), security.tradingCurrency());
      val = balance * price.rate(security.tradingCurrency());
      // adjust value of security to the currency of the account
      MyMoneySecurity accountCurrency = file->currency(acc.currencyId());
      val = val * file->price(security.tradingCurrency(), accountCurrency.id()).rate(accountCurrency.id());
      val = val.convert(acc.fraction());
      value += val;
    } catch(MyMoneyException* e) {
      tqWarning(TQString("cannot convert stock balance of %1 to base currency: %2").arg(stock.name(), e->what()));
      delete e;
    }
  }
  return value;
}

}
