/*
 * Copyright 2003-2012, Haiku. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Axel Dörfler, axeld@pinc-software.de
 *		Oliver Tappe, zooey@hirschkaefer.de
 */


#ifndef HoG_GENODE
#include <unicode/uversion.h>
#endif  // ~HoG_GENODE
#include <LocaleRoster.h>

#include <assert.h>
#include <ctype.h>

#include <new>

#include <Autolock.h>
#include <Bitmap.h>
#include <Catalog.h>
#include <Entry.h>
#include <FormattingConventions.h>
#include <fs_attr.h>
#include <IconUtils.h>
#include <Language.h>
#include <Locale.h>
#include <LocaleRosterData.h>
#include <MutableLocaleRoster.h>
#include <Node.h>
#include <Roster.h>
#include <String.h>
#include <TimeZone.h>

#ifndef HoG_GENODE
#include <ICUWrapper.h>
#include <locks.h>
#endif  // ~HoG_GENODE

// ICU includes
#ifndef HoG_GENODE
#include <unicode/locdspnm.h>
#include <unicode/locid.h>
#include <unicode/timezone.h>
#endif  // ~HoG_GENODE


#ifndef HoG_GENODE
using BPrivate::CatalogAddOnInfo;
#endif  // ~HoG_GENODE
using BPrivate::MutableLocaleRoster;


/*
 * several attributes/resource-IDs used within the Locale Kit:
 */
const char* BLocaleRoster::kCatLangAttr = "BEOS:LOCALE_LANGUAGE";
	// name of catalog language, lives in every catalog file
const char* BLocaleRoster::kCatSigAttr = "BEOS:LOCALE_SIGNATURE";
	// catalog signature, lives in every catalog file
const char* BLocaleRoster::kCatFingerprintAttr = "BEOS:LOCALE_FINGERPRINT";
	// catalog fingerprint, may live in catalog file

const char* BLocaleRoster::kEmbeddedCatAttr = "BEOS:LOCALE_EMBEDDED_CATALOG";
	// attribute which contains flattened data of embedded catalog
	// this may live in an app- or add-on-file
int32 BLocaleRoster::kEmbeddedCatResId = 0xCADA;
	// a unique value used to identify the resource (=> embedded CAtalog DAta)
	// which contains flattened data of embedded catalog.
	// this may live in an app- or add-on-file


#ifndef HoG_GENODE

static const char*
country_code_for_language(const BLanguage& language)
{
	if (language.IsCountrySpecific())
		return language.CountryCode();

	// TODO: implement for real! For now, we just map some well known
	// languages to countries to make FirstBootPrompt happy.
	switch ((tolower(language.Code()[0]) << 8) | tolower(language.Code()[1])) {
		case 'be':	// Belarus
			return "BY";
		case 'cs':	// Czech Republic
			return "CZ";
		case 'da':	// Denmark
			return "DK";
		case 'el':	// Greece
			return "GR";
		case 'en':	// United Kingdom
			return "GB";
		case 'hi':	// India
			return "IN";
		case 'ja':	// Japan
			return "JP";
		case 'ko':	// South Korea
			return "KR";
		case 'nb':	// Norway
			return "NO";
		case 'pa':	// Pakistan
			return "PK";
		case 'sv':	// Sweden
			return "SE";
		case 'uk':	// Ukraine
			return "UA";
		case 'zh':	// China
			return "CN";

		// Languages with a matching country name
		case 'de':	// Germany
		case 'es':	// Spain
		case 'fi':	// Finland
		case 'fr':	// France
		case 'hr':	// Croatia
		case 'hu':	// Hungary
		case 'it':	// Italy
		case 'lt':	// Lithuania
		case 'nl':	// Netherlands
		case 'pl':	// Poland
		case 'pt':	// Portugal
		case 'ro':	// Romania
		case 'ru':	// Russia
		case 'sk':	// Slovakia
			return language.Code();
	}

	return NULL;
}

#endif  // ~HoG_GENODE


// #pragma mark -


BLocaleRoster::BLocaleRoster()
	:
	fData(new(std::nothrow) BPrivate::LocaleRosterData(BLanguage("en_US"),
		BFormattingConventions("en_US")))
{
}


