/*
 * Copyright 2010-2014, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Oliver Tappe <zooey@hirschkaefer.de>
 *		Adrien Desutugues <pulkomandy@pulkomandy.tk>
 */

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

#include <AutoDeleter.h>
#include <Autolock.h>
#include <FormattingConventionsPrivate.h>
#include <LanguagePrivate.h>
#include <Locale.h>
#include <LocaleRoster.h>
#include <TimeZone.h>

#ifndef HoG_GENODE
#include <ICUWrapper.h>

#include <unicode/datefmt.h>
#include <unicode/dtfmtsym.h>
#include <unicode/smpdtfmt.h>
#endif  //~HoG_GENODE

#include <vector>


#ifndef HoG_GENODE

static const DateFormatSymbols::DtWidthType kDateFormatStyleToWidth[] = {
	DateFormatSymbols::WIDE,
	DateFormatSymbols::ABBREVIATED,
	DateFormatSymbols::SHORT,
	DateFormatSymbols::NARROW,
};

#endif  //~HoG_GENODE


BDateFormat::BDateFormat(const BLocale* locale)
	: BFormat(locale)
{
}


#ifndef HoG_GENODE

BDateFormat::BDateFormat(const BLanguage& language,
	const BFormattingConventions& conventions)
	: BFormat(language, conventions)
{
}


BDateFormat::BDateFormat(const BDateFormat &other)
	: BFormat(other)
{
}

#endif  //~HoG_GENODE


BDateFormat::~BDateFormat()
{
}


#ifndef HoG_GENODE

status_t
BDateFormat::GetDateFormat(BDateFormatStyle style,
	BString& outFormat) const
{
	return fConventions.GetDateFormat(style, outFormat);
}


void
BDateFormat::SetDateFormat(BDateFormatStyle style,
	const BString& format)
{
	fConventions.SetExplicitDateFormat(style, format);
}

#endif  //~HoG_GENODE


ssize_t
BDateFormat::Format(char* string, const size_t maxSize, const time_t time,
	const BDateFormatStyle style) const
{
#ifndef HoG_GENODE
	ObjectDeleter<DateFormat> dateFormatter(_CreateDateFormatter(style));
	if (dateFormatter.Get() == NULL)
		return B_NO_MEMORY;

	UnicodeString icuString;
	dateFormatter->format((UDate)time * 1000, icuString);

	CheckedArrayByteSink stringConverter(string, maxSize);
	icuString.toUTF8(stringConverter);

	if (stringConverter.Overflowed())
		return B_BAD_VALUE;

	return stringConverter.NumberOfBytesWritten();

#else

///later: strftime()... (who uses this ?) ; plus more instances down this file
	strlcpy( string, "2022_foo", maxSize );
	return 8;
#endif  //~HoG_GENODE
}


status_t
BDateFormat::Format(BString& string, const time_t time,
	const BDateFormatStyle style, const BTimeZone* timeZone) const
{
#ifndef HoG_GENODE
	ObjectDeleter<DateFormat> dateFormatter(_CreateDateFormatter(style));
	if (dateFormatter.Get() == NULL)
		return B_NO_MEMORY;

	if (timeZone != NULL) {
		ObjectDeleter<TimeZone> icuTimeZone(
			TimeZone::createTimeZone(timeZone->ID().String()));
		if (icuTimeZone.Get() == NULL)
			return B_NO_MEMORY;
		dateFormatter->setTimeZone(*icuTimeZone.Get());
	}

	UnicodeString icuString;
	dateFormatter->format((UDate)time * 1000, icuString);

	string.Truncate(0);
	BStringByteSink stringConverter(&string);
	icuString.toUTF8(stringConverter);

#else

	string = "2022-xx-xx";
#endif  // ~HoG_GENODE

	return B_OK;
}


#ifndef HoG_GENODE

status_t
BDateFormat::Format(BString& string, const BDate& time,
	const BDateFormatStyle style, const BTimeZone* timeZone) const
{
	if (!time.IsValid())
		return B_BAD_DATA;

	ObjectDeleter<DateFormat> dateFormatter(_CreateDateFormatter(style));
	if (dateFormatter.Get() == NULL)
		return B_NO_MEMORY;

	UErrorCode err = U_ZERO_ERROR;
	ObjectDeleter<Calendar> calendar(Calendar::createInstance(err));
	if (!U_SUCCESS(err))
		return B_NO_MEMORY;

	if (timeZone != NULL) {
		ObjectDeleter<TimeZone> icuTimeZone(
			TimeZone::createTimeZone(timeZone->ID().String()));
		if (icuTimeZone.Get() == NULL)
			return B_NO_MEMORY;
		dateFormatter->setTimeZone(*icuTimeZone.Get());
		calendar->setTimeZone(*icuTimeZone.Get());
	}

	// Note ICU calendar uses months in range 0..11, while we use the more
	// natural 1..12 in BDate.
	calendar->set(time.Year(), time.Month() - 1, time.Day());

	UnicodeString icuString;
	FieldPosition p;
	dateFormatter->format(*calendar.Get(), icuString, p);

	string.Truncate(0);
	BStringByteSink stringConverter(&string);
	icuString.toUTF8(stringConverter);

	return B_OK;
}


