/*
******************************************************************************
*
*   Copyright (C) 1997-2012, International Business Machines
*   Corporation and others.  All Rights Reserved.
*
******************************************************************************
*
* File DIGITLST.H
*
* Modification History:
*
*   Date        Name        Description
*   02/25/97    aliu        Converted from java.
*   03/21/97    clhuang     Updated per C++ implementation.
*   04/15/97    aliu        Changed MAX_COUNT to DBL_DIG.  Changed Digit to char.
*   09/09/97    aliu        Adapted for exponential notation support.
*   08/02/98    stephen     Added nearest/even rounding
*   06/29/99    stephen     Made LONG_DIGITS a macro to satisfy SUN compiler
*   07/09/99    stephen     Removed kMaxCount (unused, for HP compiler)
******************************************************************************
*/
 
#ifndef DIGITLST_H
#define DIGITLST_H
 
#include "unicode/uobject.h"

#if !UCONFIG_NO_FORMATTING
#include "unicode/decimfmt.h"
#include <float.h>
#include "decContext.h"
#include "decNumber.h"
#include "cmemory.h"

// Decimal digits in a 64-bit int
#define INT64_DIGITS 19

typedef enum EDigitListValues {
    MAX_DBL_DIGITS = DBL_DIG,
    MAX_I64_DIGITS = INT64_DIGITS,
    MAX_DIGITS = MAX_I64_DIGITS,
    MAX_EXPONENT = DBL_DIG,
    DIGIT_PADDING = 3,
    DEFAULT_DIGITS = 40,   // Initial storage size, will grow as needed.

     // "+." + fDigits + "e" + fDecimalAt
    MAX_DEC_DIGITS = MAX_DIGITS + DIGIT_PADDING + MAX_EXPONENT
} EDigitListValues;

U_NAMESPACE_BEGIN

class CharString;

// Export an explicit template instantiation of the MaybeStackHeaderAndArray that
//    is used as a data member of DigitList.
//
//    MSVC requires this, even though it should not be necessary. 
//    No direct access to the MaybeStackHeaderAndArray leaks out of the i18n library.
//
//    Macintosh produces duplicate definition linker errors with the explicit template
//    instantiation.
//
#if !U_PLATFORM_IS_DARWIN_BASED
template class U_I18N_API MaybeStackHeaderAndArray<decNumber, char, DEFAULT_DIGITS>;
#endif


enum EStackMode { kOnStack };

enum EFastpathBits { kFastpathOk = 1, kNoDecimal = 2 };

/**
 * Digit List is actually a Decimal Floating Point number.
 * The original implementation has been replaced by a thin wrapper onto a 
 * decimal number from the decNumber library.
 *
 * The original DigitList API has been retained, to minimize the impact of
 * the change on the rest of the ICU formatting code.
 *
 * The change to decNumber enables support for big decimal numbers, and
 * allows rounding computations to be done directly in decimal, avoiding
 * extra, and inaccurate, conversions to and from doubles.
 *
 * Original DigitList comments:
 *
 * Digit List utility class. Private to DecimalFormat.  Handles the transcoding
 * between numeric values and strings of characters.  Only handles
 * non-negative numbers.  The division of labor between DigitList and
 * DecimalFormat is that DigitList handles the radix 10 representation
 * issues; DecimalFormat handles the locale-specific issues such as
 * positive/negative, grouping, decimal point, currency, and so on.
 * <P>
 * A DigitList is really a representation of a floating point value.
 * It may be an integer value; we assume that a double has sufficient
 * precision to represent all digits of a long.
 * <P>
 * The DigitList representation consists of a string of characters,
 * which are the digits radix 10, from '0' to '9'.  It also has a radix
 * 10 exponent associated with it.  The value represented by a DigitList
 * object can be computed by mulitplying the fraction f, where 0 <= f < 1,
 * derived by placing all the digits of the list to the right of the
 * decimal point, by 10^exponent.
 *
 * --------
 *
 * DigitList vs. decimalNumber:
 *
 *    DigitList stores digits with the most significant first.
 *    decNumber stores digits with the least significant first.
 *
 *    DigitList, decimal point is before the most significant.
 *    decNumber, decimal point is after the least signficant digit.
 *
 *       digitList:    0.ddddd * 10 ^ exp
 *       decNumber:    ddddd. * 10 ^ exp
 *
 *       digitList exponent = decNumber exponent + digit count
 *
 *    digitList, digits are platform invariant chars, '0' - '9'
 *    decNumber, digits are binary, one per byte, 0 - 9.
 *
 *       (decNumber library is configurable in how digits are stored, ICU has configured
 *        it this way for convenience in replacing the old DigitList implementation.)
 */
class U_I18N_API DigitList : public UMemory { // Declare external to make compiler happy
public:

    DigitList();
    ~DigitList();

