// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html

#include "unicode/utypes.h"

#if !UCONFIG_NO_FORMATTING
#ifndef __NUMBER_TYPES_H__
#define __NUMBER_TYPES_H__

#include <cstdint>
#include "unicode/decimfmt.h"
#include "unicode/unum.h"
#include "unicode/numsys.h"
#include "unicode/numberformatter.h"
#include "unicode/utf16.h"
#include "uassert.h"
#include "unicode/platform.h"
#include "unicode/uniset.h"
#include "standardplural.h"

U_NAMESPACE_BEGIN namespace number {
namespace impl {

// Typedef several enums for brevity and for easier comparison to Java.

// Convention: bottom 4 bits for field, top 4 bits for field category.
// Field category 0 implies the number category so that the number field
// literals can be directly passed as a Field type.
// See the helper functions in "NumFieldUtils" in number_utils.h
typedef uint8_t Field;

typedef UNumberFormatRoundingMode RoundingMode;

typedef UNumberFormatPadPosition PadPosition;

typedef UNumberCompactStyle CompactStyle;

// ICU4J Equivalent: RoundingUtils.MAX_INT_FRAC_SIG
static constexpr int32_t kMaxIntFracSig = 999;

// ICU4J Equivalent: RoundingUtils.DEFAULT_ROUNDING_MODE
static constexpr RoundingMode kDefaultMode = RoundingMode::UNUM_FOUND_HALFEVEN;

// ICU4J Equivalent: Padder.FALLBACK_PADDING_STRING
static constexpr char16_t kFallbackPaddingString[] = u" ";

// Forward declarations:

class Modifier;
class MutablePatternModifier;
class DecimalQuantity;
class NumberStringBuilder;
class ModifierStore;
struct MicroProps;


enum AffixPatternType {
    // Represents a literal character; the value is stored in the code point field.
            TYPE_CODEPOINT = 0,

    // Represents a minus sign symbol '-'.
            TYPE_MINUS_SIGN = -1,

    // Represents a plus sign symbol '+'.
            TYPE_PLUS_SIGN = -2,

    // Represents a percent sign symbol '%'.
            TYPE_PERCENT = -3,

    // Represents a permille sign symbol '‰'.
            TYPE_PERMILLE = -4,

    // Represents a single currency symbol '¤'.
            TYPE_CURRENCY_SINGLE = -5,

    // Represents a double currency symbol '¤¤'.
            TYPE_CURRENCY_DOUBLE = -6,

    // Represents a triple currency symbol '¤¤¤'.
            TYPE_CURRENCY_TRIPLE = -7,

    // Represents a quadruple currency symbol '¤¤¤¤'.
            TYPE_CURRENCY_QUAD = -8,

    // Represents a quintuple currency symbol '¤¤¤¤¤'.
            TYPE_CURRENCY_QUINT = -9,

    // Represents a sequence of six or more currency symbols.
            TYPE_CURRENCY_OVERFLOW = -15
};

enum CompactType {
    TYPE_DECIMAL, TYPE_CURRENCY
};


class U_I18N_API AffixPatternProvider {
  public:
    static const int32_t AFFIX_PLURAL_MASK = 0xff;
    static const int32_t AFFIX_PREFIX = 0x100;
    static const int32_t AFFIX_NEGATIVE_SUBPATTERN = 0x200;
    static const int32_t AFFIX_PADDING = 0x400;

    // Convenience compound flags
    static const int32_t AFFIX_POS_PREFIX = AFFIX_PREFIX;
    static const int32_t AFFIX_POS_SUFFIX = 0;
    static const int32_t AFFIX_NEG_PREFIX = AFFIX_PREFIX | AFFIX_NEGATIVE_SUBPATTERN;
    static const int32_t AFFIX_NEG_SUFFIX = AFFIX_NEGATIVE_SUBPATTERN;

    virtual ~AffixPatternProvider();

    virtual char16_t charAt(int flags, int i) const = 0;

    virtual int length(int flags) const = 0;

    virtual UnicodeString getString(int flags) const = 0;

    virtual bool hasCurrencySign() const = 0;

    virtual bool positiveHasPlusSign() const = 0;

    virtual bool hasNegativeSubpattern() const = 0;

    virtual bool negativeHasMinusSign() const = 0;

    virtual bool containsSymbolType(AffixPatternType, UErrorCode&) const = 0;

    /**
     * True if the pattern has a number placeholder like "0" or "#,##0.00"; false if the pattern does not
     * have one. This is used in cases like compact notation, where the pattern replaces the entire
     * number instead of rendering the number.
     */
    virtual bool hasBody() const = 0;
};


/**
 * A Modifier is an object that can be passed through the formatting pipeline until it is finally applied to the string
 * builder. A Modifier usually contains a prefix and a suffix that are applied, but it could contain something else,
 * like a {@link com.ibm.icu.text.SimpleFormatter} pattern.
 *
 * A Modifier is usually immutable, except in cases such as {@link MutablePatternModifier}, which are mutable for performance
 * reasons.
 *
 * Exported as U_I18N_API because it is a base class for other exported types
 */
class U_I18N_API Modifier {
  public:
    virtual ~Modifier();

    /**
     * Apply this Modifier to the string builder.
     *
     * @param output
     *            The string builder to which to apply this modifier.
     * @param leftIndex
     *            The left index of the string within the builder. Equal to 0 when only one number is being formatted.
     * @param rightIndex
     *            The right index of the string within the string builder. Equal to length when only one number is being
     *            formatted.
     * @return The number of characters (UTF-16 code units) that were added to the string builder.
     */
    virtual int32_t apply(NumberStringBuilder& output, int leftIndex, int rightIndex,
                          UErrorCode& status) const = 0;

    /**
     * Gets the length of the prefix. This information can be used in combination with {@link #apply} to extract the
     * prefix and suffix strings.
     *
     * @return The number of characters (UTF-16 code units) in the prefix.
     */
    virtual int32_t getPrefixLength() const = 0;

    /**
     * Returns the number of code points in the modifier, prefix plus suffix.
     */
    virtual int32_t getCodePointCount() const = 0;

    /**
     * Whether this modifier is strong. If a modifier is strong, it should always be applied immediately and not allowed
     * to bubble up. With regard to padding, strong modifiers are considered to be on the inside of the prefix and
     * suffix.
     *
     * @return Whether the modifier is strong.
     */
    virtual bool isStrong() const = 0;

    /**
     * Whether the modifier contains at least one occurrence of the given field.
     */
    virtual bool containsField(UNumberFormatFields field) const = 0;

    /**
     * A fill-in for getParameters(). obj will always be set; if non-null, the other
     * two fields are also safe to read.
     */
    struct U_I18N_API Parameters {
        const ModifierStore* obj = nullptr;
        int8_t signum;
        StandardPlural::Form plural;

        Parameters();
        Parameters(const ModifierStore* _obj, int8_t _signum, StandardPlural::Form _plural);
    };

    /**
     * Gets a set of "parameters" for this Modifier.
     *
     * TODO: Make this return a `const Parameters*` more like Java?
     */
    virtual void getParameters(Parameters& output) const = 0;

    /**
     * Returns whether this Modifier is *semantically equivalent* to the other Modifier;
     * in many cases, this is the same as equal, but parameters should be ignored.
     */
    virtual bool semanticallyEquivalent(const Modifier& other) const = 0;
};


/**
 * This is *not* a modifier; rather, it is an object that can return modifiers
 * based on given parameters.
 *
 * Exported as U_I18N_API because it is a base class for other exported types.
 */
class U_I18N_API ModifierStore {
  public:
    virtual ~ModifierStore();

    /**
     * Returns a Modifier with the given parameters (best-effort).
     */
    virtual const Modifier* getModifier(int8_t signum, StandardPlural::Form plural) const = 0;
};


/**
 * This interface is used when all number formatting settings, including the locale, are known, except for the quantity
 * itself. The {@link #processQuantity} method performs the final step in the number processing pipeline: it uses the
 * quantity to generate a finalized {@link MicroProps}, which can be used to render the number to output.
 *
 * <p>
 * In other words, this interface is used for the parts of number processing that are <em>quantity-dependent</em>.
 *
 * <p>
 * In order to allow for multiple different objects to all mutate the same MicroProps, a "chain" of MicroPropsGenerators
 * are linked together, and each one is responsible for manipulating a certain quantity-dependent part of the
 * MicroProps. At the tail of the linked list is a base instance of {@link MicroProps} with properties that are not
 * quantity-dependent. Each element in the linked list calls {@link #processQuantity} on its "parent", then does its
 * work, and then returns the result.
 *
 * Exported as U_I18N_API because it is a base class for other exported types
 *
 */
class U_I18N_API MicroPropsGenerator {
  public:
    virtual ~MicroPropsGenerator();

    /**
     * Considers the given {@link DecimalQuantity}, optionally mutates it, and returns a {@link MicroProps}.
     *
     * @param quantity
     *            The quantity for consideration and optional mutation.
     * @param micros
     *            The MicroProps instance to populate.
     * @return A MicroProps instance resolved for the quantity.
     */
    virtual void processQuantity(DecimalQuantity& quantity, MicroProps& micros,
                                 UErrorCode& status) const = 0;
};

/**
 * An interface used by compact notation and scientific notation to choose a multiplier while rounding.
 */
class MultiplierProducer {
  public:
    virtual ~MultiplierProducer();

    /**
     * Maps a magnitude to a multiplier in powers of ten. For example, in compact notation in English, a magnitude of 5
     * (e.g., 100,000) should return a multiplier of -3, since the number is displayed in thousands.
     *
     * @param magnitude
     *            The power of ten of the input number.
     * @return The shift in powers of ten.
     */
    virtual int32_t getMultiplier(int32_t magnitude) const = 0;
};

// Exported as U_I18N_API because it is a public member field of exported DecimalFormatProperties
template<typename T>
class U_I18N_API NullableValue {
  public:
    NullableValue()
            : fNull(true) {}

    NullableValue(const NullableValue<T>& other) = default;

    explicit NullableValue(const T& other) {
        fValue = other;
        fNull = false;
    }

    NullableValue<T>& operator=(const NullableValue<T>& other) {
        fNull = other.fNull;
        if (!fNull) {
            fValue = other.fValue;
        }
        return *this;
    }

    NullableValue<T>& operator=(const T& other) {
        fValue = other;
        fNull = false;
        return *this;
    }

    bool operator==(const NullableValue& other) const {
        // "fValue == other.fValue" returns UBool, not bool (causes compiler warnings)
        return fNull ? other.fNull : (other.fNull ? false : static_cast<bool>(fValue == other.fValue));
    }

    void nullify() {
        // TODO: It might be nice to call the destructor here.
        fNull = true;
    }

    bool isNull() const {
        return fNull;
    }

    T get(UErrorCode& status) const {
        if (fNull) {
            status = U_UNDEFINED_VARIABLE;
        }
        return fValue;
    }

    T getNoError() const {
        return fValue;
    }

    T getOrDefault(T defaultValue) const {
        return fNull ? defaultValue : fValue;
    }

  private:
    bool fNull;
    T fValue;
};


} // namespace impl
} // namespace number
U_NAMESPACE_END

#endif //__NUMBER_TYPES_H__

#endif /* #if !UCONFIG_NO_FORMATTING */
