#region Apache License
//
// 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.
//
#endregion

using System;
using System.Collections;
using System.IO;

using log4net.Util;
using log4net.Util.PatternStringConverters;
using log4net.Core;

namespace log4net.Util
{
	/// <summary>
	/// This class implements a patterned string.
	/// </summary>
	/// <remarks>
	/// <para>
	/// This string has embedded patterns that are resolved and expanded
	/// when the string is formatted.
	/// </para>
	/// <para>
	/// This class functions similarly to the <see cref="log4net.Layout.PatternLayout"/>
	/// in that it accepts a pattern and renders it to a string. Unlike the 
	/// <see cref="log4net.Layout.PatternLayout"/> however the <c>PatternString</c>
	/// does not render the properties of a specific <see cref="LoggingEvent"/> but
	/// of the process in general.
	/// </para>
	/// <para>
	/// The recognized conversion pattern names are:
	/// </para>
	/// <list type="table">
	///     <listheader>
	///         <term>Conversion Pattern Name</term>
	///         <description>Effect</description>
	///     </listheader>
	///     <item>
	///         <term>appdomain</term>
	///         <description>
	///             <para>
	///             Used to output the friendly name of the current AppDomain.
	///             </para>
	///         </description>
	///     </item>
	///     <item>
	///         <term>date</term>
	///         <description>
	/// 			<para>
	/// 			Used to output the current date and time in the local time zone. 
	/// 			To output the date in universal time use the <c>%utcdate</c> pattern.
	/// 			The date conversion 
	/// 			specifier may be followed by a <i>date format specifier</i> enclosed 
	/// 			between braces. For example, <b>%date{HH:mm:ss,fff}</b> or
	/// 			<b>%date{dd MMM yyyy HH:mm:ss,fff}</b>.  If no date format specifier is 
	/// 			given then ISO8601 format is
	/// 			assumed (<see cref="log4net.DateFormatter.Iso8601DateFormatter"/>).
	/// 			</para>
	/// 			<para>
	/// 			The date format specifier admits the same syntax as the
	/// 			time pattern string of the <see cref="M:DateTime.ToString(string)"/>.
	/// 			</para>
	/// 			<para>
	/// 			For better results it is recommended to use the log4net date
	/// 			formatters. These can be specified using one of the strings
	/// 			"ABSOLUTE", "DATE" and "ISO8601" for specifying 
	/// 			<see cref="log4net.DateFormatter.AbsoluteTimeDateFormatter"/>, 
	/// 			<see cref="log4net.DateFormatter.DateTimeDateFormatter"/> and respectively 
	/// 			<see cref="log4net.DateFormatter.Iso8601DateFormatter"/>. For example, 
	/// 			<b>%date{ISO8601}</b> or <b>%date{ABSOLUTE}</b>.
	/// 			</para>
	/// 			<para>
	/// 			These dedicated date formatters perform significantly
	/// 			better than <see cref="M:DateTime.ToString(string)"/>.
	/// 			</para>
	///         </description>
	///     </item>
	///     <item>
	///         <term>env</term>
	///         <description>
	///             <para>
	/// 			Used to output the a specific environment variable. The key to 
	/// 			lookup must be specified within braces and directly following the
	/// 			pattern specifier, e.g. <b>%env{COMPUTERNAME}</b> would include the value
	/// 			of the <c>COMPUTERNAME</c> environment variable.
	///             </para>
	///             <para>
	///             The <c>env</c> pattern is not supported on the .NET Compact Framework.
	///             </para>
	///         </description>
	///     </item>
	///     <item>
	///         <term>identity</term>
	///         <description>
	///				<para>
	///				Used to output the user name for the currently active user
	///				(Principal.Identity.Name).
	///				</para>
	///         </description>
	///     </item>
	///     <item>
	///         <term>newline</term>
	///         <description>
	/// 			<para>
	/// 			Outputs the platform dependent line separator character or
	/// 			characters.
	/// 			</para>
	/// 			<para>
	/// 			This conversion pattern name offers the same performance as using 
	/// 			non-portable line separator strings such as	"\n", or "\r\n". 
	/// 			Thus, it is the preferred way of specifying a line separator.
	/// 			</para> 
	///         </description>
	///     </item>
	///     <item>
	///         <term>processid</term>
	///         <description>
	///             <para>
	///				Used to output the system process ID for the current process.
	///             </para>
	///         </description>
	///     </item>
	///     <item>
	///         <term>property</term>
	///         <description>
	/// 			<para>
	/// 			Used to output a specific context property. The key to 
	/// 			lookup must be specified within braces and directly following the
	/// 			pattern specifier, e.g. <b>%property{user}</b> would include the value
	/// 			from the property that is keyed by the string 'user'. Each property value
	/// 			that is to be included in the log must be specified separately.
	/// 			Properties are stored in logging contexts. By default 
	/// 			the <c>log4net:HostName</c> property is set to the name of machine on 
	/// 			which the event was originally logged.
	/// 			</para>
	/// 			<para>
	/// 			If no key is specified, e.g. <b>%property</b> then all the keys and their
	/// 			values are printed in a comma separated list.
	/// 			</para>
	/// 			<para>
	/// 			The properties of an event are combined from a number of different
	/// 			contexts. These are listed below in the order in which they are searched.
	/// 			</para>
	/// 			<list type="definition">
	/// 				<item>
	/// 					<term>the thread properties</term>
	/// 					<description>
	/// 					The <see cref="ThreadContext.Properties"/> that are set on the current
	/// 					thread. These properties are shared by all events logged on this thread.
	/// 					</description>
	/// 				</item>
	/// 				<item>
	/// 					<term>the global properties</term>
	/// 					<description>
	/// 					The <see cref="GlobalContext.Properties"/> that are set globally. These 
	/// 					properties are shared by all the threads in the AppDomain.
	/// 					</description>
	/// 				</item>
	/// 			</list>
	///         </description>
	///     </item>
	///     <item>
	///         <term>random</term>
	///         <description>
	///             <para>
	///             Used to output a random string of characters. The string is made up of
	///             uppercase letters and numbers. By default the string is 4 characters long.
	///             The length of the string can be specified within braces directly following the
	/// 			pattern specifier, e.g. <b>%random{8}</b> would output an 8 character string.
	///             </para>
	///         </description>
	///     </item>
	///     <item>
	///         <term>username</term>
	///         <description>
	///				<para>
	///				Used to output the WindowsIdentity for the currently
	///				active user.
	///				</para>
	///         </description>
	///     </item>
	///     <item>
	///         <term>utcdate</term>
	///         <description>
	/// 			<para>
	/// 			Used to output the date of the logging event in universal time. 
	/// 			The date conversion 
	/// 			specifier may be followed by a <i>date format specifier</i> enclosed 
	/// 			between braces. For example, <b>%utcdate{HH:mm:ss,fff}</b> or
	/// 			<b>%utcdate{dd MMM yyyy HH:mm:ss,fff}</b>.  If no date format specifier is 
	/// 			given then ISO8601 format is
	/// 			assumed (<see cref="log4net.DateFormatter.Iso8601DateFormatter"/>).
	/// 			</para>
	/// 			<para>
	/// 			The date format specifier admits the same syntax as the
	/// 			time pattern string of the <see cref="M:DateTime.ToString(string)"/>.
	/// 			</para>
	/// 			<para>
	/// 			For better results it is recommended to use the log4net date
	/// 			formatters. These can be specified using one of the strings
	/// 			"ABSOLUTE", "DATE" and "ISO8601" for specifying 
	/// 			<see cref="log4net.DateFormatter.AbsoluteTimeDateFormatter"/>, 
	/// 			<see cref="log4net.DateFormatter.DateTimeDateFormatter"/> and respectively 
	/// 			<see cref="log4net.DateFormatter.Iso8601DateFormatter"/>. For example, 
	/// 			<b>%utcdate{ISO8601}</b> or <b>%utcdate{ABSOLUTE}</b>.
	/// 			</para>
	/// 			<para>
	/// 			These dedicated date formatters perform significantly
	/// 			better than <see cref="M:DateTime.ToString(string)"/>.
	/// 			</para>
	///         </description>
	///     </item>
	///		<item>
	///			<term>%</term>
	///			<description>
	/// 			<para>
	/// 			The sequence %% outputs a single percent sign.
	/// 			</para>
	///			</description>
	///		</item>
	/// </list>
	/// <para>
	/// Additional pattern converters may be registered with a specific <see cref="PatternString"/>
	/// instance using <see cref="M:AddConverter(ConverterInfo)"/> or
	/// <see cref="M:AddConverter(string, Type)" />.
	/// </para>
	/// <para>
	/// See the <see cref="log4net.Layout.PatternLayout"/> for details on the 
	/// <i>format modifiers</i> supported by the patterns.
	/// </para>
	/// </remarks>
	/// <author>Nicko Cadell</author>
	public class PatternString : IOptionHandler
	{
		#region Static Fields

