/*
 * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#include "sun_util_locale_provider_HostLocaleProviderAdapterImpl.h"
#include "jni_util.h"
#include <windows.h>
#include <gdefs.h>
#include <stdlib.h>

#define BUFLEN 256

// java.util.Calendar constants
#define CALENDAR_FIELD_ERA              0           // Calendar.ERA
#define CALENDAR_FIELD_MONTH            2           // Calendar.MONTH
#define CALENDAR_STYLE_SHORT_MASK       0x00000001  // Calendar.SHORT
#define CALENDAR_STYLE_STANDALONE_MASK  0x00008000  // Calendar.STANDALONE

// global variables
typedef int (WINAPI *PGLIE)(const jchar *, LCTYPE, LPWSTR, int);
typedef int (WINAPI *PGCIE)(const jchar *, CALID, LPCWSTR, CALTYPE, LPWSTR, int, LPDWORD);
typedef int (WINAPI *PECIEE)(CALINFO_ENUMPROCEXEX, const jchar *, CALID, LPCWSTR, CALTYPE, LPARAM);
PGLIE pGetLocaleInfoEx;
PGCIE pGetCalendarInfoEx;
PECIEE pEnumCalendarInfoExEx;
BOOL initialized = FALSE;

// prototypes
int getLocaleInfoWrapper(const jchar *langtag, LCTYPE type, LPWSTR data, int buflen);
int getCalendarInfoWrapper(const jchar *langtag, CALID id, LPCWSTR reserved, CALTYPE type, LPWSTR data, int buflen, LPDWORD val);
jint getCalendarID(const jchar *langtag);
void replaceCalendarArrayElems(JNIEnv *env, jstring jlangtag, jint calid, jobjectArray jarray,
                       CALTYPE* pCalTypes, int offset, int length, int style);
WCHAR * getNumberPattern(const jchar * langtag, const jint numberStyle);
void getNumberPart(const jchar * langtag, const jint numberStyle, WCHAR * number);
void getFixPart(const jchar * langtag, const jint numberStyle, BOOL positive, BOOL prefix, WCHAR * ret);
int enumCalendarInfoWrapper(const jchar * langtag, CALID calid, CALTYPE type, LPWSTR buf, int buflen);
BOOL CALLBACK EnumCalendarInfoProc(LPWSTR lpCalInfoStr, CALID calid, LPWSTR lpReserved, LPARAM lParam);
jobjectArray getErasImpl(JNIEnv *env, jstring jlangtag, jint calid, jint style, jobjectArray eras);

// from java_props_md.c
extern __declspec(dllexport) const char * getJavaIDFromLangID(LANGID langID);

CALTYPE monthsType[] = {
    CAL_SMONTHNAME1,
    CAL_SMONTHNAME2,
    CAL_SMONTHNAME3,
    CAL_SMONTHNAME4,
    CAL_SMONTHNAME5,
    CAL_SMONTHNAME6,
    CAL_SMONTHNAME7,
    CAL_SMONTHNAME8,
    CAL_SMONTHNAME9,
    CAL_SMONTHNAME10,
    CAL_SMONTHNAME11,
    CAL_SMONTHNAME12,
    CAL_SMONTHNAME13,
};

CALTYPE sMonthsType[] = {
    CAL_SABBREVMONTHNAME1,
    CAL_SABBREVMONTHNAME2,
    CAL_SABBREVMONTHNAME3,
    CAL_SABBREVMONTHNAME4,
    CAL_SABBREVMONTHNAME5,
    CAL_SABBREVMONTHNAME6,
    CAL_SABBREVMONTHNAME7,
    CAL_SABBREVMONTHNAME8,
    CAL_SABBREVMONTHNAME9,
    CAL_SABBREVMONTHNAME10,
    CAL_SABBREVMONTHNAME11,
    CAL_SABBREVMONTHNAME12,
    CAL_SABBREVMONTHNAME13,
};

#define MONTHTYPES (sizeof(monthsType) / sizeof(CALTYPE))

CALTYPE wDaysType[] = {
    CAL_SDAYNAME7,
    CAL_SDAYNAME1,
    CAL_SDAYNAME2,
    CAL_SDAYNAME3,
    CAL_SDAYNAME4,
    CAL_SDAYNAME5,
    CAL_SDAYNAME6,
};

CALTYPE sWDaysType[] = {
    CAL_SABBREVDAYNAME7,
    CAL_SABBREVDAYNAME1,
    CAL_SABBREVDAYNAME2,
    CAL_SABBREVDAYNAME3,
    CAL_SABBREVDAYNAME4,
    CAL_SABBREVDAYNAME5,
    CAL_SABBREVDAYNAME6,
};

WCHAR * fixes[2][2][3][16] =
{
    { //prefix
        { //positive
            { // number
                L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"",
            },
            { // currency
                L"\xA4", L"", L"\xA4 ", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"",
            },
            { // percent
                L"", L"", L"%", L"% ", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"",
            }
        },
        { // negative
            { // number
                L"(", L"-", L"- ", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"",
            },
            { //currency
                L"(\xA4", L"-\xA4", L"\xA4-", L"\xA4", L"(", L"-", L"", L"", L"-", L"-\xA4 ", L"", L"\xA4 ", L"\xA4 -", L"", L"(\xA4 ", L"("
            },
            { // percent
                L"-", L"-", L"-%", L"%-", L"%", L"", L"", L"-% ", L"", L"% ", L"% -", L"", L"", L"", L"", L"",
            }
        }
    },
    { // suffix
        { //positive
            { // number
                L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L""
            },
            { // currency
                L"", L"\xA4 ", L"", L" \xA4", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"",
            },
            { // percent
                L" %", L"%", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"",
            }
        },
        { // negative
            { // number
                L")", L"", L" ", L"-", L" -", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"",
            },
            { //currency
                L")", L"", L"", L"-", L"\xA4)", L"\xA4", L"-\xA4", L"\xA4-", L" \xA4", L"", L" \xA4-", L"-", L"", L"- \xA4", L")", L" \xA4)"
            },
            { // percent
                L" %", L"%", L"", L"", L"-", L"-%", L"%-", L"", L" %-", L"-", L"", L"- %", L"", L"", L"", L"",
            }
        }
    }
};


// Localized region name for unknown regions (Windows 10)
#define UNKNOWN_REGION  L"Unknown Region ("
#define UNKNOWN_REGION_SIZE wcslen(UNKNOWN_REGION)

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    initialize
 * Signature: ()Z
 */
