#region License

/*
 * Copyright 2002-2004 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.Reflection.Emit;

using Spring.Proxy;
using Spring.Objects.Factory;
using Spring.Remoting.Support;
using Spring.Util;
using Spring.Core.TypeResolution;

#endregion

namespace Spring.Remoting
{
	/// <summary>
	/// Factory for creating MarshalByRefObject wrapper around target class.
	/// </summary>
	/// <author>Bruno Baia</author>
	public class RemoteObjectFactory : ConfigurableLifetime, IInitializingObject, IFactoryObject
	{
		#region Fields

		private object target;
        private Type baseType = typeof(BaseRemoteObject);
        private string[] interfaces;

		private ConstructorInfo proxyConstructor;

		#endregion

		#region Constructor(s) / Destructor

		/// <summary>
		/// Creates a new instance of the MarshalByRefObjectFactory.
		/// </summary>
		public RemoteObjectFactory()
		{
        }

		#endregion

		#region Properties

		/// <summary>
		/// Gets or sets the target object.
		/// </summary>
		public object Target
		{
			get { return target; }
			set { target = value; }
		}

        /// <summary>
        /// Gets or sets the <see cref="Spring.Remoting.Support.BaseRemoteObject"/> class or subclass 
        /// that the proxy must inherit from.
        /// </summary>
        public Type BaseType
        {
            get { return baseType; }
            set { baseType = value; }
        }

        /// <summary>
        /// Gets or sets the list of interfaces to wrap.
        /// </summary>
        /// <remarks>
        /// The default value of this property is all the interfaces 
        /// implemented or inherited by the target type.
        /// </remarks>
        /// <value>The interfaces to export.</value>
        public string[] Interfaces
        {
            get { return interfaces; }
            set { interfaces = value; }
        }

		#endregion

		#region IInitializingObject Members

		/// <summary>
		/// Initializes factory object.
		/// </summary>
		public void AfterPropertiesSet()
		{
			ValidateConfiguration();
            GenerateProxy();
		}

		#endregion

		#region IFactoryObject Members

		/// <summary>
		/// Returns type of the remotable target proxy.
		/// </summary>
		public Type ObjectType
		{
			get
			{
				return (proxyConstructor != null) ? proxyConstructor.DeclaringType : target.GetType();
			}
		}

		/// <summary>
		/// Creates new instance of the remotable target proxy.
		/// </summary>
		/// <returns>New instance of the remotable target proxy.</returns>
		public object GetObject()
		{
			if (proxyConstructor == null)
				GenerateProxy();

			return proxyConstructor.Invoke(new object[1]{ target });
		}

		/// <summary>
		/// Always returns false.
		/// </summary>
		public bool IsSingleton
		{
			get
			{
				return false;
			}
		}

		#endregion

		#region Private Methods

		private void ValidateConfiguration()
		{
			if (Target == null)
			{
				throw new ArgumentException("The Target property is required.");
			}
            if (!typeof(BaseRemoteObject).IsAssignableFrom(BaseType))
            {
                throw new ArgumentException("The type BaseRemoteObject cannot be assigned from BaseType.");
            }
		}

		private void GenerateProxy()
		{
            IProxyTypeBuilder builder = new RemoteObjectProxyTypeBuilder(this);
            builder.TargetType = target.GetType();
            builder.BaseType = baseType;
            if (interfaces != null && interfaces.Length > 0)
            {
                builder.Interfaces = TypeResolutionUtils.ResolveInterfaceArray(interfaces);
            }

			Type remotableObjectType = builder.BuildProxyType();

			proxyConstructor = remotableObjectType.GetConstructor(new Type[1] { builder.TargetType });
		}

		#endregion
	}
}