status_t
BDateFormat::Format(BString& string, int*& fieldPositions, int& fieldCount,
	const time_t time, const BDateFormatStyle style) const
{
	ObjectDeleter<DateFormat> dateFormatter(_CreateDateFormatter(style));
	if (dateFormatter.Get() == NULL)
		return B_NO_MEMORY;

	fieldPositions = NULL;
	UErrorCode error = U_ZERO_ERROR;
	icu::FieldPositionIterator positionIterator;
	UnicodeString icuString;
	dateFormatter->format((UDate)time * 1000, icuString, &positionIterator,
		error);

	if (error != U_ZERO_ERROR)
		return B_BAD_VALUE;

	icu::FieldPosition field;
	std::vector<int> fieldPosStorage;
	fieldCount  = 0;
	while (positionIterator.next(field)) {
		fieldPosStorage.push_back(field.getBeginIndex());
		fieldPosStorage.push_back(field.getEndIndex());
		fieldCount += 2;
	}

	fieldPositions = (int*) malloc(fieldCount * sizeof(int));

	for (int i = 0 ; i < fieldCount ; i++ )
		fieldPositions[i] = fieldPosStorage[i];

	string.Truncate(0);
	BStringByteSink stringConverter(&string);

	icuString.toUTF8(stringConverter);

	return B_OK;
}


status_t
BDateFormat::GetFields(BDateElement*& fields, int& fieldCount,
	BDateFormatStyle style) const
{
	ObjectDeleter<DateFormat> dateFormatter(_CreateDateFormatter(style));
	if (dateFormatter.Get() == NULL)
		return B_NO_MEMORY;

	fields = NULL;
	UErrorCode error = U_ZERO_ERROR;
	icu::FieldPositionIterator positionIterator;
	UnicodeString icuString;
	time_t now;
	dateFormatter->format((UDate)time(&now) * 1000, icuString,
		&positionIterator, error);

	if (U_FAILURE(error))
		return B_BAD_VALUE;

	icu::FieldPosition field;
	std::vector<int> fieldPosStorage;
	fieldCount  = 0;
	while (positionIterator.next(field)) {
		fieldPosStorage.push_back(field.getField());
		fieldCount ++;
	}

	fields = (BDateElement*) malloc(fieldCount * sizeof(BDateElement));

	for (int i = 0 ; i < fieldCount ; i++ ) {
		switch (fieldPosStorage[i]) {
			case UDAT_YEAR_FIELD:
				fields[i] = B_DATE_ELEMENT_YEAR;
				break;
			case UDAT_MONTH_FIELD:
				fields[i] = B_DATE_ELEMENT_MONTH;
				break;
			case UDAT_DATE_FIELD:
				fields[i] = B_DATE_ELEMENT_DAY;
				break;
			case UDAT_DAY_OF_WEEK_FIELD:
				fields[i] = B_DATE_ELEMENT_WEEKDAY;
				break;
			default:
				fields[i] = B_DATE_ELEMENT_INVALID;
				break;
		}
	}

	return B_OK;
}

#endif  // ~HoG_GENODE


status_t
BDateFormat::GetStartOfWeek(BWeekday* startOfWeek) const
{
	if (startOfWeek == NULL)
		return B_BAD_VALUE;

#ifndef HoG_GENODE
	UErrorCode err = U_ZERO_ERROR;
	ObjectDeleter<Calendar> calendar = Calendar::createInstance(
		*BFormattingConventions::Private(&fConventions).ICULocale(), err);

	if (U_FAILURE(err))
		return B_ERROR;

	UCalendarDaysOfWeek icuWeekStart = calendar->getFirstDayOfWeek(err);
	if (U_FAILURE(err))
		return B_ERROR;

	switch (icuWeekStart) {
		case UCAL_SUNDAY:
			*startOfWeek = B_WEEKDAY_SUNDAY;
			break;
		case UCAL_MONDAY:
			*startOfWeek = B_WEEKDAY_MONDAY;
			break;
		case UCAL_TUESDAY:
			*startOfWeek = B_WEEKDAY_TUESDAY;
			break;
		case UCAL_WEDNESDAY:
			*startOfWeek = B_WEEKDAY_WEDNESDAY;
			break;
		case UCAL_THURSDAY:
			*startOfWeek = B_WEEKDAY_THURSDAY;
			break;
		case UCAL_FRIDAY:
			*startOfWeek = B_WEEKDAY_FRIDAY;
			break;
		case UCAL_SATURDAY:
			*startOfWeek = B_WEEKDAY_SATURDAY;
			break;
		default:
			return B_ERROR;
	}

	return B_OK;

#else

	///ToDo: "startOfWeek", for-BCalendarView
	*startOfWeek = B_WEEKDAY_MONDAY;
	return -1;
#endif  // ~HoG_GENODE
}


