/*
 * Copyright (c) 2003-2008, 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.
 */

/**
 * @file   commodity.cc
 * @author John Wiegley
 * @date   Thu Apr 26 15:19:46 2007
 *
 * @brief  Types for dealing with commodities.
 *
 * This file defines member functions for flavors of commodity_t.
 */

#include "amount.h"

namespace ledger {

void commodity_t::add_price(const datetime_t& date,
			    const amount_t& price)
{
  if (! base->history)
    base->history = history_t();

  history_map::iterator i = base->history->prices.find(date);
  if (i != base->history->prices.end()) {
    (*i).second = price;
  } else {
    std::pair<history_map::iterator, bool> result
      = base->history->prices.insert(history_map::value_type(date, price));
    assert(result.second);
  }
}

bool commodity_t::remove_price(const datetime_t& date)
{
  if (base->history) {
    history_map::size_type n = base->history->prices.erase(date);
    if (n > 0) {
      if (base->history->prices.empty())
	base->history.reset();
      return true;
    }
  }
  return false;
}

optional<amount_t> commodity_t::value(const optional<datetime_t>& moment)
{
  optional<datetime_t> age;
  optional<amount_t> price;

  if (base->history) {
    assert(base->history->prices.size() > 0);

    if (! moment) {
      history_map::reverse_iterator r = base->history->prices.rbegin();
      age   = (*r).first;
      price = (*r).second;
    } else {
      history_map::iterator i = base->history->prices.lower_bound(*moment);
      if (i == base->history->prices.end()) {
	history_map::reverse_iterator r = base->history->prices.rbegin();
	age   = (*r).first;
	price = (*r).second;
      } else {
	age = (*i).first;
	if (*moment != *age) {
	  if (i != base->history->prices.begin()) {
	    --i;
	    age	  = (*i).first;
	    price = (*i).second;
	  } else {
	    age   = none;
	  }
	} else {
	  price = (*i).second;
	}
      }
    }
  }

  if (! has_flags(COMMODITY_STYLE_NOMARKET) && parent().get_quote) {
    if (optional<amount_t> quote = parent().get_quote
	(*this, age, moment,
	 (base->history && base->history->prices.size() > 0 ?
	  (*base->history->prices.rbegin()).first : optional<datetime_t>())))
      return *quote;
  }
  return price;
}

amount_t commodity_t::exchange(const amount_t&		   amount,
			       amount_t&		   final_cost, // out
			       amount_t&		   basis_cost, // out
			       const optional<amount_t>&   total_cost_,
			       const optional<amount_t>&   per_unit_cost_,
			       const optional<datetime_t>& moment,
			       const optional<string>&     tag)
{
  // (assert (or (and total-cost (not per-unit-cost))
  //             (and per-unit-cost (not total-cost))))

  assert((total_cost_ && ! per_unit_cost_) || (per_unit_cost_ && ! total_cost_));

  // (let* ((commodity (amount-commodity amount))
  //        (current-annotation
  //         (and (annotated-commodity-p commodity)
  //              (commodity-annotation commodity)))
  //        (base-commodity (if (annotated-commodity-p commodity)
  //                            (get-referent commodity)
  //                            commodity))
  //        (per-unit-cost (or per-unit-cost
  //                           (divide total-cost amount)))
  //        (total-cost (or total-cost
  //                        (multiply per-unit-cost amount))))

  commodity_t& commodity(amount.commodity());

  annotation_t * current_annotation = NULL;
  if (commodity.annotated)
    current_annotation = &as_annotated_commodity(commodity).details;

  commodity_t& base_commodity
    (current_annotation ?
     as_annotated_commodity(commodity).referent() : commodity);

  amount_t per_unit_cost(per_unit_cost_ ?
			 *per_unit_cost_ : *total_cost_ / amount);
  final_cost = total_cost_ ? *total_cost_ : *per_unit_cost_ * amount;

  // Add a price history entry for this conversion if we know when it took
  // place

  // (if (and moment (not (commodity-no-market-price-p base-commodity)))
  //     (add-price base-commodity per-unit-cost moment))

  if (moment && ! commodity.has_flags(COMMODITY_STYLE_NOMARKET))
    base_commodity.add_price(*moment, per_unit_cost);

  // ;; returns: ANNOTATED-AMOUNT TOTAL-COST BASIS-COST
  // (values (annotate-commodity
  //          amount
  //          (make-commodity-annotation :price per-unit-cost
  //                                     :date  moment
  //                                     :tag   tag))
  //         total-cost
  //         (if current-annotation
  //             (multiply (annotation-price current-annotation) amount)
  //             total-cost))))

  if (current_annotation && current_annotation->price)
    basis_cost = *current_annotation->price * amount;
  else
    basis_cost = final_cost;

  amount_t ann_amount(amount);
  ann_amount.annotate(annotation_t(per_unit_cost, moment->date(), tag));
  return ann_amount;
}

commodity_t::operator bool() const
{
  return this != parent().null_commodity;
}

bool commodity_t::symbol_needs_quotes(const string& symbol)
{
  foreach (char ch, symbol)
    if (std::isspace(ch) || std::isdigit(ch) || ch == '-' || ch == '.')
      return true;

  return false;
}

void commodity_t::parse_symbol(std::istream& in, string& symbol)
{
  // Invalid commodity characters:
  //   SPACE, TAB, NEWLINE, RETURN
  //   0-9 . , ; - + * / ^ ? : & | ! =
  //   < > { } [ ] ( ) @

  static int invalid_chars[256] = {
    /* 0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f */
    /* 00 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
    /* 10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    /* 20 */ 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
    /* 30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    /* 40 */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    /* 50 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0,
    /* 60 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    /* 70 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0,
    /* 80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    /* 90 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    /* a0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    /* b0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    /* c0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    /* d0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    /* e0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    /* f0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  };

  char buf[256];
  char c = peek_next_nonws(in);
  if (c == '"') {
    in.get(c);
    READ_INTO(in, buf, 255, c, c != '"');
    if (c == '"')
      in.get(c);
    else
      throw_(amount_error, "Quoted commodity symbol lacks closing quote");
  } else {
    READ_INTO(in, buf, 255, c, ! invalid_chars[static_cast<unsigned char>(c)]);
  }
  symbol = buf;
}

void commodity_t::parse_symbol(char *& p, string& symbol)
{
  if (*p == '"') {
    char * q = std::strchr(p + 1, '"');
    if (! q)
      throw_(amount_error, "Quoted commodity symbol lacks closing quote");
    symbol = string(p + 1, 0, q - p - 1);
    p = q + 2;
  } else {
    char * q = next_element(p);
    symbol = p;
    if (q)
      p = q;
    else
      p += symbol.length();
  }
  if (symbol.empty())
    throw_(amount_error, "Failed to parse commodity");
}

bool commodity_t::valid() const
{
  if (symbol().empty() && this != parent().null_commodity) {
    DEBUG("ledger.validate",
	   "commodity_t: symbol().empty() && this != null_commodity");
    return false;
  }

  if (annotated && ! base) {
    DEBUG("ledger.validate", "commodity_t: annotated && ! base");
    return false;
  }

  if (precision() > 16) {
    DEBUG("ledger.validate", "commodity_t: precision() > 16");
    return false;
  }

  return true;
}

void annotation_t::parse(std::istream& in)
{
  do {
    char buf[256];
    char c = peek_next_nonws(in);
    if (c == '{') {
      if (price)
	throw_(amount_error, "Commodity specifies more than one price");

      in.get(c);
      READ_INTO(in, buf, 255, c, c != '}');
      if (c == '}')
	in.get(c);
      else
	throw_(amount_error, "Commodity price lacks closing brace");

      amount_t temp;
      temp.parse(buf, AMOUNT_PARSE_NO_MIGRATE);
      temp.in_place_reduce();

      // Since this price will maintain its own precision, make sure
      // it is at least as large as the base commodity, since the user
      // may have only specified {$1} or something similar.

      if (temp.has_commodity() &&
	  temp.precision() < temp.commodity().precision())
	temp = temp.round();	// no need to retain individual precision

      price = temp;
    }
    else if (c == '[') {
      if (date)
	throw_(amount_error, "Commodity specifies more than one date");

      in.get(c);
      READ_INTO(in, buf, 255, c, c != ']');
      if (c == ']')
	in.get(c);
      else
	throw_(amount_error, "Commodity date lacks closing bracket");

      date = parse_date(buf);
    }
    else if (c == '(') {
      if (tag)
	throw_(amount_error, "Commodity specifies more than one tag");

      in.get(c);
      READ_INTO(in, buf, 255, c, c != ')');
      if (c == ')')
	in.get(c);
      else
	throw_(amount_error, "Commodity tag lacks closing parenthesis");

      tag = buf;
    }
    else {
      break;
    }
  } while (true);

  DEBUG("amounts.commodities",
	"Parsed commodity annotations: " << std::endl << *this);
}

bool annotated_commodity_t::operator==(const commodity_t& comm) const
{
  // If the base commodities don't match, the game's up.
  if (base != comm.base)
    return false;

  assert(annotated);
  if (! comm.annotated)
    return false;

  if (details != as_annotated_commodity(comm).details)
    return false;

  return true;
}

commodity_t&
annotated_commodity_t::strip_annotations(const bool _keep_price,
					 const bool _keep_date,
					 const bool _keep_tag)
{
  DEBUG("commodity.annotated.strip",
	"Reducing commodity " << *this << std::endl
	 << "  keep price " << _keep_price << " "
	 << "  keep date "  << _keep_date << " "
	 << "  keep tag "   << _keep_tag);

  commodity_t * new_comm;

  if ((_keep_price && details.price) ||
      (_keep_date  && details.date) ||
      (_keep_tag   && details.tag))
  {
    new_comm = parent().find_or_create
      (referent(),
       annotation_t(_keep_price ? details.price : none,
		    _keep_date  ? details.date  : none,
		    _keep_tag   ? details.tag   : none));
  } else {
    new_comm = parent().find_or_create(base_symbol());
  }

  assert(new_comm);
  return *new_comm;
}

void annotated_commodity_t::write_annotations(std::ostream&       out,
					      const annotation_t& info)
{
  if (info.price)
    out << " {" << *info.price << '}';

  if (info.date)
    out << " [" << format_date(*info.date) << ']';

  if (info.tag)
    out << " (" << *info.tag << ')';
}

bool compare_amount_commodities::operator()(const amount_t * left,
					    const amount_t * right) const
{
  commodity_t& leftcomm(left->commodity());
  commodity_t& rightcomm(right->commodity());

  DEBUG("commodity.compare", " left symbol (" << leftcomm << ")");
  DEBUG("commodity.compare", "right symbol (" << rightcomm << ")");

  int cmp = leftcomm.base_symbol().compare(rightcomm.base_symbol());
  if (cmp != 0)
    return cmp < 0;

  if (! leftcomm.annotated) {
    return rightcomm.annotated;
  }
  else if (! rightcomm.annotated) {
    return ! leftcomm.annotated;
  }
  else {
    annotated_commodity_t& aleftcomm(static_cast<annotated_commodity_t&>(leftcomm));
    annotated_commodity_t& arightcomm(static_cast<annotated_commodity_t&>(rightcomm));

    if (! aleftcomm.details.price && arightcomm.details.price)
      return true;
    if (aleftcomm.details.price && ! arightcomm.details.price)
      return false;

    if (aleftcomm.details.price && arightcomm.details.price) {
      amount_t leftprice(*aleftcomm.details.price);
      leftprice.in_place_reduce();
      amount_t rightprice(*arightcomm.details.price);
      rightprice.in_place_reduce();

      if (leftprice.commodity() == rightprice.commodity()) {
	return (leftprice - rightprice).sign() < 0;
      } else {
	// Since we have two different amounts, there's really no way
	// to establish a true sorting order; we'll just do it based
	// on the numerical values.
	leftprice.clear_commodity();
	rightprice.clear_commodity();
	return (leftprice - rightprice).sign() < 0;
      }
    }

    if (! aleftcomm.details.date && arightcomm.details.date)
      return true;
    if (aleftcomm.details.date && ! arightcomm.details.date)
      return false;

    if (aleftcomm.details.date && arightcomm.details.date) {
      date_duration_t diff = *aleftcomm.details.date - *arightcomm.details.date;
      return diff.is_negative();
    }

    if (! aleftcomm.details.tag && arightcomm.details.tag)
      return true;
    if (aleftcomm.details.tag && ! arightcomm.details.tag)
      return false;

    if (aleftcomm.details.tag && arightcomm.details.tag)
      return *aleftcomm.details.tag < *arightcomm.details.tag;

    assert(false);
    return true;
  }
}

commodity_pool_t::commodity_pool_t() : default_commodity(NULL)
{
  TRACE_CTOR(commodity_pool_t, "");
  null_commodity = create("");
  null_commodity->add_flags(COMMODITY_STYLE_NOMARKET |
			    COMMODITY_STYLE_BUILTIN);
}

commodity_t * commodity_pool_t::create(const string& symbol)
{
  shared_ptr<commodity_t::base_t>
    base_commodity(new commodity_t::base_t(symbol));
  std::auto_ptr<commodity_t> commodity(new commodity_t(this, base_commodity));

  DEBUG("amounts.commodities", "Creating base commodity " << symbol);

  // Create the "qualified symbol" version of this commodity's symbol
  if (commodity_t::symbol_needs_quotes(symbol)) {
    commodity->qualified_symbol = "\"";
    *commodity->qualified_symbol += symbol;
    *commodity->qualified_symbol += "\"";
  }

  DEBUG("amounts.commodities",
	"Creating commodity '" << commodity->symbol() << "'");

  commodity->ident = commodities.size();

  commodities.push_back(commodity.get());
  return commodity.release();
}

commodity_t * commodity_pool_t::find_or_create(const string& symbol)
{
  DEBUG("amounts.commodities", "Find-or-create commodity " << symbol);

  commodity_t * commodity = find(symbol);
  if (commodity)
    return commodity;
  return create(symbol);
}

commodity_t * commodity_pool_t::find(const string& symbol)
{
  DEBUG("amounts.commodities", "Find commodity " << symbol);

  typedef commodity_pool_t::commodities_t::nth_index<1>::type
    commodities_by_name;

  commodities_by_name& name_index = commodities.get<1>();
  commodities_by_name::const_iterator i = name_index.find(symbol);
  if (i != name_index.end())
    return *i;
  else
    return NULL;
}

commodity_t * commodity_pool_t::find(const commodity_t::ident_t ident)
{
  DEBUG("amounts.commodities", "Find commodity by ident " << ident);

  typedef commodity_pool_t::commodities_t::nth_index<0>::type
    commodities_by_ident;

  commodities_by_ident& ident_index = commodities.get<0>();
  return ident_index[ident];
}

commodity_t *
commodity_pool_t::create(const string& symbol, const annotation_t& details)
{
  commodity_t * new_comm = create(symbol);
  if (! new_comm)
    return NULL;

  if (details)
    return find_or_create(*new_comm, details);
  else
    return new_comm;
}

namespace {
  string make_qualified_name(const commodity_t&  comm,
			     const annotation_t& details)
  {
    assert(details);

    if (details.price && details.price->sign() < 0)
      throw_(amount_error, "A commodity's price may not be negative");

    std::ostringstream name;
    comm.print(name);
    annotated_commodity_t::write_annotations(name, details);

    DEBUG("amounts.commodities", "make_qualified_name for "
	  << comm.qualified_symbol << std::endl << details);
    DEBUG("amounts.commodities", "qualified_name is " << name.str());

    return name.str();
  }
}

commodity_t *
commodity_pool_t::find(const string& symbol, const annotation_t& details)
{
  commodity_t * comm = find(symbol);
  if (! comm)
    return NULL;

  if (details) {
    string name = make_qualified_name(*comm, details);

    if (commodity_t * ann_comm = find(name)) {
      assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details);
      return ann_comm;
    }
    return NULL;
  } else {
    return comm;
  }
}

commodity_t *
commodity_pool_t::find_or_create(const string& symbol,
				 const annotation_t& details)
{
  commodity_t * comm = find(symbol);
  if (! comm)
    return NULL;

  if (details)
    return find_or_create(*comm, details);
  else
    return comm;
}

commodity_t *
commodity_pool_t::create(commodity_t&	     comm,
			 const annotation_t& details,
			 const string&	     mapping_key)
{
  assert(comm);
  assert(details);
  assert(! mapping_key.empty());

  std::auto_ptr<commodity_t> commodity
    (new annotated_commodity_t(&comm, details));

  commodity->qualified_symbol = comm.symbol();
  assert(! commodity->qualified_symbol->empty());

  DEBUG("amounts.commodities", "Creating annotated commodity "
	<< "symbol " << commodity->symbol()
	<< " key "   << mapping_key << std::endl << details);

  // Add the fully annotated name to the map, so that this symbol may
  // quickly be found again.
  commodity->ident	  = commodities.size();
  commodity->mapping_key_ = mapping_key;

  commodities.push_back(commodity.get());
  return commodity.release();
}

commodity_t * commodity_pool_t::find_or_create(commodity_t&	   comm,
					       const annotation_t& details)
{
  assert(comm);
  assert(details);

  string name = make_qualified_name(comm, details);
  assert(! name.empty());

  if (commodity_t * ann_comm = find(name)) {
    assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details);
    return ann_comm;
  }
  return create(comm, details, name);
}

} // namespace ledger