    /* copy constructor
     * @param DigitList The object to be copied.
     * @return the newly created object. 
     */
    DigitList(const DigitList&); // copy constructor

    /* assignment operator
     * @param DigitList The object to be copied.
     * @return the newly created object.
     */
    DigitList& operator=(const DigitList&);  // assignment operator

    /**
     * Return true if another object is semantically equal to this one.
     * @param other The DigitList to be compared for equality
     * @return true if another object is semantically equal to this one.
     * return false otherwise.
     */
    UBool operator==(const DigitList& other) const;

    int32_t  compare(const DigitList& other);


    inline UBool operator!=(const DigitList& other) const { return !operator==(other); }

    /**
     * Clears out the digits.
     * Use before appending them.
     * Typically, you set a series of digits with append, then at the point
     * you hit the decimal point, you set myDigitList.fDecimalAt = myDigitList.fCount;
     * then go on appending digits.
     */
    void clear(void);

    /**
     *  Remove, by rounding, any fractional part of the decimal number,
     *  leaving an integer value.
     */
    void toIntegralValue();
    
    /**
     * Appends digits to the list. 
     *    CAUTION:  this function is not recommended for new code.
     *              In the original DigitList implementation, decimal numbers were
     *              parsed by appending them to a digit list as they were encountered.
     *              With the revamped DigitList based on decNumber, append is very
     *              inefficient, and the interaction with the exponent value is confusing.
     *              Best avoided.
     *              TODO:  remove this function once all use has been replaced.
     *              TODO:  describe alternative to append()
     * @param digit The digit to be appended.
     */
    void append(char digit);

    /**
     * Utility routine to get the value of the digit list
     * Returns 0.0 if zero length.
     * @return the value of the digit list.
     */
    double getDouble(void) const;

    /**
     * Utility routine to get the value of the digit list
     * Make sure that fitsIntoLong() is called before calling this function.
     * Returns 0 if zero length.
     * @return the value of the digit list, return 0 if it is zero length
     */
    int32_t getLong(void) /*const*/;

    /**
     * Utility routine to get the value of the digit list
     * Make sure that fitsIntoInt64() is called before calling this function.
     * Returns 0 if zero length.
     * @return the value of the digit list, return 0 if it is zero length
     */
    int64_t getInt64(void) /*const*/;

    /**
     *  Utility routine to get the value of the digit list as a decimal string.
     */
    void getDecimal(CharString &str, UErrorCode &status);

    /**
     * Return true if the number represented by this object can fit into
     * a long.
     * @param ignoreNegativeZero True if negative zero is ignored.
     * @return true if the number represented by this object can fit into
     * a long, return false otherwise.
     */
    UBool fitsIntoLong(UBool ignoreNegativeZero) /*const*/;

    /**
     * Return true if the number represented by this object can fit into
     * an int64_t.
     * @param ignoreNegativeZero True if negative zero is ignored.
     * @return true if the number represented by this object can fit into
     * a long, return false otherwise.
     */
    UBool fitsIntoInt64(UBool ignoreNegativeZero) /*const*/;

    /**
     * Utility routine to set the value of the digit list from a double.
     * @param source The value to be set
     */
    void set(double source);

    /**
     * Utility routine to set the value of the digit list from a long.
     * If a non-zero maximumDigits is specified, no more than that number of
     * significant digits will be produced.
     * @param source The value to be set
     */
    void set(int32_t source);

    /**
     * Utility routine to set the value of the digit list from an int64.
     * If a non-zero maximumDigits is specified, no more than that number of
     * significant digits will be produced.
     * @param source The value to be set
     */
    void set(int64_t source);

    /**
     * Utility routine to set the value of the digit list from an int64.
     * Does not set the decnumber unless requested later
     * If a non-zero maximumDigits is specified, no more than that number of
     * significant digits will be produced.
     * @param source The value to be set
     */
    void setInteger(int64_t source);

   /**
     * Utility routine to set the value of the digit list from a decimal number
     * string.
     * @param source The value to be set.  The string must be nul-terminated.
     * @param fastpathBits special flags for fast parsing
     */
    void set(const StringPiece &source, UErrorCode &status, uint32_t fastpathBits = 0);

    /**
     * Multiply    this = this * arg
     *    This digitlist will be expanded if necessary to accomodate the result.
     *  @param arg  the number to multiply by.
     */
    void mult(const DigitList &arg, UErrorCode &status);

    /**
     *   Divide    this = this / arg
     */
    void div(const DigitList &arg, UErrorCode &status);

    //  The following functions replace direct access to the original DigitList implmentation
    //  data structures.

    void setRoundingMode(DecimalFormat::ERoundingMode m); 

    /** Test a number for zero.
     * @return  TRUE if the number is zero
     */
    UBool isZero(void) const;

    /** Test for a Nan
     * @return  TRUE if the number is a NaN
     */
    UBool isNaN(void) const {return decNumberIsNaN(fDecNumber);}

