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

#include "value.h"

namespace ledger {

intrusive_ptr<value_t::storage_t> value_t::true_value;
intrusive_ptr<value_t::storage_t> value_t::false_value;

value_t::storage_t& value_t::storage_t::operator=(const value_t::storage_t& rhs)
{
  type = rhs.type;

  switch (type) {
  case DATETIME:
    new((datetime_t *) data) datetime_t(*(datetime_t *) rhs.data);
    break;

  case AMOUNT:
    new((amount_t *) data) amount_t(*(amount_t *) rhs.data);
    break;

  case BALANCE:
    *(balance_t **) data = new balance_t(**(balance_t **) rhs.data);
    break;

  case BALANCE_PAIR:
    *(balance_pair_t **) data =
      new balance_pair_t(**(balance_pair_t **) rhs.data);
    break;

  case STRING:
    new((string *) data) string(*(string *) rhs.data);
    break;

  case SEQUENCE:
    *(sequence_t **) data = new sequence_t(**(sequence_t **) rhs.data);
    break;

  default:
    // The rest are fundamental types, which can copy using std::memcpy
    std::memcpy(data, rhs.data, sizeof(data));
    break;
  }

  return *this;
}

void value_t::storage_t::destroy()
{
  switch (type) {
  case AMOUNT:
    reinterpret_cast<amount_t *>(data)->~amount_t();
    break;
  case BALANCE:
    checked_delete(*reinterpret_cast<balance_t **>(data));
    break;
  case BALANCE_PAIR:
    checked_delete(*reinterpret_cast<balance_pair_t **>(data));
    break;
  case STRING:
    reinterpret_cast<string *>(data)->~string();
    break;
  case SEQUENCE:
    checked_delete(*reinterpret_cast<sequence_t **>(data));
    break;
  case POINTER:
    reinterpret_cast<boost::any *>(data)->~any();
    break;

  default:
    break;
  }
  type = VOID;
}

void value_t::initialize()
{
#if 0
  LOGGER("value.initialize");
#endif

  true_value = new storage_t;
  true_value->type = BOOLEAN;
  *reinterpret_cast<bool *>(true_value->data) = true;

  false_value = new storage_t;
  false_value->type = BOOLEAN;
  *reinterpret_cast<bool *>(false_value->data) = false;

  BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(bool));
  BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(datetime_t));
  BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(long));
  BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(amount_t));
  BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(balance_t *));
  BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(balance_pair_t *));
  BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(string));
  BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(sequence_t *));
  BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(boost::any));

#if 0
  DEBUG_(std::setw(3) << std::right << sizeof(bool)
	 << "  sizeof(bool)");
  DEBUG_(std::setw(3) << std::right << sizeof(datetime_t)
	 << "  sizeof(datetime_t)");
  DEBUG_(std::setw(3) << std::right << sizeof(long)
	 << "  sizeof(long)");
  DEBUG_(std::setw(3) << std::right << sizeof(amount_t)
	 << "  sizeof(amount_t)");
  DEBUG_(std::setw(3) << std::right << sizeof(balance_t *)
	 << "  sizeof(balance_t *)");
  DEBUG_(std::setw(3) << std::right << sizeof(balance_pair_t *)
	 << "  sizeof(balance_pair_t *)");
  DEBUG_(std::setw(3) << std::right << sizeof(string)
	 << "  sizeof(string)");
  DEBUG_(std::setw(3) << std::right << sizeof(sequence_t *)
	 << "  sizeof(sequence_t *)");
  DEBUG_(std::setw(3) << std::right << sizeof(boost::any)
	 << "  sizeof(boost::any)");
#endif
}

void value_t::shutdown()
{
  true_value  = intrusive_ptr<storage_t>();
  false_value = intrusive_ptr<storage_t>();
}

void value_t::_dup()
{
  assert(storage);
  if (storage->refc > 1)
    storage = new storage_t(*storage.get());
}

value_t::operator bool() const
{
  switch (type()) {
  case BOOLEAN:
    return as_boolean();
  case INTEGER:
    return as_long();
  case DATETIME:
    return is_valid(as_datetime());
  case AMOUNT:
    return as_amount();
  case BALANCE:
    return as_balance();
  case BALANCE_PAIR:
    return as_balance_pair();
  case STRING:
    return ! as_string().empty();
  case SEQUENCE:
    return ! as_sequence().empty();
  case POINTER:
    return ! as_any_pointer().empty();
  default:
    assert(false);
    break;
  }
  assert(false);
  return 0;
}

