/*
**	Locale.c
**
**	Localization support routines
**
**	Copyright © 1990-1996 by Olaf `Olsen' Barthel
**		All Rights Reserved
**
**	:ts=4
*/

#ifndef _GLOBAL_H
#include "Global.h"
#endif

	/* The catalog data is stored in the following format. */

struct CatCompArrayType
{
	ULONG	cca_ID;
	STRPTR	cca_Str;
};

	/* LocaleOpen(STRPTR CatalogName,STRPTR BuiltIn):
	 *
	 *	Open string translation tables.
	 */

VOID
LocaleOpen(STRPTR CatalogName,STRPTR BuiltIn,LONG Version)
{
	DecimalPoint = '.';

	if(LocaleBase = (struct LocaleBase *)OpenLibrary("locale.library",38))
	{
		if(LocaleBase->lb_SysPatches)
		{
			strcpy(ConvNumber,"%lD");
			strcpy(ConvNumber10,"%10lD");

			if(Catalog = OpenCatalog(NULL,CatalogName,
				OC_BuiltInLanguage,	BuiltIn,
				OC_BuiltInCodeSet,	0,

				Language[0] ? OC_Language : TAG_IGNORE,Language,
			TAG_DONE))
			{
				BOOL TooOld = FALSE;

					// Don't load an outdated catalog file

				if(Catalog->cat_Version < Version)
					TooOld = TRUE;
				else
				{
					if(strcmp(GetCatalogStr(Catalog,MSG_OFFSET_TEST1_TXT,""),"v4.0"))
						TooOld = TRUE;
				}

				if(TooOld)
				{
					struct IntuitionBase *IntuitionBase;

					if(IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",0))
					{
						ShowRequest(NULL,"The catalog file is too old to be used\nwith this `term' revision.","Continue");

						CloseLibrary(IntuitionBase);
					}

					CloseCatalog(Catalog);
					Catalog = NULL;
				}
			}

			Locale = OpenLocale(NULL);

			DecimalPoint = Locale->loc_DecimalPoint[0];
		}
		else
		{
			strcpy(ConvNumber,"%ld");
			strcpy(ConvNumber10,"%10ld");

			CloseLibrary(LocaleBase);
			LocaleBase = NULL;
		}
	}
	else
	{
		strcpy(ConvNumber,"%ld");
		strcpy(ConvNumber10,"%10ld");
	}
}

	/* LocaleClose():
	 *
	 *	Close the translation tables.
	 */

VOID
LocaleClose()
{
	if(LocaleBase)
	{
		CloseLocale(Locale);
		Locale = NULL;

		CloseCatalog(Catalog);
		Catalog = NULL;

		CloseLibrary(LocaleBase);
		LocaleBase = NULL;
	}
}

	/* LanguageCheck():
	 *
	 *	Checks to see if the currently selected language
	 *	is english.
	 */

VOID
LanguageCheck()
{
	if(Locale && Catalog)
	{
		if(Locale->loc_LanguageName)
		{
			if(!Stricmp(Locale->loc_LanguageName,"english.language"))
				English = TRUE;
			else
				English = FALSE;
		}
		else
			English = FALSE;
	}
	else
		English = TRUE;
}

	/* LocaleSeconds(LONG Seconds):
	 *
	 *	Return seconds in proper format.
	 */

STRPTR
LocaleSeconds(LONG Seconds)
{
	STATIC UBYTE Time[10];

	if(Locale)
		SPrintf(Time,"%2lD%s%02lD",Seconds / 100,Locale->loc_DecimalPoint,Seconds % 100);
	else
		SPrintf(Time,"%2ld.%02ld",Seconds / 100,Seconds % 100);

	return(Time);
}

	/* SmallCurrency():
	 *
	 *	Support function for the rates control panel, returns a formatted
	 *	string to contain a string like "cents/unit".
	 */

STRPTR
SmallCurrency()
{
	STATIC UBYTE Buffer[30];

	if(Locale)
		SPrintf(Buffer,LocaleString(MSG_RATEPANEL_PAY_PER_UNIT_GAD),Locale->loc_MonSmallCS);
	else
		SPrintf(Buffer,LocaleString(MSG_RATEPANEL_PAY_PER_UNIT_GAD),"Pay");

	return(Buffer);
}

	/* InsertGrouping(STRPTR Buffer,STRPTR GroupData,STRPTR GroupSeparator):
	 *
	 *	Tricky stuff, folks! This beauty will insert grouping characters
	 *	into a readily-prepared string buffer filled with numeric
	 *	contents. It takes the group separator tokens and group separator
	 *	strings into account.
	 */

VOID
InsertGrouping(STRPTR Buffer,STRPTR GroupData,STRPTR GroupSeparator)
{
	UBYTE	LocalBuffer[80];	// Sufficient, but too large
	STRPTR	Index;
	LONG	i,j,SeparatorSize;
	LONG	Count;				// How many characters per group
	BOOL	RepeatGroupCount;	// Keep repeating group size until end

		// Set up for the first group

	switch(*GroupData)
	{
		case 0:			// Repeat current grouping scheme until end
		case 255:		// No further grouping is to be performed

			Count = 0;

			break;

		default:		// Initial group size

			RepeatGroupCount = FALSE;

			Count = *GroupData++;

			break;
	}

		// Check the size of the group separator string

	if((SeparatorSize = strlen(GroupSeparator) - 1) < 1)
		Count = 0;

		// That where we'll start

	Index = LocalBuffer;

		// Build the string back to front, we will reverse it later

	for(i = strlen(Buffer) - 1, j = 1 ; i >= 0 ; i--, j++)
	{
			// Pick up the next number character

		*Index++ = Buffer[i];

			// Are we to insert the grouping characters here?

		if(Count && j == Count)
		{
			LONG k;

				// Insert the grouping characters

			for(k = SeparatorSize ; k >= 0 ; k--)
				*Index++ = GroupSeparator[k];

				// Reset the group size counter

			j = 0;

				// Pick up the next grouping token?

			if(!RepeatGroupCount)
			{
					// Ok, what kind of token is it?

				switch(*GroupData)
				{
					case 0:		// Repeat current grouping scheme

						RepeatGroupCount = TRUE;
						break;

					case 255:	// Perform no further grouping

						Count = 0;
						break;

					default:	// New group size

						Count = *GroupData++;
						break;
				}
			}
		}
	}

		// Look how long the resulting string is

	j = ((LONG)Index - (LONG)LocalBuffer) - 1;

		// Copy it back

	Index = Buffer;

		// Reverse the order of characters while copying

	for(i = j ; i >= 0 ; i--)
		*Index++ = LocalBuffer[i];

		// Provide null-termination

	*Index = 0;
}

VOID
ConvertMonetaryQuantity(LONG Units,STRPTR Destination,BOOL UseCurrency)
{
	UBYTE	IntegerBuffer[80];	// Sufficient, but too large

	STRPTR	SignText,			// Signed/unsigned quantity text
			SpaceText,			// Currency/number separation
			Currency;			// The name of the currency
	LONG	SpaceSep,			// A space separates currency and quantity?
			SignPos,			// Where to place the sign text
			CSPos;				// Where to place the currency text
	LONG	Sign;				// Negative or positive quantity?

		// Negative quantity?

	if(Units < 0)
	{
		Sign = -1;

		Units = -Units;
	}
	else
		Sign = 1;

		// Does this currency sport a fractional smaller currency?

	if(Locale->loc_MonFracDigits)
	{
		UBYTE	NumberBuffer[5],
				FractionBuffer[40];
		LONG	Integer,
				Fraction,
				Scale;
		LONG	i;

			// Prepare the formatting string

		SPrintf(NumberBuffer,"%%0%ldld",Locale->loc_MonFracDigits);

			// Turn the number of fractional digits into a power of ten

		for(i = 0, Scale = 1 ; i < Locale->loc_MonFracDigits ; i++)
			Scale *= 10;

			// Split the quantity in integer and fractional part

		Integer		= Units / Scale;
		Fraction	= Units % Scale;

			// Build the integer text

		SPrintf(IntegerBuffer,"%ld",Integer);

		InsertGrouping(IntegerBuffer,Locale->loc_MonGrouping,Locale->loc_MonGroupSeparator);

			// Build the fractional text

		SPrintf(FractionBuffer,NumberBuffer,Fraction);

		InsertGrouping(FractionBuffer,Locale->loc_MonFracGrouping,Locale->loc_MonFracGroupSeparator);

			// Add the monetary decimal point

		strcat(IntegerBuffer,Locale->loc_MonDecimalPoint);

			// Add the fractional part

		strcat(IntegerBuffer,FractionBuffer);
	}
	else
	{
			// Build the integer text

		SPrintf(IntegerBuffer,"%ld",Units);

		InsertGrouping(IntegerBuffer,Locale->loc_MonGrouping,Locale->loc_MonGroupSeparator);
	}

		// Pick up the appropriate formatting parameters

	if(Sign < 0)
	{
		SignText	= Locale->loc_MonNegativeSign;
		SpaceSep	= Locale->loc_MonNegativeSpaceSep;
		SignPos		= Locale->loc_MonNegativeSignPos;
		CSPos		= Locale->loc_MonNegativeCSPos;
	}
	else
	{
		SignText	= Locale->loc_MonPositiveSign;
		SpaceSep	= Locale->loc_MonPositiveSpaceSep;
		SignPos		= Locale->loc_MonPositiveSignPos;
		CSPos		= Locale->loc_MonPositiveCSPos;
	}

		// Are we to use the currency symbol?

	if(UseCurrency)
	{
			// Pick up the currency text

		Currency = Locale->loc_MonCS;

			// Take care of the separation information

		if(SpaceSep == SS_NOSPACE)
			SpaceText = "";
		else
			SpaceText = " ";
	}
	else
		Currency = SpaceText = "";

		// Now merge all the information into one single string

	if(CSPos == CSP_PRECEDES)
	{
		switch(SignPos)
		{
			case SP_PARENS:

				// (Currency <Space> Sign Value)
				SPrintf(Destination,"(%s%s%s%s)",Currency,SpaceText,SignText,IntegerBuffer);
				break;

			case SP_PREC_ALL:

				// Sign Currency <Space> Value
				SPrintf(Destination,"%s%s%s%s",SignText,Currency,SpaceText,IntegerBuffer);
				break;

			case SP_SUCC_ALL:

				// Currency <Space> Value Sign
				SPrintf(Destination,"%s%s%s%s",Currency,SpaceText,IntegerBuffer,SignText);
				break;

			case SP_PREC_CURR:

				// Sign Currency <Space> Value
				SPrintf(Destination,"%s%s%s%s",SignText,Currency,SpaceText,IntegerBuffer);
				break;

			case SP_SUCC_CURR:

				// Currency Sign <Space> Value
				SPrintf(Destination,"%s%s%s%s",Currency,SignText,SpaceText,IntegerBuffer);
				break;
		}
	}
	else
	{
		switch(SignPos)
		{
			case SP_PARENS:

				// (Sign Value <Space> Currency)
				SPrintf(Destination,"(%s%s%s%s)",SignText,IntegerBuffer,SpaceText,Currency);
				break;

			case SP_PREC_ALL:

				// Sign Value <Space> Currency
				SPrintf(Destination,"%s%s%s%s",SignText,IntegerBuffer,SpaceText,Currency);
				break;

			case SP_SUCC_ALL:

				// Value <Space> Currency Sign
				SPrintf(Destination,"%s%s%s%s",IntegerBuffer,SpaceText,Currency,SignText);
				break;

			case SP_PREC_CURR:

				// Value <Space> Sign Currency
				SPrintf(Destination,"%s%s%s%s",IntegerBuffer,SpaceText,SignText,Currency);
				break;

			case SP_SUCC_CURR:

				// Value <Space> Currency Sign
				SPrintf(Destination,"%s%s%s%s",IntegerBuffer,SpaceText,Currency,SignText);
				break;
		}
	}
}

	/* CreateSum(LONG Quantity):
	 *
	 *	Create a string containing a monetary quantity formatted
	 *	according to the current locale rules.
	 */

STRPTR
CreateSum(LONG Quantity,BOOL UseCurrency)
{
	STATIC UBYTE Buffer[100];

	if(Locale)
		ConvertMonetaryQuantity(Quantity,Buffer,UseCurrency);
	else
		SPrintf(Buffer,"%ld.%02ld",Quantity / 100,Quantity % 100);

	return(Buffer);
}

	/* LocalizeString(STRPTR *Strings,LONG From,LONG To):
	 *
	 *	Localize an array of strings.
	 */

VOID
LocalizeString(STRPTR *Strings,LONG From,LONG To)
{
	if(!Strings[0])
	{
		LONG i,j;

		for(i = From, j = 0 ; i <= To ; i++, j++)
			Strings[j] = LocaleString(i);
	}
}

	/* LocalizeStringTable(STRPTR *Strings,LONG *Table)
	 *
	 *	Localize an array of strings by table.
	 */

VOID
LocalizeStringTable(STRPTR *Strings,LONG *Table)
{
	while(*Table != -1)
		*Strings++ = LocaleString(*Table++);

	*Strings = NULL;
}

VOID
LocalizeMenuTable(struct NewMenu *Menu,LONG *Table)
{
	STRPTR	Label,Shortcut;
	LONG	From = 0;

	while(Menu->nm_Type != NM_END)
	{
		Shortcut = LocaleString(Table[From]);

		if(Shortcut[0] && !Shortcut[1])
			Label = Shortcut + 2;
		else
		{
			Label		= Shortcut;
			Shortcut	= NULL;
		}

		switch(Menu->nm_Type)
		{
			case NM_TITLE:

				Menu->nm_Label = Label;

				From++;

				break;

			case NM_ITEM:
			case NM_SUB:

				if(Menu->nm_Label != NM_BARLABEL)
				{
					Menu->nm_Label		= Label;
					Menu->nm_CommKey	= Shortcut;

					From++;
				}

				break;
		}

		Menu++;
	}
}

	/* LocaleString(ULONG ID):
	 *
	 *	Obtain a string from the translation pool.
	 */

STRPTR
LocaleString(ULONG ID)
{
	extern struct CatCompArrayType	*AppStrings;
	extern WORD						 NumAppStrings;

	STRPTR Builtin;

	if(ID < NumAppStrings && AppStrings[ID].cca_ID == ID)
		Builtin = AppStrings[ID].cca_Str;
	else
	{
		LONG Left,Mid,Right;

			// Binary search, note: can be applied here
			// only because the catalog entries are
			// stored in ascending ID order. But actually,
			// this piece of code should never get called.

		Left	= 0;
		Right	= NumAppStrings - 1;

		do
		{
			Mid = (Left + Right) / 2;

			if(ID < AppStrings[Mid].cca_ID)
				Right	= Mid - 1;
			else
				Left	= Mid + 1;
		}
		while(ID != AppStrings[Mid].cca_ID && Left <= Right);

		if(ID == AppStrings[Mid].cca_ID)
			Builtin = AppStrings[Mid].cca_Str;
		else
			Builtin = "";
	}

	if(Catalog)
	{
		STRPTR String = GetCatalogStr(Catalog,ID,Builtin);

		if(String[0])
			return(String);
	}

	return(Builtin);
}

STRPTR __saveds __asm
LocaleHookFunc(REG(a0) struct Hook *Hook,REG(a1) LONG ID)
{
	return(LocaleString(ID));
}

STATIC LONG __saveds __asm
FormatDateHookFunc(REG(a0) struct Hook *Hook,REG(a1) UBYTE Char)
{
	STRPTR String = Hook->h_Data;

	*String++ = Char;

	Hook->h_Data = String;

	return(TRUE);
}

	/* FormatStamp():
	 *
	 *	Convert a date stamp into human readable
	 *	form by taking the current locale parameters
	 *	into account.
	 */

BOOL
FormatStamp(struct DateStamp *Stamp,STRPTR DateBuffer,STRPTR TimeBuffer,STRPTR BothBuffer,BOOL SubstituteDay)
{
	struct DateStamp Now;

		// If no time stamp given, do with current time

	if(!Stamp)
		DateStamp(Stamp = &Now);

		// Is the current locale available?

	if(Locale)
	{
		struct Hook LocalHook = {{NULL}, (HOOKFUNC)FormatDateHookFunc};

			// Combine date and time text?

		if(BothBuffer && !SubstituteDay)
		{
			LocalHook.h_Data = BothBuffer;

			FormatDate(Locale,Locale->loc_DateTimeFormat,Stamp,&LocalHook);

			StripSpaces(BothBuffer);
		}
		else
		{
			UBYTE	LocalDateBuffer[40],
					LocalTimeBuffer[40];

				// Provide storage space

			if(BothBuffer)
			{
				DateBuffer = LocalDateBuffer;
				TimeBuffer = LocalTimeBuffer;
			}

				// Do we have a date buffer to fill?

			if(DateBuffer)
			{
					// Are we to substitute the current day with
					// text such as today, yesterday, etc.?

				if(SubstituteDay)
				{
					struct DateStamp	Today;
					STRPTR				String;

						// Get the current time

					DateStamp(&Today);

						// Does the date refer to yesterday?

					if(Stamp->ds_Days == Today.ds_Days - 1)
						String = GetLocaleStr(Locale,YESTERDAYSTR);
					else
					{
							// Does the date refer to today?

						if(Stamp->ds_Days == Today.ds_Days)
							String = GetLocaleStr(Locale,TODAYSTR);
						else
						{
								// Does the date refer to tomorrow?

							if(Stamp->ds_Days == Today.ds_Days + 1)
								String = GetLocaleStr(Locale,TOMORROWSTR);
							else
							{
								String			= NULL;
								SubstituteDay	= NULL;
							}
						}
					}

					if(String)
						strcpy(DateBuffer,String);
					else
						DateBuffer[0] = 0;
				}

				if(!SubstituteDay)
				{
					LocalHook.h_Data = DateBuffer;

					FormatDate(Locale,Locale->loc_DateFormat,Stamp,&LocalHook);
				}

				StripSpaces(DateBuffer);
			}

			if(TimeBuffer)
			{
				LocalHook.h_Data = TimeBuffer;

				FormatDate(Locale,Locale->loc_TimeFormat,Stamp,&LocalHook);

				StripSpaces(TimeBuffer);
			}

				// Combine date and time

			if(BothBuffer)
			{
				strcpy(BothBuffer,DateBuffer);
				strcat(BothBuffer," ");
				strcat(BothBuffer,TimeBuffer);
			}
		}
	}
	else
	{
		struct DateTime	DateTime;
		UBYTE			LocalDateBuffer[40],
						LocalTimeBuffer[40];

			// Provide storage space

		if(BothBuffer)
		{
			DateBuffer = LocalDateBuffer;
			TimeBuffer = LocalTimeBuffer;
		}

			// No locale, so we will use dos.library instead.

		CopyMem(Stamp,&DateTime.dat_Stamp,sizeof(struct DateStamp));

		DateTime.dat_Format		= FORMAT_DOS;
		DateTime.dat_Flags		= SubstituteDay ? DTF_SUBST : NULL;
		DateTime.dat_StrDay		= NULL;
		DateTime.dat_StrDate	= DateBuffer;
		DateTime.dat_StrTime	= TimeBuffer;

		if(!DateToStr(&DateTime))
			return(FALSE);

		if(DateBuffer)
			StripSpaces(DateBuffer);

		if(TimeBuffer)
			StripSpaces(TimeBuffer);

			// Combine date and time

		if(BothBuffer)
		{
			strcpy(BothBuffer,DateBuffer);
			strcat(BothBuffer," ");
			strcat(BothBuffer,TimeBuffer);
		}
	}

	return(TRUE);
}

	/* FormatTime(STRPTR Buffer,LONG Hours,LONG Minutes,LONG Seconds):
	 *
	 *	Given hours, minutes and seconds, format this data into
	 *	a human-readable string.
	 */

VOID
FormatTime(STRPTR Buffer,LONG Hours,LONG Minutes,LONG Seconds)
{
	if(Locale)
	{
		struct Hook LocalHook = {{NULL}, (HOOKFUNC)FormatDateHookFunc};

		struct DateStamp Stamp;

		Stamp.ds_Days		= 0;
		Stamp.ds_Minute		= Hours * 60 + Minutes;
		Stamp.ds_Tick		= MAX(0,Seconds) * TICKS_PER_SECOND;

		LocalHook.h_Data = Buffer;

		if(Seconds < 0)
			FormatDate(Locale,Locale->loc_ShortTimeFormat,&Stamp,&LocalHook);
		else
			FormatDate(Locale,Locale->loc_TimeFormat,&Stamp,&LocalHook);
	}
	else
	{
		if(Seconds < 0)
			SPrintf(Buffer,"%02ld:%02ld",Hours,Minutes);
		else
			SPrintf(Buffer,"%02ld:%02ld:%02ld",Hours,Minutes,Seconds);
	}
}

	/* StandardShowTime(struct Gadget *SomeGadget,LONG Level):
	 *
	 *	Callback routine to display some time level.
	 */

STRPTR __saveds __stdargs
StandardShowTime(struct Gadget *SomeGadget,LONG Level)
{
	return(LocaleSeconds(Level));
}
