/*
 * 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.
 */
#include <log4cxx/logstring.h>
#include <log4cxx/helpers/simpledateformat.h>

#include <apr_time.h>
#include <apr_strings.h>
#include <sstream>
#include <log4cxx/helpers/transcoder.h>
#include <log4cxx/helpers/stringhelper.h>
#include <assert.h>
#if !defined(LOG4CXX)
	#define LOG4CXX 1
#endif
#include <log4cxx/private/log4cxx_private.h>
#include <log4cxx/helpers/pool.h>

using namespace LOG4CXX_NS;
using namespace LOG4CXX_NS::helpers;

using namespace std;

#if LOG4CXX_HAS_STD_LOCALE
	#include <locale>
#endif

#if defined(_MSC_VER) && _MSC_VER < 1300
	#define HAS_FACET(locale, type) _HAS(locale, type)
	#define USE_FACET(locale, type) _USE(locale, type)
	#define PUT_FACET(facet, os, time, spec) facet.put(os, os, time, spec)
#else
	#if defined(_RWSTD_NO_TEMPLATE_ON_RETURN_TYPE)
		#define HAS_FACET(locale, type) std::has_facet(locale, (type*) 0)
		#define USE_FACET(locale, type) std::use_facet(locale, (type*) 0)
	#else
		#define HAS_FACET(locale, type) std::has_facet < type >(locale)
		#define USE_FACET(locale, type) std::use_facet < type >(locale)
	#endif
	#define PUT_FACET(facet, os, time, spec) facet.put(os, os, os.fill(), time, spec)
#endif

namespace LOG4CXX_NS
{
namespace helpers
{
namespace SimpleDateFormatImpl
{
typedef void (*incrementFunction)(tm& time, apr_time_exp_t& apr_time);

/**
 * Abstract inner class representing one format token
 * (one or more instances of a character).
 */
class PatternToken
{
	public:
		PatternToken()
		{
		}

		virtual ~PatternToken()
		{
		}

		/**
		 * Sets the time zone.
		 * @param zone new time zone.
		 */
		virtual void setTimeZone(const TimeZonePtr& zone)
		{
		}

		/**
		 * Appends the formatted content to the string.
		 * @param s string to which format contribution is appended.
		 * @param date exploded date/time.
		 * @param p memory pool.
		 */
		virtual void format(LogString& s,
			const apr_time_exp_t& date,
			LOG4CXX_NS::helpers::Pool& p) const = 0;

	protected:

		static void incrementMonth(tm& time, apr_time_exp_t& aprtime)
		{
			time.tm_mon++;
			aprtime.tm_mon++;
		}

		static void incrementDay(tm& time, apr_time_exp_t& aprtime)
		{
			time.tm_wday++;
			aprtime.tm_wday++;
		}

		static void incrementHalfDay(tm& time, apr_time_exp_t& aprtime)
		{
			time.tm_hour += 12;
			aprtime.tm_hour += 12;
		}