bool value_t::to_boolean() const
{
  if (is_boolean()) {
    return as_boolean();
  } else {
    value_t temp(*this);
    temp.in_place_cast(BOOLEAN);
    return temp.as_boolean();
  }
}

long value_t::to_long() const
{
  if (is_long()) {
    return as_long();
  } else {
    value_t temp(*this);
    temp.in_place_cast(INTEGER);
    return temp.as_long();
  }
}

datetime_t value_t::to_datetime() const
{
  if (is_datetime()) {
    return as_datetime();
  } else {
    value_t temp(*this);
    temp.in_place_cast(DATETIME);
    return temp.as_datetime();
  }
}

amount_t value_t::to_amount() const
{
  if (is_amount()) {
    return as_amount();
  } else {
    value_t temp(*this);
    temp.in_place_cast(AMOUNT);
    return temp.as_amount();
  }
}

balance_t value_t::to_balance() const
{
  if (is_balance()) {
    return as_balance();
  } else {
    value_t temp(*this);
    temp.in_place_cast(BALANCE);
    return temp.as_balance();
  }
}

balance_pair_t value_t::to_balance_pair() const
{
  if (is_balance_pair()) {
    return as_balance_pair();
  } else {
    value_t temp(*this);
    temp.in_place_cast(BALANCE_PAIR);
    return temp.as_balance_pair();
  }
}

string value_t::to_string() const
{
  if (is_string()) {
    return as_string();
  } else {
    value_t temp(*this);
    temp.in_place_cast(STRING);
    return temp.as_string();
  }
}

value_t::sequence_t value_t::to_sequence() const
{
  if (is_sequence()) {
    return as_sequence();
  } else {
    value_t temp(*this);
    temp.in_place_cast(SEQUENCE);
    return temp.as_sequence();
  }
}


void value_t::in_place_simplify()
{
  LOGGER("amounts.values.simplify");

  if (is_realzero()) {
    DEBUG_("Zeroing type " << type());
    set_long(0L);
    return;
  }

  if (is_balance_pair() &&
      (! as_balance_pair().cost || as_balance_pair().cost->is_realzero())) {
    DEBUG_("Reducing balance pair to balance");
    in_place_cast(BALANCE);
  }

  if (is_balance() && as_balance().amounts.size() == 1) {
    DEBUG_("Reducing balance to amount");
    DEBUG("ledger.value.reduce", "as a balance it looks like: " << *this);
    in_place_cast(AMOUNT);
    DEBUG("ledger.value.reduce", "as an amount it looks like: " << *this);
  }

#if 0
  if (is_amount() && ! as_amount().has_commodity() &&
      as_amount().fits_in_long()) {
    DEBUG_("Reducing amount to integer");
    in_place_cast(INTEGER);
  }
#endif
}

