/***************************************************************************
                          reportstestcommon.cpp
                          -------------------
    copyright            : (C) 2002-2005 by Thomas Baumgart
    email                : ipwizard@users.sourceforge.net
                           Ace Jones <ace.j@hotpop.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

#include <tqvaluelist.h>
#include <tqvaluevector.h>
#include <tqdom.h>
#include <tqfile.h>

#include <kdebug.h>
#include <tdeglobal.h>
#include <tdeglobalsettings.h>
#include <tdelocale.h>
#include <tdestandarddirs.h>

#include "kreportsviewtest.h"

#define private public
#include "pivottable.h"
#include "querytable.h"
#undef private
using namespace reports;

#include "../mymoney/mymoneysecurity.h"
#include "../mymoney/mymoneyprice.h"
#include "../mymoney/storage/mymoneystoragedump.h"
#include "../mymoney/mymoneyreport.h"
#include "../mymoney/mymoneystatement.h"
#include "../mymoney/storage/mymoneystoragexml.h"
#include "reportstestcommon.h"

namespace test {

const MyMoneyMoney moCheckingOpen(0.0);
const MyMoneyMoney moCreditOpen(-0.0);
const MyMoneyMoney moConverterCheckingOpen(1418.0);
const MyMoneyMoney moConverterCreditOpen(-418.0);
const MyMoneyMoney moZero(0.0);
const MyMoneyMoney moSolo(234.12);
const MyMoneyMoney moParent1(88.01);
const MyMoneyMoney moParent2(133.22);
const MyMoneyMoney moParent(moParent1+moParent2);
const MyMoneyMoney moChild(14.00);
const MyMoneyMoney moThomas(5.11);
const MyMoneyMoney moNoPayee(8944.70);

TQString acAsset;
TQString acLiability;
TQString acExpense;
TQString acIncome;
TQString acChecking;
TQString acCredit;
TQString acSolo;
TQString acParent;
TQString acChild;
TQString acSecondChild;
TQString acGrandChild1;
TQString acGrandChild2;
TQString acForeign;
TQString acCanChecking;
TQString acJpyChecking;
TQString acCanCash;
TQString acJpyCash;
TQString inBank;
TQString eqStock1;
TQString eqStock2;
TQString acInvestment;
TQString acStock1;
TQString acStock2;
TQString acDividends;
TQString acInterest;
TQString acTax;
TQString acCash;

TransactionHelper::TransactionHelper( const TQDate& _date, const TQString& _action, MyMoneyMoney _value, const TQString& _accountid, const TQString& _categoryid, const TQString& _currencyid, const TQString& _payee )
{
  // _currencyid is the currency of the transaction, and of the _value
  // both the account and category can have their own currency (athough the category having
  // a foreign currency is not yet supported by the program, the reports will still allow it,
  // so it must be tested.)

    MyMoneyFile* file = MyMoneyFile::instance();
    bool haspayee = ! _payee.isEmpty();
    MyMoneyPayee payeeTest = file->payeeByName(_payee);

    MyMoneyFileTransaction ft;
    setPostDate(_date);

    TQString currencyid = _currencyid;
    if ( currencyid.isEmpty() )
      currencyid=MyMoneyFile::instance()->baseCurrency().id();
    setCommodity(currencyid);

    MyMoneyMoney price;
    MyMoneySplit splitLeft;
    if ( haspayee )
      splitLeft.setPayeeId(payeeTest.id());
    splitLeft.setAction(_action);
    splitLeft.setValue(-_value);
    price = MyMoneyFile::instance()->price(currencyid, file->account(_accountid).currencyId(),_date).rate(file->account(_accountid).currencyId());
    splitLeft.setShares(-_value * price);
    splitLeft.setAccountId(_accountid);
    addSplit(splitLeft);

    MyMoneySplit splitRight;
    if ( haspayee )
      splitRight.setPayeeId(payeeTest.id());
    splitRight.setAction(_action);
    splitRight.setValue(_value);
    price = MyMoneyFile::instance()->price(currencyid, file->account(_categoryid).currencyId(),_date).rate(file->account(_categoryid).currencyId());
    splitRight.setShares(_value * price );
    splitRight.setAccountId(_categoryid);
    addSplit(splitRight);

    MyMoneyFile::instance()->addTransaction(*this);
    ft.commit();
}

TransactionHelper::~TransactionHelper()
{
  MyMoneyFileTransaction ft;
  MyMoneyFile::instance()->removeTransaction(*this);
  ft.commit();
}

void TransactionHelper::update(void)
{
  MyMoneyFileTransaction ft;
  MyMoneyFile::instance()->modifyTransaction(*this);
  ft.commit();
}

InvTransactionHelper::InvTransactionHelper( const TQDate& _date, const TQString& _action, MyMoneyMoney _shares, MyMoneyMoney _price, const TQString& _stockaccountid, const TQString& _transferid, const TQString& _categoryid )
{
  init(_date, _action, _shares, _price, _stockaccountid, _transferid, _categoryid);
}

void InvTransactionHelper::init( const TQDate& _date, const TQString& _action, MyMoneyMoney _shares, MyMoneyMoney _price, const TQString& _stockaccountid, const TQString& _transferid, const TQString& _categoryid )
{
  MyMoneyFile* file = MyMoneyFile::instance();
  MyMoneyAccount stockaccount = file->account(_stockaccountid);
  MyMoneyMoney value = _shares * _price;

  setPostDate(_date);

  setCommodity("USD");
  MyMoneySplit s1;
  s1.setValue(value);
  s1.setAccountId(_stockaccountid);

  if ( _action == MyMoneySplit::ActionReinvestDividend )
  {
    s1.setShares(_shares);
    s1.setAction(MyMoneySplit::ActionReinvestDividend);

    MyMoneySplit s2;
    s2.setAccountId(_categoryid);
    s2.setShares(-value);
    s2.setValue(-value);
    addSplit(s2);
  }
  else if ( _action == MyMoneySplit::ActionDividend || _action == MyMoneySplit::ActionYield )
  {
    s1.setAccountId(_categoryid);
    s1.setShares(-value);
    s1.setValue(-value);

    // Split 2 will be the zero-amount investment split that serves to
    // mark this transaction as a cash dividend and note which stock account
    // it belongs to.
    MyMoneySplit s2;
    s2.setValue(0);
    s2.setShares(0);
    s2.setAction(_action);
    s2.setAccountId(_stockaccountid);
    addSplit(s2);

    MyMoneySplit s3;
    s3.setAccountId(_transferid);
    s3.setShares(value);
    s3.setValue(value);
    addSplit(s3);
  }
  else if ( _action == MyMoneySplit::ActionBuyShares )
  {
    s1.setShares(_shares);
    s1.setAction(MyMoneySplit::ActionBuyShares);

    MyMoneySplit s3;
    s3.setAccountId(_transferid);
    s3.setShares(-value);
    s3.setValue(-value);
    addSplit(s3);
  }
  addSplit(s1);

  //kdDebug(2) << "created transaction, now adding..." << endl;

  MyMoneyFileTransaction ft;
  file->addTransaction(*this);

  //kdDebug(2) << "updating price..." << endl;

  // update the price, while we're here
  TQString stockid = stockaccount.currencyId();
  TQString basecurrencyid = file->baseCurrency().id();
  MyMoneyPrice price = file->price( stockid, basecurrencyid, _date, true );
  if ( !price.isValid() )
  {
    MyMoneyPrice newprice( stockid, basecurrencyid, _date, _price, "test" );
    file->addPrice(newprice);
  }
  ft.commit();
  //kdDebug(2) << "successfully added " << id() << endl;
}

TQString makeAccount( const TQString& _name, MyMoneyAccount::accountTypeE _type, MyMoneyMoney _balance, const TQDate& _open, const TQString& _parent, TQString _currency, bool _taxReport )
{
  MyMoneyAccount info;
  MyMoneyFileTransaction ft;

  info.setName(_name);
  info.setAccountType(_type);
  info.setOpeningDate(_open);
  if ( _currency != "" )
    info.setCurrencyId(_currency);
  else
    info.setCurrencyId(MyMoneyFile::instance()->baseCurrency().id());

  if(_taxReport)
    info.setValue("Tax", "Yes");

  MyMoneyAccount parent = MyMoneyFile::instance()->account(_parent);
  MyMoneyFile::instance()->addAccount( info, parent );
  // create the opening balance transaction if any
  if(!_balance.isZero()) {
    MyMoneySecurity sec = MyMoneyFile::instance()->currency(info.currencyId());
    MyMoneyFile::instance()->openingBalanceAccount(sec);
    MyMoneyFile::instance()->createOpeningBalanceTransaction(info, _balance);
  }
  ft.commit();

  return info.id();
}

void makePrice(const TQString& _currencyid, const TQDate& _date, const MyMoneyMoney& _price )
{
  MyMoneyFileTransaction ft;
  MyMoneyFile* file = MyMoneyFile::instance();
  MyMoneySecurity curr = file->currency(_currencyid);
  MyMoneyPrice price(_currencyid, file->baseCurrency().id(), _date, _price, "test");
  file->addPrice(price);
  ft.commit();
}

TQString makeEquity(const TQString& _name, const TQString& _symbol )
{
  MyMoneySecurity equity;
  MyMoneyFileTransaction ft;

  equity.setName( _name );
  equity.setTradingSymbol( _symbol );
  equity.setSmallestAccountFraction( 1000 );
  equity.setSecurityType( MyMoneySecurity::SECURITY_NONE /*MyMoneyEquity::ETYPE_STOCK*/ );
  MyMoneyFile::instance()->addSecurity( equity );
  ft.commit();

  return equity.id();
}

