/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <locale.h>
#include <limits.h>
#include "libc.h"
#include <string.h>
#include "locale_impl.h"

static const struct lconv posix_lconv = {
	.decimal_point = ".",
	.thousands_sep = "",
	.grouping = "",
	.int_curr_symbol = "",
	.currency_symbol = "",
	.mon_decimal_point = "",
	.mon_thousands_sep = "",
	.mon_grouping = "",
	.positive_sign = "",
	.negative_sign = "",
	.int_frac_digits = CHAR_MAX,
	.frac_digits = CHAR_MAX,
	.p_cs_precedes = CHAR_MAX,
	.p_sep_by_space = CHAR_MAX,
	.n_cs_precedes = CHAR_MAX,
	.n_sep_by_space = CHAR_MAX,
	.p_sign_posn = CHAR_MAX,
	.n_sign_posn = CHAR_MAX,
	.int_p_cs_precedes = CHAR_MAX,
	.int_p_sep_by_space = CHAR_MAX,
	.int_n_cs_precedes = CHAR_MAX,
	.int_n_sep_by_space = CHAR_MAX,
	.int_p_sign_posn = CHAR_MAX,
	.int_n_sign_posn = CHAR_MAX,
};

#ifdef FEATURE_ICU_LOCALE
#define ICU_BUFFER_SIZE 16
#define CHAR_BUFFER_SIZE 8
#define INITIALIZE_ICURES_PTR(posix_lconv_ptr, lconv_icures_ptr, buffsize)             \
	do {                                                                               \
		memset(lconv_icures_ptr, 0, buffsize);                                         \
		strncpy(lconv_icures_ptr, posix_lconv_ptr, strlen(posix_lconv_ptr));           \
	} while (0)

static struct lconv g_lconv_icures;
static volatile int g_localeconv_initialize = 0;
static char g_decimal_point_buf[ICU_BUFFER_SIZE];
static char g_thousands_sep_buf[ICU_BUFFER_SIZE];
static char g_int_curr_symbol_buf[ICU_BUFFER_SIZE];
static char g_currency_symbol_buf[ICU_BUFFER_SIZE];
static char g_mon_decimal_point_buf[ICU_BUFFER_SIZE];
static char g_mon_thousands_sep_buf[ICU_BUFFER_SIZE];
static char g_positive_sign_buf[ICU_BUFFER_SIZE];
static char g_negative_sign_buf[ICU_BUFFER_SIZE];

typedef enum {
	ICU_DECIMAL_POINT = 0,
	ICU_THOUSANDS_SEP = 1,
	ICU_NEGATIVE_SIGN = 6,
	ICU_POSITIVE_SIGN = 7,
	ICU_CURR_SYMBOL = 8,
	ICU_INT_CURR_SYMBOL = 9,
	ICU_MON_DECIMAL_POINT = 10,
	ICU_MON_THOUSANDS_SEP = 17,
} icu_getsymbol_type;

static void update_lconv_member(u_char *icu_symbol, void *fmt, char *lconv_member, icu_getsymbol_type type)
{
	int icu_status = 0;
	if (!g_icu_opt_func.unum_get_symbol) {
		return;
	}
	(void)g_icu_opt_func.unum_get_symbol(fmt, type, icu_symbol, ICU_BUFFER_SIZE, &icu_status);
	if (icu_status <= 0) {
		if (g_icu_opt_func.u_austrncpy) {
			g_icu_opt_func.u_austrncpy(lconv_member, icu_symbol, ICU_BUFFER_SIZE);
		}
	}
}

// refresh the lconv_icures, fills with the default value
static void refresh_lconv_icures(void)
{
	int status = -1;
#ifdef a_ll
	status = a_ll(&g_localeconv_initialize);
#else
	status = g_localeconv_initialize;
#endif
	if (status == 0) {
		g_lconv_icures.grouping = posix_lconv.grouping;
		g_lconv_icures.mon_grouping = posix_lconv.mon_grouping;
		g_lconv_icures.frac_digits = posix_lconv.frac_digits;
		g_lconv_icures.int_frac_digits = posix_lconv.int_frac_digits;
		g_lconv_icures.p_cs_precedes = posix_lconv.p_cs_precedes;
		g_lconv_icures.p_sep_by_space = posix_lconv.p_sep_by_space;
		g_lconv_icures.n_cs_precedes = posix_lconv.n_cs_precedes;
		g_lconv_icures.n_sep_by_space = posix_lconv.n_sep_by_space;
		g_lconv_icures.p_sign_posn = posix_lconv.p_sign_posn;
		g_lconv_icures.n_sign_posn = posix_lconv.n_sign_posn;
		g_lconv_icures.int_p_cs_precedes = posix_lconv.int_p_cs_precedes;
		g_lconv_icures.int_p_sep_by_space = posix_lconv.int_p_sep_by_space;
		g_lconv_icures.int_n_cs_precedes = posix_lconv.int_n_cs_precedes;
		g_lconv_icures.int_n_sep_by_space = posix_lconv.int_n_sep_by_space;
		g_lconv_icures.int_p_sign_posn = posix_lconv.int_p_sign_posn;
		g_lconv_icures.int_n_sign_posn = posix_lconv.int_n_sign_posn;

		g_lconv_icures.decimal_point = g_decimal_point_buf;
		g_lconv_icures.thousands_sep = g_thousands_sep_buf;
		g_lconv_icures.int_curr_symbol = g_int_curr_symbol_buf;
		g_lconv_icures.currency_symbol = g_currency_symbol_buf;
		g_lconv_icures.mon_decimal_point = g_mon_decimal_point_buf;
		g_lconv_icures.mon_thousands_sep = g_mon_thousands_sep_buf;
		g_lconv_icures.positive_sign = g_positive_sign_buf;
		g_lconv_icures.negative_sign = g_negative_sign_buf;
		(void)a_cas(&g_localeconv_initialize, 0, 1);
		a_barrier();
	}
	a_barrier();
	INITIALIZE_ICURES_PTR(posix_lconv.decimal_point, g_lconv_icures.decimal_point, ICU_BUFFER_SIZE);
	INITIALIZE_ICURES_PTR(posix_lconv.thousands_sep, g_lconv_icures.thousands_sep, ICU_BUFFER_SIZE);
	INITIALIZE_ICURES_PTR(posix_lconv.int_curr_symbol, g_lconv_icures.int_curr_symbol, ICU_BUFFER_SIZE);
	INITIALIZE_ICURES_PTR(posix_lconv.currency_symbol, g_lconv_icures.currency_symbol, ICU_BUFFER_SIZE);
	INITIALIZE_ICURES_PTR(posix_lconv.mon_decimal_point, g_lconv_icures.mon_decimal_point, ICU_BUFFER_SIZE);
	INITIALIZE_ICURES_PTR(posix_lconv.mon_thousands_sep, g_lconv_icures.mon_thousands_sep, ICU_BUFFER_SIZE);
	INITIALIZE_ICURES_PTR(posix_lconv.positive_sign, g_lconv_icures.positive_sign, ICU_BUFFER_SIZE);
	INITIALIZE_ICURES_PTR(posix_lconv.negative_sign, g_lconv_icures.negative_sign, ICU_BUFFER_SIZE);
}
#endif