		/// <summary>
		/// Internal map of converter identifiers to converter types.
		/// </summary>
		private static Hashtable s_globalRulesRegistry;

		#endregion Static Fields

		#region Member Variables
    
		/// <summary>
		/// the pattern
		/// </summary>
		private string m_pattern;
  
		/// <summary>
		/// the head of the pattern converter chain
		/// </summary>
		private PatternConverter m_head;

		/// <summary>
		/// patterns defined on this PatternString only
		/// </summary>
		private Hashtable m_instanceRulesRegistry = new Hashtable();

		#endregion

		#region Static Constructor

		/// <summary>
		/// Initialize the global registry
		/// </summary>
		static PatternString()
		{
			s_globalRulesRegistry = new Hashtable(15);

			s_globalRulesRegistry.Add("appdomain", typeof(AppDomainPatternConverter));
			s_globalRulesRegistry.Add("date", typeof(DatePatternConverter));
#if !NETCF
			s_globalRulesRegistry.Add("env", typeof(EnvironmentPatternConverter));
            s_globalRulesRegistry.Add("envFolderPath", typeof(EnvironmentFolderPathPatternConverter));
#endif
			s_globalRulesRegistry.Add("identity", typeof(IdentityPatternConverter));
			s_globalRulesRegistry.Add("literal", typeof(LiteralPatternConverter));
			s_globalRulesRegistry.Add("newline", typeof(NewLinePatternConverter));
			s_globalRulesRegistry.Add("processid", typeof(ProcessIdPatternConverter));
			s_globalRulesRegistry.Add("property", typeof(PropertyPatternConverter));
			s_globalRulesRegistry.Add("random", typeof(RandomStringPatternConverter));
			s_globalRulesRegistry.Add("username", typeof(UserNamePatternConverter));

			s_globalRulesRegistry.Add("utcdate", typeof(UtcDatePatternConverter));
			s_globalRulesRegistry.Add("utcDate", typeof(UtcDatePatternConverter));
			s_globalRulesRegistry.Add("UtcDate", typeof(UtcDatePatternConverter));
		}

