/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1985-2011 AT&T Intellectual Property          *
*          Copyright (c) 2020-2024 Contributors to ksh 93u+m           *
*                      and is licensed under the                       *
*                 Eclipse Public License, Version 2.0                  *
*                                                                      *
*                A copy of the License is available at                 *
*      https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html      *
*         (with md5 checksum 84283fa8859daf213bdda5a9f8d1be1d)         *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                  David Korn <dgk@research.att.com>                   *
*                   Phong Vo <kpv@research.att.com>                    *
*                  Martijn Dekker <martijn@inlv.org>                   *
*            Johnothan King <johnothanking@protonmail.com>             *
*                                                                      *
***********************************************************************/
/*
 * Glenn Fowler
 * AT&T Research
 *
 * time conversion translation support
 */

#include <ast.h>
#include <cdt.h>
#include <iconv.h>
#include <mc.h>
#include <tm.h>
#include <ast_nl_types.h>

#include "lclib.h"

static struct
{
	char*		format;
	Lc_info_t*	locale;
	char		null[1];
} state;

/*
 * this is Unix dadgummit
 */

static int
standardized(Lc_info_t* li, char** b)
{
	if ((li->lc->language->flags & (LC_debug|LC_default)) || streq(li->lc->language->code, "en"))
	{
		b[TM_TIME] = "%H:%M:%S";
		b[TM_DATE] = "%m/%d/%y";
		b[TM_DEFAULT] = "%a %b %e %T %Z %Y";
		return 1;
	}
	return 0;
}

/*
 * fix up LC_TIME data after loading
 */

static void
fixup(Lc_info_t* li, char** b)
{
	char**			v;
	char**			e;
	int			n;

	static int		must[] =
	{
					TM_TIME,
					TM_DATE,
					TM_DEFAULT,
					TM_MERIDIAN,
					TM_UT,
					TM_DT,
					TM_SUFFIXES,
					TM_PARTS,
					TM_HOURS,
					TM_DAYS,
					TM_LAST,
					TM_THIS,
					TM_NEXT,
					TM_EXACT,
					TM_NOISE,
					TM_ORDINAL,
					TM_CTIME,
					TM_DATE_1,
					TM_INTERNATIONAL,
					TM_RECENT,
					TM_DISTANT,
					TM_MERIDIAN_TIME,
					TM_ORDINALS,
					TM_FINAL,
					TM_WORK,
	};

	standardized(li, b);
	for (v = b, e = b + TM_NFORM; v < e; v++)
		if (!*v)
			*v = state.null;
	for (n = 0; n < elementsof(must); n++)
		if (!*b[must[n]])
			b[must[n]] = tm_data.format[must[n]];
	if (li->lc->flags & LC_default)
		for (n = 0; n < TM_NFORM; n++)
			if (!*b[n])
				b[n] = tm_data.format[n];
	if (strchr(b[TM_UT], '%'))
	{
		tm_info.deformat = b[TM_UT];
		for (n = TM_UT; n < TM_DT; n++)
			b[n] = state.null;
	}
	else
		tm_info.deformat = b[TM_DEFAULT];
	tm_info.format = b;
	if (!(tm_info.deformat = state.format))
		tm_info.deformat = tm_info.format[TM_DEFAULT];
	li->data = b;
}

#if _WINIX

#include <ast_windows.h>

typedef struct Map_s
{
	LCID		native;
	int		local;
} Map_t;

