/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

#define __STDC_CONSTANT_MACROS
#include <log4cxx/logstring.h>
#include <log4cxx/helpers/datetimedateformat.h>
#include "../logunit.h"
#include <log4cxx/helpers/pool.h>
#include "../insertwide.h"
#include <apr.h>
#include <apr_time.h>
#include <sstream>

using namespace log4cxx;
using namespace log4cxx::helpers;
using namespace std;

#define LOCALE_US "C"
#if defined(_WIN32)
	#define LOCALE_FR "French_france"
#else
	#define LOCALE_FR "fr_FR"
#endif

#define LOG4CXX_TEST 1
#include <log4cxx/private/log4cxx_private.h>


#if LOG4CXX_HAS_STD_LOCALE
#include <locale>
#include "localechanger.h"
#define MAKE_LOCALE(ptr, id)     \
	std::locale loco(id);            \
	std::locale* ptr = &loco;
#else
#define MAKE_LOCALE(ptr, id)     \
	std::locale* ptr = NULL;
#endif


/**
   Unit test {@link DateTimeDateFormat}.


*/
LOGUNIT_CLASS(DateTimeDateFormatTestCase)
{
	LOGUNIT_TEST_SUITE( DateTimeDateFormatTestCase );
	LOGUNIT_TEST( test1 );
	LOGUNIT_TEST( test2 );
	LOGUNIT_TEST( test3 );
	LOGUNIT_TEST( test4 );
	LOGUNIT_TEST( test5 );
	LOGUNIT_TEST( test6 );
#if LOG4CXX_HAS_STD_LOCALE
	LOGUNIT_TEST( test7 );
	LOGUNIT_TEST( test8 );
#endif
	LOGUNIT_TEST_SUITE_END();



private:

#define MICROSECONDS_PER_DAY APR_INT64_C(86400000000)


	/**
	 Asserts that formatting the provided date results in the expected string.

	@param date Date date
	 @param timeZone TimeZone timezone for conversion
	 @param expected String expected string
	*/
	void assertFormattedTime( apr_time_t date, const std::locale * locale,
		const TimeZonePtr & timeZone, const LogString & expected )
	{
		DateTimeDateFormat formatter(locale);
		formatter.setTimeZone(timeZone);
		LogString actual;
		formatter.format(actual, date);
		LOGUNIT_ASSERT_EQUAL( expected, actual );
	}
public:

	/** Convert 02 Jan 2004 00:00:00 GMT for GMT. */
	void test1()
	{
		//
		//   02 Jan 2004 00:00 GMT
		//
		apr_time_t jan2 = MICROSECONDS_PER_DAY * 12419;
		MAKE_LOCALE(localeUS, LOCALE_US);
		assertFormattedTime( jan2, localeUS, TimeZone::getGMT(), LOG4CXX_STR("02 Jan 2004 00:00:00,000"));
	}

	/** Convert 03 Jan 2004 00:00:00 GMT for America/Chicago. */
	void test2()
	{
		//
		//   03 Jan 2004 00:00 GMT
		apr_time_t jan3 = MICROSECONDS_PER_DAY * 12420;
		MAKE_LOCALE(localeUS, LOCALE_US);
		assertFormattedTime( jan3, localeUS,
			TimeZone::getTimeZone(LOG4CXX_STR("GMT-6")),
			LOG4CXX_STR("02 Jan 2004 18:00:00,000"));
	}


	/** Convert 30 Jun 2004 00:00:00 GMT for GMT. */
	void test3()
	{
		apr_time_t jun30 = MICROSECONDS_PER_DAY * 12599;
		MAKE_LOCALE(localeUS, LOCALE_US);
		assertFormattedTime( jun30, localeUS, TimeZone::getGMT(),
			LOG4CXX_STR("30 Jun 2004 00:00:00,000"));
	}

	/** Convert 29 Jun 2004 00:00:00 GMT for Chicago, daylight savings in effect. */
	void test4()
	{
		apr_time_t jul1 = MICROSECONDS_PER_DAY * 12600;
		MAKE_LOCALE(localeUS, LOCALE_US);
		assertFormattedTime( jul1, localeUS,
			TimeZone::getTimeZone(LOG4CXX_STR("GMT-5")),
			LOG4CXX_STR("30 Jun 2004 19:00:00,000"));
	}

	/** Test multiple calls in close intervals. */
	void test5()
	{
		//   subsequent calls within one minute
		//     are optimized to reuse previous formatted value
		//     make a couple of nearly spaced calls
		apr_time_t ticks = MICROSECONDS_PER_DAY * 12601;
		MAKE_LOCALE(localeUS, LOCALE_US);
		assertFormattedTime( ticks, localeUS, TimeZone::getGMT(),
			LOG4CXX_STR("02 Jul 2004 00:00:00,000"));
		assertFormattedTime( ticks + 8000, localeUS, TimeZone::getGMT(),
			LOG4CXX_STR("02 Jul 2004 00:00:00,008"));
		assertFormattedTime( ticks + 17000, localeUS, TimeZone::getGMT(),
			LOG4CXX_STR("02 Jul 2004 00:00:00,017"));
		assertFormattedTime( ticks + 237000, localeUS, TimeZone::getGMT(),
			LOG4CXX_STR("02 Jul 2004 00:00:00,237"));
		assertFormattedTime( ticks + 1415000, localeUS, TimeZone::getGMT(),
			LOG4CXX_STR("02 Jul 2004 00:00:01,415"));
	}

	/** Check that caching does not disregard timezone. This test would fail for revision 1.4 of DateTimeDateFormat.java. */
	void test6()
	{
		apr_time_t jul3 = MICROSECONDS_PER_DAY * 12602;
		MAKE_LOCALE(localeUS, LOCALE_US);
		assertFormattedTime( jul3, localeUS, TimeZone::getGMT(),
			LOG4CXX_STR("03 Jul 2004 00:00:00,000"));
		assertFormattedTime( jul3, localeUS,
			TimeZone::getTimeZone(LOG4CXX_STR("GMT-5")),
			LOG4CXX_STR("02 Jul 2004 19:00:00,000"));
		assertFormattedTime( jul3, localeUS, TimeZone::getGMT(),
			LOG4CXX_STR("03 Jul 2004 00:00:00,000"));
	}

#if LOG4CXX_HAS_STD_LOCALE
	LogString formatDate(const std::locale & locale, const tm & date, const LogString & fmt)
	{
		//
		//  output the using STL
		//
		std::basic_ostringstream<logchar> buffer;
#if defined(_MSC_VER) && _MSC_VER < 1300
		_USEFAC(locale, std::time_put<logchar>)
		.put(buffer, buffer, &date, fmt.c_str(), fmt.c_str() + fmt.length());
#else
#if defined(_RWSTD_NO_TEMPLATE_ON_RETURN_TYPE)
		const std::time_put<logchar>& facet = std::use_facet(locale, (std::time_put<logchar>*) 0);
#else
		const std::time_put<logchar>& facet = std::use_facet<std::time_put<logchar> >(locale);
#endif
		facet.put(buffer, buffer, buffer.fill(), &date, fmt.c_str(), fmt.c_str() + fmt.length());
#endif
		return buffer.str();
	}

	/** Check that format is locale sensitive. */
	void test7()
	{
		apr_time_t avr11 = MICROSECONDS_PER_DAY * 12519;
		LocaleChanger localeChange(LOCALE_FR);

		if (localeChange.isEffective())
		{
			LogString formatted;
			SimpleDateFormat formatter(LOG4CXX_STR("MMM"));
			formatter.format(formatted, avr11);

			std::locale localeFR(LOCALE_FR);
			struct tm avr11tm = { 0, 0, 0, 11, 03, 104 };
			LogString expected(formatDate(localeFR, avr11tm, LOG4CXX_STR("%b")));

			LOGUNIT_ASSERT_EQUAL(expected, formatted);
		}
	}

	/** Check that format is locale sensitive. */
	void test8()
	{
		apr_time_t apr11 = MICROSECONDS_PER_DAY * 12519;
		LocaleChanger localeChange(LOCALE_US);

		if (localeChange.isEffective())
		{
			LogString formatted;
			SimpleDateFormat formatter(LOG4CXX_STR("MMM"));
			formatter.setTimeZone(TimeZone::getGMT());
			formatter.format(formatted, apr11);

			std::locale localeUS(LOCALE_US);
			struct tm apr11tm = { 0, 0, 0, 11, 03, 104 };
			LogString expected(formatDate(localeUS, apr11tm, LOG4CXX_STR("%b")));

			LOGUNIT_ASSERT_EQUAL(expected, formatted);
		}
	}
#endif

};

LOGUNIT_TEST_SUITE_REGISTRATION(DateTimeDateFormatTestCase);