		static void renderFacet(const std::locale* locale,
			incrementFunction inc,
			char spec,
			unsigned int wspec,
			const char* aprspec,
			std::vector<LogString>& values)
		{
			std::vector<LogString>::iterator valueIter = values.begin();
			tm time;
			memset(&time, 0, sizeof(time));
			apr_time_exp_t aprtime;
			memset(&aprtime, 0, sizeof(aprtime));
#if LOG4CXX_HAS_STD_LOCALE

			if (locale != NULL)
			{
#if LOG4CXX_WCHAR_T_API

				if (HAS_FACET(*locale, std::time_put<wchar_t>))
				{
					const std::time_put<wchar_t>& facet = USE_FACET(*locale, std::time_put<wchar_t>);
					size_t start = 0;
					std::basic_ostringstream<wchar_t> os;

					for (; valueIter != values.end(); valueIter++)
					{
						PUT_FACET(facet, os, &time, (char)wspec);
						Transcoder::decode(os.str().substr(start), *valueIter);
						start = os.str().length();
						(*inc)(time, aprtime);
					}
				}
				else
#endif
					if (HAS_FACET(*locale,  std::time_put<char>))
					{
						const std::time_put<char>& facet = USE_FACET(*locale, std::time_put<char> );
						size_t start = 0;
						std::ostringstream os;

						for (; valueIter != values.end(); valueIter++)
						{
							PUT_FACET(facet, os, &time, spec);
							Transcoder::decode(os.str().substr(start), *valueIter);
							start = os.str().length();
							(*inc)(time, aprtime);
						}
					}
			}

#endif
			const size_t BUFSIZE = 256;
			char buf[BUFSIZE];
			memset(buf, 0, BUFSIZE);
			apr_size_t retsize = 0;

			for (; valueIter != values.end(); valueIter++)
			{
				apr_status_t stat = apr_strftime(buf, &retsize, BUFSIZE, aprspec, &aprtime);
				(*inc)(time, aprtime);

				if (stat == APR_SUCCESS)
				{
					Transcoder::decode(std::string(buf, retsize), *valueIter);
				}
				else
				{
					valueIter->append(1, (logchar) 0x3F);
				}
			}
		}

	private:
		/**
		 * Private copy constructor.
		 */
		PatternToken(const PatternToken&);

		/**
		 * Private assignment operator.
		 */
		PatternToken& operator=(const PatternToken&);
};


class LiteralToken : public PatternToken
{
	public:
		LiteralToken( logchar ch1, int count1 ) : ch( ch1 ), count( count1 )
		{
		}

		void format( LogString& s, const apr_time_exp_t&, Pool& /* p */ ) const
		{
			s.append( count, ch );
		}

	private:
		logchar ch;
		int count;
};



class EraToken : public PatternToken
{
	public:
		EraToken( int /* count */, const std::locale* /* locale */  )
		{
		}

		void format(LogString& s, const apr_time_exp_t& /* tm */, Pool& /* p */ ) const
		{
			s.append(1, (logchar) 0x41 /* 'A' */);
			s.append(1, (logchar) 0x44 /* 'D' */);
		}
};



class NumericToken : public PatternToken
{
	public:
		NumericToken( size_t width1 ) : width( width1 )
		{
		}

		virtual int getField( const apr_time_exp_t& tm ) const = 0;

		void format( LogString& s, const apr_time_exp_t& tm, Pool& p ) const
		{
			size_t initialLength = s.length();

			StringHelper::toString( getField( tm ), p, s );
			size_t finalLength = s.length();

			if ( initialLength + width > finalLength )
			{
				s.insert( initialLength, ( initialLength + width ) - finalLength, (logchar) 0x30 /* '0' */);
			}
		}

	private:
		size_t width;
};



class YearToken : public NumericToken
{
	public:
		YearToken( int width1 ) : NumericToken( width1 )
		{
		}

		int getField( const apr_time_exp_t& tm ) const
		{
			return 1900 + tm.tm_year;
		}
};



class MonthToken : public NumericToken
{
	public:
		MonthToken( int width1 ) : NumericToken( width1 )
		{
		}

		int getField( const apr_time_exp_t& tm ) const
		{
			return tm.tm_mon + 1;
		}
};



class AbbreviatedMonthNameToken : public PatternToken
{
	public:
		AbbreviatedMonthNameToken(int, const std::locale* locale) : names( 12 )
		{
			renderFacet(locale, PatternToken::incrementMonth, 'b', 0x62, "%b", names);
		}

		void format(LogString& s, const apr_time_exp_t& tm, Pool& /* p */ ) const
		{
			s.append( names[tm.tm_mon] );
		}

	private:
		std::vector < LogString > names;
};



class FullMonthNameToken : public PatternToken
{
	public:
		FullMonthNameToken( int width, const std::locale* locale) : names( 12 )
		{
			renderFacet(locale, PatternToken::incrementMonth, 'B', 0x42, "%B", names);
		}

