#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.Runtime.Serialization;
using AopAlliance.Aop;
using AopAlliance.Intercept;
using DotNetMock.Dynamic;
using NUnit.Framework;
using Rhino.Mocks;
using Spring.Aop.Interceptor;
using Spring.Aop.Support;
using Spring.Objects;
using Spring.Proxy;
using Spring.Util;

#endregion

namespace Spring.Aop.Framework
{
    /// <summary>
    /// Unit tests for the ProxyFactory class.
    /// </summary>
    /// <author>Rod Johnson</author>
    /// <author>Choy Rim (.NET)</author>
    /// <author>Rick Evans (.NET)</author>
    [TestFixture]
    public sealed class ProxyFactoryTests
    {
        public interface IDoubleClickable
        {
            event EventHandler DoubleClick;

            void FireDoubleClickEvent();
        }

        public interface IDoubleClickableIntroduction :
            IDoubleClickable, IAdvice
        {
        }

        private class DoubleClickableIntroduction :
            IDoubleClickableIntroduction
        {
            public event EventHandler DoubleClick;

            public void FireDoubleClickEvent()
            {
                if (DoubleClick != null)
                {
                    DoubleClick(this, EventArgs.Empty);
                }
            }
        }

        [Test]
        public void AddAndRemoveEventHandlerThroughIntroduction()
        {
            TestObject target = new TestObject();
            DoubleClickableIntroduction dci = new DoubleClickableIntroduction();
            DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(dci);
            CountingBeforeAdvice countingBeforeAdvice = new CountingBeforeAdvice();
            target.Name = "SOME-NAME";
            ProxyFactory pf = new ProxyFactory(target);
            pf.AddIntroduction(advisor);
            pf.AddAdvisor(new DefaultPointcutAdvisor(countingBeforeAdvice));
            object proxy = pf.GetProxy();
            ITestObject to = proxy as ITestObject;
            Assert.IsNotNull(to);
            Assert.AreEqual("SOME-NAME", to.Name);
            IDoubleClickable doubleClickable = proxy as IDoubleClickable;
            // add event handler through introduction
            doubleClickable.DoubleClick += new EventHandler(OnClick);
            OnClickWasCalled = false;
            doubleClickable.FireDoubleClickEvent();
            Assert.IsTrue(OnClickWasCalled);
            Assert.AreEqual(3, countingBeforeAdvice.GetCalls());
            // remove event handler through introduction
            doubleClickable.DoubleClick -= new EventHandler(OnClick);
            OnClickWasCalled = false;
            doubleClickable.FireDoubleClickEvent();
            Assert.IsFalse(OnClickWasCalled);
            Assert.AreEqual(5, countingBeforeAdvice.GetCalls());
        }

        private bool OnClickWasCalled = false;

        private void OnClick(object sender, EventArgs e)
        {
            OnClickWasCalled = true;
        }

        [Test]
        public void CacheTest()
        {

            for (int i = 0; i < 2; i++)
            {
                TestObject target = new TestObject();
                NopInterceptor nopInterceptor = new NopInterceptor();
                CountingBeforeAdvice countingBeforeAdvice = new CountingBeforeAdvice();
                ProxyFactory pf = new ProxyFactory();
                pf.Target = target;
                pf.AddAdvice(nopInterceptor);
                pf.AddAdvisor(new DefaultPointcutAdvisor(countingBeforeAdvice));
                object proxy = pf.GetProxy();
            }

            // fails when running in resharper/testdriven.net 
            // DynamicProxyManager.SaveAssembly();

        }

