/*
*******************************************************************************
* Copyright (C) 2015, International Business Machines
* Corporation and others.  All Rights Reserved.
*******************************************************************************
* precision.h
*
* created on: 2015jan06
* created by: Travis Keep
*/

#ifndef __PRECISION_H__
#define __PRECISION_H__

#include "unicode/uobject.h"

#if !UCONFIG_NO_FORMATTING
#include "unicode/utypes.h"

#include "digitinterval.h"
#include "digitlst.h"
#include "significantdigitinterval.h"

U_NAMESPACE_BEGIN

class VisibleDigits;
class VisibleDigitsWithExponent;


/**
 * A precision manager for values to be formatted as fixed point.
 * Handles rounding of number to prepare it for formatting.
 */
class U_I18N_API FixedPrecision : public UMemory {
public:

    /**
     * The smallest format interval allowed. Default is 1 integer digit and no
     * fraction digits.
     */
    DigitInterval fMin;

    /**
     * The largest format interval allowed. Must contain fMin.
     *  Default is all digits.
     */
    DigitInterval fMax;

    /**
     * Min and max significant digits allowed. The default is no constraints.
     */
    SignificantDigitInterval fSignificant;

    /**
     * The rounding increment or zero if there is no rounding increment.
     * Default is zero.
     */
    DigitList fRoundingIncrement;
    
    /**
     * If set, causes round() to set status to U_FORMAT_INEXACT_ERROR if
     * any rounding is done. Default is FALSE.
     */
    UBool fExactOnly;
    
    /**
     * If set, causes round() to set status to U_ILLEGAL_ARGUMENT_ERROR if
     * rounded number has more than maximum integer digits. Default is FALSE.
     */
    UBool fFailIfOverMax;
    
    /**
     * Controls the rounding mode that initVisibleDigits uses.
     * Default is DecimalFormat::kRoundHalfEven
     */
    DecimalFormat::ERoundingMode fRoundingMode;
    
    FixedPrecision();
    
    /**
     * Returns TRUE if this object equals rhs.
     */
    UBool equals(const FixedPrecision &rhs) const {
        return (fMin.equals(rhs.fMin) &&
                fMax.equals(rhs.fMax) &&
                fSignificant.equals(rhs.fSignificant) &&
                (fRoundingIncrement == rhs.fRoundingIncrement) &&
                fExactOnly == rhs.fExactOnly &&
                fFailIfOverMax == rhs.fFailIfOverMax &&
                fRoundingMode == rhs.fRoundingMode);
    }
    
    /**
     * Rounds value in place to prepare it for formatting.
     * @param value The value to be rounded. It is rounded in place.
     * @param exponent Always pass 0 for fixed decimal formatting. scientific
     *  precision passes the exponent value.  Essentially, it divides value by
     *  10^exponent, rounds and then multiplies by 10^exponent.
     * @param status error returned here.
     * @return reference to value.
     */
    DigitList &round(DigitList &value, int32_t exponent, UErrorCode &status) const;
    
    /**
     * Returns the interval to use to format the rounded value.
     * @param roundedValue the already rounded value to format.
     * @param interval modified in place to be the interval to use to format
     *   the rounded value.
     * @return a reference to interval.
     */
    DigitInterval &getInterval(
            const DigitList &roundedValue, DigitInterval &interval) const;
    
    /**
     * Returns TRUE if this instance allows for fast formatting of integers.
     */
    UBool isFastFormattable() const;
    
    /**
     * Initializes a VisibleDigits.
     * @param value value for VisibleDigits
     *    Caller must not assume that the value of this parameter will remain
     *    unchanged.
     * @param digits This is the value that is initialized.
     * @param status any error returned here.
     * @return digits
     */
    VisibleDigits &initVisibleDigits(
            DigitList &value,
            VisibleDigits &digits,
            UErrorCode &status) const;
    
    /**
     * Initializes a VisibleDigits.
     * @param value value for VisibleDigits
     * @param digits This is the value that is initialized.
     * @param status any error returned here.
     * @return digits
     */
    VisibleDigits &initVisibleDigits(
            double value,
            VisibleDigits &digits,
            UErrorCode &status) const;
    
    /**
     * Initializes a VisibleDigits.
     * @param value value for VisibleDigits
     * @param digits This is the value that is initialized.
     * @param status any error returned here.
     * @return digits
     */
    VisibleDigits &initVisibleDigits(
            int64_t value,
            VisibleDigits &digits,
            UErrorCode &status) const;
    
    /**
     * Initializes a VisibleDigitsWithExponent.
     * @param value value for VisibleDigits
     *    Caller must not assume that the value of this parameter will remain
     *    unchanged.
     * @param digits This is the value that is initialized.
     * @param status any error returned here.
     * @return digits
     */
    VisibleDigitsWithExponent &initVisibleDigitsWithExponent(
            DigitList &value,
            VisibleDigitsWithExponent &digits,
            UErrorCode &status) const;
    