    UBool isInfinite() const {return decNumberIsInfinite(fDecNumber);}

    /**  Reduce, or normalize.  Removes trailing zeroes, adjusts exponent appropriately. */
    void     reduce();

    /**  Remove trailing fraction zeros, adjust exponent accordingly. */
    void     trim();

    /** Set to zero */
    void     setToZero() {uprv_decNumberZero(fDecNumber);}

    /** get the number of digits in the decimal number */
    int32_t  digits() const {return fDecNumber->digits;}

    /**
     * Round the number to the given number of digits.
     * @param maximumDigits The maximum number of digits to be shown.
     * Upon return, count will be less than or equal to maximumDigits.
     */
    void round(int32_t maximumDigits);

    void roundFixedPoint(int32_t maximumFractionDigits);

    /** Ensure capacity for digits.  Grow the storage if it is currently less than
     *      the requested size.   Capacity is not reduced if it is already greater
     *      than requested.
     */
    void  ensureCapacity(int32_t  requestedSize, UErrorCode &status); 

    UBool    isPositive(void) const { return decNumberIsNegative(fDecNumber) == 0;}
    void     setPositive(UBool s); 

    void     setDecimalAt(int32_t d);
    int32_t  getDecimalAt();

    void     setCount(int32_t c);
    int32_t  getCount() const;
    
    /**
     * Set the digit in platform (invariant) format, from '0'..'9'
     * @param i index of digit
     * @param v digit value, from '0' to '9' in platform invariant format
     */
    void     setDigit(int32_t i, char v);

    /**
     * Get the digit in platform (invariant) format, from '0'..'9' inclusive
     * @param i index of digit
     * @return invariant format of the digit
     */
    char     getDigit(int32_t i);


    /**
     * Get the digit's value, as an integer from 0..9 inclusive.
     * Note that internally this value is a decNumberUnit, but ICU configures it to be a uint8_t.
     * @param i index of digit
     * @return value of that digit
     */
    uint8_t     getDigitValue(int32_t i);


private:
    /*
     * These data members are intentionally public and can be set directly.
     *<P>
     * The value represented is given by placing the decimal point before
     * fDigits[fDecimalAt].  If fDecimalAt is < 0, then leading zeros between
     * the decimal point and the first nonzero digit are implied.  If fDecimalAt
     * is > fCount, then trailing zeros between the fDigits[fCount-1] and the
     * decimal point are implied.
     * <P>
     * Equivalently, the represented value is given by f * 10^fDecimalAt.  Here
     * f is a value 0.1 <= f < 1 arrived at by placing the digits in fDigits to
     * the right of the decimal.
     * <P>
     * DigitList is normalized, so if it is non-zero, fDigits[0] is non-zero.  We
     * don't allow denormalized numbers because our exponent is effectively of
     * unlimited magnitude.  The fCount value contains the number of significant
     * digits present in fDigits[].
     * <P>
     * Zero is represented by any DigitList with fCount == 0 or with each fDigits[i]
     * for all i <= fCount == '0'.
     *
     * int32_t                         fDecimalAt;
     * int32_t                         fCount;
     * UBool                           fIsPositive;
     * char                            *fDigits;
     * DecimalFormat::ERoundingMode    fRoundingMode;
     */

public:
    decContext    fContext;   // public access to status flags.  

private:
    decNumber     *fDecNumber;
    MaybeStackHeaderAndArray<decNumber, char, DEFAULT_DIGITS>  fStorage;

    /* Cached double value corresponding to this decimal number.
     * This is an optimization for the formatting implementation, which may
     * ask for the double value multiple times.
     */
    union DoubleOrInt64 {
      double        fDouble;
      int64_t       fInt64;
    } fUnion;
    enum EHave {
      kNone=0,
      kDouble,
      kInt64
    } fHave;



    UBool shouldRoundUp(int32_t maximumDigits) const;

 public:

    using UMemory::operator new;
    using UMemory::operator delete;

    /**
     * Placement new for stack usage
     * @internal
     */
    static inline void * U_EXPORT2 operator new(size_t /*size*/, void * onStack, EStackMode  /*mode*/) U_NO_THROW { return onStack; }

    /**
     * Placement delete for stack usage
     * @internal
     */
    static inline void U_EXPORT2 operator delete(void * /*ptr*/, void * /*onStack*/, EStackMode /*mode*/)  U_NO_THROW {}

 private:
    inline void internalSetDouble(double d) {
      fHave = kDouble;
      fUnion.fDouble=d;
    }
    inline void internalSetInt64(int64_t d) {
      fHave = kInt64;
      fUnion.fInt64=d;
    }
    inline void internalClear() {
      fHave = kNone;
    }
};


U_NAMESPACE_END

#endif // #if !UCONFIG_NO_FORMATTING
#endif // _DIGITLST

//eof