value_t& value_t::operator+=(const value_t& val)
{
  if (is_string()) {
    if (val.is_string())
      as_string_lval() += val.as_string();
    else
      as_string_lval() += val.to_string();
    return *this;
  }
  else if (is_sequence()) {
    if (val.is_sequence()) {
      sequence_t& seq(as_sequence_lval());
      seq.insert(seq.end(), val.as_sequence().begin(),
		 val.as_sequence().end());
    } else {
      as_sequence_lval().push_back(val);
    }
    return *this;
  }

  switch (type()) {
  case DATETIME:
    switch (val.type()) {
    case INTEGER:
      as_datetime_lval() += date_duration(val.as_long());
      return *this;
    case AMOUNT:
      as_datetime_lval() += date_duration(val.as_amount().to_long());
      return *this;
    default:
      break;
    }
    break;

  case INTEGER:
    switch (val.type()) {
    case INTEGER:
      as_long_lval() += val.as_long();
      return *this;
    case AMOUNT:
      in_place_cast(AMOUNT);
      as_amount_lval() += val.as_amount();
      return *this;
    case BALANCE:
      in_place_cast(BALANCE);
      as_balance_lval() += val.as_balance();
      return *this;
    case BALANCE_PAIR:
      in_place_cast(BALANCE_PAIR);
      as_balance_pair_lval() += val.as_balance_pair();
      return *this;
    default:
      break;
    }
    break;

  case AMOUNT:
    switch (val.type()) {
    case INTEGER:
      if (as_amount().has_commodity()) {
	in_place_cast(BALANCE);
	return *this += val;
      } else {
	as_amount_lval() += val.as_long();
	return *this;
      }
      break;

    case AMOUNT:
      if (as_amount().commodity() != val.as_amount().commodity()) {
	in_place_cast(BALANCE);
	return *this += val;
      } else {
	as_amount_lval() += val.as_amount();
	return *this;
      }
      break;

    case BALANCE:
      in_place_cast(BALANCE);
      as_balance_lval() += val.as_balance();
      return *this;

    case BALANCE_PAIR:
      in_place_cast(BALANCE_PAIR);
      as_balance_pair_lval() += val.as_balance_pair();
      return *this;
    default:
      break;
    }
    break;

  case BALANCE:
    switch (val.type()) {
    case INTEGER:
      as_balance_lval() += val.to_amount();
      return *this;
    case AMOUNT:
      as_balance_lval() += val.as_amount();
      return *this;
    case BALANCE:
      as_balance_lval() += val.as_balance();
      return *this;
    case BALANCE_PAIR:
      in_place_cast(BALANCE_PAIR);
      as_balance_pair_lval() += val.as_balance_pair();
      return *this;
    default:
      break;
    }
    break;

  case BALANCE_PAIR:
    switch (val.type()) {
    case INTEGER:
      as_balance_pair_lval() += val.to_amount();
      return *this;
    case AMOUNT:
      as_balance_pair_lval() += val.as_amount();
      return *this;
    case BALANCE:
      as_balance_pair_lval() += val.as_balance();
      return *this;
    case BALANCE_PAIR:
      as_balance_pair_lval() += val.as_balance_pair();
      return *this;
    default:
      break;
    }
    break;

  default:
    break;
  }

  throw_(value_error, "Cannot add " << val.label() << " to " << label());
  return *this;
}

value_t& value_t::operator-=(const value_t& val)
{
  if (is_sequence()) {
    sequence_t& seq(as_sequence_lval());

    if (val.is_sequence()) {
      for (sequence_t::const_iterator i = val.as_sequence().begin();
	   i != val.as_sequence().end();
	   i++) {
	sequence_t::iterator j = std::find(seq.begin(), seq.end(), *i);
	if (j != seq.end())
	  seq.erase(j);
      }
    } else {
      sequence_t::iterator i = std::find(seq.begin(), seq.end(), val);
      if (i != seq.end())
	seq.erase(i);
    }
    return *this;
  }

  switch (type()) {
  case DATETIME:
    switch (val.type()) {
    case INTEGER:
      as_datetime_lval() -= date_duration(val.as_long());
      return *this;
    case AMOUNT:
      as_datetime_lval() -= date_duration(val.as_amount().to_long());
      return *this;
    default:
      break;
    }
    break;

  case INTEGER:
    switch (val.type()) {
    case INTEGER:
      as_long_lval() -= val.as_long();
      return *this;
    case AMOUNT:
      in_place_cast(AMOUNT);
      as_amount_lval() -= val.as_amount();
      in_place_simplify();
      return *this;
    case BALANCE:
      in_place_cast(BALANCE);
      as_balance_lval() -= val.as_balance();
      in_place_simplify();
      return *this;
    case BALANCE_PAIR:
      in_place_cast(BALANCE_PAIR);
      as_balance_pair_lval() -= val.as_balance_pair();
      in_place_simplify();
      return *this;
    default:
      break;
    }
    break;

  case AMOUNT:
    switch (val.type()) {
    case INTEGER:
      if (as_amount().has_commodity()) {
	in_place_cast(BALANCE);
	*this -= val;
	in_place_simplify();
	return *this;
      } else {
	as_amount_lval() -= val.as_long();
	in_place_simplify();
	return *this;
      }
      break;

    case AMOUNT:
      if (as_amount().commodity() != val.as_amount().commodity()) {
	in_place_cast(BALANCE);
	*this -= val;
	in_place_simplify();
	return *this;
      } else {
	as_amount_lval() -= val.as_amount();
	in_place_simplify();
	return *this;
      }
      break;

    case BALANCE:
      in_place_cast(BALANCE);
      as_balance_lval() -= val.as_balance();
      in_place_simplify();
      return *this;

    case BALANCE_PAIR:
      in_place_cast(BALANCE_PAIR);
      as_balance_pair_lval() -= val.as_balance_pair();
      in_place_simplify();
      return *this;
    default:
      break;
    }
    break;

  case BALANCE:
    switch (val.type()) {
    case INTEGER:
      as_balance_lval() -= val.to_amount();
      in_place_simplify();
      return *this;
    case AMOUNT:
      as_balance_lval() -= val.as_amount();
      in_place_simplify();
      return *this;
    case BALANCE:
      as_balance_lval() -= val.as_balance();
      in_place_simplify();
      return *this;
    case BALANCE_PAIR:
      in_place_cast(BALANCE_PAIR);
      as_balance_pair_lval() -= val.as_balance_pair();
      in_place_simplify();
      return *this;
    default:
      break;
    }
    break;

  case BALANCE_PAIR:
    switch (val.type()) {
    case INTEGER:
      as_balance_pair_lval() -= val.to_amount();
      in_place_simplify();
      return *this;
    case AMOUNT:
      as_balance_pair_lval() -= val.as_amount();
      in_place_simplify();
      return *this;
    case BALANCE:
      as_balance_pair_lval() -= val.as_balance();
      in_place_simplify();
      return *this;
    case BALANCE_PAIR:
      as_balance_pair_lval() -= val.as_balance_pair();
      in_place_simplify();
      return *this;
    default:
      break;
    }
    break;

  default:
    break;
  }

  throw_(value_error, "Cannot subtract " << val.label() << " from " << label());

  return *this;
}

