/*
 * Copyright (c) 2003-2009, John Wiegley.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * - Neither the name of New Artisans LLC nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef _FILTERS_H
#define _FILTERS_H

#include "handler.h"
#include "predicate.h"
#include "entry.h"

namespace ledger {

//////////////////////////////////////////////////////////////////////
//
// Transaction filters
//

class ignore_xacts : public item_handler<xact_t>
{
public:
  virtual void operator()(xact_t&) {}
};

class clear_xact_xdata : public item_handler<xact_t>
{
public:
  virtual void operator()(xact_t& xact) {
    xact.clear_xdata();
  }
};

class xacts_iterator;

class pass_down_xacts : public item_handler<xact_t>
{
  pass_down_xacts();

public:
  pass_down_xacts(xact_handler_ptr handler, xacts_iterator& iter);

  virtual ~pass_down_xacts() {
    TRACE_DTOR(pass_down_xacts);
  }
};

class push_to_xacts_list : public item_handler<xact_t>
{
  push_to_xacts_list();

public:
  xacts_list& xacts;

  push_to_xacts_list(xacts_list& _xacts) : xacts(_xacts) {
    TRACE_CTOR(push_to_xacts_list, "xacts_list&");
  }
  virtual ~push_to_xacts_list() {
    TRACE_DTOR(push_to_xacts_list);
  }

  virtual void operator()(xact_t& xact) {
    xacts.push_back(&xact);
  }
};

class truncate_entries : public item_handler<xact_t>
{
  int head_count;
  int tail_count;

  xacts_list xacts;

  truncate_entries();

public:
  truncate_entries(xact_handler_ptr handler,
		   int _head_count, int _tail_count)
    : item_handler<xact_t>(handler),
      head_count(_head_count), tail_count(_tail_count) {
    TRACE_CTOR(truncate_entries, "xact_handler_ptr, int, int");
  }
  virtual ~truncate_entries() {
    TRACE_DTOR(truncate_entries);
  }

  virtual void flush();

  virtual void operator()(xact_t& xact) {
    if (! (tail_count == 0 && head_count > 0 &&
	   static_cast<int>(xacts.size()) >= head_count))
      xacts.push_back(&xact);
  }
};

class set_account_value : public item_handler<xact_t>
{
public:
  set_account_value(xact_handler_ptr handler = xact_handler_ptr())
    : item_handler<xact_t>(handler) {}

  virtual void operator()(xact_t& xact);
};

class sort_xacts : public item_handler<xact_t>
{
  typedef std::deque<xact_t *> xacts_deque;

  xacts_deque  xacts;
  const expr_t sort_order;

  sort_xacts();

public:
  sort_xacts(xact_handler_ptr handler,
		    const expr_t&    _sort_order)
    : item_handler<xact_t>(handler),
      sort_order(_sort_order) {
    TRACE_CTOR(sort_xacts,
	       "xact_handler_ptr, const value_expr&");
  }
  sort_xacts(xact_handler_ptr handler,
		    const string& _sort_order)
    : item_handler<xact_t>(handler),
      sort_order(_sort_order) {
    TRACE_CTOR(sort_xacts,
	       "xact_handler_ptr, const string&");
  }
  virtual ~sort_xacts() {
    TRACE_DTOR(sort_xacts);
  }

  virtual void post_accumulated_xacts();

  virtual void flush() {
    post_accumulated_xacts();
    item_handler<xact_t>::flush();
  }

  virtual void operator()(xact_t& xact) {
    xacts.push_back(&xact);
  }
};

class sort_entries : public item_handler<xact_t>
{
  sort_xacts sorter;
  entry_t *  last_entry;

  sort_entries();

public:
  sort_entries(xact_handler_ptr handler,
	       const expr_t&    _sort_order)
    : sorter(handler, _sort_order) {
    TRACE_CTOR(sort_entries,
	       "xact_handler_ptr, const value_expr&");
  }
  sort_entries(xact_handler_ptr handler,
	       const string& _sort_order)
    : sorter(handler, _sort_order) {
    TRACE_CTOR(sort_entries,
	       "xact_handler_ptr, const string&");
  }
  virtual ~sort_entries() {
    TRACE_DTOR(sort_entries);
  }

  virtual void flush() {
    sorter.flush();
    item_handler<xact_t>::flush();
  }

  virtual void operator()(xact_t& xact) {
    if (last_entry && xact.entry != last_entry)
      sorter.post_accumulated_xacts();

    sorter(xact);

    last_entry = xact.entry;
  }
};

class filter_xacts : public item_handler<xact_t>
{
  item_predicate<xact_t> pred;

  filter_xacts();

public:
  filter_xacts(xact_handler_ptr handler,
	       const expr_t&    predicate)
    : item_handler<xact_t>(handler), pred(predicate) {
    TRACE_CTOR(filter_xacts,
	       "xact_handler_ptr, const value_expr&");
  }

  filter_xacts(xact_handler_ptr handler,
	       const string& predicate)
    : item_handler<xact_t>(handler), pred(predicate) {
    TRACE_CTOR(filter_xacts,
	       "xact_handler_ptr, const string&");
  }
  virtual ~filter_xacts() {
    TRACE_DTOR(filter_xacts);
  }

  virtual void operator()(xact_t& xact) {
    if (pred(xact)) {
      xact.xdata().add_flags(XACT_EXT_MATCHES);
      (*handler)(xact);
    }
  }
};

class anonymize_xacts : public item_handler<xact_t>
{
  std::list<entry_t> entry_temps;
  std::list<xact_t>  xact_temps;

  entry_t * last_entry;

  anonymize_xacts();

public:
  anonymize_xacts(xact_handler_ptr handler)
    : item_handler<xact_t>(handler), last_entry(NULL) {
    TRACE_CTOR(anonymize_xacts, "xact_handler_ptr");
  }
  virtual ~anonymize_xacts() {
    TRACE_DTOR(anonymize_xacts);
  }

  virtual void operator()(xact_t& xact);
};

class calc_xacts : public item_handler<xact_t>
{
  xact_t * last_xact;

  calc_xacts();

public:
  calc_xacts(xact_handler_ptr handler)
    : item_handler<xact_t>(handler), last_xact(NULL) {
    TRACE_CTOR(calc_xacts, "xact_handler_ptr");
  }
  virtual ~calc_xacts() {
    TRACE_DTOR(calc_xacts);
  }

  virtual void operator()(xact_t& xact);
};

class invert_xacts : public item_handler<xact_t>
{
  invert_xacts();

public:
  invert_xacts(xact_handler_ptr handler)
    : item_handler<xact_t>(handler) {}

  virtual void operator()(xact_t& xact);
};

inline void clear_entries_xacts(std::list<entry_t>& entries_list) {
  foreach (entry_t& entry, entries_list)
    entry.xacts.clear();
}

class collapse_xacts : public item_handler<xact_t>
{
  value_t      subtotal;
  unsigned int count;
  entry_t *    last_entry;
  xact_t *     last_xact;
  account_t    totals_account;

  std::list<entry_t> entry_temps;
  std::list<xact_t>  xact_temps;

  collapse_xacts();

public:
  collapse_xacts(xact_handler_ptr handler, session_t& session)
    : item_handler<xact_t>(handler), count(0),
      last_entry(NULL), last_xact(NULL),
      totals_account(NULL, "<Total>") {
    TRACE_CTOR(collapse_xacts, "xact_handler_ptr");
  }
  virtual ~collapse_xacts() {
    TRACE_DTOR(collapse_xacts);
    clear_entries_xacts(entry_temps);
  }

  virtual void flush() {
    if (subtotal)
      report_subtotal();
    item_handler<xact_t>::flush();
  }

  void report_subtotal();

  virtual void operator()(xact_t& xact);
};

class component_xacts : public item_handler<xact_t>
{
  item_predicate<xact_t> pred;

  component_xacts();

public:
  component_xacts(xact_handler_ptr handler,
			 const expr_t&    predicate)
    : item_handler<xact_t>(handler), pred(predicate) {
    TRACE_CTOR(component_xacts,
	       "xact_handler_ptr, const value_expr&");
  }
  component_xacts(xact_handler_ptr handler,
			 const string& predicate)
    : item_handler<xact_t>(handler), pred(predicate) {
    TRACE_CTOR(component_xacts,
	       "xact_handler_ptr, const string&");
  }
  virtual ~component_xacts() throw() {
    TRACE_DTOR(component_xacts);
  }

  virtual void operator()(xact_t& xact);
};

class related_xacts : public item_handler<xact_t>
{
  xacts_list xacts;
  bool	     also_matching;

  related_xacts();

public:
  related_xacts(xact_handler_ptr handler,
		       const bool _also_matching = false)
    : item_handler<xact_t>(handler),
      also_matching(_also_matching) {
    TRACE_CTOR(related_xacts,
	       "xact_handler_ptr, const bool");
  }
  virtual ~related_xacts() throw() {
    TRACE_DTOR(related_xacts);
  }

  virtual void flush();
  virtual void operator()(xact_t& xact) {
    xact.xdata().add_flags(XACT_EXT_RECEIVED);
    xacts.push_back(&xact);
  }
};

class changed_value_xacts : public item_handler<xact_t>
{
  // This filter requires that calc_xacts be used at some point
  // later in the chain.

  expr_t   total_expr;
  bool	   changed_values_only;
  xact_t * last_xact;
  value_t  last_balance;

  std::list<entry_t> entry_temps;
  std::list<xact_t>  xact_temps;

  changed_value_xacts();

public:
  changed_value_xacts(xact_handler_ptr handler,
		      const expr_t&    _total_expr,
		      bool	       _changed_values_only)
    : item_handler<xact_t>(handler), total_expr(_total_expr),
      changed_values_only(_changed_values_only), last_xact(NULL) {
    TRACE_CTOR(changed_value_xacts,
	       "xact_handler_ptr, bool");
  }
  virtual ~changed_value_xacts() {
    TRACE_DTOR(changed_value_xacts);
    clear_entries_xacts(entry_temps);
  }

  virtual void flush() {
    if (last_xact) {
      output_diff(current_date);
      last_xact = NULL;
    }
    item_handler<xact_t>::flush();
  }

  void output_diff(const date_t& current);

  virtual void operator()(xact_t& xact);
};

class subtotal_xacts : public item_handler<xact_t>
{
  class acct_value_t
  {
    acct_value_t();

  public:
    account_t *	account;
    value_t	value;

    xacts_list components;

    acct_value_t(account_t * a) : account(a) {
      TRACE_CTOR(acct_value_t, "acount_t *");
    }
    acct_value_t(account_t * a, value_t& v) : account(a), value(v) {
      TRACE_CTOR(acct_value_t, "acount_t *, value_t&");
    }
    acct_value_t(const acct_value_t& av)
      : account(av.account), value(av.value),
	components(av.components) {
      TRACE_CTOR(acct_value_t, "copy");
    }
    ~acct_value_t() throw() {
      TRACE_DTOR(acct_value_t);
    }
  };

  typedef std::map<string, acct_value_t>  values_map;
  typedef std::pair<string, acct_value_t> values_pair;

  subtotal_xacts();

protected:
  values_map values;
  bool       remember_components;

  std::list<entry_t> entry_temps;
  std::list<xact_t>  xact_temps;

public:
  date_t start;
  date_t finish;

  subtotal_xacts(xact_handler_ptr handler,
			bool _remember_components = false)
    : item_handler<xact_t>(handler),
      remember_components(_remember_components) {
    TRACE_CTOR(subtotal_xacts,
	       "xact_handler_ptr, bool");
  }
  virtual ~subtotal_xacts() {
    TRACE_DTOR(subtotal_xacts);
    clear_entries_xacts(entry_temps);
  }

  void report_subtotal(const char * spec_fmt = NULL);

  virtual void flush() {
    if (values.size() > 0)
      report_subtotal();
    item_handler<xact_t>::flush();
  }
  virtual void operator()(xact_t& xact);
};

class interval_xacts : public subtotal_xacts
{
  interval_t      interval;
  xact_t * last_xact;
  bool            started;

  interval_xacts();

public:
  interval_xacts(xact_handler_ptr _handler,
			const interval_t& _interval,
			bool remember_components = false)
    : subtotal_xacts(_handler, remember_components),
      interval(_interval), last_xact(NULL), started(false) {
    TRACE_CTOR(interval_xacts,
	       "xact_handler_ptr, const interval_t&, bool");
  }
  interval_xacts(xact_handler_ptr _handler,
			const string& _interval,
			bool remember_components = false)
    : subtotal_xacts(_handler, remember_components),
      interval(_interval), last_xact(NULL), started(false) {
    TRACE_CTOR(interval_xacts,
	       "xact_handler_ptr, const string&, bool");
  }
  virtual ~interval_xacts() throw() {
    TRACE_DTOR(interval_xacts);
  }

  void report_subtotal(const date_t& moment = date_t());

  virtual void flush() {
    if (last_xact)
      report_subtotal();
    subtotal_xacts::flush();
  }
  virtual void operator()(xact_t& xact);
};

class by_payee_xacts : public item_handler<xact_t>
{
  typedef std::map<string, subtotal_xacts *>  payee_subtotals_map;
  typedef std::pair<string, subtotal_xacts *> payee_subtotals_pair;

  payee_subtotals_map payee_subtotals;
  bool		      remember_components;

  by_payee_xacts();

 public:
  by_payee_xacts(xact_handler_ptr handler,
			bool _remember_components = false)
    : item_handler<xact_t>(handler),
      remember_components(_remember_components) {
    TRACE_CTOR(by_payee_xacts,
	       "xact_handler_ptr, bool");
  }
  virtual ~by_payee_xacts();

  virtual void flush();
  virtual void operator()(xact_t& xact);
};

class set_comm_as_payee : public item_handler<xact_t>
{
  std::list<entry_t> entry_temps;
  std::list<xact_t>  xact_temps;

  set_comm_as_payee();

public:
  set_comm_as_payee(xact_handler_ptr handler)
    : item_handler<xact_t>(handler) {
    TRACE_CTOR(set_comm_as_payee, "xact_handler_ptr");
  }
  virtual ~set_comm_as_payee() {
    TRACE_DTOR(set_comm_as_payee);
    clear_entries_xacts(entry_temps);
  }

  virtual void operator()(xact_t& xact);
};

class set_code_as_payee : public item_handler<xact_t>
{
  std::list<entry_t> entry_temps;
  std::list<xact_t>  xact_temps;

  set_code_as_payee();

public:
  set_code_as_payee(xact_handler_ptr handler)
    : item_handler<xact_t>(handler) {
    TRACE_CTOR(set_code_as_payee, "xact_handler_ptr");
  }
  virtual ~set_code_as_payee() {
    TRACE_DTOR(set_code_as_payee);
    clear_entries_xacts(entry_temps);
  }

  virtual void operator()(xact_t& xact);
};

class dow_xacts : public subtotal_xacts
{
  xacts_list days_of_the_week[7];

  dow_xacts();

public:
  dow_xacts(xact_handler_ptr handler,
		   bool remember_components = false)
    : subtotal_xacts(handler, remember_components) {
    TRACE_CTOR(dow_xacts, "xact_handler_ptr, bool");
  }
  virtual ~dow_xacts() throw() {
    TRACE_DTOR(dow_xacts);
  }

  virtual void flush();
  virtual void operator()(xact_t& xact) {
    days_of_the_week[xact.date()->day_of_week()].push_back(&xact);
  }
};

class generate_xacts : public item_handler<xact_t>
{
  generate_xacts();

protected:
  typedef std::pair<interval_t, xact_t *> pending_xacts_pair;
  typedef std::list<pending_xacts_pair>          pending_xacts_list;

  pending_xacts_list pending_xacts;
  std::list<entry_t> entry_temps;
  std::list<xact_t>  xact_temps;

public:
  generate_xacts(xact_handler_ptr handler)
    : item_handler<xact_t>(handler) {
    TRACE_CTOR(dow_xacts, "xact_handler_ptr");
  }

  virtual ~generate_xacts() {
    TRACE_DTOR(generate_xacts);
    clear_entries_xacts(entry_temps);
  }

  void add_period_entries(period_entries_list& period_entries);

  virtual void add_xact(const interval_t& period, xact_t& xact);
};

class budget_xacts : public generate_xacts
{
#define BUDGET_NO_BUDGET  0x00
#define BUDGET_BUDGETED   0x01
#define BUDGET_UNBUDGETED 0x02

  unsigned short flags;

  budget_xacts();

public:
  budget_xacts(xact_handler_ptr handler,
		      unsigned long _flags = BUDGET_BUDGETED)
    : generate_xacts(handler), flags(_flags) {
    TRACE_CTOR(budget_xacts,
	       "xact_handler_ptr, unsigned long");
  }
  virtual ~budget_xacts() throw() {
    TRACE_DTOR(budget_xacts);
  }

  void report_budget_items(const date_t& date);

  virtual void operator()(xact_t& xact);
};

class forecast_xacts : public generate_xacts
{
  item_predicate<xact_t> pred;

 public:
  forecast_xacts(xact_handler_ptr handler, const expr_t& predicate)
    : generate_xacts(handler), pred(predicate) {
    TRACE_CTOR(forecast_xacts, "xact_handler_ptr, const expr_t&");
  }
  forecast_xacts(xact_handler_ptr handler, const string& predicate)
    : generate_xacts(handler), pred(predicate) {
    TRACE_CTOR(forecast_xacts, "xact_handler_ptr, const string&");
  }
  virtual ~forecast_xacts() throw() {
    TRACE_DTOR(forecast_xacts);
  }

  virtual void add_xact(const interval_t& period,
			       xact_t&	 xact);
  virtual void flush();
};

//////////////////////////////////////////////////////////////////////
//
// Account filters
//

class clear_account_xdata : public item_handler<account_t>
{
public:
  virtual void operator()(account_t& acct) {
    acct.clear_xdata();
  }
};

class accounts_iterator;

class pass_down_accounts : public item_handler<account_t>
{
  pass_down_accounts();

  item_predicate<account_t> pred;

public:
  pass_down_accounts(acct_handler_ptr	handler,
		     accounts_iterator& iter,
		     const expr_t&	predicate = expr_t());

  virtual ~pass_down_accounts() {
    TRACE_DTOR(pass_down_accounts);
  }
};

} // namespace ledger

#endif // _FILTERS_H