root/src/system/libroot/add-ons/icu/ICUMonetaryData.cpp
/*
 * Copyright 2010-2011, Oliver Tappe, zooey@hirschkaefer.de.
 * Distributed under the terms of the MIT License.
 */


#include "ICUMonetaryData.h"

#include <langinfo.h>
#include <limits.h>
#include <string.h>
#include <strings.h>


U_NAMESPACE_USE


namespace BPrivate {
namespace Libroot {


ICUMonetaryData::ICUMonetaryData(pthread_key_t tlsKey, struct lconv& localeConv)
        :
        inherited(tlsKey),
        fLocaleConv(localeConv),
        fPosixLocaleConv(NULL)
{
        fLocaleConv.int_curr_symbol = fIntCurrSymbol;
        fLocaleConv.currency_symbol = fCurrencySymbol;
        fLocaleConv.mon_decimal_point = fDecimalPoint;
        fLocaleConv.mon_thousands_sep = fThousandsSep;
        fLocaleConv.mon_grouping = fGrouping;
        fLocaleConv.positive_sign = fPositiveSign;
        fLocaleConv.negative_sign = fNegativeSign;
}


void
ICUMonetaryData::Initialize(LocaleMonetaryDataBridge* dataBridge)
{
        fPosixLocaleConv = dataBridge->posixLocaleConv;
}


status_t
ICUMonetaryData::SetTo(const Locale& locale, const char* posixLocaleName)
{
        status_t result = inherited::SetTo(locale, posixLocaleName);

        if (result == B_OK) {
                UErrorCode icuStatus = U_ZERO_ERROR;
                UChar intlCurrencySeparatorChar = CHAR_MAX;
                DecimalFormat* currencyFormat = dynamic_cast<DecimalFormat*>(
                        NumberFormat::createCurrencyInstance(locale, icuStatus));
                if (!U_SUCCESS(icuStatus))
                        return B_UNSUPPORTED;
                if (!currencyFormat)
                        return B_BAD_TYPE;

                const DecimalFormatSymbols* formatSymbols
                        = currencyFormat->getDecimalFormatSymbols();
                if (!formatSymbols)
                        result = B_BAD_DATA;

                if (result == B_OK) {
                        result = _SetLocaleconvEntry(formatSymbols, fDecimalPoint,
                                DecimalFormatSymbols::kMonetarySeparatorSymbol);
                }
                if (result == B_OK) {
                        result = _SetLocaleconvEntry(formatSymbols, fThousandsSep,
                                DecimalFormatSymbols::kMonetaryGroupingSeparatorSymbol);
                }
                if (result == B_OK) {
                        int32 groupingSize = currencyFormat->getGroupingSize();
                        if (groupingSize < 1)
                                fGrouping[0] = '\0';
                        else {
                                fGrouping[0] = groupingSize;
                                int32 secondaryGroupingSize
                                        = currencyFormat->getSecondaryGroupingSize();
                                if (secondaryGroupingSize < 1)
                                        fGrouping[1] = '\0';
                                else {
                                        fGrouping[1] = secondaryGroupingSize;
                                        fGrouping[2] = '\0';
                                }
                        }
                }
                if (result == B_OK) {
                        fLocaleConv.int_frac_digits
                                = currencyFormat->getMinimumFractionDigits();
                        fLocaleConv.frac_digits
                                = currencyFormat->getMinimumFractionDigits();
                }
                if (result == B_OK) {
                        UnicodeString positivePrefix, positiveSuffix, negativePrefix,
                                negativeSuffix;
                        currencyFormat->getPositivePrefix(positivePrefix);
                        currencyFormat->getPositiveSuffix(positiveSuffix);
                        currencyFormat->getNegativePrefix(negativePrefix);
                        currencyFormat->getNegativeSuffix(negativeSuffix);
                        UnicodeString currencySymbol = formatSymbols->getSymbol(
                                DecimalFormatSymbols::kCurrencySymbol);
                        UnicodeString plusSymbol = formatSymbols->getSymbol(
                                DecimalFormatSymbols::kPlusSignSymbol);
                        UnicodeString minusSymbol = formatSymbols->getSymbol(
                                DecimalFormatSymbols::kMinusSignSymbol);

                        // fill national values
                        int32 positiveCurrencyFlags = _DetermineCurrencyPosAndSeparator(
                                positivePrefix, positiveSuffix, plusSymbol, currencySymbol,
                                intlCurrencySeparatorChar);
                        fLocaleConv.p_cs_precedes
                                = (positiveCurrencyFlags & kCsPrecedesFlag) ? 1 : 0;
                        fLocaleConv.p_sep_by_space
                                = (positiveCurrencyFlags & kSepBySpaceFlag) ? 1 : 0;

                        int32 negativeCurrencyFlags = _DetermineCurrencyPosAndSeparator(
                                negativePrefix, negativeSuffix, minusSymbol, currencySymbol,
                                intlCurrencySeparatorChar);
                        fLocaleConv.n_cs_precedes
                                = (negativeCurrencyFlags & kCsPrecedesFlag) ? 1 : 0;
                        fLocaleConv.n_sep_by_space
                                = (negativeCurrencyFlags & kSepBySpaceFlag) ? 1 : 0;

                        fLocaleConv.p_sign_posn = _DetermineSignPos(positivePrefix,
                                positiveSuffix, plusSymbol, currencySymbol);
                        fLocaleConv.n_sign_posn = _DetermineSignPos(negativePrefix,
                                negativeSuffix, minusSymbol, currencySymbol);
                        if (fLocaleConv.p_sign_posn == CHAR_MAX) {
                                // usually there is no positive sign indicator, so we
                                // adopt the sign pos of the negative sign symbol
                                fLocaleConv.p_sign_posn = fLocaleConv.n_sign_posn;
                        }

                        // copy national to international values, as ICU does not seem
                        // to have separate info for those
                        fLocaleConv.int_p_cs_precedes = fLocaleConv.p_cs_precedes;
                        fLocaleConv.int_p_sep_by_space = fLocaleConv.p_sep_by_space;
                        fLocaleConv.int_n_cs_precedes = fLocaleConv.n_cs_precedes;
                        fLocaleConv.int_n_sep_by_space = fLocaleConv.n_sep_by_space;
                        fLocaleConv.int_p_sign_posn = fLocaleConv.p_sign_posn;
                        fLocaleConv.int_n_sign_posn = fLocaleConv.n_sign_posn;

                        // only set sign symbols if they are actually used in any pattern
                        if (positivePrefix.indexOf(plusSymbol) > -1
                                || positiveSuffix.indexOf(plusSymbol) > -1) {
                                result = _SetLocaleconvEntry(formatSymbols, fPositiveSign,
                                        DecimalFormatSymbols::kPlusSignSymbol);
                        } else
                                fPositiveSign[0] = '\0';
                        if (negativePrefix.indexOf(minusSymbol) > -1
                                || negativeSuffix.indexOf(minusSymbol) > -1) {
                                result = _SetLocaleconvEntry(formatSymbols, fNegativeSign,
                                        DecimalFormatSymbols::kMinusSignSymbol);
                        } else
                                fNegativeSign[0] = '\0';
                }
                if (result == B_OK) {
                        UnicodeString intlCurrencySymbol = formatSymbols->getSymbol(
                                DecimalFormatSymbols::kIntlCurrencySymbol);
                        if (intlCurrencySeparatorChar != CHAR_MAX)
                                intlCurrencySymbol += intlCurrencySeparatorChar;
                        else
                                intlCurrencySymbol += ' ';
                        result = _ConvertUnicodeStringToLocaleconvEntry(intlCurrencySymbol,
                                fIntCurrSymbol, skLCBufSize);
                }
                if (result == B_OK) {
                        result = _SetLocaleconvEntry(formatSymbols, fCurrencySymbol,
                                DecimalFormatSymbols::kCurrencySymbol);
                        if (fCurrencySymbol[0] == '\0') {
                                // fall back to the international currency symbol
                                result = _SetLocaleconvEntry(formatSymbols, fCurrencySymbol,
                                        DecimalFormatSymbols::kIntlCurrencySymbol);
                                fCurrencySymbol[3] = '\0';
                                        // drop separator char that's contained in int-curr-symbol
                        }
                }

                delete currencyFormat;
        }

        return result;
}


status_t
ICUMonetaryData::SetToPosix()
{
        status_t result = inherited::SetToPosix();

        if (result == B_OK) {
                strcpy(fDecimalPoint, fPosixLocaleConv->mon_decimal_point);
                strcpy(fThousandsSep, fPosixLocaleConv->mon_thousands_sep);
                strcpy(fGrouping, fPosixLocaleConv->mon_grouping);
                strcpy(fCurrencySymbol, fPosixLocaleConv->currency_symbol);
                strcpy(fIntCurrSymbol, fPosixLocaleConv->int_curr_symbol);
                strcpy(fPositiveSign, fPosixLocaleConv->positive_sign);
                strcpy(fNegativeSign, fPosixLocaleConv->negative_sign);
                fLocaleConv.int_frac_digits = fPosixLocaleConv->int_frac_digits;
                fLocaleConv.frac_digits = fPosixLocaleConv->frac_digits;
                fLocaleConv.p_cs_precedes = fPosixLocaleConv->p_cs_precedes;
                fLocaleConv.p_sep_by_space = fPosixLocaleConv->p_sep_by_space;
                fLocaleConv.n_cs_precedes = fPosixLocaleConv->n_cs_precedes;
                fLocaleConv.n_sep_by_space = fPosixLocaleConv->n_sep_by_space;
                fLocaleConv.p_sign_posn = fPosixLocaleConv->p_sign_posn;
                fLocaleConv.n_sign_posn = fPosixLocaleConv->n_sign_posn;
                fLocaleConv.int_p_cs_precedes = fPosixLocaleConv->int_p_cs_precedes;
                fLocaleConv.int_p_sep_by_space = fPosixLocaleConv->int_p_sep_by_space;
                fLocaleConv.int_n_cs_precedes = fPosixLocaleConv->int_n_cs_precedes;
                fLocaleConv.int_n_sep_by_space = fPosixLocaleConv->int_n_sep_by_space;
                fLocaleConv.int_p_sign_posn = fPosixLocaleConv->int_p_sign_posn;
                fLocaleConv.int_n_sign_posn = fPosixLocaleConv->int_n_sign_posn;
        }

        return result;
}


const char*
ICUMonetaryData::GetLanginfo(int index)
{
        switch(index) {
                case CRNCYSTR:
                        return fCurrencySymbol;
                default:
                        return "";
        }
}


int32
ICUMonetaryData::_DetermineCurrencyPosAndSeparator(const UnicodeString& prefix,
        const UnicodeString& suffix, const UnicodeString& signSymbol,
        const UnicodeString& currencySymbol, UChar& currencySeparatorChar)
{
        int32 result = 0;

        int32 currencySymbolPos = prefix.indexOf(currencySymbol);
        if (currencySymbolPos > -1) {
                result |= kCsPrecedesFlag;
                int32 signSymbolPos = prefix.indexOf(signSymbol);
                // if a char is following the currency symbol, we assume it's
                // the separator (usually space), but we need to take care to
                // skip over the sign symbol, if found
                int32 potentialSeparatorPos
                        = currencySymbolPos + currencySymbol.length();
                if (potentialSeparatorPos == signSymbolPos)
                        potentialSeparatorPos++;
                if (prefix.charAt(potentialSeparatorPos) != 0xFFFF) {
                        // We can't use the actual separator char since this is usually
                        // 'c2a0' (non-breakable space), which is not available in the
                        // ASCII charset used/assumed by POSIX lconv. So we use space
                        // instead.
                        currencySeparatorChar = ' ';
                        result |= kSepBySpaceFlag;
                }
        } else {
                currencySymbolPos = suffix.indexOf(currencySymbol);
                if (currencySymbolPos > -1) {
                        int32 signSymbolPos = suffix.indexOf(signSymbol);
                        // if a char is preceding the currency symbol, we assume
                        // it's the separator (usually space), but we need to take
                        // care to skip the sign symbol, if found
                        int32 potentialSeparatorPos = currencySymbolPos - 1;
                        if (potentialSeparatorPos == signSymbolPos)
                                potentialSeparatorPos--;
                        if (suffix.charAt(potentialSeparatorPos) != 0xFFFF) {
                                // We can't use the actual separator char since this is usually
                                // 'c2a0' (non-breakable space), which is not available in the
                                // ASCII charset used/assumed by POSIX lconv. So we use space
                                // instead.
                                currencySeparatorChar = ' ';
                                result |= kSepBySpaceFlag;
                        }
                }
        }

        return result;
}


/*
 * This method determines the positive/negative sign position value according to
 * the following map (where '$' indicated the currency symbol, '#' the number
 * value, and '-' or parantheses the sign symbol):
 *              ($#)    ->      0
 *              (#$)    ->      0
 *              -$#     ->      1
 *              -#$             ->      1
 *              $-#             ->      4
 *              $#-             ->      2
 *              #$-             ->      2
 *              #-$             ->      3
 */
int32
ICUMonetaryData::_DetermineSignPos(const UnicodeString& prefix,
        const UnicodeString& suffix, const UnicodeString& signSymbol,
        const UnicodeString& currencySymbol)
{
        if (prefix.indexOf(UnicodeString("(", "")) >= 0
                && suffix.indexOf(UnicodeString(")", "")) >= 0)
                return kParenthesesAroundCurrencyAndValue;

        UnicodeString value("#", "");
        UnicodeString prefixNumberSuffixString = prefix + value + suffix;
        int32 signSymbolPos = prefixNumberSuffixString.indexOf(signSymbol);
        if (signSymbolPos >= 0) {
                int32 valuePos = prefixNumberSuffixString.indexOf(value);
                int32 currencySymbolPos
                        = prefixNumberSuffixString.indexOf(currencySymbol);

                if (signSymbolPos < valuePos && signSymbolPos < currencySymbolPos)
                        return kSignPrecedesCurrencyAndValue;
                if (signSymbolPos > valuePos && signSymbolPos > currencySymbolPos)
                        return kSignSucceedsCurrencyAndValue;
                if (signSymbolPos == currencySymbolPos - 1)
                        return kSignImmediatelyPrecedesCurrency;
                if (signSymbolPos == currencySymbolPos + currencySymbol.length())
                        return kSignImmediatelySucceedsCurrency;
        }

        return CHAR_MAX;
}


}       // namespace Libroot
}       // namespace BPrivate