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

using Spring.Core;
using Spring.Core.TypeResolution;
using Spring.Util;

#endregion

namespace Spring.Objects.Factory.Config
{
	/// <summary>
	/// <see cref="Spring.Objects.Factory.IFactoryObject"/> implementation that
	/// retrieves a <see lang="static"/> or non-static <b>public</b> property value.
	/// </summary>
	/// <remarks>
	/// <p>
	/// Typically used for retrieving <b>public</b> property values.
	/// </p>
	/// </remarks>
	/// <author>Rick Evans (.NET)</author>
    [Serializable]
    public class PropertyRetrievingFactoryObject : AbstractFactoryObject, IInitializingObject
	{
		#region Constructor (s) / Destructor

		/// <summary>
		/// Creates a new instance of the
		/// <see cref="Spring.Objects.Factory.Config.PropertyRetrievingFactoryObject"/> class.
		/// </summary>
		public PropertyRetrievingFactoryObject()
		{
			Arguments = new object[] {};
		}

		#endregion

		#region Properties

		/// <summary>
		/// The <see cref="System.Type.AssemblyQualifiedName"/> of the static property
		/// to be retrieved.
		/// </summary>
		public string StaticProperty
		{
			set
			{
				AssertUtils.ArgumentNotNull(value, "StaticProperty");
                TypeAssemblyHolder info = new TypeAssemblyHolder(value);
				string typeName = info.TypeName;
				int indexWherePropertyStarts = 0;
				do
				{
					try
					{
						indexWherePropertyStarts = typeName.LastIndexOf('.');

						#region Sanity Check

						if (indexWherePropertyStarts == -1
							|| indexWherePropertyStarts == typeName.Length)
						{
							throw new ArgumentException(
								"The value passed to the StaticProperty property must be a fully " +
									"qualified Type plus property name: " +
									"e.g. 'Example.MyExampleClass.MyProperty, MyAssembly'");
						}

						#endregion

						typeName = typeName.Substring(0, indexWherePropertyStarts);
						StringBuilder buffer = new StringBuilder(typeName);
						if (info.IsAssemblyQualified)
						{
                            buffer.Append(TypeAssemblyHolder.TypeAssemblySeparator);
							buffer.Append(info.AssemblyName);
						}
						TargetType = TypeResolutionUtils.ResolveType(buffer.ToString());
					}
					catch (TypeLoadException)
					{
					}
				} while (TargetType == null);
				TargetProperty = info.TypeName.Substring(indexWherePropertyStarts + 1);
			}
		}

		/// <summary>
		/// Arguments for the property invocation.
		/// </summary>
		/// <remarks>
		/// <p>
		/// If this property is not set, or the value passed to the setter invocation
		/// is a null or zero-length array, a property with no arguments is assumed.
		/// </p>
		/// </remarks>
		public object[] Arguments
		{
			get { return _arguments; }
			set
			{
				if (value != null)
				{
					this._arguments = value;
				}
			}
		}

		/// <summary>
		/// The name of the property the value of which is to be retrieved.
		/// </summary>
		/// <remarks>
		/// <p>
		/// Refers to either a <see lang="static"/> property or a non-static property,
		/// depending on a target object being set.
		/// </p>
		/// </remarks>
		public string TargetProperty
		{
			get { return _targetProperty; }
			set { _targetProperty = value; }
		}

		/// <summary>
		/// The object instance on which the property is defined.
		/// </summary>
		public object TargetObject
		{
			get { return _targetObject; }
			set
			{
				_targetObject = value;
				_targetObjectWrapper = new ObjectWrapper(_targetObject);
			}
		}

		/// <summary>
		/// The <see cref="System.Type"/> on which the property is defined.
		/// </summary>
		public Type TargetType
		{
			get { return _targetType; }
			set { _targetType = value; }
		}

		/// <summary>
		/// Return the type of object that this
		/// <see cref="Spring.Objects.Factory.IFactoryObject"/> creates, or
		/// <see lang="null"/> if not known in advance.
		/// </summary>
		public override Type ObjectType
		{
			get { return (Property == null) ? null : Property.PropertyType; }
		}

		private PropertyInfo Property
		{
			get { return _property; }
			set { _property = value; }
		}

		#endregion

		#region Methods

		/// <summary>
		/// Invoked by an <see cref="Spring.Objects.Factory.IObjectFactory"/>
		/// after it has set all object properties supplied
		/// (and satisfied <see cref="Spring.Objects.Factory.IObjectFactoryAware"/>
		/// and ApplicationContextAware).
		/// </summary>
		/// <exception cref="System.Exception">
		/// In the event of misconfiguration (such as failure to set an essential
		/// property) or if initialization fails.
		/// </exception>
		public override void AfterPropertiesSet()
		{
			if (TargetType == null
				&& TargetObject == null)
			{
				throw new ArgumentException("One of the TargetType or TargetObject properties must be set.");
			}
			if (TargetProperty == null)
			{
				throw new ArgumentException("The TargetProperty property is required.");
			}
			Type targetType = null;
			BindingFlags propertyFlags = BindingFlags.Public | BindingFlags.IgnoreCase;
			if (TargetObject == null)
			{
				// a static property...
				propertyFlags |= BindingFlags.Static;
				targetType = TargetType;
				if (TargetProperty.IndexOf(".") == -1)
				{
					Property = targetType.GetProperty(TargetProperty, propertyFlags);
				}
				else
				{
					// $£%#@! a nested static property... recurse to the end property
					string property = TargetProperty;
					int propertyIndex = property.IndexOf(".");
					string startProperty = property.Substring(0, propertyIndex);
					Property = targetType.GetProperty(startProperty, propertyFlags);
					TargetObject = Property.GetValue(null, new object[] {});
					TargetProperty = property.Substring(propertyIndex + 1);
					AfterPropertiesSet();
				}
			}
			else
			{
				// an instance property...
				propertyFlags |= BindingFlags.Instance;
				targetType = TargetObject.GetType();

				// using the object wrapper does nested property lookup
				Property = _targetObjectWrapper.GetPropertyInfo(TargetProperty);
			}
			if (Property == null)
			{
				throw new InvalidPropertyException(targetType, TargetProperty);
			}
			if (!Property.CanRead)
			{
				throw new NotWritablePropertyException(TargetProperty, targetType);
			}
			base.AfterPropertiesSet();
		}

		/// <summary>
		/// Template method that subclasses must override to construct the object
		/// returned by this factory.
		/// </summary>
		/// <exception cref="System.Exception">
		/// If an exception occured during object creation.
		/// </exception>
		/// <returns>The object returned by this factory.</returns>
		protected override object CreateInstance()
		{
			object instance = null;
			object target = null;
			if (TargetObject != null)
			{
				target = TargetObject;
			}
			try
			{
				if (Arguments.Length == 0 && target != null)
				{
					// using object wrapper supports nested property lookup...               
					instance = _targetObjectWrapper.GetPropertyValue(_targetProperty);
				}
				else
				{
					instance = Property.GetValue(target, Arguments);
				}
			}
			catch (Exception ex)
			{
				throw new FatalObjectException("Error reading property value.", ex);
			}
			return instance;
		}

		#endregion

		#region Fields

		private string _targetProperty;
		private ObjectWrapper _targetObjectWrapper;
		private object _targetObject;
		private Type _targetType;
		private PropertyInfo _property;
		private object[] _arguments;

		#endregion
	}
}