		#endregion Static Constructor

		#region Constructors

		/// <summary>
		/// Default constructor
		/// </summary>
		/// <remarks>
		/// <para>
		/// Initialize a new instance of <see cref="PatternString"/>
		/// </para>
		/// </remarks>
		public PatternString()
		{
		}

		/// <summary>
		/// Constructs a PatternString
		/// </summary>
		/// <param name="pattern">The pattern to use with this PatternString</param>
		/// <remarks>
		/// <para>
		/// Initialize a new instance of <see cref="PatternString"/> with the pattern specified.
		/// </para>
		/// </remarks>
		public PatternString(string pattern)
		{
			m_pattern = pattern;
			ActivateOptions();
		}

		#endregion
  
		/// <summary>
		/// Gets or sets the pattern formatting string
		/// </summary>
		/// <value>
		/// The pattern formatting string
		/// </value>
		/// <remarks>
		/// <para>
		/// The <b>ConversionPattern</b> option. This is the string which
		/// controls formatting and consists of a mix of literal content and
		/// conversion specifiers.
		/// </para>
		/// </remarks>
		public string ConversionPattern
		{
			get { return m_pattern;	}
			set { m_pattern = value; }
		}

		#region Implementation of IOptionHandler

		/// <summary>
		/// Initialize object options
		/// </summary>
		/// <remarks>
		/// <para>
		/// This is part of the <see cref="IOptionHandler"/> delayed object
		/// activation scheme. The <see cref="ActivateOptions"/> method must 
		/// be called on this object after the configuration properties have
		/// been set. Until <see cref="ActivateOptions"/> is called this
		/// object is in an undefined state and must not be used. 
		/// </para>
		/// <para>
		/// If any of the configuration properties are modified then 
		/// <see cref="ActivateOptions"/> must be called again.
		/// </para>
		/// </remarks>
		virtual public void ActivateOptions() 
		{
			m_head = CreatePatternParser(m_pattern).Parse();
		}

