text: // © 2018 and later: Unicode, Inc. and others.
  // License & terms of use: http://www.unicode.org/copyright.html
  
  #include "unicode/utypes.h"
  
  #if !UCONFIG_NO_FORMATTING
  
  // Allow implicit conversion from char16_t* to UnicodeString for this file:
  // Helpful in toString methods and elsewhere.
  #define UNISTR_FROM_STRING_EXPLICIT
  
  #include "number_mapper.h"
  #include "number_patternstring.h"
  #include "unicode/errorcode.h"
  #include "number_utils.h"
  #include "number_currencysymbols.h"
  
  using namespace icu;
  using namespace icu::number;
  using namespace icu::number::impl;
  
  
  UnlocalizedNumberFormatter NumberPropertyMapper::create(const DecimalFormatProperties& properties,
                                                          const DecimalFormatSymbols& symbols,
                                                          DecimalFormatWarehouse& warehouse,
                                                          UErrorCode& status) {
      return NumberFormatter::with().macros(oldToNew(properties, symbols, warehouse, nullptr, status));
  }
  
  UnlocalizedNumberFormatter NumberPropertyMapper::create(const DecimalFormatProperties& properties,
                                                          const DecimalFormatSymbols& symbols,
                                                          DecimalFormatWarehouse& warehouse,
                                                          DecimalFormatProperties& exportedProperties,
                                                          UErrorCode& status) {
      return NumberFormatter::with().macros(
              oldToNew(
                      properties, symbols, warehouse, &exportedProperties, status));
  }
  
  MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& properties,
                                            const DecimalFormatSymbols& symbols,
                                            DecimalFormatWarehouse& warehouse,
                                            DecimalFormatProperties* exportedProperties,
                                            UErrorCode& status) {
      MacroProps macros;
      Locale locale = symbols.getLocale();
  
      /////////////
      // SYMBOLS //
      /////////////
  
      macros.symbols.setTo(symbols);
  
      //////////////////
      // PLURAL RULES //
      //////////////////
  
      if (!properties.currencyPluralInfo.fPtr.isNull()) {
          macros.rules = properties.currencyPluralInfo.fPtr->getPluralRules();
      }
  
      /////////////
      // AFFIXES //
      /////////////
  
      AffixPatternProvider* affixProvider;
      if (properties.currencyPluralInfo.fPtr.isNull()) {
          warehouse.currencyPluralInfoAPP.setToBogus();
          warehouse.propertiesAPP.setTo(properties, status);
          affixProvider = &warehouse.propertiesAPP;
      } else {
          warehouse.currencyPluralInfoAPP.setTo(*properties.currencyPluralInfo.fPtr, properties, status);
          warehouse.propertiesAPP.setToBogus();
          affixProvider = &warehouse.currencyPluralInfoAPP;
      }
      macros.affixProvider = affixProvider;
  
      ///////////
      // UNITS //
      ///////////
  
      bool useCurrency = (
              !properties.currency.isNull() || !properties.currencyPluralInfo.fPtr.isNull() ||
              !properties.currencyUsage.isNull() || affixProvider->hasCurrencySign());
      CurrencyUnit currency = resolveCurrency(properties, locale, status);
      UCurrencyUsage currencyUsage = properties.currencyUsage.getOrDefault(UCURR_USAGE_STANDARD);
      if (useCurrency) {
          // NOTE: Slicing is OK.
          macros.unit = currency; // NOLINT
      }
      warehouse.currencySymbols = {currency, locale, symbols, status};
      macros.currencySymbols = &warehouse.currencySymbols;
  
      ///////////////////////
      // ROUNDING STRATEGY //
      ///////////////////////
  
      int32_t maxInt = properties.maximumIntegerDigits;
      int32_t minInt = properties.minimumIntegerDigits;
      int32_t maxFrac = properties.maximumFractionDigits;
      int32_t minFrac = properties.minimumFractionDigits;
      int32_t minSig = properties.minimumSignificantDigits;
      int32_t maxSig = properties.maximumSignificantDigits;
      double roundingIncrement = properties.roundingIncrement;
      RoundingMode roundingMode = properties.roundingMode.getOrDefault(UNUM_ROUND_HALFEVEN);
      bool explicitMinMaxFrac = minFrac != -1 || maxFrac != -1;
      bool explicitMinMaxSig = minSig != -1 || maxSig != -1;
      // Resolve min/max frac for currencies, required for the validation logic and for when minFrac or
      // maxFrac was
      // set (but not both) on a currency instance.
      // NOTE: Increments are handled in "Precision.constructCurrency()".
      if (useCurrency && (minFrac == -1 || maxFrac == -1)) {
          int32_t digits = ucurr_getDefaultFractionDigitsForUsage(
                  currency.getISOCurrency(), currencyUsage, &status);
          if (minFrac == -1 && maxFrac == -1) {
              minFrac = digits;
              maxFrac = digits;
          } else if (minFrac == -1) {
              minFrac = std::min(maxFrac, digits);
          } else /* if (maxFrac == -1) */ {
              maxFrac = std::max(minFrac, digits);
          }
      }
      // Validate min/max int/frac.
      // For backwards compatibility, minimum overrides maximum if the two conflict.
      // The following logic ensures that there is always a minimum of at least one digit.
      if (minInt == 0 && maxFrac != 0) {
          // Force a digit after the decimal point.
          minFrac = minFrac <= 0 ? 1 : minFrac;
          maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac;
          minInt = 0;
          maxInt = maxInt < 0 ? -1 : maxInt > kMaxIntFracSig ? -1 : maxInt;
      } else {
          // Force a digit before the decimal point.
          minFrac = minFrac < 0 ? 0 : minFrac;
          maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac;
          minInt = minInt <= 0 ? 1 : minInt > kMaxIntFracSig ? 1 : minInt;
          maxInt = maxInt < 0 ? -1 : maxInt < minInt ? minInt : maxInt > kMaxIntFracSig ? -1 : maxInt;
      }
      Precision precision;
      if (!properties.currencyUsage.isNull()) {
          precision = Precision::constructCurrency(currencyUsage).withCurrency(currency);
      } else if (roundingIncrement != 0.0) {
          precision = Precision::constructIncrement(roundingIncrement, minFrac);
      } else if (explicitMinMaxSig) {
          minSig = minSig < 1 ? 1 : minSig > kMaxIntFracSig ? kMaxIntFracSig : minSig;
          maxSig = maxSig < 0 ? kMaxIntFracSig : maxSig < minSig ? minSig : maxSig > kMaxIntFracSig
                                                                            ? kMaxIntFracSig : maxSig;
          precision = Precision::constructSignificant(minSig, maxSig);
      } else if (explicitMinMaxFrac) {
          precision = Precision::constructFraction(minFrac, maxFrac);
      } else if (useCurrency) {
          precision = Precision::constructCurrency(currencyUsage);
      }
      if (!precision.isBogus()) {
          precision = precision.withMode(roundingMode);
          macros.precision = precision;
      }
  
      ///////////////////
      // INTEGER WIDTH //
      ///////////////////
  
      macros.integerWidth = IntegerWidth(
              static_cast<digits_t>(minInt),
              static_cast<digits_t>(maxInt),
              properties.formatFailIfMoreThanMaxDigits);
  
      ///////////////////////
      // GROUPING STRATEGY //
      ///////////////////////
  
      macros.grouper = Grouper::forProperties(properties);
  
      /////////////
      // PADDING //
      /////////////
  
      if (properties.formatWidth != -1) {
          macros.padder = Padder::forProperties(properties);
      }
  
      ///////////////////////////////
      // DECIMAL MARK ALWAYS SHOWN //
      ///////////////////////////////
  
      macros.decimal = properties.decimalSeparatorAlwaysShown ? UNUM_DECIMAL_SEPARATOR_ALWAYS
                                                              : UNUM_DECIMAL_SEPARATOR_AUTO;
  
      ///////////////////////
      // SIGN ALWAYS SHOWN //
      ///////////////////////
  
      macros.sign = properties.signAlwaysShown ? UNUM_SIGN_ALWAYS : UNUM_SIGN_AUTO;
  
      /////////////////////////
      // SCIENTIFIC NOTATION //
      /////////////////////////
  
      if (properties.minimumExponentDigits != -1) {
          // Scientific notation is required.
          // This whole section feels like a hack, but it is needed for regression tests.
          // The mapping from property bag to scientific notation is nontrivial due to LDML rules.
          if (maxInt > 8) {
              // But #13110: The maximum of 8 digits has unknown origins and is not in the spec.
              // If maxInt is greater than 8, it is set to minInt, even if minInt is greater than 8.
              maxInt = minInt;
              macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt);
          } else if (maxInt > minInt && minInt > 1) {
              // Bug #13289: if maxInt > minInt > 1, then minInt should be 1.
              minInt = 1;
              macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt);
          }
          int engineering = maxInt < 0 ? -1 : maxInt;
          macros.notation = ScientificNotation(
                  // Engineering interval:
                  static_cast<int8_t>(engineering),
                  // Enforce minimum integer digits (for patterns like "000.00E0"):
                  (engineering == minInt),
                  // Minimum exponent digits:
                  static_cast<digits_t>(properties.minimumExponentDigits),
                  // Exponent sign always shown:
                  properties.exponentSignAlwaysShown ? UNUM_SIGN_ALWAYS : UNUM_SIGN_AUTO);
          // Scientific notation also involves overriding the rounding mode.
          // TODO: Overriding here is a bit of a hack. Should this logic go earlier?
          if (macros.precision.fType == Precision::PrecisionType::RND_FRACTION) {
              // For the purposes of rounding, get the original min/max int/frac, since the local
              // variables
              // have been manipulated for display purposes.
              int minInt_ = properties.minimumIntegerDigits;
              int minFrac_ = properties.minimumFractionDigits;
              int maxFrac_ = properties.maximumFractionDigits;
              if (minInt_ == 0 && maxFrac_ == 0) {
                  // Patterns like "#E0" and "##E0", which mean no rounding!
                  macros.precision = Precision::unlimited().withMode(roundingMode);
              } else if (minInt_ == 0 && minFrac_ == 0) {
                  // Patterns like "#.##E0" (no zeros in the mantissa), which mean round to maxFrac+1
                  macros.precision = Precision::constructSignificant(1, maxFrac_ + 1).withMode(roundingMode);
              } else {
                  // All other scientific patterns, which mean round to minInt+maxFrac
                  macros.precision = Precision::constructSignificant(
                          minInt_ + minFrac_, minInt_ + maxFrac_).withMode(roundingMode);
              }
          }
      }
  
      //////////////////////
      // COMPACT NOTATION //
      //////////////////////
  
      if (!properties.compactStyle.isNull()) {
          if (properties.compactStyle.getNoError() == UNumberCompactStyle::UNUM_LONG) {
              macros.notation = Notation::compactLong();
          } else {
              macros.notation = Notation::compactShort();
          }
          // Do not forward the affix provider.
          macros.affixProvider = nullptr;
      }
  
      /////////////////
      // MULTIPLIERS //
      /////////////////
  
      macros.scale = scaleFromProperties(properties);
  
      //////////////////////
      // PROPERTY EXPORTS //
      //////////////////////
  
      if (exportedProperties != nullptr) {
  
          exportedProperties->currency = currency;
          exportedProperties->roundingMode = roundingMode;
          exportedProperties->minimumIntegerDigits = minInt;
          exportedProperties->maximumIntegerDigits = maxInt == -1 ? INT32_MAX : maxInt;
  
          Precision rounding_;
          if (precision.fType == Precision::PrecisionType::RND_CURRENCY) {
              rounding_ = precision.withCurrency(currency, status);
          } else {
              rounding_ = precision;
          }
          int minFrac_ = minFrac;
          int maxFrac_ = maxFrac;
          int minSig_ = minSig;
          int maxSig_ = maxSig;
          double increment_ = 0.0;
          if (rounding_.fType == Precision::PrecisionType::RND_FRACTION) {
              minFrac_ = rounding_.fUnion.fracSig.fMinFrac;
              maxFrac_ = rounding_.fUnion.fracSig.fMaxFrac;
          } else if (rounding_.fType == Precision::PrecisionType::RND_INCREMENT) {
              increment_ = rounding_.fUnion.increment.fIncrement;
              minFrac_ = rounding_.fUnion.increment.fMinFrac;
              maxFrac_ = rounding_.fUnion.increment.fMinFrac;
          } else if (rounding_.fType == Precision::PrecisionType::RND_SIGNIFICANT) {
              minSig_ = rounding_.fUnion.fracSig.fMinSig;
              maxSig_ = rounding_.fUnion.fracSig.fMaxSig;
          }
  
          exportedProperties->minimumFractionDigits = minFrac_;
          exportedProperties->maximumFractionDigits = maxFrac_;
          exportedProperties->minimumSignificantDigits = minSig_;
          exportedProperties->maximumSignificantDigits = maxSig_;
          exportedProperties->roundingIncrement = increment_;
      }
  
      return macros;
  }
  
  
  void PropertiesAffixPatternProvider::setTo(const DecimalFormatProperties& properties, UErrorCode&) {
      fBogus = false;
  
      // There are two ways to set affixes in DecimalFormat: via the pattern string (applyPattern), and via the
      // explicit setters (setPositivePrefix and friends).  The way to resolve the settings is as follows:
      //
      // 1) If the explicit setting is present for the field, use it.
      // 2) Otherwise, follows UTS 35 rules based on the pattern string.
      //
      // Importantly, the explicit setters affect only the one field they override.  If you set the positive
      // prefix, that should not affect the negative prefix.  Since it is impossible for the user of this class
      // to know whether the origin for a string was the override or the pattern, we have to say that we always
      // have a negative subpattern and perform all resolution logic here.
  
      // Convenience: Extract the properties into local variables.
      // Variables are named with three chars: [p/n][p/s][o/p]
      // [p/n] => p for positive, n for negative
      // [p/s] => p for prefix, s for suffix
      // [o/p] => o for escaped custom override string, p for pattern string
      UnicodeString ppo = AffixUtils::escape(properties.positivePrefix);
      UnicodeString pso = AffixUtils::escape(properties.positiveSuffix);
      UnicodeString npo = AffixUtils::escape(properties.negativePrefix);
      UnicodeString nso = AffixUtils::escape(properties.negativeSuffix);
      const UnicodeString& ppp = properties.positivePrefixPattern;
      const UnicodeString& psp = properties.positiveSuffixPattern;
      const UnicodeString& npp = properties.negativePrefixPattern;
      const UnicodeString& nsp = properties.negativeSuffixPattern;
  
      if (!properties.positivePrefix.isBogus()) {
          posPrefix = ppo;
      } else if (!ppp.isBogus()) {
          posPrefix = ppp;
      } else {
          // UTS 35: Default positive prefix is empty string.
          posPrefix = u"";
      }
  
      if (!properties.positiveSuffix.isBogus()) {
          posSuffix = pso;
      } else if (!psp.isBogus()) {
          posSuffix = psp;
      } else {
          // UTS 35: Default positive suffix is empty string.
          posSuffix = u"";
      }
  
      if (!properties.negativePrefix.isBogus()) {
          negPrefix = npo;
      } else if (!npp.isBogus()) {
          negPrefix = npp;
      } else {
          // UTS 35: Default negative prefix is "-" with positive prefix.
          // Important: We prepend the "-" to the pattern, not the override!
          negPrefix = ppp.isBogus() ? u"-" : u"-" + ppp;
      }
  
      if (!properties.negativeSuffix.isBogus()) {
          negSuffix = nso;
      } else if (!nsp.isBogus()) {
          negSuffix = nsp;
      } else {
          // UTS 35: Default negative prefix is the positive prefix.
          negSuffix = psp.isBogus() ? u"" : psp;
      }
  }
  
  char16_t PropertiesAffixPatternProvider::charAt(int flags, int i) const {
      return getStringInternal(flags).charAt(i);
  }
  
  int PropertiesAffixPatternProvider::length(int flags) const {
      return getStringInternal(flags).length();
  }
  
  UnicodeString PropertiesAffixPatternProvider::getString(int32_t flags) const {
      return getStringInternal(flags);
  }
  
  const UnicodeString& PropertiesAffixPatternProvider::getStringInternal(int32_t flags) const {
      bool prefix = (flags & AFFIX_PREFIX) != 0;
      bool negative = (flags & AFFIX_NEGATIVE_SUBPATTERN) != 0;
      if (prefix && negative) {
          return negPrefix;
      } else if (prefix) {
          return posPrefix;
      } else if (negative) {
          return negSuffix;
      } else {
          return posSuffix;
      }
  }
  
  bool PropertiesAffixPatternProvider::positiveHasPlusSign() const {
      // TODO: Change the internal APIs to propagate out the error?
      ErrorCode localStatus;
      return AffixUtils::containsType(posPrefix, TYPE_PLUS_SIGN, localStatus) ||
             AffixUtils::containsType(posSuffix, TYPE_PLUS_SIGN, localStatus);
  }
  
  bool PropertiesAffixPatternProvider::hasNegativeSubpattern() const {
      // See comments in the constructor for more information on why this is always true.
      return true;
  }
  
  bool PropertiesAffixPatternProvider::negativeHasMinusSign() const {
      ErrorCode localStatus;
      return AffixUtils::containsType(negPrefix, TYPE_MINUS_SIGN, localStatus) ||
             AffixUtils::containsType(negSuffix, TYPE_MINUS_SIGN, localStatus);
  }
  
  bool PropertiesAffixPatternProvider::hasCurrencySign() const {
      ErrorCode localStatus;
      return AffixUtils::hasCurrencySymbols(posPrefix, localStatus) ||
             AffixUtils::hasCurrencySymbols(posSuffix, localStatus) ||
             AffixUtils::hasCurrencySymbols(negPrefix, localStatus) ||
             AffixUtils::hasCurrencySymbols(negSuffix, localStatus);
  }
  
  bool PropertiesAffixPatternProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const {
      return AffixUtils::containsType(posPrefix, type, status) ||
             AffixUtils::containsType(posSuffix, type, status) ||
             AffixUtils::containsType(negPrefix, type, status) ||
             AffixUtils::containsType(negSuffix, type, status);
  }
  
  bool PropertiesAffixPatternProvider::hasBody() const {
      return true;
  }
  
  
  void CurrencyPluralInfoAffixProvider::setTo(const CurrencyPluralInfo& cpi,
                                              const DecimalFormatProperties& properties,
                                              UErrorCode& status) {
      // We need to use a PropertiesAffixPatternProvider, not the simpler version ParsedPatternInfo,
      // because user-specified affix overrides still need to work.
      fBogus = false;
      DecimalFormatProperties pluralProperties(properties);
      for (int32_t plural = 0; plural < StandardPlural::COUNT; plural++) {
          const char* keyword = StandardPlural::getKeyword(static_cast<StandardPlural::Form>(plural));
          UnicodeString patternString;
          patternString = cpi.getCurrencyPluralPattern(keyword, patternString);
          PatternParser::parseToExistingProperties(
                  patternString,
                  pluralProperties,
                  IGNORE_ROUNDING_NEVER,
                  status);
          affixesByPlural[plural].setTo(pluralProperties, status);
      }
  }
  
  char16_t CurrencyPluralInfoAffixProvider::charAt(int32_t flags, int32_t i) const {
      int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
      return affixesByPlural[pluralOrdinal].charAt(flags, i);
  }
  
  int32_t CurrencyPluralInfoAffixProvider::length(int32_t flags) const {
      int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
      return affixesByPlural[pluralOrdinal].length(flags);
  }
  
  UnicodeString CurrencyPluralInfoAffixProvider::getString(int32_t flags) const {
      int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
      return affixesByPlural[pluralOrdinal].getString(flags);
  }
  
  bool CurrencyPluralInfoAffixProvider::positiveHasPlusSign() const {
      return affixesByPlural[StandardPlural::OTHER].positiveHasPlusSign();
  }
  
  bool CurrencyPluralInfoAffixProvider::hasNegativeSubpattern() const {
      return affixesByPlural[StandardPlural::OTHER].hasNegativeSubpattern();
  }
  
  bool CurrencyPluralInfoAffixProvider::negativeHasMinusSign() const {
      return affixesByPlural[StandardPlural::OTHER].negativeHasMinusSign();
  }
  
  bool CurrencyPluralInfoAffixProvider::hasCurrencySign() const {
      return affixesByPlural[StandardPlural::OTHER].hasCurrencySign();
  }
  
  bool CurrencyPluralInfoAffixProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const {
      return affixesByPlural[StandardPlural::OTHER].containsSymbolType(type, status);
  }
  
  bool CurrencyPluralInfoAffixProvider::hasBody() const {
      return affixesByPlural[StandardPlural::OTHER].hasBody();
  }
  
  
  #endif /* #if !UCONFIG_NO_FORMATTING */
  