		void format( LogString& s, const apr_time_exp_t& tm, Pool& /* p */ ) const
		{
			s.append( names[tm.tm_mon] );
		}

	private:
		std::vector < LogString > names;
};



class WeekInYearToken : public NumericToken
{
	public:
		WeekInYearToken( int width1 ) : NumericToken( width1 )
		{
		}

		int getField( const apr_time_exp_t& tm ) const
		{
			return tm.tm_yday / 7;
		}
};



class WeekInMonthToken : public NumericToken
{
	public:
		WeekInMonthToken( int width1 ) : NumericToken( width1 )
		{
		}

		int getField( const apr_time_exp_t& tm ) const
		{
			return tm.tm_mday / 7;
		}
};



class DayInMonthToken : public NumericToken
{
	public:
		DayInMonthToken( int width1 ) : NumericToken( width1 )
		{
		}

		int getField( const apr_time_exp_t& tm ) const
		{
			return tm.tm_mday;
		}
};



class DayInYearToken : public NumericToken
{
	public:
		DayInYearToken( int width1 ) : NumericToken( width1 )
		{
		}

		int getField( const apr_time_exp_t& tm ) const
		{
			return tm.tm_yday;
		}
};



class DayOfWeekInMonthToken : public NumericToken
{
	public:
		DayOfWeekInMonthToken( int width1 ) : NumericToken( width1 )
		{
		}

		int getField( const apr_time_exp_t& /* tm */ ) const
		{
			return -1;
		}
};



class AbbreviatedDayNameToken : public PatternToken
{
	public:
		AbbreviatedDayNameToken( int width, const std::locale* locale) : names( 7 )
		{
			renderFacet(locale, PatternToken::incrementDay, 'a', 0x61, "%a", names);
		}

		void format( LogString& s, const apr_time_exp_t& tm, Pool& /* p */ ) const
		{
			s.append( names[tm.tm_wday] );
		}

	private:
		std::vector < LogString > names;

};



class FullDayNameToken : public PatternToken
{
	public:
		FullDayNameToken( int width, const std::locale* locale) : names( 7 )
		{
			renderFacet(locale, PatternToken::incrementDay, 'A', 0x41, "%A", names);
		}

		void format( LogString& s, const apr_time_exp_t& tm, Pool& /* p */ ) const
		{
			s.append( names[tm.tm_wday] );
		}

	private:
		std::vector < LogString > names;

};



class MilitaryHourToken : public NumericToken
{
	public:
		MilitaryHourToken( int width1, int offset1 ) : NumericToken( width1 ), offset( offset1 )
		{
		}

		int getField( const apr_time_exp_t& tm ) const
		{
			return tm.tm_hour + offset;
		}

	private:
		int offset;
};



class HourToken : public NumericToken
{
	public:
		HourToken( int width1, int /* offset1 */ ) : NumericToken( width1 ), offset( 0 )
		{
		}

		int getField( const apr_time_exp_t& tm ) const
		{
			return ( ( tm.tm_hour + 12 - offset ) % 12 ) + offset;
		}

	private:
		int offset;
};



class MinuteToken : public NumericToken
{
	public:
		MinuteToken( int width1 ) : NumericToken( width1 )
		{
		}

		int getField( const apr_time_exp_t& tm ) const
		{
			return tm.tm_min;
		}
};



class SecondToken : public NumericToken
{
	public:
		SecondToken( int width1 ) : NumericToken( width1 )
		{
		}

		int getField( const apr_time_exp_t& tm ) const
		{
			return tm.tm_sec;
		}
};



class MillisecondToken : public NumericToken
{
	public:
		MillisecondToken( int width1 ) : NumericToken( width1 )
		{
		}

		int getField( const apr_time_exp_t& tm ) const
		{
			return tm.tm_usec / 1000;
		}
};



class MicrosecondToken : public NumericToken
{
	public:
		MicrosecondToken( int width1 ) : NumericToken( width1 )
		{
		}