BLocaleRoster::~BLocaleRoster()
{
	delete fData;
}



/*static*/ BLocaleRoster*
BLocaleRoster::Default()
{
	return MutableLocaleRoster::Default();
}


status_t
BLocaleRoster::Refresh()
{
	return fData->Refresh();
}


#ifndef HoG_GENODE

status_t
BLocaleRoster::GetDefaultTimeZone(BTimeZone* timezone) const
{
	if (!timezone)
		return B_BAD_VALUE;

	BAutolock lock(fData->fLock);
	if (!lock.IsLocked())
		return B_ERROR;

	*timezone = fData->fDefaultTimeZone;

	return B_OK;
}

#endif  // ~HoG_GENODE


const BLocale*
BLocaleRoster::GetDefaultLocale() const
{
	return &fData->fDefaultLocale;
}


#ifndef HoG_GENODE

status_t
BLocaleRoster::GetLanguage(const char* languageCode,
	BLanguage** _language) const
{
	if (_language == NULL || languageCode == NULL || languageCode[0] == '\0')
		return B_BAD_VALUE;

	BLanguage* language = new(std::nothrow) BLanguage(languageCode);
	if (language == NULL)
		return B_NO_MEMORY;

	*_language = language;
	return B_OK;
}


status_t
BLocaleRoster::GetPreferredLanguages(BMessage* languages) const
{
	if (!languages)
		return B_BAD_VALUE;

	BAutolock lock(fData->fLock);
	if (!lock.IsLocked())
		return B_ERROR;

	*languages = fData->fPreferredLanguages;

	return B_OK;
}


/**
 * \brief Fills \c message with 'language'-fields containing the language-
 * ID(s) of all available languages.
 */
status_t
BLocaleRoster::GetAvailableLanguages(BMessage* languages) const
{
	if (!languages)
		return B_BAD_VALUE;

	int32_t localeCount;
	const Locale* icuLocaleList = Locale::getAvailableLocales(localeCount);

	for (int i = 0; i < localeCount; i++)
		languages->AddString("language", icuLocaleList[i].getName());

	return B_OK;
}


status_t
BLocaleRoster::GetAvailableCountries(BMessage* countries) const
{
	if (!countries)
		return B_BAD_VALUE;

	int32 i;
	const char* const* countryList = uloc_getISOCountries();

	for (i = 0; countryList[i] != NULL; i++)
		countries->AddString("country", countryList[i]);

	return B_OK;
}


status_t
BLocaleRoster::GetAvailableTimeZones(BMessage* timeZones) const
{
	if (!timeZones)
		return B_BAD_VALUE;

	status_t status = B_OK;

	StringEnumeration* zoneList = TimeZone::createEnumeration();

	UErrorCode icuStatus = U_ZERO_ERROR;
	int32 count = zoneList->count(icuStatus);
	if (U_SUCCESS(icuStatus)) {
		for (int i = 0; i < count; ++i) {
			const char* zoneID = zoneList->next(NULL, icuStatus);
			if (zoneID == NULL || !U_SUCCESS(icuStatus)) {
				status = B_ERROR;
				break;
			}
 			timeZones->AddString("timeZone", zoneID);
		}
	} else
		status = B_ERROR;

	delete zoneList;

	return status;
}


status_t
BLocaleRoster::GetAvailableTimeZonesWithRegionInfo(BMessage* timeZones) const
{
	if (!timeZones)
		return B_BAD_VALUE;

	status_t status = B_OK;

	UErrorCode icuStatus = U_ZERO_ERROR;

	StringEnumeration* zoneList = TimeZone::createTimeZoneIDEnumeration(
		UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, icuStatus);

	int32 count = zoneList->count(icuStatus);
	if (U_SUCCESS(icuStatus)) {
		for (int i = 0; i < count; ++i) {
			const char* zoneID = zoneList->next(NULL, icuStatus);
			if (zoneID == NULL || !U_SUCCESS(icuStatus)) {
				status = B_ERROR;
				break;
			}
			timeZones->AddString("timeZone", zoneID);

			char region[5];
			icuStatus = U_ZERO_ERROR;
			TimeZone::getRegion(zoneID, region, 5, icuStatus);
			if (!U_SUCCESS(icuStatus)) {
				status = B_ERROR;
				break;
			}
			timeZones->AddString("region", region);
		}
	} else
		status = B_ERROR;

	delete zoneList;

	return status;
}


