/***************************************************************************

                          mymoneytransaction.cpp
                             -------------------
    copyright            : (C) 2000 by Michael Edwardes,
                               2002 by Thomas Baumgart
    email                : mte@users.sourceforge.net,
                           ipwizard@users.sourceforge.net
 ***************************************************************************/

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

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

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

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

#include "mymoneytransaction.h"

MyMoneyTransaction::MyMoneyTransaction() :
  MyMoneyObject()
{
  m_nextSplitID = 1;
  m_entryDate = TQDate();
  m_postDate = TQDate();
}

MyMoneyTransaction::MyMoneyTransaction(const TQString id, const MyMoneyTransaction& transaction) :
  MyMoneyObject(id)
{
  *this = transaction;
  m_id = id;
  if(m_entryDate == TQDate())
    m_entryDate = TQDate::currentDate();

  TQValueList<MyMoneySplit>::Iterator it;
  for(it = m_splits.begin(); it != m_splits.end(); ++it) {
    (*it).setTransactionId(id);
  }
}

MyMoneyTransaction::MyMoneyTransaction(const TQDomElement& node, const bool forceId) :
  MyMoneyObject(node, forceId)
{
  if("TRANSACTION" != node.tagName())
    throw new MYMONEYEXCEPTION("Node was not TRANSACTION");

  m_nextSplitID = 1;

  m_postDate = stringToDate(node.attribute("postdate"));
  m_entryDate = stringToDate(node.attribute("entrydate"));
  m_bankID = TQStringEmpty(node.attribute("bankid"));
  m_memo = TQStringEmpty(node.attribute("memo"));
  m_commodity = TQStringEmpty(node.attribute("commodity"));

  TQDomNode child = node.firstChild();
  while ( !child.isNull() && child.isElement() )
  {
    TQDomElement c = child.toElement();
    if(c.tagName() == "SPLITS") {
      //  Process any split information found inside the transaction entry.
      TQDomNodeList nodeList = c.elementsByTagName("SPLIT");
      for(unsigned int i = 0; i < nodeList.count(); ++i) {
        MyMoneySplit s(nodeList.item(i).toElement());
        if(!m_bankID.isEmpty())
          s.setBankID(m_bankID);
        if(!s.accountId().isEmpty())
          addSplit(s);
        else
          tqDebug("Dropped split because it did not have an account id");
      }

    } else if(c.tagName() == "KEYVALUEPAIRS") {
      MyMoneyKeyValueContainer kvp(c);
      setPairs(kvp.pairs());
    }
    child = child.nextSibling();
  }
  m_bankID = TQString();
}

MyMoneyTransaction::~MyMoneyTransaction()
{
}

bool MyMoneyTransaction::operator == (const MyMoneyTransaction& right) const
{
  return (MyMoneyObject::operator==(right) &&
      MyMoneyKeyValueContainer::operator==(right) &&
      (m_commodity == right.m_commodity) &&
      ((m_memo.length() == 0 && right.m_memo.length() == 0) || (m_memo == right.m_memo)) &&
      (m_splits == right.m_splits) &&
      (m_entryDate == right.m_entryDate) &&
      (m_postDate == right.m_postDate) );
}

bool MyMoneyTransaction::accountReferenced(const TQString& id) const
{
  TQValueList<MyMoneySplit>::ConstIterator it;

  for(it = m_splits.begin(); it != m_splits.end(); ++it) {
    if((*it).accountId() == id)
      return true;
  }
  return false;
}