static const Map_t map[] =
{
	LOCALE_S1159,			(TM_MERIDIAN+0),
	LOCALE_S2359,			(TM_MERIDIAN+1),
	LOCALE_SABBREVDAYNAME1,		(TM_DAY_ABBREV+1),
	LOCALE_SABBREVDAYNAME2,		(TM_DAY_ABBREV+2),
	LOCALE_SABBREVDAYNAME3,		(TM_DAY_ABBREV+3),
	LOCALE_SABBREVDAYNAME4,		(TM_DAY_ABBREV+4),
	LOCALE_SABBREVDAYNAME5,		(TM_DAY_ABBREV+5),
	LOCALE_SABBREVDAYNAME6,		(TM_DAY_ABBREV+6),
	LOCALE_SABBREVDAYNAME7,		(TM_DAY_ABBREV+0),
	LOCALE_SABBREVMONTHNAME1,	(TM_MONTH_ABBREV+0),
	LOCALE_SABBREVMONTHNAME2,	(TM_MONTH_ABBREV+1),
	LOCALE_SABBREVMONTHNAME3,	(TM_MONTH_ABBREV+2),
	LOCALE_SABBREVMONTHNAME4,	(TM_MONTH_ABBREV+3),
	LOCALE_SABBREVMONTHNAME5,	(TM_MONTH_ABBREV+4),
	LOCALE_SABBREVMONTHNAME6,	(TM_MONTH_ABBREV+5),
	LOCALE_SABBREVMONTHNAME7,	(TM_MONTH_ABBREV+6),
	LOCALE_SABBREVMONTHNAME8,	(TM_MONTH_ABBREV+7),
	LOCALE_SABBREVMONTHNAME9,	(TM_MONTH_ABBREV+8),
	LOCALE_SABBREVMONTHNAME10,	(TM_MONTH_ABBREV+9),
	LOCALE_SABBREVMONTHNAME11,	(TM_MONTH_ABBREV+10),
	LOCALE_SABBREVMONTHNAME12,	(TM_MONTH_ABBREV+11),
	LOCALE_SDAYNAME1,		(TM_DAY+1),
	LOCALE_SDAYNAME2,		(TM_DAY+2),
	LOCALE_SDAYNAME3,		(TM_DAY+3),
	LOCALE_SDAYNAME4,		(TM_DAY+4),
	LOCALE_SDAYNAME5,		(TM_DAY+5),
	LOCALE_SDAYNAME6,		(TM_DAY+6),
	LOCALE_SDAYNAME7,		(TM_DAY+0),
	LOCALE_SMONTHNAME1,		(TM_MONTH+0),
	LOCALE_SMONTHNAME2,		(TM_MONTH+1),
	LOCALE_SMONTHNAME3,		(TM_MONTH+2),
	LOCALE_SMONTHNAME4,		(TM_MONTH+3),
	LOCALE_SMONTHNAME5,		(TM_MONTH+4),
	LOCALE_SMONTHNAME6,		(TM_MONTH+5),
	LOCALE_SMONTHNAME7,		(TM_MONTH+6),
	LOCALE_SMONTHNAME8,		(TM_MONTH+7),
	LOCALE_SMONTHNAME9,		(TM_MONTH+8),
	LOCALE_SMONTHNAME10,		(TM_MONTH+9),
	LOCALE_SMONTHNAME11,		(TM_MONTH+10),
	LOCALE_SMONTHNAME12,		(TM_MONTH+11),
};

/*
 * convert MS Word date spec w to POSIX strftime format f
 * next char after f returned
 * the caller already made sure f is big enough
 */

static char*
word2posix(char* f, char* w, int alternate)
{
	char*	r;
	int	c;
	int	p;
	int	n;

	while (*w)
	{
		p = 0;
		r = w;
		while (*++w == *r);
		if ((n = w - r) > 3 && alternate)
			n--;
		switch (*r)
		{
		case 'a':
		case 'A':
			if (!strncasecmp(w, "am/pm", 5))
				w += 5;
			else if (!strncasecmp(w, "a/p", 3))
				w += 3;
			c = 'p';
			break;
		case 'd':
			switch (n)
			{
			case 1:
				p = '-';
				/* FALLTHROUGH */
			case 2:
				c = 'd';
				break;
			case 3:
				c = 'a';
				break;
			default:
				c = 'A';
				break;
			}
			break;
		case 'h':
			switch (n)
			{
			case 1:
				p = '-';
				/* FALLTHROUGH */
			default:
				c = 'I';
				break;
			}
			break;
		case 'H':
			switch (n)
			{
			case 1:
				p = '-';
				/* FALLTHROUGH */
			default:
				c = 'H';
				break;
			}
			break;
		case 'M':
			switch (n)
			{
			case 1:
				p = '-';
				/* FALLTHROUGH */
			case 2:
				c = 'm';
				break;
			case 3:
				c = 'b';
				break;
			default:
				c = 'B';
				break;
			}
			break;
		case 'm':
			switch (n)
			{
			case 1:
				p = '-';
				/* FALLTHROUGH */
			default:
				c = 'M';
				break;
			}
			break;
		case 's':
			switch (n)
			{
			case 1:
				p = '-';
				/* FALLTHROUGH */
			default:
				c = 'S';
				break;
			}
			break;
		case 'y':
			switch (n)
			{
			case 1:
				p = '-';
				/* FALLTHROUGH */
			case 2:
				c = 'y';
				break;
			default:
				c = 'Y';
				break;
			}
			break;
		case '\'':
			if (n & 1)
				for (w = r + 1; *w; *f++ = *w++)
					if (*w == '\'')
					{
						w++;
						break;
					}
			continue;
		case '%':
			while (r < w)
			{
				*f++ = *r++;
				*f++ = *r++;
			}
			continue;
		default:
			while (r < w)
				*f++ = *r++;
			continue;
		}
		*f++ = '%';
		if (p)
			*f++ = '-';
		*f++ = c;
	}
	*f++ = 0;
	return f;
}

/*
 * load the native LC_TIME data for the current locale
 */