void makeEquityPrice(const TQString& _id, const TQDate& _date, const MyMoneyMoney& _price )
{
  MyMoneyFile* file = MyMoneyFile::instance();
  MyMoneyFileTransaction ft;
  TQString basecurrencyid = file->baseCurrency().id();
  MyMoneyPrice price = file->price( _id, basecurrencyid, _date, true );
  if ( !price.isValid() )
  {
    MyMoneyPrice newprice( _id, basecurrencyid, _date, _price, "test" );
    file->addPrice(newprice);
  }
  ft.commit();
}

void writeRCFtoXMLDoc( const MyMoneyReport& filter, TQDomDocument* doc )
{
 TQDomProcessingInstruction instruct = doc->createProcessingInstruction(TQString("xml"), TQString("version=\"1.0\" encoding=\"utf-8\""));
  doc->appendChild(instruct);

  TQDomElement root = doc->createElement("KMYMONEY-FILE");
  doc->appendChild(root);

  TQDomElement reports = doc->createElement("REPORTS");
  root.appendChild(reports);

  TQDomElement report = doc->createElement("REPORT");
  filter.write(report,doc);
  reports.appendChild(report);

}

void writeTabletoHTML( const PivotTable& table, const TQString& _filename )
{
  static unsigned filenumber = 1;
  TQString filename = _filename;
  if ( filename.isEmpty() )
  {
    filename = TQString("report-%1%2.html").arg((filenumber<10)?"0":"").arg(filenumber);
    ++filenumber;
  }

  TQFile g( filename );
  g.open( IO_WriteOnly );
  TQTextStream(&g) << table.renderHTML();
  g.close();

}

