#region License

/*
 * Copyright © 2002-2005 the original author or authors.
 *
 * 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

#region Imports

using System;
using System.Globalization;
using System.Reflection;
using Spring.Util;

#endregion

namespace Spring.Objects.Support
{
	/// <summary>
	/// Base class for all <see cref="Spring.Objects.IEventHandlerValue"/>
	/// implemenations that actually perform event wiring.
	/// </summary>
	/// <author>Rick Evans</author>
	public abstract class AbstractWiringEventHandlerValue : AbstractEventHandlerValue
	{
		#region Constructor (s) / Destructor

		/// <summary>
		/// Creates a new instance of the
		/// <see cref="Spring.Objects.Support.AbstractWiringEventHandlerValue"/> class.
		/// </summary>
		/// <remarks>
		/// <p>
		/// This is an <see langword="abstract"/> class, and as such exposes no public constructors.
		/// </p>
		/// </remarks>
		protected AbstractWiringEventHandlerValue()
		{
		}

		/// <summary>
		/// Creates a new instance of the
		/// <see cref="Spring.Objects.Support.AbstractWiringEventHandlerValue"/> class.
		/// </summary>
		/// <param name="source">
		/// The object (possibly unresolved) that is exposing the event.
		/// </param>
		/// <param name="methodName">
		/// The name of the method on the handler that is going to handle the event.
		/// </param>
		/// <remarks>
		/// <p>
		/// This is an <see langword="abstract"/> class, and as such exposes no public constructors.
		/// </p>
		/// </remarks>
		protected AbstractWiringEventHandlerValue(object source, string methodName)
			: base(source, methodName)
		{
		}

		#endregion

		#region Methods

		/// <summary>
		/// Wires up the specified handler to the named event on the
		/// supplied event source.
		/// </summary>
		/// <param name="source">
		/// The object (an object instance, a <see cref="System.Type"/>, etc)
		/// exposing the named event.
		/// </param>
		/// <param name="handler">
		/// The handler for the event (an object instance, a
		/// <see cref="System.Type"/>, etc).
		/// </param>
		public override void Wire(object source, object handler)
		{
			Type sourceType = ReflectionUtils.TypeOfOrType(source);
			EventInfo eventInfo = sourceType.GetEvent(EventName);
			Delegate callback = GetHandler(handler, eventInfo);
			eventInfo.AddEventHandler(source, callback);
		}

		/// <summary>
		/// Gets the event handler.
		/// </summary>
		/// <param name="instance">
		/// The instance that is registering for the event notification.
		/// </param>
		/// <param name="info">
		/// Event metadata about the event.
		/// </param>
		/// <returns>
		/// The event handler.
		/// </returns>
		protected abstract Delegate GetHandler(object instance, EventInfo info);

		/// <summary>
		/// Resolves the method metadata that describes the method that is to be used
		/// as the argument to a delegate constructor.
		/// </summary>
		/// <param name="handlerType">
		/// The <see cref="System.Type"/> exposing the method.
		/// </param>
		/// <param name="delegateType">
		/// The <see cref="System.Type"/> of the delegate (e.g. System.EventHandler).
		/// </param>
		/// <param name="flags">
		/// The custom binding flags to use when searching for the method.
		/// </param>
		/// <returns>The method metadata.</returns>
		/// <exception cref="Spring.Objects.FatalObjectException">
		/// If the method could not be found.
		/// </exception>
		protected virtual MethodInfo ResolveHandlerMethod(
			Type handlerType, Type delegateType, BindingFlags flags)
		{
			MethodInfo method = handlerType.GetMethod(
				MethodName,
				flags,
				Type.DefaultBinder,
				CallingConventions.Standard,
				// cache this? for EventHandler types, we're always gonna get the same parameters
				new DelegateInfo(delegateType).GetParameterTypes(),
				null);

			#region Sanity Check

			if (method == null)
			{
				throw new FatalObjectException(string.Format(
					CultureInfo.InvariantCulture,
					"The '{0}' method could not be found on this object [{1}]",
					MethodName, handlerType));
			}

			#endregion

			return method;
		}

		#endregion
	}
}