void MyMoneyTransaction::addSplit(MyMoneySplit& split)
{
  if(!split.id().isEmpty())
    throw new MYMONEYEXCEPTION("Cannot add split with assigned id (" + split.id() + ")");

/*
  TQValueList<MyMoneySplit>::Iterator it;

  // if the account referenced in this split is already
  // referenced in another split, we add the amount of
  // this split to the other one. All other data contained
  // in the new split will be discarded.
  for(it = m_splits.begin(); it != m_splits.end(); ++it) {
    if((*it).accountId() == split.accountId()) {
      (*it).setValue((*it).value()+split.value());
      split = (*it);
      return;
    }
  }
*/

  if(split.accountId().isEmpty())
    throw new MYMONEYEXCEPTION("Cannot add split that does not contain an account reference");

  MyMoneySplit newSplit(nextSplitID(), split);
  split = newSplit;
  split.setTransactionId(id());
  m_splits.append(split);
}

void MyMoneyTransaction::modifySplit(MyMoneySplit& split)
{
// This version of the routine allows only a single
// split to reference one account. If a second split
// is modified to reference an account already referenced
// by another split, the values will be added and the
// duplicate removed.
/*
  TQValueList<MyMoneySplit>::Iterator it;
  TQValueList<MyMoneySplit>::Iterator self = m_splits.end();
  TQValueList<MyMoneySplit>::Iterator dup = self;
  bool duplicateAccount = false;

  for(it = m_splits.begin(); it != m_splits.end(); ++it) {
    if(split.id() == (*it).id()) {
      self = it;
    } else if(split.accountId() == (*it).accountId()) {
      (*it).setValue((*it).value() + split.value());
      dup = it;
      duplicateAccount = true;
    }
  }

  if(self == m_splits.end())
    throw new MYMONEYEXCEPTION("Invalid split id '" + split.id() + "'");

  if(duplicateAccount) {
    m_splits.remove(self);
    split = *dup;
  } else
    *self = split;
*/

// This is the other version which allows having more splits referencing
// the same account.
  if(split.accountId().isEmpty())
    throw new MYMONEYEXCEPTION("Cannot modify split that does not contain an account reference");

  TQValueList<MyMoneySplit>::Iterator it;
  for(it = m_splits.begin(); it != m_splits.end(); ++it) {
    if(split.id() == (*it).id()) {
      *it = split;
      return;
    }
  }
  throw new MYMONEYEXCEPTION(TQString("Invalid split id '%1'").arg(split.id()));
}

void MyMoneyTransaction::removeSplit(const MyMoneySplit& split)
{
  TQValueList<MyMoneySplit>::Iterator it;

  for(it = m_splits.begin(); it != m_splits.end(); ++it) {
    if(split.id() == (*it).id()) {
      m_splits.remove(it);
      break;
    }
  }
  if(it == m_splits.end())
    throw new MYMONEYEXCEPTION(TQString("Invalid split id '%1'").arg(split.id()));
}

void MyMoneyTransaction::removeSplits(void)
{
  m_splits.clear();
  m_nextSplitID = 1;
}

const MyMoneySplit& MyMoneyTransaction::splitByPayee(const TQString& payeeId) const
{
  TQValueList<MyMoneySplit>::ConstIterator it;

  for(it = m_splits.begin(); it != m_splits.end(); ++it) {
    if((*it).payeeId() == payeeId)
      return *it;
  }
  throw new MYMONEYEXCEPTION(TQString("Split not found for payee '%1'").arg(TQString(payeeId)));
}

const MyMoneySplit& MyMoneyTransaction::splitByAccount(const TQString& accountId, const bool match) const
{
  TQValueList<MyMoneySplit>::ConstIterator it;

  for(it = m_splits.begin(); it != m_splits.end(); ++it) {
    if(match == true && (*it).accountId() == accountId)
      return *it;
    if(match == false && (*it).accountId() != accountId)
      return *it;
  }
  throw new MYMONEYEXCEPTION(TQString("Split not found for account %1%2").arg(match?"":"!").arg(TQString(accountId)));
}

const MyMoneySplit& MyMoneyTransaction::splitByAccount(const TQStringList& accountIds, const bool match) const
{
  TQValueList<MyMoneySplit>::ConstIterator it;

  for(it = m_splits.begin(); it != m_splits.end(); ++it) {
    if(match == true && accountIds.contains((*it).accountId()) )
      return *it;
    if(match == false && !accountIds.contains((*it).accountId()))
      return *it;
  }
  throw new MYMONEYEXCEPTION(TQString("Split not found for account  %1%1...%2").arg(match?"":"!").arg(accountIds.front(),accountIds.back()));
}