value_t& value_t::operator*=(const value_t& val)
{
  if (is_string()) {
    string temp;
    long count = val.to_long();
    for (long i = 0; i < count; i++)
      temp += as_string();
    set_string(temp);
    return *this;
  }
  else if (is_sequence()) {
    value_t temp;
    long count = val.to_long();
    for (long i = 0; i < count; i++)
      temp += as_sequence();
    return *this = temp;
  }

  switch (type()) {
  case INTEGER:
    switch (val.type()) {
    case INTEGER:
      as_long_lval() *= val.as_long();
      return *this;
    case AMOUNT:
      set_amount(val.as_amount() * as_long());
      return *this;
    default:
      break;
    }
    break;

  case AMOUNT:
    switch (val.type()) {
    case INTEGER:
      as_amount_lval() *= val.as_long();
      return *this;
    case AMOUNT:
      if (as_amount().commodity() == val.as_amount().commodity() ||
	  ! val.as_amount().has_commodity()) {
	as_amount_lval() *= val.as_amount();
	return *this;
      }
      break;
    default:
      break;
    }
    break;

  case BALANCE:
    switch (val.type()) {
    case INTEGER:
      as_balance_lval() *= val.as_long();
      return *this;
    case AMOUNT:
      if (! val.as_amount().has_commodity()) {
	as_balance_lval() *= val.as_amount();
	return *this;
      }
      break;
    default:
      break;
    }
    break;

  case BALANCE_PAIR:
    switch (val.type()) {
    case INTEGER:
      as_balance_pair_lval() *= val.as_long();
      return *this;
    case AMOUNT:
      if (! val.as_amount().has_commodity()) {
	as_balance_pair_lval() *= val.as_amount();
	return *this;
      }
      break;
    default:
      break;
    }
    break;

  default:
    break;
  }

  throw_(value_error, "Cannot multiply " << label() << " with " << val.label());

  return *this;
}

value_t& value_t::operator/=(const value_t& val)
{
  switch (type()) {
  case INTEGER:
    switch (val.type()) {
    case INTEGER:
      as_long_lval() /= val.as_long();
      return *this;
    case AMOUNT:
      set_amount(val.as_amount() / as_long());
      return *this;
    default:
      break;
    }
    break;

  case AMOUNT:
    switch (val.type()) {
    case INTEGER:
      as_amount_lval() /= val.as_long();
      return *this;

    case AMOUNT:
      if (as_amount().commodity() == val.as_amount().commodity() ||
	  ! val.as_amount().has_commodity()) {
	as_amount_lval() /= val.as_amount();
	return *this;
      }
      break;
    default:
      break;
    }
    break;

  case BALANCE:
    switch (val.type()) {
    case INTEGER:
      as_balance_lval() /= val.as_long();
      return *this;
    case AMOUNT:
      if (! val.as_amount().has_commodity()) {
	as_balance_lval() /= val.as_amount();
	return *this;
      }
      break;
    default:
      break;
    }
    break;

  case BALANCE_PAIR:
    switch (val.type()) {
    case INTEGER:
      as_balance_pair_lval() /= val.as_long();
      return *this;
    case AMOUNT:
      if (! val.as_amount().has_commodity()) {
	as_balance_pair_lval() /= val.as_amount();
	return *this;
      }
      break;
    default:
      break;
    }
    break;

  default:
    break;
  }

  throw_(value_error, "Cannot divide " << label() << " by " << val.label());

  return *this;
}