struct lconv *localeconv(void)
{
#ifdef FEATURE_ICU_LOCALE
	if ((libc.global_locale.cat[LC_NUMERIC] && libc.global_locale.cat[LC_NUMERIC]->flag == ICU_VALID) ||
		(libc.global_locale.cat[LC_MONETARY] && libc.global_locale.cat[LC_MONETARY]->flag == ICU_VALID)) {
		int cur_status = 0;
		refresh_lconv_icures();

		/* ICU function: unum_getSymbol, return specific information, e.g. currency symbol */
		get_icu_symbol(ICU_I18N, (void **)&(g_icu_opt_func.unum_get_symbol), ICU_UNUM_GET_SYMBOL_SYMBOL);
		/* ICU function: u_austrncpy, transfer result from unum_getSymbol to utf-8 string */
		get_icu_symbol(ICU_UC, (void **)&(g_icu_opt_func.u_austrncpy), ICU_AUSTRNCPY_SYMBOL);

		if (!(g_icu_opt_func.unum_get_symbol) || !(g_icu_opt_func.u_austrncpy)) {
			return (void *)&g_lconv_icures;
		}
		
		u_char icu_symbol[ICU_BUFFER_SIZE] = { 0 };
		void *fmt = NULL;

		if (libc.global_locale.cat[LC_NUMERIC]) {
			char *icu_locale_name_num = calloc(1, MAX_VALID_ICU_NAME_LEN);
			get_valid_icu_locale_name(libc.global_locale.cat[LC_NUMERIC]->name, icu_locale_name_num,
				MAX_VALID_ICU_NAME_LEN);
			fmt = icu_unum_open(icu_locale_name_num, &cur_status);
			free(icu_locale_name_num);
			if (cur_status == 0) {
				memset(icu_symbol, 0, ICU_BUFFER_SIZE);
				update_lconv_member(icu_symbol, fmt, g_lconv_icures.decimal_point, ICU_DECIMAL_POINT);
				memset(icu_symbol, 0, ICU_BUFFER_SIZE);
				update_lconv_member(icu_symbol, fmt, g_lconv_icures.thousands_sep, ICU_THOUSANDS_SEP);
				icu_unum_close(fmt);
			}
		}
		
		if (libc.global_locale.cat[LC_MONETARY]) {
			char *icu_locale_name_mon = calloc(1, MAX_VALID_ICU_NAME_LEN);
			get_valid_icu_locale_name(libc.global_locale.cat[LC_MONETARY]->name, icu_locale_name_mon,
				MAX_VALID_ICU_NAME_LEN);
			fmt = icu_unum_open(icu_locale_name_mon, &cur_status);
			free(icu_locale_name_mon);
			if (cur_status == 0) {
				memset(icu_symbol, 0, ICU_BUFFER_SIZE);
				update_lconv_member(icu_symbol, fmt, g_lconv_icures.int_curr_symbol, ICU_INT_CURR_SYMBOL);
				memset(icu_symbol, 0, ICU_BUFFER_SIZE);
				update_lconv_member(icu_symbol, fmt, g_lconv_icures.currency_symbol, ICU_CURR_SYMBOL);
				memset(icu_symbol, 0, ICU_BUFFER_SIZE);
				update_lconv_member(icu_symbol, fmt, g_lconv_icures.mon_decimal_point, ICU_MON_DECIMAL_POINT);
				memset(icu_symbol, 0, ICU_BUFFER_SIZE);
				update_lconv_member(icu_symbol, fmt, g_lconv_icures.mon_thousands_sep, ICU_MON_THOUSANDS_SEP);
				memset(icu_symbol, 0, ICU_BUFFER_SIZE);
				update_lconv_member(icu_symbol, fmt, g_lconv_icures.positive_sign, ICU_POSITIVE_SIGN);
				memset(icu_symbol, 0, ICU_BUFFER_SIZE);
				update_lconv_member(icu_symbol, fmt, g_lconv_icures.negative_sign, ICU_NEGATIVE_SIGN);
				icu_unum_close(fmt);
			}
		}
		return (void *)&g_lconv_icures;
	}
#endif
	return (void *)&posix_lconv;
}