        [Test]
        public void AddAndRemoveEventHandlerThroughInterceptor()
        {
            TestObject target = new TestObject();
            NopInterceptor nopInterceptor = new NopInterceptor();
            CountingBeforeAdvice countingBeforeAdvice = new CountingBeforeAdvice();
            target.Name = "SOME-NAME";
            ProxyFactory pf = new ProxyFactory(target);
            pf.AddAdvice(nopInterceptor);
            pf.AddAdvisor(new DefaultPointcutAdvisor(countingBeforeAdvice));
            object proxy = pf.GetProxy();
            ITestObject to = proxy as ITestObject;
            // add event handler through proxy
            to.Click += new EventHandler(OnClick);
            OnClickWasCalled = false;
            to.OnClick();
            Assert.IsTrue(OnClickWasCalled);
            Assert.AreEqual(2, countingBeforeAdvice.GetCalls());
            // remove event handler through proxy
            to.Click -= new EventHandler(OnClick);
            OnClickWasCalled = false;
            to.OnClick();
            Assert.IsFalse(OnClickWasCalled);
            Assert.AreEqual(4, countingBeforeAdvice.GetCalls());
        }

        private class TestObject2 : TestObject
        {
            public bool EqualsOverrideWasCalled = false;

            public override bool Equals(object obj)
            {
                EqualsOverrideWasCalled = true;
                return true;
            }

            public override int GetHashCode()
            {
                return base.GetHashCode();
            }
        }

        [Test]
        public void CallsEqualsOverride()
        {
            TestObject2 target = new TestObject2();
            target.Name = "SOME-NAME";
            ProxyFactory pf = new ProxyFactory(target);
            object proxy = pf.GetProxy();
            ITestObject to = proxy as ITestObject;
            Assert.IsNotNull(to);
            Assert.AreEqual("SOME-NAME", to.Name);

            target.EqualsOverrideWasCalled = false;
            Assert.IsTrue(to.Equals(proxy));
            Assert.IsTrue(target.EqualsOverrideWasCalled);

            target.EqualsOverrideWasCalled = false;
            Assert.IsTrue(proxy.Equals(to));
            Assert.IsTrue(target.EqualsOverrideWasCalled);

            target.EqualsOverrideWasCalled = false;
            Assert.IsTrue(target.Equals(to));
            Assert.IsTrue(target.EqualsOverrideWasCalled);

            target.EqualsOverrideWasCalled = false;
            Assert.IsTrue(to.Equals(target));
            Assert.IsTrue(target.EqualsOverrideWasCalled);
        }

        [Test]
        public void CreateProxyFactoryWithoutTargetThenSetTarget()
        {
            TestObject target = new TestObject();
            target.Name = "Adam";
            NopInterceptor nopInterceptor = new NopInterceptor();
            CountingBeforeAdvice countingBeforeAdvice = new CountingBeforeAdvice();
            ProxyFactory pf = new ProxyFactory();
            pf.Target = target;
            pf.AddAdvice(nopInterceptor);
            pf.AddAdvisor(new DefaultPointcutAdvisor(countingBeforeAdvice));
            object proxy = pf.GetProxy();
            ITestObject to = (ITestObject)proxy;
            Assert.AreEqual("Adam", to.Name);
            Assert.AreEqual(1, countingBeforeAdvice.GetCalls());
        }

