#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.Text;
using System.IO;
using System.Collections;

using log4net.Util;

namespace log4net.ObjectRenderer
{
	/// <summary>
	/// The default object Renderer.
	/// </summary>
	/// <remarks>
	/// <para>
	/// The default renderer supports rendering objects and collections to strings.
	/// </para>
	/// <para>
	/// See the <see cref="RenderObject"/> method for details of the output.
	/// </para>
	/// </remarks>
	/// <author>Nicko Cadell</author>
	/// <author>Gert Driesen</author>
	public sealed class DefaultRenderer : IObjectRenderer
	{
		#region Constructors

		/// <summary>
		/// Default constructor
		/// </summary>
		/// <remarks>
		/// <para>
		/// Default constructor
		/// </para>
		/// </remarks>
		public DefaultRenderer()
		{
		}

		#endregion

		#region Implementation of IObjectRenderer

		/// <summary>
		/// Render the object <paramref name="obj"/> to a string
		/// </summary>
		/// <param name="rendererMap">The map used to lookup renderers</param>
		/// <param name="obj">The object to render</param>
		/// <param name="writer">The writer to render to</param>
		/// <remarks>
		/// <para>
		/// Render the object <paramref name="obj"/> to a string.
		/// </para>
		/// <para>
		/// The <paramref name="rendererMap"/> parameter is
		/// provided to lookup and render other objects. This is
		/// very useful where <paramref name="obj"/> contains
		/// nested objects of unknown type. The <see cref="RendererMap.FindAndRender(object)"/>
		/// method can be used to render these objects.
		/// </para>
		/// <para>
		/// The default renderer supports rendering objects to strings as follows:
		/// </para>
		/// <list type="table">
		///		<listheader>
		///			<term>Value</term>
		///			<description>Rendered String</description>
		///		</listheader>
		///		<item>
		///			<term><c>null</c></term>
		///			<description>
		///			<para>"(null)"</para>
		///			</description>
		///		</item>
		///		<item>
		///			<term><see cref="Array"/></term>
		///			<description>
		///			<para>
		///			For a one dimensional array this is the
		///			array type name, an open brace, followed by a comma
		///			separated list of the elements (using the appropriate
		///			renderer), followed by a close brace. 
		///			</para>
		///			<para>
		///			For example: <c>int[] {1, 2, 3}</c>.
		///			</para>
		///			<para>
		///			If the array is not one dimensional the 
		///			<c>Array.ToString()</c> is returned.
		///			</para>
		///			</description>
		///		</item>
		///		<item>
		///			<term><see cref="IEnumerable"/>, <see cref="ICollection"/> &amp; <see cref="IEnumerator"/></term>
		///			<description>
		///			<para>
		///			Rendered as an open brace, followed by a comma
		///			separated list of the elements (using the appropriate
		///			renderer), followed by a close brace.
		///			</para>
		///			<para>
		///			For example: <c>{a, b, c}</c>.
		///			</para>
		///			<para>
		///			All collection classes that implement <see cref="ICollection"/> its subclasses, 
		///			or generic equivalents all implement the <see cref="IEnumerable"/> interface.
		///			</para>
		///			</description>
		///		</item>		
		///		<item>
		///			<term><see cref="DictionaryEntry"/></term>
		///			<description>
		///			<para>
		///			Rendered as the key, an equals sign ('='), and the value (using the appropriate
		///			renderer). 
		///			</para>
		///			<para>
		///			For example: <c>key=value</c>.
		///			</para>
		///			</description>
		///		</item>		
		///		<item>
		///			<term>other</term>
		///			<description>
		///			<para><c>Object.ToString()</c></para>
		///			</description>
		///		</item>
		/// </list>
		/// </remarks>
		public void RenderObject(RendererMap rendererMap, object obj, TextWriter writer)
		{
			if (rendererMap == null)
			{
				throw new ArgumentNullException("rendererMap");
			}

			if (obj == null)
			{
				writer.Write(SystemInfo.NullText);
				return;
			}
			
			Array objArray = obj as Array;
			if (objArray != null)
			{
				RenderArray(rendererMap, objArray, writer);
				return;
			}

			// Test if we are dealing with some form of collection object
			IEnumerable objEnumerable = obj as IEnumerable;
			if (objEnumerable != null)
			{
				// Get a collection interface if we can as its .Count property may be more
				// performant than getting the IEnumerator object and trying to advance it.
				ICollection objCollection = obj as ICollection;
				if (objCollection != null && objCollection.Count == 0)
				{
					writer.Write("{}");
					return;
				}
				
				// This is a special check to allow us to get the enumerator from the IDictionary
				// interface as this guarantees us DictionaryEntry objects. Note that in .NET 2.0
				// the generic IDictionary<> interface enumerates KeyValuePair objects rather than
				// DictionaryEntry ones. However the implementation of the plain IDictionary 
				// interface on the generic Dictionary<> still returns DictionaryEntry objects.
				IDictionary objDictionary = obj as IDictionary;
				if (objDictionary != null)
				{
					RenderEnumerator(rendererMap, objDictionary.GetEnumerator(), writer);
					return;
				}

				RenderEnumerator(rendererMap, objEnumerable.GetEnumerator(), writer);
				return;
			}

			IEnumerator objEnumerator = obj as IEnumerator;
			if (objEnumerator != null)
			{
				RenderEnumerator(rendererMap, objEnumerator, writer);
				return;
			}
			
			if (obj is DictionaryEntry)
			{
				RenderDictionaryEntry(rendererMap, (DictionaryEntry)obj, writer);
				return;
			}

			string str = obj.ToString();
			writer.Write( (str==null) ? SystemInfo.NullText : str );
		}

