#region License

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

using System;
using System.Collections;
using System.Collections.Specialized;
using System.Reflection;
using System.Runtime.Serialization;
using System.Security.Permissions;
using AopAlliance.Aop;
using AopAlliance.Intercept;
using Spring.Util;

namespace Spring.Aop.Framework.DynamicProxy
{
    /// <summary>
    /// Represents the AOP configuration data built-in with the proxy.
    /// </summary>
    /// <author>Bruno Baia</author>
    [Serializable]
    public class AdvisedProxy : IAdvised //, ISerializable
    {
        #region Fields

        /// <summary>
        /// Should we use dynamic reflection for method invocation ?
        /// </summary>
        public static bool UseDynamicReflection;

        /// <summary>
        /// Optimization fields
        /// </summary>
        private static IList EmptyList = ArrayList.ReadOnly(new ArrayList());

        /// <summary>
        /// IAdvised delegate
        /// </summary>
        public IAdvised m_advised;

        /// <summary>
        /// Array of introduction delegates
        /// </summary>
        public IAdvice[] m_introductions;

        /// <summary>
        /// Target source wrapper
        /// </summary>
        public ITargetSourceWrapper m_targetSourceWrapper;

        /// <summary>
        /// Type of target object.
        /// </summary>
        public Type m_targetType;

        #endregion

        #region Constructor (s) / Destructor

        /// <summary>
        /// Creates a new instance of the <see cref="AdvisedProxy"/> class.
        /// </summary>
        static AdvisedProxy()
        {
            string appSettingsKey = typeof(AdvisedProxy).FullName + ".UseDynamicReflection";
            NameValueCollection appSettings =
                ConfigurationUtils.GetSection("appSettings") as NameValueCollection;

            if (appSettings != null && StringUtils.HasLength(appSettings[appSettingsKey]))
            {
                UseDynamicReflection = bool.Parse(appSettings[appSettingsKey]);
            }
            else
            {
                UseDynamicReflection = true;
            }
        }

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

        /// <summary>
        /// Creates a new instance of the <see cref="AdvisedProxy"/> class.
        /// </summary>
        /// <param name="advised">The proxy configuration.</param>
        protected AdvisedProxy(IAdvised advised)
        {
            m_advised = advised;
        }

        /// <summary>
        /// Creates a new instance of the
        /// <see cref="Spring.Aop.Framework.DynamicProxy.AdvisedProxy"/> class.
        /// </summary>
        /// <param name="advised">The proxy configuration.</param>
        /// <param name="proxy">The proxy.</param>
        public AdvisedProxy(IAdvised advised, IAopProxy proxy)
        {
            Initialize(advised, proxy);
        }

        /// <summary>
        /// Deserialization constructor.
        /// </summary>
        /// <param name="info">Serialization data.</param>
        /// <param name="context">Serialization context.</param>
        protected AdvisedProxy(SerializationInfo info, StreamingContext context)
        {
            m_advised = (IAdvised)info.GetValue("advised", typeof(IAdvised));
            m_introductions = (IAdvice[])info.GetValue("introductions", typeof(IAdvice[]));
            m_targetSourceWrapper = (ITargetSourceWrapper)info.GetValue("tsWrapper", typeof(ITargetSourceWrapper));
            m_targetType = (Type)info.GetValue("targetType", typeof(Type));
        }