status_t
BLocaleRoster::GetAvailableTimeZonesForCountry(BMessage* timeZones,
	const char* countryCode) const
{
	if (!timeZones)
		return B_BAD_VALUE;

	status_t status = B_OK;

	StringEnumeration* zoneList = TimeZone::createEnumeration(countryCode);
		// countryCode == NULL will yield all timezones not bound to a country

	UErrorCode icuStatus = U_ZERO_ERROR;
	int32 count = zoneList->count(icuStatus);
	if (U_SUCCESS(icuStatus)) {
		for (int i = 0; i < count; ++i) {
			const char* zoneID = zoneList->next(NULL, icuStatus);
			if (zoneID == NULL || !U_SUCCESS(icuStatus)) {
				status = B_ERROR;
				break;
			}
			timeZones->AddString("timeZone", zoneID);
		}
	} else
		status = B_ERROR;

	delete zoneList;

	return status;
}


status_t
BLocaleRoster::GetFlagIconForCountry(BBitmap* flagIcon, const char* countryCode)
{
	if (countryCode == NULL)
		return B_BAD_VALUE;

	BAutolock lock(fData->fLock);
	if (!lock.IsLocked())
		return B_ERROR;

	BResources* resources;
	status_t status = fData->GetResources(&resources);
	if (status != B_OK)
		return status;

	// Normalize the country code: 2 letters uppercase
	// filter things out so that "pt_BR" gives the flag for brazil

	int codeLength = strlen(countryCode);
	if (codeLength < 2)
		return B_BAD_VALUE;

	char normalizedCode[3];
	normalizedCode[0] = toupper(countryCode[codeLength - 2]);
	normalizedCode[1] = toupper(countryCode[codeLength - 1]);
	normalizedCode[2] = '\0';

	size_t size;
	const void* buffer = resources->LoadResource(B_VECTOR_ICON_TYPE,
		normalizedCode, &size);
	if (buffer == NULL || size == 0)
		return B_NAME_NOT_FOUND;

	return BIconUtils::GetVectorIcon(static_cast<const uint8*>(buffer), size,
		flagIcon);
}


status_t
BLocaleRoster::GetFlagIconForLanguage(BBitmap* flagIcon,
	const char* languageCode)
{
	if (languageCode == NULL || languageCode[0] == '\0'
		|| languageCode[1] == '\0')
		return B_BAD_VALUE;

	BAutolock lock(fData->fLock);
	if (!lock.IsLocked())
		return B_ERROR;

	BResources* resources;
	status_t status = fData->GetResources(&resources);
	if (status != B_OK)
		return status;

	// Normalize the language code: first two letters, lowercase

	char normalizedCode[3];
	normalizedCode[0] = tolower(languageCode[0]);
	normalizedCode[1] = tolower(languageCode[1]);
	normalizedCode[2] = '\0';

	size_t size;
	const void* buffer = resources->LoadResource(B_VECTOR_ICON_TYPE,
		normalizedCode, &size);
	if (buffer != NULL && size != 0) {
		return BIconUtils::GetVectorIcon(static_cast<const uint8*>(buffer),
			size, flagIcon);
	}

	// There is no language flag, try to get the default country's flag for
	// the language instead.

	BLanguage language(languageCode);
	const char* countryCode = country_code_for_language(language);
	if (countryCode == NULL)
		return B_NAME_NOT_FOUND;

	return GetFlagIconForCountry(flagIcon, countryCode);
}


status_t
BLocaleRoster::GetAvailableCatalogs(BMessage*  languageList,
	const char* sigPattern,	const char* langPattern, int32 fingerprint) const
{
	if (languageList == NULL)
		return B_BAD_VALUE;

	BAutolock lock(fData->fLock);
	if (!lock.IsLocked())
		return B_ERROR;

	int32 count = fData->fCatalogAddOnInfos.CountItems();
	for (int32 i = 0; i < count; ++i) {
		CatalogAddOnInfo* info
			= (CatalogAddOnInfo*)fData->fCatalogAddOnInfos.ItemAt(i);

		if (!info->MakeSureItsLoaded() || !info->fLanguagesFunc)
			continue;

		info->fLanguagesFunc(languageList, sigPattern, langPattern,
			fingerprint);
	}

	return B_OK;
}