const MyMoneySplit& MyMoneyTransaction::splitById(const TQString& splitId) const
{
  TQValueList<MyMoneySplit>::ConstIterator it;

  for(it = m_splits.begin(); it != m_splits.end(); ++it) {
    if((*it).id() == splitId)
      return *it;
  }
  throw new MYMONEYEXCEPTION(TQString("Split not found for id '%1'").arg(TQString(splitId)));
}

const TQString MyMoneyTransaction::nextSplitID()
{
  TQString id;
  id = "S" + TQString(id.setNum(m_nextSplitID++)).rightJustify(SPLIT_ID_SIZE, '0');
  return id;
}

const TQString MyMoneyTransaction::firstSplitID()
{
  TQString id;
  id = "S" + TQString(id.setNum(1)).rightJustify(SPLIT_ID_SIZE, '0');
  return id;
}

const MyMoneyMoney MyMoneyTransaction::splitSum(void) const
{
  MyMoneyMoney result(0);
  TQValueList<MyMoneySplit>::ConstIterator it;

  for(it = m_splits.begin(); it != m_splits.end(); ++it) {
    result += (*it).value();
  }
  return result;
}

void MyMoneyTransaction::setPostDate(const TQDate& date) { m_postDate = date; }
void MyMoneyTransaction::setEntryDate(const TQDate& date) { m_entryDate = date; }
void MyMoneyTransaction::setMemo(const TQString& memo) { m_memo = memo; }

bool MyMoneyTransaction::isLoanPayment(void) const
{
  try {
    TQValueList<MyMoneySplit>::ConstIterator it;

    for(it = m_splits.begin(); it != m_splits.end(); ++it) {
      if((*it).isAmortizationSplit())
        return true;
    }
  } catch (MyMoneyException *e) {
    delete e;
  }
  return false;
}

const MyMoneySplit& MyMoneyTransaction::amortizationSplit(void) const
{
  static MyMoneySplit nullSplit;

  TQValueList<MyMoneySplit>::ConstIterator it;

  for(it = m_splits.begin(); it != m_splits.end(); ++it) {
    if((*it).isAmortizationSplit() && (*it).isAutoCalc())
      return *it;
  }
  return nullSplit;
}

const MyMoneySplit& MyMoneyTransaction::interestSplit(void) const
{
  static MyMoneySplit nullSplit;

  TQValueList<MyMoneySplit>::ConstIterator it;

  for(it = m_splits.begin(); it != m_splits.end(); ++it) {
    if((*it).isInterestSplit() && (*it).isAutoCalc())
      return *it;
  }
  return nullSplit;
}

unsigned long MyMoneyTransaction::hash(const TQString& txt, unsigned long h)
{
  unsigned long g;

  for(unsigned i=0; i < txt.length(); ++i) {
    unsigned short uc = txt[i].unicode();
    for(unsigned j = 0; j < 2; ++j) {
      unsigned char c = uc & 0xff;
      // if either the cell or the row of the Unicode char is 0, stop processing
      if(!c)
        break;
      h = (h << 4) + c;
      if( (g = (h & 0xf0000000)) ) {
        h = h ^ (g >> 24);
        h = h ^ g;
      }
      uc >>= 8;
    }
  }
  return h;
}

bool MyMoneyTransaction::isStockSplit(void) const
{
  return (m_splits.count() == 1 && m_splits[0].action() == MyMoneySplit::ActionSplitShares);
}

bool MyMoneyTransaction::isImported(void) const
{
  return value("Imported").lower() == TQString("true");
}

void MyMoneyTransaction::setImported(bool state)
{
  if(state)
    setValue("Imported", "true");
  else
    deletePair("Imported");
}

