using System;
using System.Collections.Generic;

using Spring.Aop.Support;
using Spring.Aop.Target;

namespace Spring.Aop.Framework.DynamicProxy
{
    /// <summary>
    /// A reusable base implementation of <see cref="IAopProxyFactory"/>, providing
    /// some useful default implementations
    /// </summary>
    /// <author>Erich Eichinger</author>
    [Serializable]
    public abstract class AbstractAopProxyFactory : IAopProxyFactory
    {
        /// <summary>
        /// Creates an <see cref="Spring.Aop.Framework.IAopProxy"/> for the
        /// supplied <paramref name="advisedSupport"/> configuration.
        /// </summary>
        /// <param name="advisedSupport">The AOP configuration.</param>
        /// <returns>An <see cref="Spring.Aop.Framework.IAopProxy"/>.</returns>
        /// <exception cref="AopConfigException">
        /// If the supplied <paramref name="advisedSupport"/> configuration is
        /// invalid.
        /// </exception>
        /// <seealso cref="Spring.Aop.Framework.IAopProxyFactory.CreateAopProxy"/>
        public IAopProxy CreateAopProxy(AdvisedSupport advisedSupport)
        {
            if (advisedSupport == null)
            {
                throw new AopConfigException("Cannot create IAopProxy with null ProxyConfig");
            }
            if (advisedSupport.Advisors.Count == 0 && advisedSupport.TargetSource == EmptyTargetSource.Empty)
            {
                throw new AopConfigException("Cannot create IAopProxy with no advisors and no target source");
            }

            EliminateDuplicateAdvisors(advisedSupport);

            return DoCreateAopProxyInstance(advisedSupport);
        }

        /// <summary>
        /// Actually creates the proxy instance based on the supplied <see cref="AdvisedSupport"/>.
        /// </summary>
        /// <returns>the proxy instance described by <paramref name="advisedSupport"/>. Must not be <c>null</c></returns>
        protected abstract IAopProxy DoCreateAopProxyInstance(AdvisedSupport advisedSupport);

        /// <summary>
        /// If possible, checks for advisor duplicates on the supplied <paramref name="advisedSupport"/> and 
        /// eliminates them.
        /// </summary>
        protected virtual void EliminateDuplicateAdvisors(AdvisedSupport advisedSupport)
        {
            if (advisedSupport.TargetSource is SingletonTargetSource
                && IsAopProxyType(advisedSupport.TargetSource))
            {
                IAdvised innerProxy = (IAdvised)advisedSupport.TargetSource.GetTarget();
                // eliminate duplicate advisors
                List<IAdvisor> thisAdvisors = new List<IAdvisor>(advisedSupport.Advisors);
                foreach (IAdvisor innerAdvisor in innerProxy.Advisors)
                {
                    foreach (IAdvisor thisAdvisor in thisAdvisors)
                    {
                        if (ReferenceEquals(thisAdvisor, innerAdvisor)
                            || (thisAdvisor.GetType() == typeof(DefaultPointcutAdvisor)
                                && thisAdvisor.Equals(innerAdvisor)
                               )
                            )
                        {
                            advisedSupport.RemoveAdvisor(thisAdvisor);
                        }
                    }
                }

                // elimination of duplicate introductions is not necessary 
                // since they do not propagate to nested proxy anyway
            }
        }

        /// <summary>
        /// Checks, if the given <paramref name="targetSource"/> holds a proxy generated by this factory.
        /// </summary>
        /// <returns></returns>
        protected virtual bool IsAopProxyType(ITargetSource targetSource)
        {
            return AopUtils.IsAopProxyType(targetSource.TargetType);
        }
    }
}