bool value_t::operator==(const value_t& val) const
{
  switch (type()) {
  case BOOLEAN:
    if (val.is_boolean())
      return as_boolean() == val.as_boolean();
    break;

  case DATETIME:
    if (val.is_datetime())
      return as_datetime() == val.as_datetime();
    break;

  case INTEGER:
    switch (val.type()) {
    case INTEGER:
      return as_long() == val.as_long();
    case AMOUNT:
      return val.as_amount() == to_amount();
    case BALANCE:
      return val.as_balance() == to_amount();
    case BALANCE_PAIR:
      return val.as_balance_pair() == to_amount();
    default:
      break;
    }
    break;

  case AMOUNT:
    switch (val.type()) {
    case INTEGER:
      return as_amount() == val.as_long();
    case AMOUNT:
      return as_amount() == val.as_amount();
    case BALANCE:
      return val.as_balance() == as_amount();
    case BALANCE_PAIR:
      return val.as_balance_pair() == as_amount();
    default:
      break;
    }
    break;

  case BALANCE:
    switch (val.type()) {
    case INTEGER:
      return as_balance() == val.to_amount();
    case AMOUNT:
      return as_balance() == val.as_amount();
    case BALANCE:
      return as_balance() == val.as_balance();
    case BALANCE_PAIR:
      return val.as_balance_pair() == as_balance();
    default:
      break;
    }
    break;

  case BALANCE_PAIR:
    switch (val.type()) {
    case INTEGER:
      return as_balance_pair() == val.to_amount();
    case AMOUNT:
      return as_balance_pair() == val.as_amount();
    case BALANCE:
      return as_balance_pair() == val.as_balance();
    case BALANCE_PAIR:
      return as_balance_pair() == val.as_balance_pair();
    default:
      break;
    }
    break;

  case STRING:
    if (val.is_string())
      return as_string() == val.as_string();
    break;

  case SEQUENCE:
    if (val.is_sequence())
      return as_sequence() == val.as_sequence();
    break;

  default:
    break;
  }

  throw_(value_error, "Cannot compare " << label() << " to " << val.label());

  return *this;
}

bool value_t::operator<(const value_t& val) const
{
  switch (type()) {
  case DATETIME:
    if (val.is_datetime())
      return as_datetime() < val.as_datetime();
    break;

  case INTEGER:
    switch (val.type()) {
    case INTEGER:
      return as_long() < val.as_long();
    case AMOUNT:
      return val.as_amount() < as_long();
    default:
      break;
    }
    break;

  case AMOUNT:
    switch (val.type()) {
    case INTEGER:
      return as_amount() < val.as_long();
    case AMOUNT:
      return as_amount() < val.as_amount();
    default:
      break;
    }
    break;

  case STRING:
    if (val.is_string())
      return as_string() < val.as_string();
    break;

  default:
    break;
  }

  throw_(value_error, "Cannot compare " << label() << " to " << val.label());

  return *this;
}

#if 0
bool value_t::operator>(const value_t& val) const
{
  switch (type()) {
  case DATETIME:
    if (val.is_datetime())
      return as_datetime() > val.as_datetime();
    break;

  case INTEGER:
    switch (val.type()) {
    case INTEGER:
      return as_long() > val.as_long();
    case AMOUNT:
      return val.as_amount() > as_long();
    default:
      break;
    }
    break;

  case AMOUNT:
    switch (val.type()) {
    case INTEGER:
      return as_amount() > val.as_long();
    case AMOUNT:
      return as_amount() > val.as_amount();
    default:
      break;
    }
    break;

  case STRING:
    if (val.is_string())
      return as_string() > val.as_string();
    break;

  default:
    break;
  }

  throw_(value_error, "Cannot compare " << label() << " to " << val.label());

  return *this;
}
#endif