bool MyMoneyTransaction::isDuplicate(const MyMoneyTransaction& r) const
{
  bool rc = true;
  if(splitCount() != r.splitCount()) {
    rc = false;
  } else {
    if(abs(m_postDate.daysTo(r.postDate())) > 3) {
      rc = false;
    } else {
      unsigned long accHash[2];
      unsigned long valHash[2];
      unsigned long numHash[2];
      for(int i = 0; i < 2; ++i)
        accHash[i] = valHash[i] = numHash[i] = 0;

      TQValueList<MyMoneySplit>::ConstIterator it;
      for(it = splits().begin(); it != splits().end(); ++it) {
        accHash[0] += hash((*it).accountId());
        valHash[0] += hash((*it).value().formatMoney("", 4));
        numHash[0] += hash((*it).number());
      }
      for(it = r.splits().begin(); it != r.splits().end(); ++it) {
        accHash[1] += hash((*it).accountId());
        valHash[1] += hash((*it).value().formatMoney("", 4));
        numHash[1] += hash((*it).number());
      }

      if(accHash[0] != accHash[1]
      || valHash[0] != valHash[1]
      || numHash[0] != numHash[1]
      ) {
        rc = false;
      }
    }
  }

  return rc;
}

void MyMoneyTransaction::writeXML(TQDomDocument& document, TQDomElement& parent) const
{
  TQDomElement el = document.createElement("TRANSACTION");

  writeBaseXML(document, el);

  el.setAttribute("postdate", dateToString(m_postDate));
  el.setAttribute("memo", m_memo);
  el.setAttribute("entrydate", dateToString(m_entryDate));
  el.setAttribute("commodity", m_commodity);

  TQDomElement splits = document.createElement("SPLITS");
  TQValueList<MyMoneySplit>::ConstIterator it;
  for(it = m_splits.begin(); it != m_splits.end(); ++it) {
    (*it).writeXML(document, splits);
  }
  el.appendChild(splits);

  MyMoneyKeyValueContainer::writeXML(document, el);

  parent.appendChild(el);
}

bool MyMoneyTransaction::hasReferenceTo(const TQString& id) const
{
  TQValueList<MyMoneySplit>::const_iterator it;
  bool rc = (id == m_commodity);
  for(it = m_splits.begin(); rc == false && it != m_splits.end(); ++it) {
    rc = (*it).hasReferenceTo(id);
  }
  return rc;
}

bool MyMoneyTransaction::hasAutoCalcSplit(void) const
{
  TQValueList<MyMoneySplit>::ConstIterator it;

  for(it = m_splits.begin(); it != m_splits.end(); ++it) {
    if((*it).isAutoCalc())
      return true;
  }
  return false;
}

TQString MyMoneyTransaction::accountSignature(bool includeSplitCount) const
{
  TQMap<TQString, int> accountList;
  TQValueList<MyMoneySplit>::const_iterator it_s;
  for(it_s = m_splits.begin(); it_s != m_splits.end(); ++it_s) {
    accountList[(*it_s).accountId()] += 1;
  }

  TQMap<TQString, int>::const_iterator it_a;
  TQString rc;
  for(it_a = accountList.begin(); it_a != accountList.end(); ++it_a) {
    if(it_a != accountList.begin())
      rc += "-";
    rc += it_a.key();
    if(includeSplitCount)
      rc += TQString("*%1").arg(*it_a);
  }
  return rc;
}

TQString MyMoneyTransaction::uniqueSortKey(void) const
{
  TQString year, month, day, key;
  const TQDate& postdate = postDate();
  year = TQString(year.setNum(postdate.year())).rightJustify(YEAR_SIZE, '0');
  month = TQString(month.setNum(postdate.month())).rightJustify(MONTH_SIZE, '0');
  day = TQString(day.setNum(postdate.day())).rightJustify(DAY_SIZE, '0');
  key = year + "-" + month + "-" + day + "-" + m_id;
  return key;
}