		#endregion

		/// <summary>
		/// Render the array argument into a string
		/// </summary>
		/// <param name="rendererMap">The map used to lookup renderers</param>
		/// <param name="array">the array to render</param>
		/// <param name="writer">The writer to render to</param>
		/// <remarks>
		/// <para>
		/// For a one dimensional array this is the
		///	array type name, an open brace, followed by a comma
		///	separated list of the elements (using the appropriate
		///	renderer), followed by a close brace. For example:
		///	<c>int[] {1, 2, 3}</c>.
		///	</para>
		///	<para>
		///	If the array is not one dimensional the 
		///	<c>Array.ToString()</c> is returned.
		///	</para>
		/// </remarks>
		private void RenderArray(RendererMap rendererMap, Array array, TextWriter writer)
		{
			if (array.Rank != 1)
			{
				writer.Write(array.ToString());
			}
			else
			{
				writer.Write(array.GetType().Name + " {");
				int len = array.Length;

				if (len > 0)
				{
					rendererMap.FindAndRender(array.GetValue(0), writer);
					for(int i=1; i<len; i++)
					{
						writer.Write(", ");
						rendererMap.FindAndRender(array.GetValue(i), writer);
					}
				}
				writer.Write("}");
			}
		}

		/// <summary>
		/// Render the enumerator argument into a string
		/// </summary>
		/// <param name="rendererMap">The map used to lookup renderers</param>
		/// <param name="enumerator">the enumerator to render</param>
		/// <param name="writer">The writer to render to</param>
		/// <remarks>
		/// <para>
		/// Rendered as an open brace, followed by a comma
		///	separated list of the elements (using the appropriate
		///	renderer), followed by a close brace. For example:
		///	<c>{a, b, c}</c>.
		///	</para>
		/// </remarks>
		private void RenderEnumerator(RendererMap rendererMap, IEnumerator enumerator, TextWriter writer)
		{
			writer.Write("{");

			if (enumerator != null && enumerator.MoveNext())
			{
				rendererMap.FindAndRender(enumerator.Current, writer);

				while (enumerator.MoveNext())
				{
					writer.Write(", ");
					rendererMap.FindAndRender(enumerator.Current, writer);
				}
			}

			writer.Write("}");
		}

		/// <summary>
		/// Render the DictionaryEntry argument into a string
		/// </summary>
		/// <param name="rendererMap">The map used to lookup renderers</param>
		/// <param name="entry">the DictionaryEntry to render</param>
		/// <param name="writer">The writer to render to</param>
		/// <remarks>
		/// <para>
		/// Render the key, an equals sign ('='), and the value (using the appropriate
		///	renderer). For example: <c>key=value</c>.
		///	</para>
		/// </remarks>
		private void RenderDictionaryEntry(RendererMap rendererMap, DictionaryEntry entry, TextWriter writer)
		{
			rendererMap.FindAndRender(entry.Key, writer);
			writer.Write("=");
			rendererMap.FindAndRender(entry.Value, writer);
		}	
	}
}
