#region Copyright & License
//
// Copyright 2001-2005 The Apache Software Foundation
//
// Licensed 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.
//
#endregion

using System;
using System.IO;
using System.Text;

namespace log4net.DateFormatter
{
	/// <summary>
	/// Formats a <see cref="DateTime"/> as <c>"HH:mm:ss,fff"</c>.
	/// </summary>
	/// <remarks>
	/// <para>
	/// Formats a <see cref="DateTime"/> in the format <c>"HH:mm:ss,fff"</c> for example, <c>"15:49:37,459"</c>.
	/// </para>
	/// </remarks>
	/// <author>Nicko Cadell</author>
	/// <author>Gert Driesen</author>
	public class AbsoluteTimeDateFormatter : IDateFormatter
	{
		#region Protected Instance Methods

		/// <summary>
		/// Renders the date into a string. Format is <c>"HH:mm:ss"</c>.
		/// </summary>
		/// <param name="dateToFormat">The date to render into a string.</param>
		/// <param name="buffer">The string builder to write to.</param>
		/// <remarks>
		/// <para>
		/// Subclasses should override this method to render the date
		/// into a string using a precision up to the second. This method
		/// will be called at most once per second and the result will be
		/// reused if it is needed again during the same second.
		/// </para>
		/// </remarks>
		virtual protected void FormatDateWithoutMillis(DateTime dateToFormat, StringBuilder buffer)
		{
			int hour = dateToFormat.Hour;
			if (hour < 10) 
			{
				buffer.Append('0');
			}
			buffer.Append(hour);
			buffer.Append(':');

			int mins = dateToFormat.Minute;
			if (mins < 10) 
			{
				buffer.Append('0');
			}
			buffer.Append(mins);
			buffer.Append(':');
	
			int secs = dateToFormat.Second;
			if (secs < 10) 
			{
				buffer.Append('0');
			}
			buffer.Append(secs);
		}

		#endregion Protected Instance Methods

		#region Implementation of IDateFormatter

		/// <summary>
		/// Renders the date into a string. Format is "HH:mm:ss,fff".
		/// </summary>
		/// <param name="dateToFormat">The date to render into a string.</param>
		/// <param name="writer">The writer to write to.</param>
		/// <remarks>
		/// <para>
		/// Uses the <see cref="FormatDateWithoutMillis"/> method to generate the
		/// time string up to the seconds and then appends the current
		/// milliseconds. The results from <see cref="FormatDateWithoutMillis"/> are
		/// cached and <see cref="FormatDateWithoutMillis"/> is called at most once
		/// per second.
		/// </para>
		/// <para>
		/// Sub classes should override <see cref="FormatDateWithoutMillis"/>
		/// rather than <see cref="FormatDate"/>.
		/// </para>
		/// </remarks>
		virtual public void FormatDate(DateTime dateToFormat, TextWriter writer)
		{
			// Calculate the current time precise only to the second
			long currentTimeToTheSecond = (dateToFormat.Ticks - (dateToFormat.Ticks % TimeSpan.TicksPerSecond));

			// Compare this time with the stored last time
			// If we are in the same second then append
			// the previously calculated time string
			if (s_lastTimeToTheSecond != currentTimeToTheSecond)
			{
				// lock so that only one thread can use the buffer and
				// update the s_lastTimeToTheSecond and s_lastTimeString

				// PERF: Try removing this lock and using a new StringBuilder each time
				lock(s_lastTimeBuf)
				{
					if (s_lastTimeToTheSecond != currentTimeToTheSecond)
					{
						// We are in a new second.
						s_lastTimeBuf.Length = 0;

						// Calculate the new string for this second
						FormatDateWithoutMillis(dateToFormat, s_lastTimeBuf);

						// Render the string buffer to a string
						string currentDateWithoutMillis = s_lastTimeBuf.ToString();

#if NET_1_1
						// Ensure that the above string is written into the variable NOW on all threads.
						// This is only required on multiprocessor machines with weak memeory models
						System.Threading.Thread.MemoryBarrier();
#endif
						// Store the time as a string (we only have to do this once per second)
						s_lastTimeString = currentDateWithoutMillis;
						s_lastTimeToTheSecond = currentTimeToTheSecond;
					}
				}
			}
			writer.Write(s_lastTimeString);
	
			// Append the current millisecond info
			writer.Write(',');
			int millis = dateToFormat.Millisecond;
			if (millis < 100) 
			{
				writer.Write('0');
			}
			if (millis < 10) 
			{
				writer.Write('0');
			}
			writer.Write(millis);
		}

		#endregion Implementation of IDateFormatter

		#region Public Static Fields

		/// <summary>
		/// String constant used to specify AbsoluteTimeDateFormat in layouts. Current value is <b>ABSOLUTE</b>.
		/// </summary>
		public const string AbsoluteTimeDateFormat = "ABSOLUTE";

		/// <summary>
		/// String constant used to specify DateTimeDateFormat in layouts.  Current value is <b>DATE</b>.
		/// </summary>
		public const string DateAndTimeDateFormat = "DATE";

		/// <summary>
		/// String constant used to specify ISO8601DateFormat in layouts. Current value is <b>ISO8601</b>.
		/// </summary>
		public const string Iso8601TimeDateFormat = "ISO8601";

		#endregion Public Static Fields

		#region Private Static Fields

		/// <summary>
		/// Last stored time with precision up to the second.
		/// </summary>
		private static long s_lastTimeToTheSecond = 0;

		/// <summary>
		/// Last stored time with precision up to the second, formatted
		/// as a string.
		/// </summary>
		private static StringBuilder s_lastTimeBuf = new StringBuilder();

		/// <summary>
		/// Last stored time with precision up to the second, formatted
		/// as a string.
		/// </summary>
		private static string s_lastTimeString;

		#endregion Private Static Fields
	}
}