    /**
     * Initializes a VisibleDigitsWithExponent.
     * @param value value for VisibleDigits
     * @param digits This is the value that is initialized.
     * @param status any error returned here.
     * @return digits
     */
    VisibleDigitsWithExponent &initVisibleDigitsWithExponent(
            double value,
            VisibleDigitsWithExponent &digits,
            UErrorCode &status) const;
    
    /**
     * Initializes a VisibleDigitsWithExponent.
     * @param value value for VisibleDigits
     * @param digits This is the value that is initialized.
     * @param status any error returned here.
     * @return digits
     */
    VisibleDigitsWithExponent &initVisibleDigitsWithExponent(
            int64_t value,
            VisibleDigitsWithExponent &digits,
            UErrorCode &status) const;
    
private:
    /**
     * Attempts to initialize 'digits' using simple mod 10 arithmetic.
     * Returns FALSE if this is not possible such as when rounding
     * would change the value. Otherwise returns TRUE.
     *
     * If the method returns FALSE, caller should create a DigitList
     * and use it to initialize 'digits'. If this method returns TRUE,
     * caller should accept the value stored in 'digits'. If this
     * method returns TRUE along with a non zero error, caller must accept
     * the error and not try again with a DigitList.
     *
     * Before calling this method, caller must verify that this object
     * has no rounding increment set.
     *
     * The value that 'digits' is initialized to is mantissa * 10^exponent.
     * For example mantissa = 54700 and exponent = -3 means 54.7. The
     * properties of this object (such as min and max fraction digits),
     * not the number of trailing zeros in the mantissa, determine whether or
     * not the result contains any trailing 0's after the decimal point.
     *
     * @param mantissa the digits. May be positive or negative. May contain
     *  trailing zeros.
     * @param exponent must always be zero or negative. An exponent > 0
     *  yields undefined results! 
     * @param digits result stored here.
     * @param status any error returned here.
     */
    UBool
    initVisibleDigits(
            int64_t mantissa,
            int32_t exponent,
            VisibleDigits &digits,
            UErrorCode &status) const;
    UBool isRoundingRequired(
            int32_t upperExponent, int32_t lowerExponent) const;
    DigitInterval &getIntervalForZero(DigitInterval &interval) const;
    DigitInterval &getInterval(
            int32_t upperExponent, DigitInterval &interval) const;
    static UBool handleNonNumeric(DigitList &value, VisibleDigits &digits);
    
    friend class ScientificPrecision;
};

/**
 * A precision manager for values to be expressed as scientific notation.
 */
class U_I18N_API ScientificPrecision : public UMemory {
public:
    FixedPrecision fMantissa;
    int32_t fMinExponentDigits;

    ScientificPrecision();

    /**
     * rounds value in place to prepare it for formatting.
     * @param value The value to be rounded. It is rounded in place.
     * @param status error returned here.
     * @return reference to value.
     */
    DigitList &round(DigitList &value, UErrorCode &status) const;

    /**
     * Converts value to a mantissa and exponent.
     *
     * @param value modified in place to be the mantissa. Depending on
     *   the precision settings, the resulting mantissa may not fall
     *   between 1.0 and 10.0.
     * @return the exponent of value.
     */
    int32_t toScientific(DigitList &value) const;

    /**
     * Returns TRUE if this object equals rhs.
     */
    UBool equals(const ScientificPrecision &rhs) const {
        return fMantissa.equals(rhs.fMantissa) && fMinExponentDigits == rhs.fMinExponentDigits;
    }

    /**
     * Initializes a VisibleDigitsWithExponent.
     * @param value the value
     *    Caller must not assume that the value of this parameter will remain
     *    unchanged.
     * @param digits This is the value that is initialized.
     * @param status any error returned here.
     * @return digits
     */
    VisibleDigitsWithExponent &initVisibleDigitsWithExponent(
            DigitList &value,
            VisibleDigitsWithExponent &digits,
            UErrorCode &status) const;
    
    /**
     * Initializes a VisibleDigitsWithExponent.
     * @param value the value
     * @param digits This is the value that is initialized.
     * @param status any error returned here.
     * @return digits
     */
    VisibleDigitsWithExponent &initVisibleDigitsWithExponent(
            double value,
            VisibleDigitsWithExponent &digits,
            UErrorCode &status) const;
    
    /**
     * Initializes a VisibleDigitsWithExponent.
     * @param value the value
     * @param digits This is the value that is initialized.
     * @param status any error returned here.
     * @return digits
     */
    VisibleDigitsWithExponent &initVisibleDigitsWithExponent(
            int64_t value,
            VisibleDigitsWithExponent &digits,
            UErrorCode &status) const;
    
private:
    int32_t getMultiplier() const;

};



U_NAMESPACE_END
#endif // #if !UCONFIG_NO_FORMATTING
#endif  // __PRECISION_H__