void value_t::in_place_cast(type_t cast_type)
{
  if (type() == cast_type)
    return;

  if (cast_type == BOOLEAN) {
    set_boolean(bool(*this));
    return;
  }
  else if (cast_type == SEQUENCE) {
    sequence_t temp;
    if (! is_null())
      temp.push_back(*this);
    set_sequence(temp);
    return;
  }

  switch (type()) {
  case BOOLEAN:
    switch (cast_type) {
    case STRING:
      set_string(as_boolean() ? "true" : "false");
      return;
    default:
      break;
    }
    break;

  case INTEGER:
    switch (cast_type) {
    case AMOUNT:
      set_amount(as_long());
      return;
    case BALANCE:
      set_balance(to_amount());
      return;
    case BALANCE_PAIR:
      set_balance_pair(to_amount());
      return;
    case STRING:
      set_string(lexical_cast<string>(as_long()));
      return;
    default:
      break;
    }
    break;

  case AMOUNT: {
    const amount_t& amt(as_amount());
    switch (cast_type) {
    case INTEGER:
      if (amt.is_null())
	set_long(0L);
      else
	set_long(as_amount().to_long());
      return;
    case BALANCE:
      if (amt.is_null())
	set_balance(balance_t());
      else
	set_balance(as_amount()); // creates temporary
      return;
    case BALANCE_PAIR:
      if (amt.is_null())
	set_balance_pair(balance_pair_t());
      else
	set_balance_pair(as_amount()); // creates temporary
      return;
    case STRING:
      if (amt.is_null())
	set_string("");
      else
	set_string(as_amount().to_string());
      return;
    default:
      break;
    }
    break;
  }

  case BALANCE:
    switch (cast_type) {
    case AMOUNT: {
      const balance_t& temp(as_balance());
      if (temp.amounts.size() == 1) {
	// Because we are changing the current balance value to an amount
	// value, and because set_amount takes a reference (and that memory is
	// about to be repurposed), we must pass in a copy.
	set_amount(amount_t((*temp.amounts.begin()).second));
	return;
      }
      else if (temp.amounts.size() == 0) {
	set_amount(0L);
	return;
      }
      else {
	throw_(value_error, "Cannot convert " << label() <<
	       " with multiple commodities to " << label(cast_type));
      }
      break;
    }
    case BALANCE_PAIR:
      set_balance_pair(as_balance());
      return;
    default:
      break;
    }
    break;

  case BALANCE_PAIR:
    switch (cast_type) {
    case AMOUNT: {
      const balance_t& temp(as_balance_pair().quantity());
      if (temp.amounts.size() == 1) {
	set_amount(amount_t((*temp.amounts.begin()).second));
	return;
      }
      else if (temp.amounts.size() == 0) {
	set_amount(0L);
	return;
      }
      else {
	throw_(value_error, "Cannot convert " << label() <<
	       " with multiple commodities to " << label(cast_type));
      }
      break;
    }
    case BALANCE:
      // A temporary is required, becaues set_balance is going to wipe us out
      // before assigned the value passed in.
      set_balance(balance_t(as_balance_pair().quantity()));
      return;
    default:
      break;
    }
    break;

  case STRING:
    switch (cast_type) {
    case INTEGER: {
      if (all(as_string(), is_digit())) {
	set_long(lexical_cast<long>(as_string()));
	return;
      } else {
	throw_(value_error,
	       "Cannot convert string '" << *this << "' to an integer");
      }
      break;
    }
    case AMOUNT:
      set_amount(amount_t(as_string()));
      return;
    default:
      break;
    }
    break;

  default:
    break;
  }

  throw_(value_error,
	 "Cannot convert " << label() << " to " << label(cast_type));
}

void value_t::in_place_negate()
{
  switch (type()) {
  case BOOLEAN:
    set_boolean(! as_boolean());
    return;
  case INTEGER:
  case DATETIME:
    set_long(- as_long());
    return;
  case AMOUNT:
    as_amount_lval().in_place_negate();
    return;
  case BALANCE:
    as_balance_lval().in_place_negate();
    return;
  case BALANCE_PAIR:
    as_balance_pair_lval().in_place_negate();
    return;
  default:
    break;
  }

  throw_(value_error, "Cannot negate " << label());
}

bool value_t::is_realzero() const
{
  switch (type()) {
  case BOOLEAN:
    return ! as_boolean();
  case INTEGER:
    return as_long() == 0;
  case DATETIME:
    return ! is_valid(as_datetime());
  case AMOUNT:
    return as_amount().is_realzero();
  case BALANCE:
    return as_balance().is_realzero();
  case BALANCE_PAIR:
    return as_balance_pair().is_realzero();
  case STRING:
    return as_string().empty();
  case SEQUENCE:
    return as_sequence().empty();

  case POINTER:
    return as_any_pointer().empty();

  default:
    assert(false);
    break;
  }
  assert(false);
  return true;
}