JNIEXPORT jboolean JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_initialize
  (JNIEnv *env, jclass cls) {
    if (!initialized) {
        pGetLocaleInfoEx = (PGLIE)GetProcAddress(
            GetModuleHandle("kernel32.dll"),
            "GetLocaleInfoEx");
        pGetCalendarInfoEx = (PGCIE)GetProcAddress(
            GetModuleHandle("kernel32.dll"),
            "GetCalendarInfoEx");
        pEnumCalendarInfoExEx = (PECIEE)GetProcAddress(
            GetModuleHandle("kernel32.dll"),
            "EnumCalendarInfoExEx");
        initialized =TRUE;
    }

    return pGetLocaleInfoEx != NULL &&
           pGetCalendarInfoEx != NULL &&
           pEnumCalendarInfoExEx != NULL;
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getDefaultLocale
 * Signature: (I)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getDefaultLocale
  (JNIEnv *env, jclass cls, jint cat) {
    char * localeString = NULL;
    LANGID langid;
    jstring ret;

    switch (cat) {
        case sun_util_locale_provider_HostLocaleProviderAdapterImpl_CAT_DISPLAY:
            langid = LANGIDFROMLCID(GetUserDefaultUILanguage());
            break;
        case sun_util_locale_provider_HostLocaleProviderAdapterImpl_CAT_FORMAT:
        default:
            langid = LANGIDFROMLCID(GetUserDefaultLCID());
            break;
    }

    localeString = (char *)getJavaIDFromLangID(langid);
    if (localeString != NULL) {
        ret = (*env)->NewStringUTF(env, localeString);
        free(localeString);
    } else {
        JNU_ThrowOutOfMemoryError(env, "memory allocation error");
        ret = NULL;
    }
    return ret;
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getDateTimePattern
 * Signature: (IILjava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getDateTimePattern
  (JNIEnv *env, jclass cls, jint dateStyle, jint timeStyle, jstring jlangtag) {
    WCHAR pattern[BUFLEN];
    const jchar *langtag = (*env)->GetStringChars(env, jlangtag, JNI_FALSE);
    CHECK_NULL_RETURN(langtag, NULL);

    pattern[0] = L'\0';

    if (dateStyle == 0 || dateStyle == 1) {
        getLocaleInfoWrapper(langtag, LOCALE_SLONGDATE, pattern, BUFLEN);
    } else if (dateStyle == 2 || dateStyle == 3) {
        getLocaleInfoWrapper(langtag, LOCALE_SSHORTDATE, pattern, BUFLEN);
    }

    if (timeStyle == 0 || timeStyle == 1) {
        getLocaleInfoWrapper(langtag, LOCALE_STIMEFORMAT, pattern, BUFLEN);
    } else if (timeStyle == 2 || timeStyle == 3) {
        getLocaleInfoWrapper(langtag, LOCALE_SSHORTTIME, pattern, BUFLEN);
    }

    (*env)->ReleaseStringChars(env, jlangtag, langtag);

    return (*env)->NewString(env, pattern, (jsize)wcslen(pattern));
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getCalendarID
 * Signature: (Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getCalendarID
  (JNIEnv *env, jclass cls, jstring jlangtag) {
    const jchar *langtag;
    jint ret;
    langtag = (*env)->GetStringChars(env, jlangtag, JNI_FALSE);
    CHECK_NULL_RETURN(langtag, 0);
    ret = getCalendarID(langtag);
    (*env)->ReleaseStringChars(env, jlangtag, langtag);
    return ret;
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getAmPmStrings
 * Signature: (Ljava/lang/String;[Ljava/lang/String;)[Ljava/lang/String;
 */
JNIEXPORT jobjectArray JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getAmPmStrings
  (JNIEnv *env, jclass cls, jstring jlangtag, jobjectArray ampms) {
    WCHAR buf[BUFLEN];
    const jchar *langtag;
    jstring tmp_string;

    // AM
    int got;
    langtag = (*env)->GetStringChars(env, jlangtag, JNI_FALSE);
    CHECK_NULL_RETURN(langtag, NULL);
    got = getLocaleInfoWrapper(langtag, LOCALE_S1159, buf, BUFLEN);
    if (got) {
        tmp_string = (*env)->NewString(env, buf, (jsize)wcslen(buf));
        if (tmp_string != NULL) {
            (*env)->SetObjectArrayElement(env, ampms, 0, tmp_string);
        }
    }

    if (!(*env)->ExceptionCheck(env)){
        // PM
        got = getLocaleInfoWrapper(langtag, LOCALE_S2359, buf, BUFLEN);
        if (got) {
            tmp_string = (*env)->NewString(env, buf, (jsize)wcslen(buf));
            if (tmp_string != NULL) {
                (*env)->SetObjectArrayElement(env, ampms, 1, tmp_string);
            }
        }
    }

    (*env)->ReleaseStringChars(env, jlangtag, langtag);

    return ampms;
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getEras
 * Signature: (Ljava/lang/String;[Ljava/lang/String;)[Ljava/lang/String;
 */
JNIEXPORT jobjectArray JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getEras
  (JNIEnv *env, jclass cls, jstring jlangtag, jobjectArray eras) {
    return getErasImpl(env, jlangtag, -1, 0, eras);
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getMonths
 * Signature: (Ljava/lang/String;[Ljava/lang/String;)[Ljava/lang/String;
 */
JNIEXPORT jobjectArray JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getMonths
  (JNIEnv *env, jclass cls, jstring jlangtag, jobjectArray months) {
    replaceCalendarArrayElems(env, jlangtag, -1, months, monthsType,
                      0, MONTHTYPES, 0);
    return months;
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getShortMonths
 * Signature: (Ljava/lang/String;[Ljava/lang/String;)[Ljava/lang/String;
 */
JNIEXPORT jobjectArray JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getShortMonths
  (JNIEnv *env, jclass cls, jstring jlangtag, jobjectArray smonths) {
    replaceCalendarArrayElems(env, jlangtag, -1, smonths, sMonthsType,
                      0, MONTHTYPES, 0);
    return smonths;
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getWeekdays
 * Signature: (Ljava/lang/String;[Ljava/lang/String;)[Ljava/lang/String;
 */
JNIEXPORT jobjectArray JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getWeekdays
  (JNIEnv *env, jclass cls, jstring jlangtag, jobjectArray wdays) {
    replaceCalendarArrayElems(env, jlangtag, -1, wdays, wDaysType,
                      1, sizeof(wDaysType)/sizeof(CALTYPE), 0);
    return wdays;
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getShortWeekdays
 * Signature: (Ljava/lang/String;[Ljava/lang/String;)[Ljava/lang/String;
 */
JNIEXPORT jobjectArray JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getShortWeekdays
  (JNIEnv *env, jclass cls, jstring jlangtag, jobjectArray swdays) {
    replaceCalendarArrayElems(env, jlangtag, -1, swdays, sWDaysType,
                      1, sizeof(sWDaysType)/sizeof(CALTYPE), 0);
    return swdays;
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getNumberPattern
 * Signature: (ILjava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getNumberPattern
  (JNIEnv *env, jclass cls, jint numberStyle, jstring jlangtag) {
    const jchar *langtag;
    jstring ret;
    WCHAR * pattern;

    langtag = (*env)->GetStringChars(env, jlangtag, JNI_FALSE);
    CHECK_NULL_RETURN(langtag, NULL);
    pattern = getNumberPattern(langtag, numberStyle);
    CHECK_NULL_RETURN(pattern, NULL);

    (*env)->ReleaseStringChars(env, jlangtag, langtag);
    ret = (*env)->NewString(env, pattern, (jsize)wcslen(pattern));
    free(pattern);

    return ret;
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    isNativeDigit
 * Signature: (Ljava/lang/String;)Z
 */
JNIEXPORT jboolean JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_isNativeDigit
  (JNIEnv *env, jclass cls, jstring jlangtag) {
    DWORD num;
    int got;
    const jchar *langtag = (*env)->GetStringChars(env, jlangtag, JNI_FALSE);
    CHECK_NULL_RETURN(langtag, JNI_FALSE);
    got = getLocaleInfoWrapper(langtag,
        LOCALE_IDIGITSUBSTITUTION | LOCALE_RETURN_NUMBER,
        (LPWSTR)&num, sizeof(num));
    (*env)->ReleaseStringChars(env, jlangtag, langtag);

    return got && num == 2; // 2: native digit substitution
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getCurrencySymbol
 * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getCurrencySymbol
  (JNIEnv *env, jclass cls, jstring jlangtag, jstring currencySymbol) {
    WCHAR buf[BUFLEN];
    int got;
    const jchar *langtag = (*env)->GetStringChars(env, jlangtag, JNI_FALSE);
    CHECK_NULL_RETURN(langtag, currencySymbol);
    got = getLocaleInfoWrapper(langtag, LOCALE_SCURRENCY, buf, BUFLEN);
    (*env)->ReleaseStringChars(env, jlangtag, langtag);

    if (got) {
        return (*env)->NewString(env, buf, (jsize)wcslen(buf));
    } else {
        return currencySymbol;
    }
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getDecimalSeparator
 * Signature: (Ljava/lang/String;C)C
 */
JNIEXPORT jchar JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getDecimalSeparator
  (JNIEnv *env, jclass cls, jstring jlangtag, jchar decimalSeparator) {
    WCHAR buf[BUFLEN];
    int got;
    const jchar *langtag = (*env)->GetStringChars(env, jlangtag, JNI_FALSE);
    CHECK_NULL_RETURN(langtag, decimalSeparator);
    got = getLocaleInfoWrapper(langtag, LOCALE_SDECIMAL, buf, BUFLEN);
    (*env)->ReleaseStringChars(env, jlangtag, langtag);

    if (got) {
        return buf[0];
    } else {
        return decimalSeparator;
    }
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getGroupingSeparator
 * Signature: (Ljava/lang/String;C)C
 */
JNIEXPORT jchar JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getGroupingSeparator
  (JNIEnv *env, jclass cls, jstring jlangtag, jchar groupingSeparator) {
    WCHAR buf[BUFLEN];
    int got;
    const jchar *langtag = (*env)->GetStringChars(env, jlangtag, JNI_FALSE);
    CHECK_NULL_RETURN(langtag, groupingSeparator);
    got = getLocaleInfoWrapper(langtag, LOCALE_STHOUSAND, buf, BUFLEN);
    (*env)->ReleaseStringChars(env, jlangtag, langtag);

    if (got) {
        return buf[0];
    } else {
        return groupingSeparator;
    }
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getInfinity
 * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getInfinity
  (JNIEnv *env, jclass cls, jstring jlangtag, jstring infinity) {
    WCHAR buf[BUFLEN];
    int got;
    const jchar *langtag = (*env)->GetStringChars(env, jlangtag, JNI_FALSE);
    CHECK_NULL_RETURN(langtag, infinity);
    got = getLocaleInfoWrapper(langtag, LOCALE_SPOSINFINITY, buf, BUFLEN);
    (*env)->ReleaseStringChars(env, jlangtag, langtag);

    if (got) {
        return (*env)->NewString(env, buf, (jsize)wcslen(buf));
    } else {
        return infinity;
    }
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getInternationalCurrencySymbol
 * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getInternationalCurrencySymbol
  (JNIEnv *env, jclass cls, jstring jlangtag, jstring internationalCurrencySymbol) {
    WCHAR buf[BUFLEN];
    int got;
    const jchar *langtag = (*env)->GetStringChars(env, jlangtag, JNI_FALSE);
    CHECK_NULL_RETURN(langtag, internationalCurrencySymbol);
    got = getLocaleInfoWrapper(langtag, LOCALE_SINTLSYMBOL, buf, BUFLEN);
    (*env)->ReleaseStringChars(env, jlangtag, langtag);

    if (got) {
        return (*env)->NewString(env, buf, (jsize)wcslen(buf));
    } else {
        return internationalCurrencySymbol;
    }
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getMinusSign
 * Signature: (Ljava/lang/String;C)C
 */
JNIEXPORT jchar JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getMinusSign
  (JNIEnv *env, jclass cls, jstring jlangtag, jchar minusSign) {
    WCHAR buf[BUFLEN];
    int got;
    const jchar *langtag = (*env)->GetStringChars(env, jlangtag, JNI_FALSE);
    CHECK_NULL_RETURN(langtag, minusSign);
    got = getLocaleInfoWrapper(langtag, LOCALE_SNEGATIVESIGN, buf, BUFLEN);
    (*env)->ReleaseStringChars(env, jlangtag, langtag);

    if (got) {
        return buf[0];
    } else {
        return minusSign;
    }
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getMonetaryDecimalSeparator
 * Signature: (Ljava/lang/String;C)C
 */
JNIEXPORT jchar JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getMonetaryDecimalSeparator
  (JNIEnv *env, jclass cls, jstring jlangtag, jchar monetaryDecimalSeparator) {
    WCHAR buf[BUFLEN];
    int got;
    const jchar *langtag = (*env)->GetStringChars(env, jlangtag, JNI_FALSE);
    CHECK_NULL_RETURN(langtag, monetaryDecimalSeparator);
    got = getLocaleInfoWrapper(langtag, LOCALE_SMONDECIMALSEP, buf, BUFLEN);
    (*env)->ReleaseStringChars(env, jlangtag, langtag);

    if (got) {
        return buf[0];
    } else {
        return monetaryDecimalSeparator;
    }
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getNaN
 * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getNaN
  (JNIEnv *env, jclass cls, jstring jlangtag, jstring nan) {
    WCHAR buf[BUFLEN];
    int got;
    const jchar *langtag = (*env)->GetStringChars(env, jlangtag, JNI_FALSE);
    CHECK_NULL_RETURN(langtag, nan);
    got = getLocaleInfoWrapper(langtag, LOCALE_SNAN, buf, BUFLEN);
    (*env)->ReleaseStringChars(env, jlangtag, langtag);

    if (got) {
        return (*env)->NewString(env, buf, (jsize)wcslen(buf));
    } else {
        return nan;
    }
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getPercent
 * Signature: (Ljava/lang/String;C)C
 */
JNIEXPORT jchar JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getPercent
  (JNIEnv *env, jclass cls, jstring jlangtag, jchar percent) {
    WCHAR buf[BUFLEN];
    int got;
    const jchar *langtag = (*env)->GetStringChars(env, jlangtag, JNI_FALSE);
    CHECK_NULL_RETURN(langtag, percent);
    got = getLocaleInfoWrapper(langtag, LOCALE_SPERCENT, buf, BUFLEN);
    (*env)->ReleaseStringChars(env, jlangtag, langtag);

    if (got) {
        return buf[0];
    } else {
        return percent;
    }
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getPerMill
 * Signature: (Ljava/lang/String;C)C
 */
JNIEXPORT jchar JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getPerMill
  (JNIEnv *env, jclass cls, jstring jlangtag, jchar perMill) {
    WCHAR buf[BUFLEN];
    const jchar *langtag;
    int got;
    langtag = (*env)->GetStringChars(env, jlangtag, JNI_FALSE);
    CHECK_NULL_RETURN(langtag, perMill);
    got = getLocaleInfoWrapper(langtag, LOCALE_SPERMILLE, buf, BUFLEN);

    (*env)->ReleaseStringChars(env, jlangtag, langtag);

    if (got) {
        return buf[0];
    } else {
        return perMill;
    }
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getZeroDigit
 * Signature: (Ljava/lang/String;C)C
 */
JNIEXPORT jchar JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getZeroDigit
  (JNIEnv *env, jclass cls, jstring jlangtag, jchar zeroDigit) {
    WCHAR buf[BUFLEN];
    const jchar *langtag;
    int got;
    langtag = (*env)->GetStringChars(env, jlangtag, JNI_FALSE);
    CHECK_NULL_RETURN(langtag, zeroDigit);
    got = getLocaleInfoWrapper(langtag, LOCALE_SNATIVEDIGITS, buf, BUFLEN);

    (*env)->ReleaseStringChars(env, jlangtag, langtag);

    if (got) {
        return buf[0];
    } else {
        return zeroDigit;
    }
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getCalendarDataValue
 * Signature: (Ljava/lang/String;I)I
 */
JNIEXPORT jint JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getCalendarDataValue
  (JNIEnv *env, jclass cls, jstring jlangtag, jint type) {
    DWORD num;
    const jchar *langtag;
    int got = 0;

    langtag = (*env)->GetStringChars(env, jlangtag, JNI_FALSE);
    CHECK_NULL_RETURN(langtag, -1);
    switch (type) {
    case sun_util_locale_provider_HostLocaleProviderAdapterImpl_CD_FIRSTDAYOFWEEK:
        got = getLocaleInfoWrapper(langtag,
            LOCALE_IFIRSTDAYOFWEEK | LOCALE_RETURN_NUMBER,
            (LPWSTR)&num, sizeof(num));
        break;
    }

    (*env)->ReleaseStringChars(env, jlangtag, langtag);

    if (got) {
        return num;
    } else {
        return -1;
    }
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getCalendarDisplayStrings
 * Signature: (Ljava/lang/String;III)[Ljava/lang/String;
 */
JNIEXPORT jobjectArray JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getCalendarDisplayStrings
  (JNIEnv *env, jclass cls, jstring jlangtag, jint calid, jint field, jint style) {
    jobjectArray ret = NULL;
    CALTYPE * pCalType = NULL;

    switch (field) {
    case CALENDAR_FIELD_ERA:
        return getErasImpl(env, jlangtag, calid, style, NULL);

    case CALENDAR_FIELD_MONTH:
        ret = (*env)->NewObjectArray(env, MONTHTYPES,
                (*env)->FindClass(env, "java/lang/String"), NULL);
        if (ret != NULL) {
            if (style & CALENDAR_STYLE_SHORT_MASK) {
                pCalType = sMonthsType;
            } else {
                pCalType = monthsType;
            }

            replaceCalendarArrayElems(env, jlangtag, calid, ret, pCalType,
                          0, MONTHTYPES, style);
        }
        return ret;

    default:
        // not supported
        return NULL;
    }
}

/*
 * Class:     sun_util_locale_provider_HostLocaleProviderAdapterImpl
 * Method:    getDisplayString
 * Signature: (Ljava/lang/String;ILjava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_sun_util_locale_provider_HostLocaleProviderAdapterImpl_getDisplayString
  (JNIEnv *env, jclass cls, jstring jlangtag, jint type, jstring jvalue) {
    LCTYPE lcType;
    jstring jStr;
    const jchar * pjChar;
    WCHAR buf[BUFLEN];
    int got = 0;

    switch (type) {
        case sun_util_locale_provider_HostLocaleProviderAdapterImpl_DN_CURRENCY_NAME:
            lcType = LOCALE_SNATIVECURRNAME;
            jStr = jlangtag;
            break;
        case sun_util_locale_provider_HostLocaleProviderAdapterImpl_DN_CURRENCY_SYMBOL:
            lcType = LOCALE_SCURRENCY;
            jStr = jlangtag;
            break;
        case sun_util_locale_provider_HostLocaleProviderAdapterImpl_DN_LOCALE_LANGUAGE:
            lcType = LOCALE_SLOCALIZEDLANGUAGENAME;
            jStr = jvalue;
            break;
        case sun_util_locale_provider_HostLocaleProviderAdapterImpl_DN_LOCALE_REGION:
            lcType = LOCALE_SLOCALIZEDCOUNTRYNAME;
            jStr = jvalue;
            break;
        default:
            return NULL;
    }

    pjChar = (*env)->GetStringChars(env, jStr, JNI_FALSE);
    CHECK_NULL_RETURN(pjChar, NULL);
    got = getLocaleInfoWrapper(pjChar, lcType, buf, BUFLEN);
    (*env)->ReleaseStringChars(env, jStr, pjChar);

    if (got) {
        // Hack: Windows 10 returns "Unknown Region (XX)" for localized XX region name.
        // Take that as not known.
        if (type != sun_util_locale_provider_HostLocaleProviderAdapterImpl_DN_LOCALE_REGION ||
            wcsncmp(UNKNOWN_REGION, buf, UNKNOWN_REGION_SIZE) != 0) {
            return (*env)->NewString(env, buf, (jsize)wcslen(buf));
        }
    }

    return NULL;
}

int getLocaleInfoWrapper(const jchar *langtag, LCTYPE type, LPWSTR data, int buflen) {
    if (pGetLocaleInfoEx) {
        if (wcscmp(L"und", (LPWSTR)langtag) == 0) {
            // defaults to "en"
            return pGetLocaleInfoEx(L"en", type, data, buflen);
        } else {
            return pGetLocaleInfoEx((LPWSTR)langtag, type, data, buflen);
        }
    } else {
        // If we ever wanted to support WinXP, we will need extra module from
        // MS...
        // return GetLocaleInfo(DownlevelLocaleNameToLCID(langtag, 0), type, data, buflen);
        return 0;
    }
}

int getCalendarInfoWrapper(const jchar *langtag, CALID id, LPCWSTR reserved, CALTYPE type, LPWSTR data, int buflen, LPDWORD val) {
    if (pGetCalendarInfoEx) {
        if (wcscmp(L"und", (LPWSTR)langtag) == 0) {
            // defaults to "en"
            return pGetCalendarInfoEx(L"en", id, reserved, type, data, buflen, val);
        } else {
            return pGetCalendarInfoEx((LPWSTR)langtag, id, reserved, type, data, buflen, val);
        }
    } else {
        // If we ever wanted to support WinXP, we will need extra module from
        // MS...
        // return GetCalendarInfo(DownlevelLocaleNameToLCID(langtag, 0), ...);
        return 0;
    }
}

jint getCalendarID(const jchar *langtag) {
    DWORD type = -1;
    int got = getLocaleInfoWrapper(langtag,
        LOCALE_ICALENDARTYPE | LOCALE_RETURN_NUMBER,
        (LPWSTR)&type, sizeof(type));

    if (got) {
        switch (type) {
            case CAL_GREGORIAN:
            case CAL_GREGORIAN_US:
            case CAL_JAPAN:
            case CAL_TAIWAN:
            case CAL_HIJRI:
            case CAL_THAI:
            case CAL_GREGORIAN_ME_FRENCH:
            case CAL_GREGORIAN_ARABIC:
            case CAL_GREGORIAN_XLIT_ENGLISH:
            case CAL_GREGORIAN_XLIT_FRENCH:
            case CAL_UMALQURA:
                break;

            default:
                // non-supported calendars return -1
                type = -1;
                break;
        }
    }

    return type;
}

void replaceCalendarArrayElems(JNIEnv *env, jstring jlangtag, jint calid, jobjectArray jarray, CALTYPE* pCalTypes, int offset, int length, int style) {
    WCHAR name[BUFLEN];
    const jchar *langtag = (*env)->GetStringChars(env, jlangtag, JNI_FALSE);
    jstring tmp_string;
    CALTYPE isGenitive;

    CHECK_NULL(langtag);

    if (calid < 0) {
        calid = getCalendarID(langtag);
    }

    if (calid != -1) {
        int i;

        if (!(style & CALENDAR_STYLE_STANDALONE_MASK)) {
            isGenitive = CAL_RETURN_GENITIVE_NAMES;
        }

        for (i = 0; i < length; i++) {
            if (getCalendarInfoWrapper(langtag, calid, NULL,
                              pCalTypes[i] | isGenitive, name, BUFLEN, NULL) != 0) {
                tmp_string = (*env)->NewString(env, name, (jsize)wcslen(name));
                if (tmp_string != NULL) {
                    (*env)->SetObjectArrayElement(env, jarray, i + offset, tmp_string);
                }
            }
        }
    }

    (*env)->ReleaseStringChars(env, jlangtag, langtag);
}

WCHAR * getNumberPattern(const jchar * langtag, const jint numberStyle) {
    WCHAR ret[BUFLEN];
    WCHAR number[BUFLEN];
    WCHAR fix[BUFLEN];

    getFixPart(langtag, numberStyle, TRUE, TRUE, ret); // "+"
    getNumberPart(langtag, numberStyle, number);
    wcscat_s(ret, BUFLEN-wcslen(ret), number);      // "+12.34"
    getFixPart(langtag, numberStyle, TRUE, FALSE, fix);
    wcscat_s(ret, BUFLEN-wcslen(ret), fix);         // "+12.34$"
    wcscat_s(ret, BUFLEN-wcslen(ret), L";");        // "+12.34$;"
    getFixPart(langtag, numberStyle, FALSE, TRUE, fix);
    wcscat_s(ret, BUFLEN-wcslen(ret), fix);         // "+12.34$;("
    wcscat_s(ret, BUFLEN-wcslen(ret), number);      // "+12.34$;(12.34"
    getFixPart(langtag, numberStyle, FALSE, FALSE, fix);
    wcscat_s(ret, BUFLEN-wcslen(ret), fix);         // "+12.34$;(12.34$)"

    return _wcsdup(ret);
}

void getNumberPart(const jchar * langtag, const jint numberStyle, WCHAR * number) {
    DWORD digits = 0;
    DWORD leadingZero = 0;
    WCHAR grouping[BUFLEN];
    int groupingLen;
    WCHAR fractionPattern[BUFLEN];
    WCHAR * integerPattern = number;
    WCHAR * pDest;

    // Get info from Windows
    switch (numberStyle) {
        case sun_util_locale_provider_HostLocaleProviderAdapterImpl_NF_CURRENCY:
            getLocaleInfoWrapper(langtag,
                LOCALE_ICURRDIGITS | LOCALE_RETURN_NUMBER,
                (LPWSTR)&digits, sizeof(digits));
            break;

        case sun_util_locale_provider_HostLocaleProviderAdapterImpl_NF_INTEGER:
            break;

        case sun_util_locale_provider_HostLocaleProviderAdapterImpl_NF_NUMBER:
        case sun_util_locale_provider_HostLocaleProviderAdapterImpl_NF_PERCENT:
        default:
            getLocaleInfoWrapper(langtag,
                LOCALE_IDIGITS | LOCALE_RETURN_NUMBER,
                (LPWSTR)&digits, sizeof(digits));
            break;
    }

    getLocaleInfoWrapper(langtag,
        LOCALE_ILZERO | LOCALE_RETURN_NUMBER,
        (LPWSTR)&leadingZero, sizeof(leadingZero));
    groupingLen = getLocaleInfoWrapper(langtag, LOCALE_SGROUPING, grouping, BUFLEN);

    // fraction pattern
    if (digits > 0) {
        int i;
        for(i = digits;  i > 0; i--) {
            fractionPattern[i] = L'0';
        }
        fractionPattern[0] = L'.';
        fractionPattern[digits+1] = L'\0';
    } else {
        fractionPattern[0] = L'\0';
    }

    // integer pattern
    pDest = integerPattern;
    if (groupingLen > 0) {
        int cur = groupingLen - 1;// subtracting null terminator
        while (--cur >= 0) {
            int repnum;

            if (grouping[cur] == L';') {
                continue;
            }

            repnum = grouping[cur] - 0x30;
            if (repnum > 0) {
                *pDest++ = L'#';
                *pDest++ = L',';
                while(--repnum > 0) {
                    *pDest++ = L'#';
                }
            }
        }
    }

    if (leadingZero != 0) {
        *pDest++ = L'0';
    } else {
        *pDest++ = L'#';
    }
    *pDest = L'\0';

    wcscat_s(integerPattern, BUFLEN, fractionPattern);
}

void getFixPart(const jchar * langtag, const jint numberStyle, BOOL positive, BOOL prefix, WCHAR * ret) {
    DWORD pattern = 0;
    int style = numberStyle;
    int got = 0;

    if (positive) {
        if (style == sun_util_locale_provider_HostLocaleProviderAdapterImpl_NF_CURRENCY) {
            got = getLocaleInfoWrapper(langtag,
                LOCALE_ICURRENCY | LOCALE_RETURN_NUMBER,
                (LPWSTR)&pattern, sizeof(pattern));
        } else if (style == sun_util_locale_provider_HostLocaleProviderAdapterImpl_NF_PERCENT) {
            got = getLocaleInfoWrapper(langtag,
                LOCALE_IPOSITIVEPERCENT | LOCALE_RETURN_NUMBER,
                (LPWSTR)&pattern, sizeof(pattern));
        }
    } else {
        if (style == sun_util_locale_provider_HostLocaleProviderAdapterImpl_NF_CURRENCY) {
            got = getLocaleInfoWrapper(langtag,
                LOCALE_INEGCURR | LOCALE_RETURN_NUMBER,
                (LPWSTR)&pattern, sizeof(pattern));
        } else if (style == sun_util_locale_provider_HostLocaleProviderAdapterImpl_NF_PERCENT) {
            got = getLocaleInfoWrapper(langtag,
                LOCALE_INEGATIVEPERCENT | LOCALE_RETURN_NUMBER,
                (LPWSTR)&pattern, sizeof(pattern));
        } else {
            got = getLocaleInfoWrapper(langtag,
                LOCALE_INEGNUMBER | LOCALE_RETURN_NUMBER,
                (LPWSTR)&pattern, sizeof(pattern));
        }
    }

    if (numberStyle == sun_util_locale_provider_HostLocaleProviderAdapterImpl_NF_INTEGER) {
        style = sun_util_locale_provider_HostLocaleProviderAdapterImpl_NF_NUMBER;
    }

    wcscpy(ret, fixes[!prefix][!positive][style][pattern]);
}

int enumCalendarInfoWrapper(const jchar *langtag, CALID calid, CALTYPE type, LPWSTR buf, int buflen) {
    if (pEnumCalendarInfoExEx) {
        if (wcscmp(L"und", (LPWSTR)langtag) == 0) {
            // defaults to "en"
            return pEnumCalendarInfoExEx(&EnumCalendarInfoProc, L"en",
                calid, NULL, type, (LPARAM)buf);
        } else {
            return pEnumCalendarInfoExEx(&EnumCalendarInfoProc, langtag,
                calid, NULL, type, (LPARAM)buf);
        }
    } else {
        return 0;
    }
}

BOOL CALLBACK EnumCalendarInfoProc(LPWSTR lpCalInfoStr, CALID calid, LPWSTR lpReserved, LPARAM lParam) {
    wcscat_s((LPWSTR)lParam, BUFLEN, lpCalInfoStr);
    wcscat_s((LPWSTR)lParam, BUFLEN, L",");
    return TRUE;
}

jobjectArray getErasImpl(JNIEnv *env, jstring jlangtag, jint calid, jint style, jobjectArray eras) {
    const jchar * langtag = (*env)->GetStringChars(env, jlangtag, JNI_FALSE);
    WCHAR buf[BUFLEN];
    jobjectArray ret = eras;
    CALTYPE type;

    CHECK_NULL_RETURN(langtag, ret);

    buf[0] = '\0';
    if (style & CALENDAR_STYLE_SHORT_MASK) {
        type = CAL_SABBREVERASTRING;
    } else {
        type = CAL_SERASTRING;
    }

    if (calid < 0) {
        calid = getCalendarID(langtag);
    }

    if (calid != -1 && enumCalendarInfoWrapper(langtag, calid, type, buf, BUFLEN)) {
        // format in buf: "era0,era1,era2," where era0 is the current one
        int eraCount;
        LPWSTR current;
        jsize array_length;

        for(eraCount = 0, current = buf; *current != '\0'; current++) {
            if (*current == L',') {
                eraCount ++;
            }
        }

        if (eras != NULL) {
            array_length = (*env)->GetArrayLength(env, eras);
        } else {
            // +1 for the "before" era, e.g., BC, which Windows does not return.
            array_length = (jsize)eraCount + 1;
            ret = (*env)->NewObjectArray(env, array_length,
                (*env)->FindClass(env, "java/lang/String"), NULL);
        }

        if (ret != NULL) {
            int eraIndex;
            LPWSTR era;

            for(eraIndex = 0, era = current = buf; eraIndex < eraCount; era = current, eraIndex++) {
                while (*current != L',') {
                    current++;
                }
                *current++ = '\0';

                if (eraCount - eraIndex < array_length &&
                    *era != '\0') {
                    (*env)->SetObjectArrayElement(env, ret,
                        (jsize)(eraCount - eraIndex),
                        (*env)->NewString(env, era, (jsize)wcslen(era)));
                }
            }

            // Hack for the Japanese Imperial Calendar to insert Gregorian era for
            // "Before Meiji"
            if (calid == CAL_JAPAN) {
                buf[0] = '\0';
                if (enumCalendarInfoWrapper(langtag, CAL_GREGORIAN, type, buf, BUFLEN)) {
                    jsize len = (jsize)wcslen(buf);
                    buf[--len] = '\0'; // remove the last ','
                    (*env)->SetObjectArrayElement(env, ret, 0, (*env)->NewString(env, buf, len));
                }
            }
        }
    }

    (*env)->ReleaseStringChars(env, jlangtag, langtag);
    return ret;
}
