#region License

/*
 * Copyright 2002-2008 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

#if (!NET_1_0 && !MONO)

#region Imports

using System;
using System.Collections;
using System.EnterpriseServices;
using System.Reflection;
using System.Reflection.Emit;
using Spring.Core.TypeResolution;
using Spring.Objects.Factory;
using Spring.Proxy;

#endregion

namespace Spring.EnterpriseServices
{
    /// <summary>
    /// Encapsulates information necessary to create ServicedComponent
    /// wrapper around target class.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Instances of this class should be used as elements in the Components
    /// list of the <see cref="EnterpriseServicesExporter"/> class, which will
    /// register them with COM+ Services. For a full description on how to export 
    /// and use services with COM+, see the <see cref="EnterpriseServicesExporter"/> reference.
    /// </para>
    /// </remarks>
    /// <seealso cref="EnterpriseServicesExporter"/>
    /// <author>Aleksandar Seovic</author>
    /// <author>Erich Eichinger</author>
    public class ServicedComponentExporter : IInitializingObject, IObjectNameAware
    {
        #region Fields

        private string _objectName;
        private string _targetName;
        private string[] _interfaces;
        private IList _typeAttributes = new ArrayList();
        private IDictionary _memberAttributes = new Hashtable();

        #endregion

        #region Constructor(s) / Destructor

        /// <summary>
        /// Creates a new instance of the
        /// <see cref="ServicedComponentExporter"/> class.
        /// </summary>
        public ServicedComponentExporter()
        {}

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets name of the target object that should be exposed as a serviced component.
        /// </summary>
        public string TargetName
        {
            get { return _targetName; }
            set { _targetName = value; }
        }

        /// <summary>
        /// Gets or sets the list of interfaces whose methods should be exported.
        /// </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; }
        }

        /// <summary>
        /// Gets or sets a list of custom attributes
        /// that should be applied to a proxy class.
        /// </summary>
        public IList TypeAttributes
        {
            get { return _typeAttributes; }
            set { _typeAttributes = value; }
        }

        /// <summary>
        /// Gets or sets a dictionary of custom attributes
        /// that should be applied to proxy members.
        /// </summary>
        /// <remarks>
        /// Map key is an expression that members can be matched against. Value is a list
        /// of attributes that should be applied to each member that matches expression.
        /// </remarks>
        public IDictionary MemberAttributes
        {
            get { return _memberAttributes; }
            set { _memberAttributes = value; }
        }

        #endregion

        #region IInitializingObject Members

        /// <summary>
        /// Validate configuration.
        /// </summary>
        public void AfterPropertiesSet()
        {
            ValidateConfiguration();
        }

        #endregion

        #region IObjectNameAware Members

        /// <summary>
        /// Set the name of the object in the object factory
        /// that created this object.
        /// </summary>
        public string ObjectName
        {
            set { _objectName = value; }
        }

        #endregion

        #region Methods

        private void ValidateConfiguration()
        {
            if (TargetName == null)
            {
                throw new ArgumentException("The TargetName property is required.");
            }
        }

        /// <summary>
        /// Creates ServicedComponent wrapper around target class.
        /// </summary>
        /// <param name="module">Dynamic module builder to use</param>
        /// <param name="baseType"></param>
        /// <param name="targetType">Type of the exported object.</param>
        /// <param name="springManagedLifecycle">whether to generate lookups in ContextRegistry for each service method call or use a 'new'ed target instance</param>
        /// <remarks>
        /// if <paramref name="springManagedLifecycle"/> is <c>true</c>, each ServicedComponent method call will look similar to
        /// <code>
        /// class MyServicedComponent {
        ///   void MethodX() {
        ///     ContextRegistry.GetContext().GetObject("TargetName").MethodX();
        ///   }
        /// }
        /// </code>
        /// <br/>
        /// if <paramref name="springManagedLifecycle"/> is <c>false</c>, the instance will be simply created at component activation using 'new':
        /// <code>
        /// class MyServicedComponent {
        ///   TargetType target = new TargetType();
        /// 
        ///   void MethodX() {
        ///     target.MethodX();
        ///   }
        /// }
        /// </code>
        /// <br/>
        /// The differences are of course that in the former case, the target lifecycle is entirely managed by Spring, thus avoiding
        /// issues with ServiceComponent activation/deactivation as well as removing the need for default constructors. 
        /// </remarks>
        public Type CreateWrapperType(ModuleBuilder module, Type baseType, Type targetType, bool springManagedLifecycle)
        {
            ValidateConfiguration();

            // create wrapper using appropriate proxy builder
            IProxyTypeBuilder proxyBuilder;
            if (springManagedLifecycle)
            {
                proxyBuilder = new SpringManagedServicedComponentProxyTypeBuilder(module, baseType, this);                
            }
            else
            {
                proxyBuilder = new SimpleServicedComponentProxyTypeBuilder(module, baseType);
            }

            proxyBuilder.Name = _objectName;
            proxyBuilder.TargetType = targetType;
            if (_interfaces != null && _interfaces.Length > 0)
            {
                proxyBuilder.Interfaces = TypeResolutionUtils.ResolveInterfaceArray(_interfaces);
            }
            proxyBuilder.TypeAttributes = TypeAttributes;
            proxyBuilder.MemberAttributes = MemberAttributes;

            Type componentType = proxyBuilder.BuildProxyType();

            // create and register client-side proxy factory for component

            return componentType;
        }

        #endregion

        #region ServicedComponentProxyTypeBuilder inner class definition

        private class ServicedComponentTargetProxyMethodBuilder : TargetProxyMethodBuilder
        {
            public ServicedComponentTargetProxyMethodBuilder(TypeBuilder typeBuilder, IProxyTypeGenerator proxyGenerator, bool explicitImplementation) : base(typeBuilder, proxyGenerator, explicitImplementation)
            {}

            /// <summary>
            /// Suppress output to avoid Spring.Core dependency
            /// </summary>
            protected override void CallAssertUnderstands(ILGenerator il, MethodInfo method, LocalBuilder targetRef, string targetName)
            {
//                base.CallAssertUnderstands(il, method, targetRef, targetName);
            }
        }

        private class ServicedComponentProxyTypeBuilder : CompositionProxyTypeBuilder
        {
            /// <summary>
            /// Implements default constructor for the proxy class.
            /// </summary>
            protected override void ImplementConstructors(TypeBuilder builder)
            {
                MethodAttributes attributes = MethodAttributes.Public |
                    MethodAttributes.HideBySig | MethodAttributes.SpecialName |
                    MethodAttributes.RTSpecialName;
                ConstructorBuilder cb = builder.DefineConstructor(attributes,
                  CallingConventions.Standard, Type.EmptyTypes);

                ILGenerator il = cb.GetILGenerator();
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Call, builder.BaseType.GetConstructor(Type.EmptyTypes));
                il.Emit(OpCodes.Ret);
            }

            protected override IProxyMethodBuilder CreateTargetProxyMethodBuilder(TypeBuilder typeBuilder)
            {
                return new ServicedComponentTargetProxyMethodBuilder(typeBuilder, this,
                                                                     this.ExplicitInterfaceImplementation);
            }
        }

        private sealed class SimpleServicedComponentProxyTypeBuilder : ServicedComponentProxyTypeBuilder
        {
            private readonly ModuleBuilder module;

            #region Constructor(s) / Destructor

            public SimpleServicedComponentProxyTypeBuilder(ModuleBuilder module, Type baseType)
            {
                this.module = module;
                BaseType = baseType;
            }

            #endregion

            #region Protected Methods

            protected override TypeBuilder CreateTypeBuilder(string name, Type baseType)
            {
                return module.DefineType(name, System.Reflection.TypeAttributes.Public, baseType);
            }

            #endregion
        }

        private sealed class SpringManagedServicedComponentProxyTypeBuilder : ServicedComponentProxyTypeBuilder
        {
            #region Fields

            private delegate object GetTargetDelegate(ServicedComponent component, string name);
            private static readonly MethodInfo ServicedComponentExporter_GetTarget =  new GetTargetDelegate(ServicedComponentHelper.GetObject).Method;

            #endregion

            #region Fields

            private readonly ServicedComponentExporter exporter;
            private readonly ModuleBuilder module;

            #endregion

            #region Constructor(s) / Destructor

            public SpringManagedServicedComponentProxyTypeBuilder(ModuleBuilder module, Type baseType, ServicedComponentExporter exporter)
            {
                this.module = module;
                this.exporter = exporter;
                BaseType = baseType;
            }

            #endregion

            #region Protected Methods

            protected override TypeBuilder CreateTypeBuilder(string name, Type baseType)
            {
                return module.DefineType(name, System.Reflection.TypeAttributes.Public, baseType);
            }

            protected override void DeclareTargetInstanceField(TypeBuilder builder)
            {
                //base.DeclareTargetInstanceField(builder);
            }

            #endregion

            #region IProxyTypeGenerator Members

            /// <summary>
            /// Generates the IL instructions that pushes 
            /// the target instance on which calls should be delegated to.
            /// </summary>
            /// <param name="il">The IL generator to use.</param>
            public override void PushTarget( ILGenerator il )
            {
                FieldInfo getObjectRef = BaseType.GetField("getObject", BindingFlags.NonPublic|BindingFlags.Static);
                il.Emit(OpCodes.Ldsfld, getObjectRef);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit( OpCodes.Ldstr, this.exporter.TargetName );
                il.Emit( OpCodes.Callvirt,  getObjectRef.FieldType.GetMethod("Invoke"));
            }
            #endregion

        }

        #endregion
    }
}

#endif // (!NET_1_0)
