#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.Diagnostics;
using System.Reflection;
using System.Security;
using System.Security.Permissions;
using NUnit.Framework;
using Spring.Context.Support;

#endregion

namespace Spring.Reflection.Dynamic
{
    /// <summary>
    /// Unit tests for the DynamicField class.
    /// </summary>
    /// <author>Aleksandar Seovic</author>
    [TestFixture]
    public class DynamicFieldTests
    {
        protected const BindingFlags BINDANY = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
        protected Inventor tesla;
        protected Inventor pupin;
        protected 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

        protected virtual IDynamicField Create( FieldInfo field )
        {
            return DynamicField.Create( field );
        }

        [Test]
        public void TestInstanceFields()
        {
            IDynamicField name = Create( typeof( Inventor ).GetField( "Name" ) );
            Assert.AreEqual( tesla.Name, name.GetValue( tesla ) );
            name.SetValue( tesla, "Tesla, Nikola" );
            Assert.AreEqual( "Tesla, Nikola", tesla.Name );
            Assert.AreEqual( "Tesla, Nikola", name.GetValue( tesla ) );

            MyStruct myYearHolder = new MyStruct();
            myYearHolder.Year = 2004;
            IDynamicField year = Create( typeof( MyStruct ).GetField( "year", BINDANY ) );
            Assert.AreEqual( 2004, year.GetValue( myYearHolder ) );
        }

        [Test]
        [ExpectedException( typeof( InvalidOperationException ) )]
        public void TestAttemptingToSetFieldOfValueTypeInstance()
        {
            MyStruct myYearHolder = new MyStruct();
            IDynamicField year = Create( typeof( MyStruct ).GetField( "year" ) );
            year.SetValue( myYearHolder, 2004 );
        }

        [Test]
        public void TestStaticFieldsOfClass()
        {
            IDynamicField myField = Create( typeof( MyStaticClass ).GetField( "myField" ) );
            myField.SetValue( null, "here we go..." );

            IDynamicField myConst = Create( typeof( MyStaticClass ).GetField( "MyConst" ) );
            Assert.AreEqual( 3456, myConst.GetValue( null ) );
            try
            {
                myConst.SetValue( null, 7890 );
            }
            catch (InvalidOperationException) { }

            IDynamicField myReadonlyField = Create( typeof( MyStaticClass ).GetField( "myReadonlyField" ) );
            string s2 = (string) myReadonlyField.GetValue(null);
            string s1 = MyStaticClass.myReadonlyField;
            Assert.AreEqual(s1, s2);
        }


#if NET_2_0

        [Test]
        public void CanReadPrivateReadOnlyField()
        {
            IDynamicField myPrivateReadonlyField2 = null;
            FieldInfo fieldInfo = typeof(MyStaticClass).GetField("myPrivateReadonlyField", BindingFlags.Static | BindingFlags.NonPublic);

            myPrivateReadonlyField2 = Create(fieldInfo);

            string u2 = (string)myPrivateReadonlyField2.GetValue(null);
            string u1 = "hahaha";
            Assert.AreEqual(u1, u2);
        }

        [Test, Ignore("TODO: this works as expected when run using TD.NET & R# (in VS2k8), but fails with nant/NET 2.0 ?!?")]
        public void CannotReadPrivateReadOnlyFieldIfNoReflectionPermission()
        {
            FieldInfo fieldInfo = typeof(MyStaticClass).GetField("myPrivateReadonlyField", BindingFlags.Static | BindingFlags.NonPublic);
            try
            {
                SecurityTemplate.MediumTrustInvoke( delegate
                {
                    IDynamicField myPrivateReadonlyField2 = Create(fieldInfo);
                });
                Assert.Fail("private field must not be accessible in medium trust: " + fieldInfo);
            }
            catch (SecurityException sex)
            {
                Assert.IsTrue( sex.Message.IndexOf("ReflectionPermission") > -1 );
            }
        }
#endif

        [Test]
        public void CannotSetStaticReadOnlyField()
        {
            IDynamicField myReadonlyField = Create(typeof(MyStaticClass).GetField("myReadonlyField"));
            try
            {
                myReadonlyField.SetValue(null, "some other string");
                Assert.Fail();
            }
            catch (InvalidOperationException) { }
        }

        [Test]
        public void TestStaticFieldsOfStruct()
        {
            // static readonly
            IDynamicField maxValue = Create( typeof( DateTime ).GetField( "MaxValue" ) );
            Assert.AreEqual( DateTime.MaxValue, maxValue.GetValue( null ) );
            try
            {
                maxValue.SetValue( null, DateTime.Now );
            }
            catch (InvalidOperationException) { }

            // const
            IDynamicField int64max = Create( typeof( Int64 ).GetField( "MaxValue", BindingFlags.Public | BindingFlags.Static ) );
            Assert.AreEqual( Int64.MaxValue, int64max.GetValue( null ) );
            try
            {
                int64max.SetValue( null, 0 );
            }
            catch (InvalidOperationException) { }

            // pure static
            IDynamicField myField = Create( typeof( MyStaticStruct ).GetField( "staticYear" ) );
            myField.SetValue( null, 2008 );
            Assert.AreEqual( 2008, myField.GetValue( null ) );
        }

        #region Performance tests

        private DateTime start, stop;

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

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

            start = DateTime.Now;
            IDynamicField placeOfBirth = Create( typeof( Inventor ).GetField( "Name" ) );
            for (int i = 0; i < n; i++)
            {
                x = placeOfBirth.GetValue( tesla );
            }
            stop = DateTime.Now;
            PrintTest( "tesla.Name (dynamic reflection)", n, Elapsed );

            start = DateTime.Now;
            FieldInfo placeOfBirthFi = typeof( Inventor ).GetField( "Name" );
            for (int i = 0; i < n; i++)
            {
                x = placeOfBirthFi.GetValue( tesla );
            }
            stop = DateTime.Now;
            PrintTest( "tesla.Name (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 IL generation helper classes (they help if you look at them in Reflector ;-)

    public class ValueTypeField : IDynamicField
    {
        public object GetValue( object target )
        {
            return ((Inventor)target).Name;
        }

        public void SetValue( object target, object value )
        {
            ((Inventor)target).Name = (string)value;
        }
    }

    public class ValueTypeTargetField : IDynamicField
    {
        public object GetValue( object target )
        {
            return ((MyStruct)target).year;
        }

        public void SetValue( object target, object value )
        {
            MyStruct o = (MyStruct)target;
            o.Year = (int)value;
        }
    }

    public class StaticField : IDynamicField
    {
        public object GetValue( object target )
        {
            return MyStaticClass.myField;
        }

        public void SetValue( object target, object value )
        {
            MyStaticClass.myField = (string)value;
        }
    }

    public class StaticConst : IDynamicField
    {
        public object GetValue( object target )
        {
            return MyStaticClass.MyConst;
        }

        public void SetValue( object target, object value )
        {
        }
    }

    #endregion

}