        /// <summary>
        /// Serializes this instance.
        /// </summary>
        /// <param name="info">Serialization data.</param>
        /// <param name="context">Serialization context.</param>
        [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
        protected virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("advised", m_advised);
            info.AddValue("introductions", m_introductions);
            info.AddValue("tsWrapper", m_targetSourceWrapper);
            info.AddValue("targetType", m_targetType);
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Initialization method.
        /// </summary>
        /// <param name="advised">The proxy configuration.</param>
        /// <param name="proxy">
        /// The current <see cref="Spring.Aop.Framework.IAopProxy"/> implementation.
        /// </param>
        protected void Initialize(IAdvised advised, IAopProxy proxy)
        {
            this.m_advised = advised;
            this.m_targetType = advised.TargetSource.TargetType;

            // initialize target
            if (advised.TargetSource.IsStatic)
            {
                this.m_targetSourceWrapper = new StaticTargetSourceWrapper(advised.TargetSource);
            }
            else
            {
                this.m_targetSourceWrapper = new DynamicTargetSourceWrapper(advised.TargetSource);
            }

            // initialize introduction advice
            this.m_introductions = new IAdvice[advised.Introductions.Length];
            for (int i = 0; i < advised.Introductions.Length; i++)
            {
                this.m_introductions[i] = advised.Introductions[i].Advice;

                // set target proxy on introduction instance if it implements ITargetAware
                if (this.m_introductions[i] is ITargetAware)
                {
                    ((ITargetAware)this.m_introductions[i]).TargetProxy = proxy;
                }
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Invokes intercepted methods using reflection
        /// </summary>
        /// <param name="proxy">proxy object</param>
        /// <param name="target">target object to invoke method on</param>
        /// <param name="targetType">target type</param>
        /// <param name="targetMethod">taget method to invoke</param>
        /// <param name="proxyMethod">The method to invoke on proxy.</param>
        /// <param name="args">method arguments</param>
        /// <param name="interceptors">interceptor chain</param>
        /// <returns>value returned by invocation chain</returns>
        public object Invoke(object proxy, object target, Type targetType,
            MethodInfo targetMethod, MethodInfo proxyMethod, object[] args, IList interceptors)
        {
            IMethodInvocation invocation = null;
            if (UseDynamicReflection)
            {
                invocation = new DynamicMethodInvocation(
                    proxy, target, targetMethod, proxyMethod, args, targetType, interceptors);
            }
            else
            {
                invocation = new ReflectiveMethodInvocation(
                    proxy, target, targetMethod, proxyMethod, args, targetType, interceptors);
            }
            return invocation.Proceed();
        }

        /// <summary>
        /// Returns a list of method interceptors
        /// </summary>
        /// <param name="targetType">target type</param>
        /// <param name="method">target method</param>
        /// <returns>list of inteceptors for the specified method</returns>
        public IList GetInterceptors(Type targetType, MethodInfo method)
        {
            if (m_advised.Advisors.Length == 0)
            {
                return EmptyList;
            }
            else
            {
                return m_advised.AdvisorChainFactory.GetInterceptors(m_advised, this, method, targetType);
            }
        }

        #endregion

        #region IAdvised Members

        bool IAdvised.ExposeProxy
        {
            get { return m_advised.ExposeProxy; }
        }

        IAdvisorChainFactory IAdvised.AdvisorChainFactory
        {
            get { return m_advised.AdvisorChainFactory; }
        }

        bool IAdvised.ProxyTargetType
        {
            get { return m_advised.ProxyTargetType; }
        }

        bool IAdvised.ProxyTargetAttributes
        {
            get { return m_advised.ProxyTargetAttributes; }
        }

        IAdvisor[] IAdvised.Advisors
        {
            get { return m_advised.Advisors; }
        }

        IIntroductionAdvisor[] IAdvised.Introductions
        {
            get { return m_advised.Introductions; }
        }

        Type[] IAdvised.Interfaces
        {
            get { return m_advised.Interfaces; }
        }

        IDictionary IAdvised.InterfaceMap
        {
            get { return m_advised.InterfaceMap; }
        }

        bool IAdvised.IsFrozen
        {
            get { return m_advised.IsFrozen; }
        }

        ITargetSource IAdvised.TargetSource
        {
            get { return m_advised.TargetSource; }
        }

        bool IAdvised.IsSerializable
        {
            get { return m_advised.IsSerializable; }
        }

        /// <summary>
        /// Adds the supplied <paramref name="advice"/> to the end (or tail)
        /// of the advice (interceptor) chain.
        /// </summary>
        /// <param name="advice">
        /// The <see cref="AopAlliance.Aop.IAdvice"/> to be added.
        /// </param>
        /// <seealso cref="Spring.Aop.Support.DefaultPointcutAdvisor"/>
        /// <seealso cref="Spring.Aop.Framework.IAdvised.AddAdvice(int,IAdvice)"/>
        public void AddAdvice(IAdvice advice)
        {
            this.m_advised.AddAdvice(advice);
        }

        /// <summary>
        /// Adds the supplied <paramref name="advice"/> to the supplied
        /// <paramref name="position"/> in the advice (interceptor) chain.
        /// </summary>
        /// <param name="position">
        /// The zero (0) indexed position (from the head) at which the
        /// supplied <paramref name="advice"/> is to be inserted into the
        /// advice (interceptor) chain.
        /// </param>
        /// <param name="advice">
        /// The <see cref="AopAlliance.Aop.IAdvice"/> to be added.
        /// </param>
        /// <seealso cref="Spring.Aop.Support.DefaultPointcutAdvisor"/>
        /// <seealso cref="Spring.Aop.Framework.IAdvised.AddAdvice(IAdvice)"/>
        public void AddAdvice(int position, IAdvice advice)
        {
            this.m_advised.AddAdvice(position, advice);
        }

        bool IAdvised.IsInterfaceProxied(Type intf)
        {
            return m_advised.IsInterfaceProxied(intf);
        }

        void IAdvised.AddAdvisors(IAdvisors advisors)
        {
            m_advised.AddAdvisors(advisors);
        }

        void IAdvised.AddAdvisor(IAdvisor advisor)
        {
            m_advised.AddAdvisor(advisor);
        }

        void IAdvised.AddAdvisor(int pos, IAdvisor advisor)
        {
            m_advised.AddAdvisor(pos, advisor);
        }

        void IAdvised.AddIntroduction(IIntroductionAdvisor advisor)
        {
            m_advised.AddIntroduction(advisor);
        }

        void IAdvised.AddIntroduction(int pos, IIntroductionAdvisor advisor)
        {
            m_advised.AddIntroduction(pos, advisor);
        }

        int IAdvised.IndexOf(IAdvisor advisor)
        {
            return m_advised.IndexOf(advisor);
        }

        int IAdvised.IndexOf(IIntroductionAdvisor advisor)
        {
            return m_advised.IndexOf(advisor);
        }

        bool IAdvised.RemoveAdvisor(IAdvisor advisor)
        {
            return m_advised.RemoveAdvisor(advisor);
        }

        void IAdvised.RemoveAdvisor(int index)
        {
            m_advised.RemoveAdvisor(index);
        }

        bool IAdvised.RemoveAdvice(IAdvice advice)
        {
            return m_advised.RemoveAdvice(advice);
        }

        bool IAdvised.RemoveIntroduction(IIntroductionAdvisor advisor)
        {
            return m_advised.RemoveIntroduction(advisor);
        }

        void IAdvised.RemoveIntroduction(int index)
        {
            m_advised.RemoveIntroduction(index);
        }

        void IAdvised.ReplaceIntroduction(int index, IIntroductionAdvisor advisor)
        {
            m_advised.ReplaceIntroduction(index, advisor);
        }

        bool IAdvised.ReplaceAdvisor(IAdvisor a, IAdvisor b)
        {
            return m_advised.ReplaceAdvisor(a, b);
        }

        string IAdvised.ToProxyConfigString()
        {
            return m_advised.ToProxyConfigString();
        }

        #endregion

        #region ITargetTypeAware implementation

        /// <summary>
        /// Gets the target type behind the implementing object.
        /// Ttypically a proxy configuration or an actual proxy.
        /// </summary>
        /// <value>The type of the target or null if not known.</value>
        public Type TargetType
        {
            get { return m_targetType; }
        }

        #endregion
    }
}