void writeTabletoHTML( const QueryTable& table, const TQString& _filename )
{
  static unsigned filenumber = 1;
  TQString filename = _filename;
  if ( filename.isEmpty() )
  {
    filename = TQString("report-%1%2.html").arg((filenumber<10)?"0":"").arg(filenumber);
    ++filenumber;
  }

  TQFile g( filename );
  g.open( IO_WriteOnly );
  TQTextStream(&g) << table.renderHTML();
  g.close();
}

void writeTabletoCSV( const PivotTable& table, const TQString& _filename )
{
  static unsigned filenumber = 1;
  TQString filename = _filename;
  if ( filename.isEmpty() )
  {
    filename = TQString("report-%1%2.csv").arg((filenumber<10)?"0":"").arg(filenumber);
    ++filenumber;
  }

  TQFile g( filename );
  g.open( IO_WriteOnly );
  TQTextStream(&g) << table.renderCSV();
  g.close();

}

void writeTabletoCSV( const QueryTable& table, const TQString& _filename )
{
  static unsigned filenumber = 1;
  TQString filename = _filename;
  if ( filename.isEmpty() )
  {
    filename = TQString("qreport-%1%2.csv").arg((filenumber<10)?"0":"").arg(filenumber);
    ++filenumber;
  }

  TQFile g( filename );
  g.open( IO_WriteOnly );
  TQTextStream(&g) << table.renderCSV();
  g.close();

}

