#ifndef NUMBER_HPP_
#define NUMBER_HPP_

#include <ostream>
#include <string>

#include "rational.hpp"

/// Forward declaration of the implementation base class.
class number_impl;

/** Number class represents a numeric value.
 * Numbers of different types are supported, as well as conversions between types.
 */
class number
{
public:
  /** Construct a void value.
   * The calculator does not currently use void, but I can't think of a better
   * value for a default number constructor.
   */
  number();
  /** Construct an @c int value.
   * For the sake of simplicitly, all integers are stored as @c long values.
   * This overloaded operator exists so simple uses of, say, number(0),
   * are not ambiguous.
   * @param x The value of the number
   */
  number(int x);
  /** Construct a @c long value.
   * For the sake of simplicitly, all integers are stored as @c long values.
   * @param x The value of the number
   */
  number(long x);
  /** Construct a floating-point value.
   * For the sake of simplicitly, all floating-point numbers
   * are stored as @c double values.
   * @param x The value of the number
   */
  number(double x);
  /** Construct a rational number.
   * @param x The value of the number
   */
  number(rational<long> const& x);
  /** Copy constructor.
   * The actual number_impl is not copied; only the reference count is incremented.
   * @param n The number to copy
   */
  number(number const& n);
  /** Copy assignment operator.
   * The actual number_impl is not copied; only the reference count is incremented.
   * @param n The number to copy
   */
  number& operator=(number const& n);
  /** Construct a number by reading from a library file. */
  number(std::istream& stream);
  /** Destructor decrements the reference count.
   */
  ~number();

  /** Save a number to a library file.
   * @param stream the library stream
   */
  void save(std::ostream& stream)  const;

  /** Test for equality.
   * Coerce @c this and @p b to the same type, then compare.
   * @param b the number to compare
   * @return true if @c *this is numerically equal to @p b
   */
  bool equals(number b);

  /** Test for less-than.
   * Coerce @c this and @p b to the same type, then compare.
   * @param b the number to compare
   * @return true if @c *this is numerically less than @p b
   */
  bool less(number b);

  /** Coerce @c this and @p rhs to the same arithmetic type.
   * Promote the "smaller" number to the "larger" type.
   * - number_long promotes to number_double
   * - everything promotes to void
   * @param[in,out] rhs the right-hand-side operand
   */
  void coerce(number& rhs);

  /** Convert this number to a human-readable string.
   */
  std::string to_string() const;

  /** Print a number to a stream.
   * @param stream The output stream
   */
  void print(std::ostream& stream);

  /** Addition operator.
   * Coerce @c this and @p rhs to the same type, then add @p rhs and @c this.
   * @param rhs The right-hand-side operand
   * @return @c *this + @p rhs
   */
  number operator+(number rhs);
  /** Subtraction operator.
   * Coerce @c this and @p rhs to the same type, then subtract @p rhs from @c this.
   * @param rhs The right-hand-side operand
   * @return @c *this - @p rhs
   */
  number operator-(number rhs);
  /** Multiplication operator.
   * Coerce @c this and @p rhs to the same type, then multiply @c this by @p rhs.
   * @param rhs The right-hand-side operand
   * @return @c *this * @p rhs
   */
  number operator*(number rhs);
  /** Division operator.
   * Coerce @c this and @p rhs to the same type, then divide @c this by @p rhs.
   * @param rhs The right-hand-side operand
   * @return @c *this / @p rhs
   * @throw calc_error if @p rhs == 0.
   */
  number operator/(number rhs);

private:
  /** Private constructor to create a number from a number_impl.
   * @param ptr Pointer to a number_impl.
   */
  number(number_impl* ptr);

  number_impl* pimpl_;
};

/** Compare two number objects for numeric equality.
 * The objects are coerced to a common type before comparing.
 * @param a The left-hand operand
 * @param b The right-hand operand
 * @return true if a == b (watch out for floating-point comparisons).
 */
inline bool operator==(number a, number b)
{
  return a.equals(b);
}

/** Compare two number objects for numeric inequality.
 * The objects are coerced to a common type before comparing.
 * @param a The left-hand operand
 * @param b The right-hand operand
 * @return true if a != b (watch out for floating-point comparisons).
 */
inline bool operator!=(number a, number b)
{
  return not (a == b);
}

/** Compare two number objects for numeric less-than.
 * The objects are coerced to a common type before comparing.
 * @param a The left-hand operand
 * @param b The right-hand operand
 * @return true if a \< b (watch out for floating-point comparisons).
 */
inline bool operator<(number a, number b)
{
  return a.less(b);
}

/** Compare two number objects for numeric less-than-or-equal.
 * The objects are coerced to a common type before comparing.
 * @param a The left-hand operand
 * @param b The right-hand operand
 * @return true if a \<= b (watch out for floating-point comparisons).
 */
inline bool operator<=(number a, number b)
{
  return not(b < a);
}

/** Compare two number objects for numeric greater-than.
 * The objects are coerced to a common type before comparing.
 * @param a The left-hand operand
 * @param b The right-hand operand
 * @return true if a > b (watch out for floating-point comparisons).
 */
inline bool operator>(number a, number b)
{
  return b < a;
}

/** Compare two number objects for numeric greater-than-or-equal.
 * The objects are coerced to a common type before comparing.
 * @param a The left-hand operand
 * @param b The right-hand operand
 * @return true if a >= b (watch out for floating-point comparisons).
 */
inline bool operator>=(number a, number b)
{
  return not (a < b);
}

/** Negate a number.
 * @param n the number
 * @return -@p n
 */
inline number operator-(number n)
{
  return number(0) - n;
}

/** Print a number
 * @param stream The output stream
 * @param n The number to print
 */
inline std::ostream& operator<<(std::ostream& stream, number n)
{
  n.print(stream);
  return stream;
}

#endif