		int getField( const apr_time_exp_t& tm ) const
		{
			return tm.tm_usec;
		}
};



class AMPMToken : public PatternToken
{
	public:
		AMPMToken( int width, const std::locale* locale) : names( 2 )
		{
			renderFacet(locale, PatternToken::incrementHalfDay, 'p', 0x70, "%p", names);
		}

		void format( LogString& s, const apr_time_exp_t& tm, Pool& /* p */ ) const
		{
			s.append( names[tm.tm_hour / 12] );
		}

	private:
		std::vector < LogString > names;
};



class GeneralTimeZoneToken : public PatternToken
{
	public:
		GeneralTimeZoneToken( int /* width */ )
		{
		}

		void format( LogString& s, const apr_time_exp_t&, Pool& /* p */ ) const
		{
			s.append(timeZone->getID());
		}

		void setTimeZone( const TimeZonePtr& zone )
		{
			timeZone = zone;
		}

	private:
		TimeZonePtr timeZone;
};



class RFC822TimeZoneToken : public PatternToken
{
	public:
		RFC822TimeZoneToken( int /* width */ )
		{
		}

		void format( LogString& s, const apr_time_exp_t& tm, Pool& p ) const
		{
			if ( tm.tm_gmtoff == 0 )
			{
				s.append( 1, (logchar) 0x5A /* 'Z'  */ );
			}
			else
			{
				apr_int32_t off = tm.tm_gmtoff;
				size_t basePos = s.length();
				s.append( LOG4CXX_STR( "+0000" ) );

				if ( off < 0 )
				{
					s[basePos] = 0x2D; // '-'
					off = -off;
				}

				LogString hours;
				StringHelper::toString( off / 3600, p, hours );
				size_t hourPos = basePos + 2;

				//
				//   assumes that point values for 0-9 are same between char and wchar_t
				//
				for ( size_t i = hours.length(); i-- > 0; )
				{
					s[hourPos--] = hours[i];
				}

				LogString min;
				StringHelper::toString( ( off % 3600 ) / 60, p, min );
				size_t minPos = basePos + 4;

				//
				//   assumes that point values for 0-9 are same between char and wchar_t
				//
				for ( size_t j = min.length(); j-- > 0; )
				{
					s[minPos--] = min[j];
				}
			}
		}
};




}
}
}


using namespace LOG4CXX_NS::helpers::SimpleDateFormatImpl;

void SimpleDateFormat::addToken(const logchar spec, const int repeat, const std::locale* locale,
	std::vector < PatternToken* >& pattern )
{
	PatternToken* token = NULL;

	switch ( spec )
	{
		case 0x47: // 'G'
			token = ( new EraToken( repeat, locale ) );
			break;

		case 0x79: // 'y'
			token = ( new YearToken( repeat ) );
			break;

		case 0x4D: // 'M'
			if ( repeat <= 2 )
			{
				token = ( new MonthToken( repeat ) );
			}
			else if ( repeat <= 3 )
			{
				token = ( new AbbreviatedMonthNameToken( repeat, locale ) );
			}
			else
			{
				token = ( new FullMonthNameToken( repeat, locale ) );
			}

			break;

		case 0x77: // 'w'
			token = ( new WeekInYearToken( repeat ) );
			break;

		case 0x57: // 'W'
			token = ( new WeekInMonthToken( repeat ) );
			break;

		case 0x44: // 'D'
			token = ( new DayInYearToken( repeat ) );
			break;

		case 0x64: // 'd'
			token = ( new DayInMonthToken( repeat ) );
			break;

		case 0x46: // 'F'
			token = ( new DayOfWeekInMonthToken( repeat ) );
			break;

		case 0x45: // 'E'
			if ( repeat <= 3 )
			{
				token = ( new AbbreviatedDayNameToken( repeat, locale ) );
			}
			else
			{
				token = ( new FullDayNameToken( repeat, locale ) );
			}

			break;

		case 0x61: // 'a'
			token = ( new AMPMToken( repeat, locale ) );
			break;

		case 0x48: // 'H'
			token = ( new MilitaryHourToken( repeat, 0 ) );
			break;

		case 0x6B: // 'k'
			token = ( new MilitaryHourToken( repeat, 1 ) );
			break;

		case 0x4B: // 'K'
			token = ( new HourToken( repeat, 0 ) );
			break;

		case 0x68: // 'h'
			token = ( new HourToken( repeat, 1 ) );
			break;

		case 0x6D: // 'm'
			token = ( new MinuteToken( repeat ) );
			break;

		case 0x73: // 's'
			token = ( new SecondToken( repeat ) );
			break;

		case 0x53: // 'S'
			if ( repeat == 6 )
			{
				token = ( new MicrosecondToken( repeat ) );
			}
			else
			{
				// It would be nice to support patterns with arbitrary
				// subsecond precision (like "s.S" or "s.SSSS"), but we
				// don't; so this is a back-compatible default.
				token = ( new MillisecondToken( repeat ) );
			}

			break;

		case 0x7A: // 'z'
			token = ( new GeneralTimeZoneToken( repeat ) );
			break;

		case 0x5A: // 'Z'
			token = ( new RFC822TimeZoneToken( repeat ) );
			break;

		default:
			token = ( new LiteralToken( spec, repeat ) );
	}

	assert( token != NULL );
	pattern.push_back( token );
}