void writeRCFtoXML( const MyMoneyReport& filter, const TQString& _filename )
{
  static unsigned filenum = 1;
  TQString filename = _filename;
  if ( filename.isEmpty() ) {
    filename = TQString("report-%1%2.xml").arg(TQString::number(filenum).rightJustify(2, '0'));
    ++filenum;
  }

  TQDomDocument* doc = new TQDomDocument("KMYMONEY-FILE");
  TQ_CHECK_PTR(doc);

  writeRCFtoXMLDoc(filter,doc);

  TQFile g( filename );
  g.open( IO_WriteOnly );

  TQTextStream stream(&g);
  stream.setEncoding(TQTextStream::UnicodeUTF8);
  stream << doc->toString();
  g.close();

  delete doc;
}

bool readRCFfromXMLDoc( TQValueList<MyMoneyReport>& list, TQDomDocument* doc )
{
  bool result = false;

    TQDomElement rootElement = doc->documentElement();
    if(!rootElement.isNull())
    {
      TQDomNode child = rootElement.firstChild();
      while(!child.isNull() && child.isElement())
      {
        TQDomElement childElement = child.toElement();
        if("REPORTS" == childElement.tagName())
        {
    result = true;
          TQDomNode subchild = child.firstChild();
          while(!subchild.isNull() && subchild.isElement())
          {
            MyMoneyReport filter;
      if ( filter.read(subchild.toElement()))
      {
              list += filter;
      }
      subchild = subchild.nextSibling();
         }
       }
       child = child.nextSibling();
      }
    }
  return result;
}

bool readRCFfromXML( TQValueList<MyMoneyReport>& list, const TQString& filename )
{
  int result = false;
  TQFile f( filename );
  f.open( IO_ReadOnly );
  TQDomDocument* doc = new TQDomDocument;
  if(doc->setContent(&f, false))
  {
    result = readRCFfromXMLDoc(list,doc);
  }
  delete doc;

  return result;

}

void XMLandback( MyMoneyReport& filter )
{
  // this function writes the filter to XML, and then reads
  // it back from XML overwriting the original filter;
  // in all cases, the result should be the same if the read
  // & write methods are working correctly.

  TQDomDocument* doc = new TQDomDocument("KMYMONEY-FILE");
  TQ_CHECK_PTR(doc);

  writeRCFtoXMLDoc(filter,doc);
  TQValueList<MyMoneyReport> list;
  if ( readRCFfromXMLDoc(list,doc) && list.count() > 0 )
    filter = list[0];
  else
    throw new MYMONEYEXCEPTION("Failed to load report from XML");

  delete doc;

}

MyMoneyMoney searchHTML(const TQString& _html, const TQString& _search)
{
  TQRegExp re(TQString("%1[<>/td]*([\\-.0-9,]*)").arg(_search));
  re.search(_html);
  TQString found = re.cap(1);
  found.remove(',');

  return MyMoneyMoney(found.toDouble());
}

} // end namespace test