        [Test]
        [ExpectedException(typeof(AopConfigException))]
        public void InstantiateWithNullTarget()
        {
            new ProxyFactory((object)null);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void AddNullInterface()
        {
            new ProxyFactory().AddInterface(null);
        }

        [Test]
        [ExpectedException(typeof(AopConfigException))]
        public void AddInterfaceWhenConfigurationIsFrozen()
        {
            ProxyFactory factory = new ProxyFactory();
            factory.IsFrozen = true;
            factory.AddInterface(typeof(ITestObject));
        }

        [Test]
        public void IndexOfMethods()
        {
            TestObject target = new TestObject();
            ProxyFactory pf = new ProxyFactory(target);
            NopInterceptor nop = new NopInterceptor();
            IAdvisor advisor = new DefaultPointcutAdvisor(new CountingBeforeAdvice());
            IAdvised advised = (IAdvised)pf.GetProxy();
            // Can use advised and ProxyFactory interchangeably
            advised.AddAdvice(nop);
            pf.AddAdvisor(advisor);
            Assert.AreEqual(-1, pf.IndexOf((IInterceptor)null));
            Assert.AreEqual(-1, pf.IndexOf(new NopInterceptor()));
            Assert.AreEqual(0, pf.IndexOf(nop));
            Assert.AreEqual(-1, advised.IndexOf((IAdvisor)null));
            Assert.AreEqual(1, pf.IndexOf(advisor));
            Assert.AreEqual(-1, advised.IndexOf(new DefaultPointcutAdvisor(null)));
        }

        [Test]
        public void RemoveAdvisorByReference()
        {
            TestObject target = new TestObject();
            ProxyFactory pf = new ProxyFactory(target);
            NopInterceptor nop = new NopInterceptor();
            CountingBeforeAdvice cba = new CountingBeforeAdvice();
            IAdvisor advisor = new DefaultPointcutAdvisor(cba);
            pf.AddAdvice(nop);
            pf.AddAdvisor(advisor);
            ITestObject proxied = (ITestObject)pf.GetProxy();
            proxied.Age = 5;
            Assert.AreEqual(1, cba.GetCalls());
            Assert.AreEqual(1, nop.Count);
            Assert.IsFalse(pf.RemoveAdvisor(null));
            Assert.IsTrue(pf.RemoveAdvisor(advisor));
            Assert.AreEqual(5, proxied.Age);
            Assert.AreEqual(1, cba.GetCalls());
            Assert.AreEqual(2, nop.Count);
            Assert.IsFalse(pf.RemoveAdvisor(new DefaultPointcutAdvisor(null)));
        }

        [Test]
        public void RemoveAdvisorByIndex()
        {
            TestObject target = new TestObject();
            ProxyFactory pf = new ProxyFactory(target);
            NopInterceptor nop = new NopInterceptor();
            CountingBeforeAdvice cba = new CountingBeforeAdvice();
            IAdvisor advisor = new DefaultPointcutAdvisor(cba);
            pf.AddAdvice(nop);
            pf.AddAdvisor(advisor);
            NopInterceptor nop2 = new NopInterceptor(2); // make instance unique (see SPRNET-847)
            pf.AddAdvice(nop2);
            ITestObject proxied = (ITestObject)pf.GetProxy();
            proxied.Age = 5;
            Assert.AreEqual(1, cba.GetCalls());
            Assert.AreEqual(1, nop.Count);
            Assert.AreEqual(1, nop2.Count);
            // Removes counting before advisor
            pf.RemoveAdvisor(1);
            Assert.AreEqual(5, proxied.Age);
            Assert.AreEqual(1, cba.GetCalls());
            Assert.AreEqual(2, nop.Count);
            Assert.AreEqual(2, nop2.Count);
            // Removes Nop1
            pf.RemoveAdvisor(0);
            Assert.AreEqual(5, proxied.Age);
            Assert.AreEqual(1, cba.GetCalls());
            Assert.AreEqual(2, nop.Count);
            Assert.AreEqual(3, nop2.Count);

            // Check out of bounds
            try
            {
                pf.RemoveAdvisor(-1);
                Assert.Fail("Supposed to throw exception");
            }
            catch (AopConfigException)
            {
                // Ok
            }

            try
            {
                pf.RemoveAdvisor(2);
                Assert.Fail("Supposed to throw exception");
            }
            catch (AopConfigException)
            {
                // Ok
            }

            Assert.AreEqual(5, proxied.Age);
            Assert.AreEqual(4, nop2.Count);
        }

        [Test]
        public void TryRemoveNonProxiedInterface()
        {
            ProxyFactory factory = new ProxyFactory(new TestObject());
            Assert.IsFalse(factory.RemoveInterface(typeof(IServiceProvider)));
        }

        [Test]
        public void RemoveProxiedInterface()
        {
            ProxyFactory factory = new ProxyFactory(new TestObject());
            Assert.IsTrue(factory.RemoveInterface(typeof(ITestObject)));
        }

        [Test]
        public void ReplaceAdvisor()
        {
            TestObject target = new TestObject();
            ProxyFactory pf = new ProxyFactory(target);
            NopInterceptor nop = new NopInterceptor();
            CountingBeforeAdvice cba1 = new CountingBeforeAdvice();
            CountingBeforeAdvice cba2 = new CountingBeforeAdvice();
            IAdvisor advisor1 = new DefaultPointcutAdvisor(cba1);
            IAdvisor advisor2 = new DefaultPointcutAdvisor(cba2);
            pf.AddAdvisor(advisor1);
            pf.AddAdvice(nop);
            ITestObject proxied = (ITestObject)pf.GetProxy();
            // Use the type cast feature
            // Replace etc methods on advised should be same as on ProxyFactory
            IAdvised advised = (IAdvised)proxied;
            proxied.Age = 5;
            Assert.AreEqual(1, cba1.GetCalls());
            Assert.AreEqual(0, cba2.GetCalls());
            Assert.AreEqual(1, nop.Count);
            Assert.IsFalse(advised.ReplaceAdvisor(null, null));
            Assert.IsFalse(advised.ReplaceAdvisor(null, advisor2));
            Assert.IsFalse(advised.ReplaceAdvisor(advisor1, null));
            Assert.IsTrue(advised.ReplaceAdvisor(advisor1, advisor2));
            Assert.AreEqual(advisor2, pf.Advisors[0]);
            Assert.AreEqual(5, proxied.Age);
            Assert.AreEqual(1, cba1.GetCalls());
            Assert.AreEqual(2, nop.Count);
            Assert.AreEqual(1, cba2.GetCalls());
            Assert.IsFalse(pf.ReplaceAdvisor(new DefaultPointcutAdvisor(null), advisor1));
        }

        [Test]
        public void IgnoresAdvisorDuplicates()
        {
            CountingBeforeAdvice cba1 = new CountingBeforeAdvice();
            IAdvisor advisor1 = new DefaultPointcutAdvisor(cba1);

            AdvisedSupport advSup = new AdvisedSupport();
            advSup.AddAdvisor(advisor1);
            advSup.AddAdvisor(advisor1);

            Assert.AreEqual(1, advSup.Advisors.Length);
        }

        private class AnonymousClassTimeStamped : ITimeStamped
        {
            public AnonymousClassTimeStamped(ProxyFactoryTests enclosingInstance)
            {
                InitBlock(enclosingInstance);
            }

            private void InitBlock(ProxyFactoryTests enclosingInstance)
            {
                this.enclosingInstance = enclosingInstance;
            }

            private ProxyFactoryTests enclosingInstance;

            public ProxyFactoryTests Enclosing_Instance
            {
                get { return enclosingInstance; }

            }

            public DateTime TimeStamp
            {
                get { throw new NotSupportedException("TimeStamp"); }
            }
        }

        [Test]
        public void AddRepeatedInterface()
        {
            ITimeStamped tst = new AnonymousClassTimeStamped(this);
            ProxyFactory pf = new ProxyFactory(tst);
            // We've already implicitly added this interface.
            // This call should be ignored without error
            pf.AddInterface(typeof(ITimeStamped));
            // All cool
            ITimeStamped ts = (ITimeStamped)pf.GetProxy();
        }

        internal class TestObjectSubclass : TestObject, IComparable
        {
            public override int CompareTo(Object arg0)
            {
                throw new NotSupportedException("compareTo");
            }
        }

        [Test]
        public void GetsAllInterfaces()
        {
            // Extend to get new interface
            TestObjectSubclass raw = new TestObjectSubclass();
            ProxyFactory factory = new ProxyFactory(raw);
            Assert.AreEqual(8, factory.Interfaces.Length, "Found correct number of interfaces");
            //System.out.println("Proxied interfaces are " + StringUtils.arrayToDelimitedString(factory.getProxiedInterfaces(), ","));
            ITestObject tb = (ITestObject)factory.GetProxy();
            Assert.IsTrue(tb is IOther, "Picked up secondary interface");

            raw.Age = 25;
            Assert.IsTrue(tb.Age == raw.Age);

            DateTime t = new DateTime(2004, 8, 1);
            TimestampIntroductionInterceptor ti = new TimestampIntroductionInterceptor(t);

            Console.WriteLine(StringUtils.ArrayToDelimitedString(factory.Interfaces, "/"));

            //factory.addAdvisor(0, new DefaultIntroductionAdvisor(ti, typeof(ITimeStamped)));
            factory.AddIntroduction(
                new DefaultIntroductionAdvisor(ti, typeof(ITimeStamped))
                );

            Console.WriteLine(StringUtils.ArrayToDelimitedString(factory.Interfaces, "/"));

            ITimeStamped ts = (ITimeStamped)factory.GetProxy();
            Assert.IsTrue(ts.TimeStamp == t);
            // Shouldn't fail;
            ((IOther)ts).Absquatulate();
        }

        private class AnonymousClassInterceptor : IInterceptor
        {
        }

        [Test]
        public void CanOnlyAddMethodInterceptors()
        {
            ProxyFactory factory = new ProxyFactory(new TestObject());
            factory.AddAdvice(0, new NopInterceptor());
            try
            {
                factory.AddAdvice(0, new AnonymousClassInterceptor());
                Assert.Fail("Should only be able to add MethodInterceptors");
            }
            catch (AopConfigException)
            {
            }

            // Check we can still use it
            IOther other = (IOther)factory.GetProxy();
            other.Absquatulate();
        }

        [Test]
        public void InterceptorInclusionMethods()
        {
            NopInterceptor di = new NopInterceptor();
            NopInterceptor diUnused = new NopInterceptor(1); // // make instance unique (see SPRNET-847)
            ProxyFactory factory = new ProxyFactory(new TestObject());
            factory.AddAdvice(0, di);
            ITestObject tb = (ITestObject)factory.GetProxy();
            Assert.IsTrue(factory.AdviceIncluded(di));
            Assert.IsTrue(!factory.AdviceIncluded(diUnused));
            Assert.IsTrue(factory.CountAdviceOfType(typeof(NopInterceptor)) == 1);

            factory.AddAdvice(0, diUnused);
            Assert.IsTrue(factory.AdviceIncluded(diUnused));
            Assert.IsTrue(factory.CountAdviceOfType(typeof(NopInterceptor)) == 2);
        }

        [Test]
        public void AddAdvisedSupportListener()
        {
            IDynamicMock mock = new DynamicMock(typeof(IAdvisedSupportListener));
            IAdvisedSupportListener listener = (IAdvisedSupportListener)mock.Object;

            mock.Expect("Activated");
            ProxyFactory factory = new ProxyFactory(new TestObject());
            factory.AddListener(listener);
            factory.GetProxy();
            mock.Verify();
        }

        [Test]
        public void AdvisedSupportListenerMethodsAreCalledAppropriately()
        {
            IDynamicMock mock = new DynamicMock(typeof(IAdvisedSupportListener));
            IAdvisedSupportListener listener = (IAdvisedSupportListener)mock.Object;

            mock.Expect("Activated");
            mock.Expect("AdviceChanged");
            mock.Expect("InterfacesChanged");

            ProxyFactory factory = new ProxyFactory(new TestObject());
            factory.AddListener(listener);

            // must fire the Activated callback...
            factory.GetProxy();
            // must fire the AdviceChanged callback...
            factory.AddAdvice(new NopInterceptor());
            // must fire the InterfacesChanged callback...
            factory.AddInterface(typeof(ISerializable));

            mock.Verify();
        }

        [Test]
        public void AdvisedSupportListenerMethodsAre_NOT_CalledIfProxyHasNotBeenCreated()
        {
            IDynamicMock mock = new DynamicMock(typeof(IAdvisedSupportListener));
            IAdvisedSupportListener listener = (IAdvisedSupportListener)mock.Object;

            ProxyFactory factory = new ProxyFactory(new TestObject());
            factory.AddListener(listener);

            // must not fire the AdviceChanged callback...
            factory.AddAdvice(new NopInterceptor());
            // must not fire the InterfacesChanged callback...
            factory.AddInterface(typeof(ISerializable));

            mock.Verify();
        }

        [Test]
        public void AddNullAdvisedSupportListenerIsOk()
        {
            ProxyFactory factory = new ProxyFactory(new TestObject());
            factory.AddListener(null);
        }

        [Test]
        public void RemoveNullAdvisedSupportListenerIsOk()
        {
            ProxyFactory factory = new ProxyFactory(new TestObject());
            factory.RemoveListener(null);
        }

        [Test]
        public void RemoveAdvisedSupportListener()
        {
            IDynamicMock mock = new DynamicMock(typeof(IAdvisedSupportListener));
            IAdvisedSupportListener listener = (IAdvisedSupportListener)mock.Object;

            ProxyFactory factory = new ProxyFactory(new TestObject());
            factory.AddListener(listener);
            factory.RemoveListener(listener);

            factory.GetProxy();

            // check that no lifecycle callback methods were invoked on the listener...
            mock.Verify();
        }

        [Test]
        [ExpectedException(typeof(AopConfigException))]
        public void Frozen_RemoveAdvisor()
        {
            ProxyFactory factory = new ProxyFactory();
            factory.IsFrozen = true;
            factory.RemoveAdvisor(null);
        }

        public interface IMultiProxyingTestInterface
        {
            string TestMethod(string arg);
        }

        public interface IMultiProxyingTestInterface2 : IMultiProxyingTestInterface { }

        public class MultiProxyingTestClass : IMultiProxyingTestInterface2
        {
            public int InvocationCounter;

            public string TestMethod(string arg)
            {
                InvocationCounter++;
                return arg + "|" + arg;
            }
        }

        public interface ICountingIntroduction
        {
            void Inc();
        }

        public class TestCountingIntroduction : ICountingIntroduction, IAdvice
        {
            public int Counter;

            public void Inc()
            {
                Counter++;
            }
        }

        [Test]
        public void NestedProxiesDontInvokeSameAdviceOrIntroductionTwice()
        {
            MultiProxyingTestClass testObj = new MultiProxyingTestClass();
            ProxyFactory pf1 = new ProxyFactory();
            pf1.Target = testObj;

            NopInterceptor di = new NopInterceptor();
            NopInterceptor diUnused = new NopInterceptor(1); // // make instance unique (see SPRNET-847)
            TestCountingIntroduction countingMixin = new TestCountingIntroduction();

            pf1.AddAdvice(diUnused);                                                      
            pf1.AddAdvisor(new DefaultPointcutAdvisor(di));
            pf1.AddIntroduction(new DefaultIntroductionAdvisor(countingMixin));

            object innerProxy = pf1.GetProxy();
            ProxyFactory pf2 = new ProxyFactory();
            pf2.Target = innerProxy;
            pf2.AddAdvice(diUnused);
            pf2.AddAdvisor(new DefaultPointcutAdvisor(di));
            pf2.AddIntroduction(new DefaultIntroductionAdvisor(countingMixin));

            object outerProxy = pf2.GetProxy();

            // any advice instance is invoked once only
            string result = ((IMultiProxyingTestInterface)outerProxy).TestMethod("arg");
            Assert.AreEqual(1, testObj.InvocationCounter);
            Assert.AreEqual("arg|arg", result);
            Assert.AreEqual(1, di.Count);

            // any introduction instance is invoked once only
            ((ICountingIntroduction)outerProxy).Inc();
            Assert.AreEqual(1, countingMixin.Counter);
        }
    }
}