static void
native_lc_time(Lc_info_t* li)
{
	char*	s;
	char*	t;
	char**	b;
	int	n;
	int	m;
	int	i;
	LCID	lcid;
	int	nt;
	int	ns;
	int	nl;
	int	clock_24;
	int	leading_0;
	char	buf[256];

	lcid = li->lc->index;
	nt = 2 * GetLocaleInfo(lcid, LOCALE_STIME, 0, 0) + 7; /* HH:MM:SS */
	ns = 3 * GetLocaleInfo(lcid, LOCALE_SSHORTDATE, 0, 0);
	nl = 3 * GetLocaleInfo(lcid, LOCALE_SLONGDATE, 0, 0);
	n = nt + ns + nl;
	for (i = 0; i < elementsof(map); i++)
		n += GetLocaleInfo(lcid, map[i].native, 0, 0);
	if (!(b = newof(0, char*, TM_NFORM, n)))
		return;
	s = (char*)(b + TM_NFORM);
	for (i = 0; i < elementsof(map); i++)
	{
		if (!(m = GetLocaleInfo(lcid, map[i].native, s, n)))
			goto bad;
		b[map[i].local] = s;
		s += m;
	}
	if (!standardized(li, b))
	{
		/*
		 * synthesize TM_TIME format from the MS Word template
		 */

		if (!GetLocaleInfo(lcid, LOCALE_ITIME, buf, sizeof(buf)))
			goto bad;
		clock_24 = atoi(buf);
		if (!GetLocaleInfo(lcid, LOCALE_ITLZERO, buf, sizeof(buf)))
			goto bad;
		leading_0 = atoi(buf);
		if (!GetLocaleInfo(lcid, LOCALE_STIME, buf, sizeof(buf)))
			goto bad;
		b[TM_TIME] = s;
		*s++ = '%';
		if (!leading_0)
			*s++ = '-';
		*s++ = clock_24 ? 'H' : 'I';
		for (t = buf; *s = *t++; s++);
		*s++ = '%';
		if (!leading_0)
			*s++ = '-';
		*s++ = 'M';
		for (t = buf; *s = *t++; s++);
		*s++ = '%';
		if (!leading_0)
			*s++ = '-';
		*s++ = 'S';
		*s++ = 0;

		/*
		 * synthesize TM_DATE format
		 */

		if (!GetLocaleInfo(lcid, LOCALE_SSHORTDATE, buf, sizeof(buf)))
			goto bad;
		b[TM_DATE] = s;
		s = word2posix(s, buf, 1);

		/*
		 * synthesize TM_DEFAULT format
		 */

		if (!GetLocaleInfo(lcid, LOCALE_SLONGDATE, buf, sizeof(buf)))
			goto bad;
		b[TM_DEFAULT] = s;
		s = word2posix(s, buf, 1);
		strcpy(s - 1, " %X");
	}

	/*
	 * done
	 */

	fixup(li, b);
	return;
 bad:
	free(b);
}

#else

#if _lib_nl_langinfo && _hdr_langinfo

#if _hdr_nl_types
#include <nl_types.h>
#endif

#include <langinfo.h>

typedef struct Map_s
{
	int		native;
	int		local;
} Map_t;

static const Map_t map[] =
{
	AM_STR,				(TM_MERIDIAN+0),
	PM_STR,				(TM_MERIDIAN+1),
	ABDAY_1,			(TM_DAY_ABBREV+0),
	ABDAY_2,			(TM_DAY_ABBREV+1),
	ABDAY_3,			(TM_DAY_ABBREV+2),
	ABDAY_4,			(TM_DAY_ABBREV+3),
	ABDAY_5,			(TM_DAY_ABBREV+4),
	ABDAY_6,			(TM_DAY_ABBREV+5),
	ABDAY_7,			(TM_DAY_ABBREV+6),
	ABMON_1,			(TM_MONTH_ABBREV+0),
	ABMON_2,			(TM_MONTH_ABBREV+1),
	ABMON_3,			(TM_MONTH_ABBREV+2),
	ABMON_4,			(TM_MONTH_ABBREV+3),
	ABMON_5,			(TM_MONTH_ABBREV+4),
	ABMON_6,			(TM_MONTH_ABBREV+5),
	ABMON_7,			(TM_MONTH_ABBREV+6),
	ABMON_8,			(TM_MONTH_ABBREV+7),
	ABMON_9,			(TM_MONTH_ABBREV+8),
	ABMON_10,			(TM_MONTH_ABBREV+9),
	ABMON_11,			(TM_MONTH_ABBREV+10),
	ABMON_12,			(TM_MONTH_ABBREV+11),
	DAY_1,				(TM_DAY+0),
	DAY_2,				(TM_DAY+1),
	DAY_3,				(TM_DAY+2),
	DAY_4,				(TM_DAY+3),
	DAY_5,				(TM_DAY+4),
	DAY_6,				(TM_DAY+5),
	DAY_7,				(TM_DAY+6),
	MON_1,				(TM_MONTH+0),
	MON_2,				(TM_MONTH+1),
	MON_3,				(TM_MONTH+2),
	MON_4,				(TM_MONTH+3),
	MON_5,				(TM_MONTH+4),
	MON_6,				(TM_MONTH+5),
	MON_7,				(TM_MONTH+6),
	MON_8,				(TM_MONTH+7),
	MON_9,				(TM_MONTH+8),
	MON_10,				(TM_MONTH+9),
	MON_11,				(TM_MONTH+10),
	MON_12,				(TM_MONTH+11),
#ifdef _DATE_FMT
	_DATE_FMT,			TM_DEFAULT,
#else
	D_T_FMT,			TM_DEFAULT,
#endif
	D_FMT,				TM_DATE,
	T_FMT,				TM_TIME,
#ifdef ERA
	ERA,				TM_ERA,
	ERA_D_T_FMT,			TM_ERA_DEFAULT,
	ERA_D_FMT,			TM_ERA_DATE,
	ERA_T_FMT,			TM_ERA_TIME,
#endif
#ifdef ALT_DIGITS
	ALT_DIGITS,			TM_DIGITS,
#endif
};