void SimpleDateFormat::parsePattern( const LogString& fmt, const std::locale* locale,
	std::vector < PatternToken* >& pattern )
{
	if ( !fmt.empty() )
	{
		LogString::const_iterator iter = fmt.begin();
		int repeat = 1;
		logchar prevChar = * iter;

		for ( iter++; iter != fmt.end(); iter++ )
		{
			if ( * iter == prevChar )
			{
				repeat++;
			}
			else
			{
				addToken( prevChar, repeat, locale, pattern );
				prevChar = * iter;
				repeat = 1;
			}
		}

		addToken( prevChar, repeat, locale, pattern );
	}
}


struct SimpleDateFormat::SimpleDateFormatPrivate{
	SimpleDateFormatPrivate() :
		timeZone(TimeZone::getDefault())
	{}

	/**
	 * Time zone.
	 */
	TimeZonePtr timeZone;

	/**
	 * List of tokens.
	 */
	PatternTokenList pattern;
};

SimpleDateFormat::SimpleDateFormat( const LogString& fmt ) : m_priv(std::make_unique<SimpleDateFormatPrivate>())
{
#if LOG4CXX_HAS_STD_LOCALE
	std::locale defaultLocale;
	parsePattern( fmt, & defaultLocale, m_priv->pattern );
#else
	parsePattern( fmt, NULL, m_priv->pattern );
#endif

	for (auto const& item : m_priv->pattern)
	{
		item->setTimeZone( m_priv->timeZone );
	}
}

SimpleDateFormat::SimpleDateFormat( const LogString& fmt, const std::locale* locale ) : m_priv(std::make_unique<SimpleDateFormatPrivate>())
{
	parsePattern( fmt, locale, m_priv->pattern );

	for (auto const& item : m_priv->pattern)
	{
		item->setTimeZone( m_priv->timeZone );
	}
}


SimpleDateFormat::~SimpleDateFormat()
{
	for (auto item : m_priv->pattern)
	{
		delete item;
	}
}


void SimpleDateFormat::format( LogString& s, log4cxx_time_t time, Pool& p ) const
{
	apr_time_exp_t exploded;
	apr_status_t stat = m_priv->timeZone->explode( & exploded, time );

	if ( stat == APR_SUCCESS )
	{
		for ( PatternTokenList::const_iterator iter = m_priv->pattern.begin(); iter != m_priv->pattern.end(); iter++ )
		{
			( * iter )->format( s, exploded, p );
		}
	}
}

void SimpleDateFormat::setTimeZone( const TimeZonePtr& zone )
{
	m_priv->timeZone = zone;
}