		#endregion

		/// <summary>
		/// Create the <see cref="PatternParser"/> used to parse the pattern
		/// </summary>
		/// <param name="pattern">the pattern to parse</param>
		/// <returns>The <see cref="PatternParser"/></returns>
		/// <remarks>
		/// <para>
		/// Returns PatternParser used to parse the conversion string. Subclasses
		/// may override this to return a subclass of PatternParser which recognize
		/// custom conversion pattern name.
		/// </para>
		/// </remarks>
		private PatternParser CreatePatternParser(string pattern) 
		{
			PatternParser patternParser = new PatternParser(pattern);

			// Add all the builtin patterns
			foreach(DictionaryEntry entry in s_globalRulesRegistry)
			{
                ConverterInfo converterInfo = new ConverterInfo();
                converterInfo.Name = (string)entry.Key;
                converterInfo.Type = (Type)entry.Value;
                patternParser.PatternConverters.Add(entry.Key, converterInfo);
			}
			// Add the instance patterns
			foreach(DictionaryEntry entry in m_instanceRulesRegistry)
			{
				patternParser.PatternConverters[entry.Key] = entry.Value;
			}

			return patternParser;
		}
  
		/// <summary>
		/// Produces a formatted string as specified by the conversion pattern.
		/// </summary>
		/// <param name="writer">The TextWriter to write the formatted event to</param>
		/// <remarks>
		/// <para>
		/// Format the pattern to the <paramref name="writer"/>.
		/// </para>
		/// </remarks>
		public void Format(TextWriter writer) 
		{
			if (writer == null)
			{
				throw new ArgumentNullException("writer");
			}

			PatternConverter c = m_head;

			// loop through the chain of pattern converters
			while(c != null) 
			{
				c.Format(writer, null);
				c = c.Next;
			}
		}

		/// <summary>
		/// Format the pattern as a string
		/// </summary>
		/// <returns>the pattern formatted as a string</returns>
		/// <remarks>
		/// <para>
		/// Format the pattern to a string.
		/// </para>
		/// </remarks>
		public string Format() 
		{
			StringWriter writer = new StringWriter(System.Globalization.CultureInfo.InvariantCulture);
			Format(writer);
			return writer.ToString();
		}

		/// <summary>
		/// Add a converter to this PatternString
		/// </summary>
		/// <param name="converterInfo">the converter info</param>
		/// <remarks>
		/// <para>
		/// This version of the method is used by the configurator.
		/// Programmatic users should use the alternative <see cref="M:AddConverter(string,Type)"/> method.
		/// </para>
		/// </remarks>
		public void AddConverter(ConverterInfo converterInfo)
		{
            if (converterInfo == null) throw new ArgumentNullException("converterInfo");

            if (!typeof(PatternConverter).IsAssignableFrom(converterInfo.Type))
            {
                throw new ArgumentException("The converter type specified [" + converterInfo.Type + "] must be a subclass of log4net.Util.PatternConverter", "converterInfo");
            }
            m_instanceRulesRegistry[converterInfo.Name] = converterInfo;
		}

		/// <summary>
		/// Add a converter to this PatternString
		/// </summary>
		/// <param name="name">the name of the conversion pattern for this converter</param>
		/// <param name="type">the type of the converter</param>
		/// <remarks>
		/// <para>
		/// Add a converter to this PatternString
		/// </para>
		/// </remarks>
		public void AddConverter(string name, Type type)
		{
			if (name == null) throw new ArgumentNullException("name");
			if (type == null) throw new ArgumentNullException("type");

            ConverterInfo converterInfo = new ConverterInfo();
            converterInfo.Name = name;
            converterInfo.Type = type;

            AddConverter(converterInfo);
		}
	}
}