#endif  // ~HoG_GENODE


bool
BLocaleRoster::IsFilesystemTranslationPreferred() const
{
	BAutolock lock(fData->fLock);
	if (!lock.IsLocked())
///maybe-later: this should probably return a _bool_, i.e. "return false"
		return B_ERROR;

	return fData->fIsFilesystemTranslationPreferred;
}


/*!	\brief Looks up a localized filename from a catalog.
	\param localizedFileName A pre-allocated BString object for the result
		of the lookup.
	\param ref An entry_ref with an attribute holding data for catalog lookup.
	\param traverse A boolean to decide if symlinks are to be traversed.
	\return
	- \c B_OK: success
	- \c B_ENTRY_NOT_FOUND: failure. Attribute not found, entry not found
		in catalog, etc
	- other error codes: failure

	Attribute format:  "signature:context:string"
	(no colon in any of signature, context and string)

	Lookup is done for the top preferred language, only.
	Lookup fails if a comment is present in the catalog entry.
*/
status_t
BLocaleRoster::GetLocalizedFileName(BString& localizedFileName,
	const entry_ref& ref, bool traverse)
{
#ifndef HoG_GENODE
	BString signature;
	BString context;
	BString string;

	status_t status = _PrepareCatalogEntry(ref, signature, context, string,
		traverse);

	if (status != B_OK)
		return status;

	// Try to get entry_ref for signature from above
	BRoster roster;
	entry_ref catalogRef;
	// The signature is missing application/
	signature.Prepend("application/");
	status = roster.FindApp(signature, &catalogRef);
	if (status != B_OK)
		return status;

	BCatalog catalog(catalogRef);
	const char* temp = catalog.GetString(string, context);

	if (temp == NULL)
		return B_ENTRY_NOT_FOUND;

	localizedFileName = temp;
	return B_OK;

#else

	return B_ENTRY_NOT_FOUND;
#endif  // ~HoG_GENODE
}


#ifndef HoG_GENODE

static status_t
_InitializeCatalog(void* param)
{
	BCatalog* catalog = (BCatalog*)param;

	// figure out image (shared object) from catalog address
	image_info info;
	int32 cookie = 0;
	bool found = false;

	while (get_next_image_info(0, &cookie, &info) == B_OK) {
		if ((char*)info.data < (char*)catalog && (char*)info.data
				+ info.data_size > (char*)catalog) {
			found = true;
			break;
		}
	}

	if (!found)
		return B_NAME_NOT_FOUND;

	// load the catalog for this mimetype
	entry_ref ref;
	if (BEntry(info.name).GetRef(&ref) == B_OK && catalog->SetTo(ref) == B_OK)
		return B_OK;
	
	return B_ERROR;
}


BCatalog*
BLocaleRoster::_GetCatalog(BCatalog* catalog, int32* catalogInitStatus)
{
	// This function is used in the translation macros, so it can't return a
	// status_t. Maybe it could throw exceptions ?

	__init_once(catalogInitStatus, _InitializeCatalog, catalog);
	return catalog;
}


status_t
BLocaleRoster::_PrepareCatalogEntry(const entry_ref& ref, BString& signature,
	BString& context, BString& string, bool traverse)
{
	BEntry entry(&ref, traverse);
	if (!entry.Exists())
		return B_ENTRY_NOT_FOUND;

	BNode node(&entry);
	status_t status = node.InitCheck();
	if (status != B_OK)
		return status;

	status = node.ReadAttrString("SYS:NAME", &signature);
	if (status != B_OK)
		return status;

	int32 first = signature.FindFirst(':');
	int32 last = signature.FindLast(':');
	if (first == last)
		return B_ENTRY_NOT_FOUND;

	context = signature;
	string = signature;

	signature.Truncate(first);
	context.Truncate(last);
	context.Remove(0, first + 1);
	string.Remove(0, last + 1);

	if (signature.Length() == 0 || context.Length() == 0
		|| string.Length() == 0)
		return B_ENTRY_NOT_FOUND;

	return B_OK;
}

#endif  // ~HoG_GENODE
