using System;
using System.Reflection;
using System.Collections;

namespace Orciid.Core.Util
{
	/// <summary>
	/// Initialization class
	/// </summary>
	/// <remarks>
	/// This class supports an initialization mechanism for different parts of a system.
	/// Static public methods can be tagged using the <see cref="InitializeAttribute"/> 
	/// attribute and will be called when <see cref="Initialization.Run()"/> is called on
	/// the assembly containing the methods.  If multiple such methods exist, the order
	/// in which they are called is not predetermined, so these methods should not rely
	/// on each other or on a certain call order.
	/// </remarks>
	public class Initialization
	{
		/// <summary>
		/// Run initialization
		/// </summary>
		/// <remarks>
		/// Runs the initialization process on the executing assembly in a production context.
		/// </remarks>
		public static void Run()
		{
			Run(Assembly.GetExecutingAssembly(), false);
		}

		/// <summary>
		/// Run initialization
		/// </summary>
		/// <remarks>
		/// Runs the initialization process on the executing assembly.
		/// </remarks>
		/// <param name="test">Determines if the initialization is run in a test or production
		/// context.  This parameter should only be <c>true</c> when called from a unit test.</param>
		public static void Run(bool test)
		{
			Run(Assembly.GetExecutingAssembly(), test);
		}

		/// <summary>
		/// Run initialization
		/// </summary>
		/// <remarks>
		/// Runs the initialization process on the given assembly in a production context.
		/// </remarks>
		/// <param name="assembly">The assembly to check for initialization methods</param>
		public static void Run(Assembly assembly)
		{
			Run(assembly, false);
		}

		/// <summary>
		/// Run initialization
		/// </summary>
		/// <remarks>
		/// Runs the initialization process on the given assembly.
		/// </remarks>
		/// <param name="assembly">The assembly to check for initialization methods</param>
		/// <param name="test">Determines if the initialization is run in a test or production
		/// context.  This parameter should only be <c>true</c> when called from a unit test.</param>
		public static void Run(Assembly assembly, bool test)
		{
			ArrayList exceptions = new ArrayList();
			foreach (Type type in assembly.GetTypes())
			{
				foreach (MethodInfo method in 
					type.GetMethods(BindingFlags.Static | BindingFlags.Public))
				{
					InitializeAttribute attribute = (InitializeAttribute)
						Attribute.GetCustomAttribute(method, typeof(InitializeAttribute));
					if (attribute != null && method.GetParameters().Length == 0 &&
						attribute.Test == test)
					{
						try
						{
							method.Invoke(null, null);
						}
						catch (Exception ex)
						{
							exceptions.Add(ex);
						}
					}
				}
			}
			if (exceptions.Count > 0)
			{
				throw new InitializationException("Exceptions occurred during initialization",
					(Exception[])exceptions.ToArray(typeof(Exception)));
			}
		}
	}

	/// <summary>
	/// Initialization attribute
	/// </summary>
	/// <remarks>
	/// Use to flag initialization methods
	/// </remarks>
	[AttributeUsage(AttributeTargets.Method)]
	public class InitializeAttribute : System.Attribute
	{
		private bool test = false;

		/// <summary>
		/// Test parameter
		/// </summary>
		/// <remarks>
		/// Determines if the initialization is run in a test or production
		/// context.  This parameter should only be <c>true</c> when called from a unit test.
		/// </remarks>
		/// <value>
		/// <c>true</c> if tagged method should only be run in a test context.
		/// </value>
		public bool Test
		{
			get
			{
				return test;
			}
			set
			{
				test = value;
			}
		}
	}

	/// <summary>
	/// Initialization exception
	/// </summary>
	/// <remarks>
	/// Raised if an exception occurs during execution of an initialization method.
	/// </remarks>
	public class InitializationException : System.Exception
	{
		private Exception[] exceptions;

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="message">Exception message</param>
		/// <param name="exceptions">Inner exceptions</param>
		public InitializationException(string message, Exception[] exceptions):
			base(message)
		{
			this.exceptions = exceptions;
		}

		/// <summary>
		/// Inner exceptions
		/// </summary>
		/// <remarks>
		/// Exceptions that caused this exception to be raised
		/// </remarks>
		/// <value>
		/// Array of inner exceptions
		/// </value>
		public Exception[] Exceptions
		{
			get
			{
				return exceptions;
			}
		}
	}
}