bool value_t::is_zero() const
{
  switch (type()) {
  case BOOLEAN:
    return ! as_boolean();
  case INTEGER:
    return as_long() == 0;
  case DATETIME:
    return ! is_valid(as_datetime());
  case AMOUNT:
    return as_amount().is_zero();
  case BALANCE:
    return as_balance().is_zero();
  case BALANCE_PAIR:
    return as_balance_pair().is_zero();
  case STRING:
    return as_string().empty();
  case SEQUENCE:
    return as_sequence().empty();

  case POINTER:
    return as_any_pointer().empty();

  default:
    assert(false);
    break;
  }
  assert(false);
  return true;
}

value_t value_t::value(const optional<datetime_t>& moment) const
{
  switch (type()) {
  case INTEGER:
    return *this;

  case AMOUNT: {
    if (optional<amount_t> val = as_amount().value(moment))
      return *val;
    return false;
  }
  case BALANCE: {
    if (optional<balance_t> bal = as_balance().value(moment))
      return *bal;
    return false;
  }
  case BALANCE_PAIR: {
    if (optional<balance_t> bal_pair =
	as_balance_pair().quantity().value(moment))
      return *bal_pair;
    return false;
  }

  default:
    break;
  }

  throw_(value_error, "Cannot find the value of " << label());
  return NULL_VALUE;
}

void value_t::in_place_reduce()
{
  switch (type()) {
  case INTEGER:
    return;
  case AMOUNT:
    as_amount_lval().in_place_reduce();
    return;
  case BALANCE:
    as_balance_lval().in_place_reduce();
    return;
  case BALANCE_PAIR:
    as_balance_pair_lval().in_place_reduce();
    return;
  default:
    break;
  }

  throw_(value_error, "Cannot reduce " << label());
}

value_t value_t::abs() const
{
  switch (type()) {
  case INTEGER: {
    long val = as_long();
    if (val < 0)
      return - val;
    return val;
  }
  case AMOUNT:
    return as_amount().abs();
  case BALANCE:
    return as_balance().abs();
  case BALANCE_PAIR:
    return as_balance_pair().abs();
  default:
    break;
  }

  throw_(value_error, "Cannot abs " << label());
  return NULL_VALUE;
}

value_t value_t::round() const
{
  switch (type()) {
  case INTEGER:
    return *this;
  case AMOUNT:
    return as_amount().round();
  case BALANCE:
    return as_balance().round();
  case BALANCE_PAIR:
    return as_balance_pair().round();
  default:
    break;
  }

  throw_(value_error, "Cannot round " << label());
  return NULL_VALUE;
}

value_t value_t::unround() const
{
  switch (type()) {
  case INTEGER:
    return *this;
  case AMOUNT:
    return as_amount().unround();
  default:
    break;
  }

  throw_(value_error, "Cannot unround " << label());
  return NULL_VALUE;
}

value_t value_t::annotated_price() const
{
  switch (type()) {
  case AMOUNT: {
    optional<amount_t> temp = as_amount().annotation_details().price;
    if (! temp)
      return false;
    return *temp;
  }

  default:
    break;
  }

  throw_(value_error, "Cannot find the annotated price of " << label());
  return NULL_VALUE;
}

value_t value_t::annotated_date() const
{
  switch (type()) {
  case DATETIME:
    return *this;

  case AMOUNT: {
    optional<datetime_t> temp = as_amount().annotation_details().date;
    if (! temp)
      return false;
    return *temp;
  }

  default:
    break;
  }

  throw_(value_error, "Cannot find the annotated date of " << label());
  return NULL_VALUE;
}

value_t value_t::annotated_tag() const
{
  switch (type()) {
  case DATETIME:
    return *this;

  case AMOUNT: {
    optional<string> temp = as_amount().annotation_details().tag;
    if (! temp)
      return false;
    return value_t(*temp, true);
  }

  default:
    break;
  }

  throw_(value_error, "Cannot find the annotated tag of " << label());
  return NULL_VALUE;
}

value_t value_t::strip_annotations(const bool keep_price,
				   const bool keep_date,
				   const bool keep_tag) const
{
  switch (type()) {
  case VOID:
  case BOOLEAN:
  case INTEGER:
  case DATETIME:
  case STRING:
  case POINTER:
    return *this;

  case SEQUENCE: {
    sequence_t temp;
    foreach (const value_t& value, as_sequence())
      temp.push_back(value.strip_annotations(keep_price, keep_date, keep_tag));
    return temp;
  }

  case AMOUNT:
    return as_amount().strip_annotations(keep_price, keep_date, keep_tag);
  case BALANCE:
    return as_balance().strip_annotations(keep_price, keep_date, keep_tag);
  case BALANCE_PAIR:
    return as_balance_pair().quantity().strip_annotations(keep_price, keep_date,
							  keep_tag);

  default:
    assert(false);
    break;
  }
  assert(false);
  return NULL_VALUE;
}