static void
native_lc_time(Lc_info_t* li)
{
	char*	s;
	char*	t;
	char**	b;
	int	n;
	int	i;

	n = 0;
	for (i = 0; i < elementsof(map); i++)
	{
		if (!(t = nl_langinfo(map[i].native)))
			t = tm_data.format[map[i].local];
		n += strlen(t) + 1;
	}
	if (!(b = newof(0, char*, TM_NFORM, n)))
		return;
	s = (char*)(b + TM_NFORM);
	for (i = 0; i < elementsof(map); i++)
	{
		b[map[i].local] = s;
		if (!(t = nl_langinfo(map[i].native)))
			t = tm_data.format[map[i].local];
		while (*s++ = *t++);
	}
	fixup(li, b);
}

#else

#define native_lc_time(li)	((li->data=(tm_info.format=tm_data.format)),(tm_info.deformat=tm_info.format[TM_DEFAULT]))

#endif

#endif

/*
 * load the LC_TIME data for the current locale
 */

static void
load(Lc_info_t* li)
{
	char*		s;
	char**		b;
	char**		v;
	char**		e;
	unsigned char*		u;
	ssize_t			n;
	iconv_t			cvt;
	Sfio_t*			sp;
	Sfio_t*			tp;
	char			path[PATH_MAX];

	if (b = (char**)li->data)
	{
		tm_info.format = b;
		if (!(tm_info.deformat = state.format))
			tm_info.deformat = tm_info.format[TM_DEFAULT];
		return;
	}
	tm_info.format = tm_data.format;
	if (!(tm_info.deformat = state.format))
		tm_info.deformat = tm_info.format[TM_DEFAULT];
	if (mcfind(NULL, NULL, LC_TIME, 0, path, sizeof(path)) && (sp = sfopen(NULL, path, "r")))
	{
		n = sfsize(sp);
		tp = 0;
		if (u = (unsigned char*)sfreserve(sp, 3, 1))
		{
			if (u[0] == 0xef && u[1] == 0xbb && u[2] == 0xbf && (cvt = iconv_open("", "utf")) != (iconv_t)(-1))
			{
				if (tp = sfstropen())
				{
					sfread(sp, u, 3);
					n = iconv_move(cvt, sp, tp, SFIO_UNBOUND, NULL);
				}
				iconv_close(cvt);
			}
			if (!tp)
				sfread(sp, u, 0);
		}
		if (b = newof(0, char*, TM_NFORM, n + 2))
		{
			v = b;
			e = b + TM_NFORM;
			s = (char*)e;
			if (tp && memcpy(s, sfstrbase(tp), n) || !tp && sfread(sp, s, n) == n)
			{
				s[n] = '\n';
				while (v < e)
				{
					*v++ = s;
					if (!(s = strchr(s, '\n')))
						break;
					*s++ = 0;
				}
				fixup(li, b);
			}
			else
				free(b);
		}
		if (tp)
			sfclose(tp);
		sfclose(sp);
	}
	else
		native_lc_time(li);
}

/*
 * check that tm_info.format matches the current locale
 */

char**
tmlocale(void)
{
	Lc_info_t*	li;

	if (!tm_info.format)
	{
		tm_info.format = tm_data.format;
		if (!tm_info.deformat)
			tm_info.deformat = tm_info.format[TM_DEFAULT];
		else if (tm_info.deformat != tm_info.format[TM_DEFAULT])
			state.format = tm_info.deformat;
	}

	/* load the locale set in LC_TIME */
	li = LCINFO(AST_LC_TIME);
	if (!li->data || state.locale != li)
	{
		load(li);
		state.locale = li;
	}

	return tm_info.format;
}
