/*
 * 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.
 */

/**
 * @addtogroup math
 */

/**
 * @file   commodity.h
 * @author John Wiegley
 *
 * @ingroup math
 *
 * @brief  Types for handling commodities
 *
 * This file contains one of the most basic types in Ledger:
 * commodity_t, and its annotated cousin, annotated_commodity_t.
 */
#ifndef _COMMODITY_H
#define _COMMODITY_H

namespace ledger {

class keep_details_t;

DECLARE_EXCEPTION(commodity_error, std::runtime_error);

/**
 * @brief Brief
 *
 * Long.
 */
struct price_point_t
{
  datetime_t when;
  amount_t   price;

#if defined(HAVE_BOOST_SERIALIZATION)
private:
  /** Serialization. */

  friend class boost::serialization::access;

  template<class Archive>
  void serialize(Archive & ar, const unsigned int /* version */) {
    ar & when;
    ar & price;
  }
#endif // HAVE_BOOST_SERIALIZATION
};

/**
 * @brief Brief
 *
 * Long.
 */
class commodity_t
  : public delegates_flags<uint_least16_t>,
    public equality_comparable1<commodity_t, noncopyable>
{
  friend class commodity_pool_t;

public:
  class base_t : public noncopyable, public supports_flags<uint_least16_t>
  {
    base_t() {}

  public:
    typedef std::map<const datetime_t, amount_t> history_map;

    struct history_t
    {
      history_map prices;

      void add_price(commodity_t&	source,
		     const datetime_t&	date,
		     const amount_t&	price,
		     const bool		reflexive = true);
      bool remove_price(const datetime_t& date);

      optional<price_point_t>
      find_price(const optional<datetime_t>&   moment = none,
		 const optional<datetime_t>&   oldest = none
#if defined(DEBUG_ON)
		 , const int indent = 0
#endif
		 ) const;

#if defined(HAVE_BOOST_SERIALIZATION)
    private:
      /** Serialization. */

      friend class boost::serialization::access;

      template<class Archive>
      void serialize(Archive & ar, const unsigned int /* version */) {
	ar & prices;
      }
#endif // HAVE_BOOST_SERIALIZATION
    };

    typedef std::map<commodity_t *, history_t> history_by_commodity_map;

    struct varied_history_t
    {
      history_by_commodity_map histories;

      void add_price(commodity_t&	source,
		     const datetime_t&	date,
		     const amount_t&	price,
		     const bool		reflexive = true);
      bool remove_price(const datetime_t& date, commodity_t& commodity);

      optional<price_point_t>
      find_price(const commodity_t&	       source,
		 const optional<commodity_t&>& commodity = none,
		 const optional<datetime_t>&   moment    = none,
		 const optional<datetime_t>&   oldest    = none
#if defined(DEBUG_ON)
		 , const int indent = 0
#endif
		 ) const;

      optional<history_t&>
      history(const optional<commodity_t&>& commodity = none);

#if defined(HAVE_BOOST_SERIALIZATION)
    private:
      /** Serialization. */

      friend class boost::serialization::access;

      template<class Archive>
      void serialize(Archive & ar, const unsigned int /* version */) {
	ar & histories;
      }
#endif // HAVE_BOOST_SERIALIZATION
    };

#define COMMODITY_STYLE_DEFAULTS  0x000
#define COMMODITY_STYLE_SUFFIXED  0x001
#define COMMODITY_STYLE_SEPARATED 0x002
#define COMMODITY_STYLE_EUROPEAN  0x004
#define COMMODITY_STYLE_THOUSANDS 0x008
#define COMMODITY_NOMARKET        0x010
#define COMMODITY_BUILTIN         0x020
#define COMMODITY_WALKED          0x040
#define COMMODITY_KNOWN           0x080
#define COMMODITY_PRIMARY         0x100

    string		       symbol;
    amount_t::precision_t      precision;
    optional<string>	       name;
    optional<string>	       note;
    optional<varied_history_t> varied_history;
    optional<amount_t>	       smaller;
    optional<amount_t>	       larger;

    mutable bool               searched;

  public:
    explicit base_t(const string& _symbol)
      : supports_flags<uint_least16_t>(COMMODITY_STYLE_DEFAULTS),
	symbol(_symbol), precision(0), searched(false) {
      TRACE_CTOR(base_t, "const string&");
    }
    ~base_t() {
      TRACE_DTOR(base_t);
    }

#if defined(HAVE_BOOST_SERIALIZATION)
    private:
      /** Serialization. */

      friend class boost::serialization::access;

      template<class Archive>
      void serialize(Archive & ar, const unsigned int /* version */) {
	ar & boost::serialization::base_object<supports_flags<uint_least16_t> >(*this);
	ar & symbol;
	ar & precision;
	ar & name;
	ar & note;
	ar & varied_history;
	ar & smaller;
	ar & larger;
      }
#endif // HAVE_BOOST_SERIALIZATION
  };

public:
  static bool symbol_needs_quotes(const string& symbol);

  typedef base_t::history_t		   history_t;
  typedef base_t::history_map		   history_map;
  typedef base_t::varied_history_t	   varied_history_t;
  typedef base_t::history_by_commodity_map history_by_commodity_map;

  shared_ptr<base_t> base;

  commodity_pool_t * parent_;
  optional<string>   qualified_symbol;
  optional<string>   mapping_key_;
  bool		     annotated;

public:
  explicit commodity_t(commodity_pool_t *	 _parent,
		       const shared_ptr<base_t>& _base)
    : delegates_flags<uint_least16_t>(*_base.get()), base(_base),
      parent_(_parent), annotated(false) {
    TRACE_CTOR(commodity_t, "");
  }
  virtual ~commodity_t() {
    TRACE_DTOR(commodity_t);
  }

  operator bool() const;

  bool is_annotated() const {
    return annotated;
  }

  virtual bool operator==(const commodity_t& comm) const {
    if (comm.annotated)
      return comm == *this;
    return base.get() == comm.base.get();
  }

  virtual commodity_t& referent() {
    return *this;
  }
  virtual const commodity_t& referent() const {
    return *this;
  }
  virtual commodity_t& strip_annotations(const keep_details_t&) {
    return *this;
  }
  virtual void write_annotations(std::ostream&) const {}

  commodity_pool_t& parent() const {
    return *parent_;
  }

  string base_symbol() const {
    return base->symbol;
  }
  string symbol() const {
    return qualified_symbol ? *qualified_symbol : base_symbol();
  }

  string mapping_key() const {
    if (mapping_key_)
      return *mapping_key_;
    else
      return base_symbol();
  }

  optional<string> name() const {
    return base->name;
  }
  void set_name(const optional<string>& arg = none) {
    base->name = arg;
  }

  optional<string> note() const {
    return base->note;
  }
  void set_note(const optional<string>& arg = none) {
    base->note = arg;
  }

  amount_t::precision_t precision() const {
    return base->precision;
  }
  void set_precision(amount_t::precision_t arg) {
    base->precision = arg;
  }

  optional<amount_t> smaller() const {
    return base->smaller;
  }
  void set_smaller(const optional<amount_t>& arg = none) {
    base->smaller = arg;
  }

  optional<amount_t> larger() const {
    return base->larger;
  }
  void set_larger(const optional<amount_t>& arg = none) {
    base->larger = arg;
  }

  optional<varied_history_t&> varied_history() {
    if (base->varied_history)
      return *base->varied_history;
    return none;
  }

  optional<history_t&> history(const optional<commodity_t&>& commodity);

public:
  // These methods provide a transparent pass-through to the underlying
  // base->varied_history object.

  void add_price(const datetime_t& date, const amount_t& price,
		 const bool reflexive = true) {
    if (! base->varied_history)
      base->varied_history = varied_history_t();

    base->varied_history->add_price(*this, date, price, reflexive);
  }
  bool remove_price(const datetime_t& date, commodity_t& commodity) {
    if (base->varied_history)
      base->varied_history->remove_price(date, commodity);
    return false;
  }

  optional<price_point_t>
  find_price(const optional<commodity_t&>& commodity = none,
	     const optional<datetime_t>&   moment    = none,
	     const optional<datetime_t>&   oldest    = none
#if defined(DEBUG_ON)
	     , const int indent = 0
#endif
	     ) const {
    if (base->varied_history && ! has_flags(COMMODITY_WALKED)) {
      const_cast<commodity_t&>(*this).add_flags(COMMODITY_WALKED);
      optional<price_point_t> point =
	base->varied_history->find_price(*this, commodity, moment, oldest
#if defined(DEBUG_ON)
					 , indent
#endif
					 );
      const_cast<commodity_t&>(*this).drop_flags(COMMODITY_WALKED);
      return point;
    }
    return none;
  }    

  optional<price_point_t>
  check_for_updated_price(const optional<price_point_t>& point,
			  const optional<datetime_t>&	 moment,
			  const optional<commodity_t&>&  in_terms_of);

  // Methods related to parsing, reading, writing, etc., the commodity
  // itself.

  static void parse_symbol(std::istream& in, string& symbol);
  static void parse_symbol(char *& p, string& symbol);
  static string parse_symbol(std::istream& in) {
    string temp;
    parse_symbol(in, temp);
    return temp;
  }

  void print(std::ostream& out) const {
    out << symbol();
  }

  bool valid() const;

#if defined(HAVE_BOOST_SERIALIZATION)
private:
  supports_flags<uint_least16_t> temp_flags;

protected:
  explicit commodity_t()
    : delegates_flags<uint_least16_t>(temp_flags), parent_(NULL),
      annotated(false) {}

private:
  /** Serialization. */

  friend class boost::serialization::access;

  template<class Archive>
  void serialize(Archive & ar, const unsigned int /* version */) {
    ar & boost::serialization::base_object<delegates_flags<uint_least16_t> >(*this);
    ar & base;
    ar & parent_;
    ar & qualified_symbol;
    ar & mapping_key_;
    ar & annotated;
  }
#endif // HAVE_BOOST_SERIALIZATION
};

inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) {
  comm.print(out);
  return out;
}

/**
 * @brief Brief
 *
 * Long.
 */
struct compare_amount_commodities {
  bool operator()(const amount_t * left, const amount_t * right) const;
};

} // namespace ledger

#endif // _COMMODITY_H