value_t value_t::cost() const
{
  switch (type()) {
  case INTEGER:
  case AMOUNT:
  case BALANCE:
    return *this;

  case BALANCE_PAIR:
    assert(as_balance_pair().cost);
    if (as_balance_pair().cost)
      return *(as_balance_pair().cost);
    else
      return as_balance_pair().quantity();

  default:
    break;
  }

  throw_(value_error, "Cannot find the cost of " << label());
  return NULL_VALUE;
}

value_t& value_t::add(const amount_t& amount, const optional<amount_t>& tcost)
{
  switch (type()) {
  case INTEGER:
  case AMOUNT:
    if (tcost) {
      in_place_cast(BALANCE_PAIR);
      return add(amount, tcost);
    }
    else if ((is_amount() &&
	      as_amount().commodity() != amount.commodity()) ||
	     (! is_amount() && amount.commodity())) {
      in_place_cast(BALANCE);
      return add(amount, tcost);
    }
    else if (! is_amount()) {
      in_place_cast(AMOUNT);
    }
    *this += amount;
    break;

  case BALANCE:
    if (tcost) {
      in_place_cast(BALANCE_PAIR);
      return add(amount, tcost);
    }
    *this += amount;
    break;

  case BALANCE_PAIR:
    as_balance_pair_lval().add(amount, tcost);
    break;

  default:
    break;
  }

  throw_(value_error, "Cannot add an amount to " << label());
  return *this;
}

void value_t::dump(std::ostream& out, const int first_width,
		   const int latter_width) const
{
  switch (type()) {
  case VOID:
    out << "VOID";
    break;

  case BOOLEAN:
    out << as_boolean();
    break;

  case DATETIME:
    out << as_datetime();
    break;

  case INTEGER:
    out << as_long();
    break;

  case AMOUNT:
    out << as_amount();
    break;

  case STRING:
    out << as_string();
    break;

  case POINTER:
    out << boost::unsafe_any_cast<const void *>(&as_any_pointer());
    break;

  case SEQUENCE: {
    out << '(';
    bool first = true;
    foreach (const value_t& value, as_sequence()) {
      if (first)
	first = false;
      else
	out << ", ";

      value.dump(out, first_width, latter_width);
    }
    out << ')';
    break;
  }

  case BALANCE:
    as_balance().print(out, first_width, latter_width);
    break;
  case BALANCE_PAIR:
    as_balance_pair().print(out, first_width, latter_width);
    break;
  default:
    assert(false);
    break;
  }
}

void value_t::print(std::ostream& out, const bool relaxed) const
{
  switch (type()) {
  case VOID:
    out << "";
    break;

  case BOOLEAN:
    if (as_boolean())
      out << "true";
    else
      out << "false";
    break;

  case INTEGER:
    out << as_long();
    break;

  case AMOUNT:
    if (! relaxed)
      out << '{';
    out << as_amount();
    if (! relaxed)
      out << '}';
    break;

  case BALANCE:
  case BALANCE_PAIR:
    assert(false);
    break;

  case DATETIME:
    out << '[' << format_datetime(as_datetime()) << ']';
    break;

  case STRING:
    out << '"' << as_string() << '"';
    break;

  case POINTER:
    assert(false);
    break;

  case SEQUENCE: {
    out << '(';
    bool first = true;
    foreach (const value_t& value, as_sequence()) {
      if (first)
	first = false;
      else
	out << ", ";

      value.print(out, relaxed);
    }
    out << ')';
    break;
  }
  }
}

bool value_t::valid() const
{
  switch (type()) {
  case AMOUNT:
    return as_amount().valid();
  case BALANCE:
    return as_balance().valid();
  case BALANCE_PAIR:
    return as_balance_pair().valid();
  default:
    break;
  }
  return true;
}

void value_context::describe(std::ostream& out) const throw()
{
  if (! desc.empty())
    out << desc << std::endl;

  out << std::right;
  out.width(20);
  bal.print(out);
  out << std::endl;
}

} // namespace ledger