#region License

/*
 * Copyright 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.Collections;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using NUnit.Framework;
using Spring.Context.Support;
using Spring.Util;

#endregion

namespace Spring.Reflection.Dynamic
{
	/// <summary>
    /// Unit tests for the DynamicMethod class.
    /// </summary>
    /// <author>Aleksandar Seovic</author>
	[TestFixture]
    public sealed class DynamicMethodTests
    {
        private Inventor tesla;
        private Inventor pupin;
        private Society ieee;

	    #region SetUp and TearDown

        /// <summary>
        /// The setup logic executed before the execution of each individual test.
        /// </summary>
        [SetUp]
        public void SetUp()
        {
            ContextRegistry.Clear();
            tesla = new Inventor("Nikola Tesla", new DateTime(1856, 7, 9), "Serbian");
            tesla.Inventions = new string[]
                {
                    "Telephone repeater", "Rotating magnetic field principle",
                    "Polyphase alternating-current system", "Induction motor",
                    "Alternating-current power transmission", "Tesla coil transformer",
                    "Wireless communication", "Radio", "Fluorescent lights"
                };
            tesla.PlaceOfBirth.City = "Smiljan";

            pupin = new Inventor("Mihajlo Pupin", new DateTime(1854, 10, 9), "Serbian");
            pupin.Inventions = new string[] { "Long distance telephony & telegraphy", "Secondary X-Ray radiation", "Sonar" };
            pupin.PlaceOfBirth.City = "Idvor";
            pupin.PlaceOfBirth.Country = "Serbia";

            ieee = new Society();
            ieee.Members.Add(tesla);
            ieee.Members.Add(pupin);
            ieee.Officers["president"] = pupin;
            ieee.Officers["advisors"] = new Inventor[] { tesla, pupin }; // not historically accurate, but I need an array in the map ;-)
        }
	    
        [TestFixtureTearDown]
        public void TearDown()
        {
            //DynamicReflectionManager.SaveAssembly();
        }
	    
        #endregion

#if NET_2_0
	    private void RespectsPermissionsPrivateMethod() {}

        public void RespectsPermissionsPublicMethod() {}

        [Test]
        public void CanCreateWithRestrictedPermissions()
        {
            SecurityTemplate.MediumTrustInvoke(new ThreadStart(CanCreateWithRestrictedPermissionsImpl));
        }

        private void CanCreateWithRestrictedPermissionsImpl()
	    {
	        MethodInfo method = this.GetType().GetMethod("RespectsPermissionsPublicMethod");
	        IDynamicMethod m = DynamicMethod.Create(method);
	        m.Invoke(this, null);
	    }

	    [Test]
        public void CanCreatePrivateMethodButThrowsOnInvoke()
        {
            SecurityTemplate.MediumTrustInvoke(new ThreadStart(CanCreatePrivateMethodButThrowsOnInvokeImpl));
        }

        private void CanCreatePrivateMethodButThrowsOnInvokeImpl()
	    {
	        MethodInfo privateMethod = this.GetType().GetMethod("RespectsPermissionsPrivateMethod", BindingFlags.NonPublic | BindingFlags.Instance);
	        IDynamicMethod m = DynamicMethod.Create(privateMethod);

	        try
	        {
	            m.Invoke(this, null);
	            Assert.Fail();
	        }
	        catch(MethodAccessException)
	        {}
	    }
#endif

	    [Test]
        public void TestInstanceMethods() 
        {
            IDynamicMethod getAge = DynamicMethod.Create(typeof(Inventor).GetMethod("GetAge"));
            Assert.AreEqual(tesla.GetAge(DateTime.Today), getAge.Invoke(tesla, new object[] {DateTime.Today}));

	        MethodTarget target = new MethodTarget();
            IDynamicMethod test = DynamicMethod.Create(typeof(MethodTarget).GetMethod("MethodReturningString"));
            Assert.AreEqual(tesla.Name, test.Invoke(target, new object[] { 5, DateTime.Today, new String[] {"xyz", "abc"}, tesla}));

	        ArrayList list = new ArrayList(new string[] {"one", "two", "three"});
            IDynamicMethod removeAt = DynamicMethod.Create(typeof(ArrayList).GetMethod("RemoveAt"));
            removeAt.Invoke(list, new object[] {1});
	        Assert.AreEqual(2, list.Count);
	        Assert.AreEqual("three", list[1]);
        }

	    [Test]
        public void TestStaticMethods()
        {
            IDynamicMethod isNullOrEmpty = DynamicMethod.Create(typeof(StringUtils).GetMethod("IsNullOrEmpty"));
            Assert.IsTrue((bool) isNullOrEmpty.Invoke(null, new object[] { null }));
            Assert.IsTrue((bool) isNullOrEmpty.Invoke(null, new object[] { String.Empty }));
            Assert.IsFalse((bool) isNullOrEmpty.Invoke(null, new object[] { "Ana Maria" }));
        }

#if NET_2_0
        [Test]
        public void PassNullableArguments()
        {
            IDynamicMethod dm = DynamicMethod.Create(typeof(TestMethods).GetMethod("PassNullableArgumentStatic"));
            DateTime dt = DateTime.Now;

            Assert.AreEqual(dt, dm.Invoke(null, dt));
        }
#endif
        [Test]
        public void PassInvalidNumberOfArguments()
        { 
            IDynamicMethod dm = DynamicMethod.Create(typeof(TestMethods).GetMethod("PassReferenceArgumentStatic"));
            DateTime dt = DateTime.Now;

            Assert.IsNull(dm.Invoke(null, null)); // this is ok
            try
            {
                dm.Invoke( null ); // this is not ok 
                Assert.Fail();
            }
            catch (ArgumentException) { }
            try
            {
                dm.Invoke( null, null, null ); // this is not ok 
                Assert.Fail();
            }
            catch (ArgumentException) { }
        }

        [Test]
        public void TestArgumentTypeCasts()
        {
            IDynamicMethod sqrt = DynamicMethod.Create(typeof(Math).GetMethod("Sqrt"));
            object result = sqrt.Invoke(null, new object[] { 4 } );
            Assert.AreEqual( Math.Sqrt(4), result );

            try
            {
                sqrt.Invoke(null, new object[] { null } );
                Assert.Fail();
            }
            catch (InvalidCastException)
            {
            }

            try
            {
                sqrt.Invoke(null, new object[] { "4" } );
                Assert.Fail();
            }
            catch (InvalidCastException)
            {}
        }

        private void CodeForReflection()
        {
            object val = 4;
            Type argType = typeof(double);
            Math.Sqrt( (double)Convert.ChangeType(val, argType) );            
        }

	    [Test]
        public void TestRefOutMethods()
	    {
            IDynamicMethod refMethod = DynamicMethod.Create(typeof(MethodTarget).GetMethod("MethodWithRefParameter"));

            MethodTarget target = new MethodTarget();
            object[] args = new object[] {"aleks", 5};
	        refMethod.Invoke(target, args);
            Assert.AreEqual("ALEKS", args[0]);
            Assert.AreEqual(25, args[1]);

            IDynamicMethod outMethod = DynamicMethod.Create(typeof(MethodTarget).GetMethod("MethodWithOutParameter"));
            args = new object[] { "aleks", null };
            outMethod.Invoke(target, args);
            Assert.AreEqual("ALEKS", args[1]);

            IDynamicMethod refOutMethod = DynamicMethod.Create(typeof(RefOutTestObject).GetMethod("DoIt"));
            RefOutTestObject refOutTarget = new RefOutTestObject();

            args = new object[] { 0, 1, null };
            refOutMethod.Invoke(refOutTarget, args);
            Assert.AreEqual(2, args[1]);
            Assert.AreEqual("done", args[2]);
            refOutMethod.Invoke(refOutTarget, args);
            Assert.AreEqual(3, args[1]);
            Assert.AreEqual("done", args[2]);

	        int count = 0;
	        string done;
            target.DoItCaller(0, ref count, out done);
            Assert.AreEqual(1, count);
            Assert.AreEqual("done", done);
        }
	    
        #region Performance tests

        private DateTime start, stop;

        //[Test]
        public void PerformanceTests()
        {
            int n = 10000000;
            object x = null;

            // tesla.GetAge
            start = DateTime.Now;
            for (int i = 0; i < n; i++)
            {
                x = tesla.GetAge(DateTime.Today);
            }
            stop = DateTime.Now;
            PrintTest("tesla.GetAge (direct)", n, Elapsed);

            start = DateTime.Now;
            IDynamicMethod getAge = DynamicMethod.Create(typeof(Inventor).GetMethod("GetAge"));
            for (int i = 0; i < n; i++)
            {
                object[] args = new object[] { DateTime.Today };
                x = getAge.Invoke(tesla, args);
            }
            stop = DateTime.Now;
            PrintTest("tesla.GetAge (dynamic reflection)", n, Elapsed);

            start = DateTime.Now;
            MethodInfo getAgeMi = typeof(Inventor).GetMethod("GetAge");
            for (int i = 0; i < n; i++)
            {
                object[] args = new object[] { DateTime.Today };
                x = getAgeMi.Invoke(tesla, args);
            }
            stop = DateTime.Now;
            PrintTest("tesla.GetAge (standard reflection)", n, Elapsed);
        }

        private double Elapsed
        {
            get { return (stop.Ticks - start.Ticks) / 10000000f; }
        }

        private void PrintTest(string name, int iterations, double duration)
        {
            Debug.WriteLine(String.Format("{0,-60} {1,12:#,###} {2,12:##0.000} {3,12:#,###}", name, iterations, duration, iterations / duration));
        }

        #endregion

        #region Helper Classes

        public class TestMethods
        {
            public static object PassReferenceArgumentStatic( object arg )
            { 
                return arg;
            }
#if NET_2_0
            public static object Invoke( object target, object[] args )
            { 
                return PassNullableArgumentStatic( (DateTime?)(args[0]) );
            }

            public DateTime? PassNullableArgument( DateTime? arg )
            { 
                return PassNullableArgumentStatic( arg );
            }

            public static DateTime? PassNullableArgumentStatic( DateTime? arg )
            { 
                return arg;
            }
#endif
        }
        #endregion
    }

    #region IL generation helper classes (they help if you look at them in Reflector ;-)

    public class InstanceMethod : IDynamicMethod
    {
        public object Invoke(object target, object[] args)
        {
            return ((MethodTarget) target).MethodReturningString(
                (int) args[0], (DateTime) args[1], (string[]) args[2], (Inventor) args[3]);
        }

        public object InvokeVoid(object target, object[] args)
        {
            ((MethodTarget) target).RemoveAt(5);
            return null;
        }

        public object InvokeWithOut(object target, object[] args)
        {
            string outVar = null;
            ((MethodTarget)target).MethodWithOutParameter((string) args[0], out outVar);
            args[1] = outVar;
            return null;
        }

        public object InvokeWithRef(object target, object[] args)
        {
            string refVar1 = (string) args[0];
            int refVar2 = (int) args[0];
            ((MethodTarget)target).MethodWithRefParameter(ref refVar1, ref refVar2);
            args[0] = refVar1;
            args[1] = refVar2;
            return null;
        }

        public object InvokeDoIt(object target, object[] args)
        {
            int reference = (int) args[1];
            string output;
            ((RefOutTestObject) target).DoIt((int) args[0], ref reference, out output);
            args[1] = reference;
            args[2] = output;
            return null;
        }
    }

    public class MethodTarget
    {
        public string MethodReturningString(int arg1, DateTime arg2, string[] arg3, Inventor arg4)
        {
            return arg4.Name;
        }
        
        public void RemoveAt(int index)
        {}

        public void MethodWithOutParameter(string lower, out string upper)
        {
            upper = lower.ToUpper();
        }

        public void MethodWithRefParameter(ref string lowerUpper, ref int square)
        {
            lowerUpper = lowerUpper.ToUpper();
            square = square * square;
        }

        public void DoItCaller(int count, ref int reference, out string output)
        {
            InstanceMethod caller = new InstanceMethod();

            RefOutTestObject target = new RefOutTestObject();
            object[] args = new object[] {count, reference, null};
            caller.InvokeDoIt(target, args);

            reference = (int) args[1];
            output = (string) args[2];
        }
    }

    public interface IRefOutTestObject
    {
        void DoIt(int count, ref int reference, out string output);
    }

    public class RefOutTestObject : IRefOutTestObject
    {
        public void DoIt(int count, ref int reference, out string output)
        {
            output = "done";
            reference++;
        }
    }

    #endregion
 
   
}