#ifndef HoG_GENODE

status_t
BDateFormat::GetMonthName(int month, BString& outName,
	const BDateFormatStyle style) const
{
	if (style < 0 || style >= B_DATE_FORMAT_STYLE_COUNT)
		return B_BAD_VALUE;

	DateFormat* format = _CreateDateFormatter(B_LONG_DATE_FORMAT);

	SimpleDateFormat* simpleFormat = dynamic_cast<SimpleDateFormat*>(format);
	if (simpleFormat == NULL) {
		delete format;
		return B_ERROR;
	}

	const DateFormatSymbols* symbols = simpleFormat->getDateFormatSymbols();

	int32_t count;
	const UnicodeString* names = symbols->getMonths(count,
		DateFormatSymbols::STANDALONE, kDateFormatStyleToWidth[style]);

	if (month > count || month <= 0) {
		delete simpleFormat;
		return B_BAD_DATA;
	}

	BStringByteSink stringConverter(&outName);
	names[month - 1].toUTF8(stringConverter);

	delete simpleFormat;
	return B_OK;
}

#endif  // ~HoG_GENODE


status_t
BDateFormat::GetDayName(int day, BString& outName,
	const BDateFormatStyle style) const
{
	if (style < 0 || style >= B_DATE_FORMAT_STYLE_COUNT)
		return B_BAD_VALUE;

#ifndef HoG_GENODE
	DateFormat* format = _CreateDateFormatter(B_LONG_DATE_FORMAT);

	SimpleDateFormat* simpleFormat = dynamic_cast<SimpleDateFormat*>(format);
	if (simpleFormat == NULL) {
		delete format;
		return B_ERROR;
	}

	const DateFormatSymbols* symbols = simpleFormat->getDateFormatSymbols();

	int32_t count;
	const UnicodeString* names = symbols->getWeekdays(count,
		DateFormatSymbols::STANDALONE, kDateFormatStyleToWidth[style]);

	if (day >= count || day <= 0) {
		delete simpleFormat;
		return B_BAD_DATA;
	}

	BStringByteSink stringConverter(&outName);
	names[_ConvertDayNumberToICU(day)].toUTF8(stringConverter);

	delete simpleFormat;
	return B_OK;

#else

	static const char *days[] = { "?day-0?", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
	///later-2: localize weekday name (used by deskbar calendar)
	if( day < 0 || unsigned(day) >= B_COUNT_OF(days) )
	{
		outName = "?day?";
		return B_ERROR;
	}
	
	outName = days[day];
	return B_OK;
#endif  // ~HoG_GENODE
}


#ifndef HoG_GENODE

status_t
BDateFormat::Parse(BString source, BDateFormatStyle style, BDate& output)
{
	// FIXME currently this parses a date in any timezone (or the local one if
	// none is specified) to a BDate in UTC. This may not be a good idea, we
	// may want to parse to a "local" date instead. But BDate should be made
	// timezone aware so things like BDate::Difference can work for dates in
	// different timezones.
	ObjectDeleter<DateFormat> dateFormatter(_CreateDateFormatter(style));
	if (dateFormatter.Get() == NULL)
		return B_NO_MEMORY;

	ParsePosition p(0);
	UDate date = dateFormatter->parse(UnicodeString::fromUTF8(source.String()),
		p);

	output.SetDate(1970, 1, 1);
	output.AddDays(date / U_MILLIS_PER_DAY + 0.5);

	return B_OK;
}


DateFormat*
BDateFormat::_CreateDateFormatter(const BDateFormatStyle style) const
{
	Locale* icuLocale
		= fConventions.UseStringsFromPreferredLanguage()
			? BLanguage::Private(&fLanguage).ICULocale()
			: BFormattingConventions::Private(&fConventions).ICULocale();

	icu::DateFormat* dateFormatter
		= icu::DateFormat::createDateInstance(DateFormat::kShort, *icuLocale);
	if (dateFormatter == NULL)
		return NULL;

	SimpleDateFormat* dateFormatterImpl
		= static_cast<SimpleDateFormat*>(dateFormatter);

	BString format;
	fConventions.GetDateFormat(style, format);

	UnicodeString pattern(format.String());
	dateFormatterImpl->applyPattern(pattern);

	return dateFormatter;
}

#endif  // ~HoG_GENODE

