using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Odbc;
using System.Data.OleDb;
using System.Data.SqlClient;
using System.Linq;
using System.Reflection;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Tests.DynamicsAndReflection;
using Umbraco.Web.Cache;
using Umbraco.Web.Models;
using Umbraco.Web.Scheduling;
using UmbracoExamine.DataServices;

namespace Umbraco.Tests.Plugins
{
    /// <summary>
    /// Tests for TypeHelper
    /// </summary>
    [TestFixture]
    public class TypeHelperTests
    {

   


        class Base<T> { }

        interface IBase<T> { }

        interface IDerived<T> : IBase<T> { }

        class Derived<T> : Base<T>, IBase<T> { }

        class Derived2<T> : Derived<T> { }

        class DerivedI<T> : IDerived<T> { }

        [Test]
        public void Is_Static_Class()
        {
            Assert.IsTrue(TypeHelper.IsStaticClass(typeof(TypeHelper)));
            Assert.IsFalse(TypeHelper.IsStaticClass(typeof(TypeHelperTests)));
        }

        [Test]
        public void Find_Common_Base_Class()
        {
            var t1 = TypeHelper.GetLowestBaseType(typeof (OleDbCommand),
                                                  typeof (OdbcCommand),
                                                  typeof (SqlCommand));
            Assert.IsFalse(t1.Success);

            var t2 = TypeHelper.GetLowestBaseType(typeof (OleDbCommand),
                                                  typeof (OdbcCommand),
                                                  typeof (SqlCommand),
                                                  typeof (Component));
            Assert.IsTrue(t2.Success);
            Assert.AreEqual(typeof(Component), t2.Result);

            var t3 = TypeHelper.GetLowestBaseType(typeof (OleDbCommand),
                                                  typeof (OdbcCommand),
                                                  typeof (SqlCommand),
                                                  typeof (Component),
                                                  typeof (Component).BaseType);
            Assert.IsTrue(t3.Success);
            Assert.AreEqual(typeof(MarshalByRefObject), t3.Result);

            var t4 = TypeHelper.GetLowestBaseType(typeof(OleDbCommand),
                                                   typeof(OdbcCommand),
                                                   typeof(SqlCommand),
                                                   typeof(Component),
                                                   typeof(Component).BaseType,
                                                   typeof(int));
            Assert.IsFalse(t4.Success);

            var t5 = TypeHelper.GetLowestBaseType(typeof(PropertyAliasDto));
            Assert.IsTrue(t5.Success);
            Assert.AreEqual(typeof(PropertyAliasDto), t5.Result);

            var t6 = TypeHelper.GetLowestBaseType(typeof (IApplicationEventHandler),
                                                  typeof (Scheduler),
                                                  typeof(CacheRefresherEventHandler));
            Assert.IsTrue(t6.Success);
            Assert.AreEqual(typeof(IApplicationEventHandler), t6.Result);

        }

        [Test]
        public void MatchTypesTest()
        {
            var bindings = new Dictionary<string, Type>();
            Assert.IsTrue(TypeHelper.MatchType(typeof(int), typeof(int), bindings));
            Assert.AreEqual(0, bindings.Count);

            bindings = new Dictionary<string, Type>();
            Assert.IsFalse(TypeHelper.MatchType(typeof(int), typeof(string), bindings));
            Assert.AreEqual(0, bindings.Count);

            bindings = new Dictionary<string, Type>();
            Assert.IsTrue(TypeHelper.MatchType(typeof(List<int>), typeof(System.Collections.IEnumerable), bindings));
            Assert.AreEqual(0, bindings.Count);

            var t1 = typeof(IList<>); // IList<>
            var a1 = t1.GetGenericArguments()[0]; // <T>
            t1 = t1.MakeGenericType(a1); // IList<T>
            var t2 = a1;

            bindings = new Dictionary<string, Type>();
            Assert.IsTrue(TypeHelper.MatchType(typeof(int), t2, bindings));
            Assert.AreEqual(1, bindings.Count);
            Assert.AreEqual(typeof(int), bindings["T"]);

            bindings = new Dictionary<string, Type>();
            Assert.IsTrue(TypeHelper.MatchType(typeof(IList<int>), t1, bindings));
            Assert.AreEqual(1, bindings.Count);
            Assert.AreEqual(typeof(int), bindings["T"]);

            bindings = new Dictionary<string, Type>();
            Assert.IsTrue(TypeHelper.MatchType(typeof(List<int>), typeof(IList<int>), bindings));
            Assert.AreEqual(0, bindings.Count);

            bindings = new Dictionary<string, Type>();
            Assert.IsTrue(TypeHelper.MatchType(typeof(List<int>), t1, bindings));
            Assert.AreEqual(1, bindings.Count);
            Assert.AreEqual(typeof(int), bindings["T"]);

            bindings = new Dictionary<string, Type>();
            Assert.IsTrue(TypeHelper.MatchType(typeof(Dictionary<int, string>), typeof(IDictionary<,>), bindings));
            Assert.AreEqual(2, bindings.Count);
            Assert.AreEqual(typeof(int), bindings["TKey"]);
            Assert.AreEqual(typeof(string), bindings["TValue"]);

            t1 = typeof(IDictionary<,>); // IDictionary<,>
            a1 = t1.GetGenericArguments()[0]; // <TKey>
            t1 = t1.MakeGenericType(a1, a1); // IDictionary<TKey,TKey>

            bindings = new Dictionary<string, Type>();
            Assert.IsFalse(TypeHelper.MatchType(typeof(Dictionary<int, string>), t1, bindings));

            bindings = new Dictionary<string, Type>();
            Assert.IsTrue(TypeHelper.MatchType(typeof(Dictionary<int, int>), t1, bindings));
            Assert.AreEqual(1, bindings.Count);
            Assert.AreEqual(typeof(int), bindings["TKey"]);
        }

        [Test]
        public void MatchType()
        {
            // both are OK
            Assert.IsTrue(TypeHelper.MatchType(typeof(List<int>), typeof(IEnumerable<>)));

            var t1 = typeof (IDictionary<,>); // IDictionary<,>
            var a1 = t1.GetGenericArguments()[0];
            t1 = t1.MakeGenericType(a1, a1); // IDictionary<T,T>

            // both are OK

            Assert.IsTrue(TypeHelper.MatchType(typeof(Dictionary<int, int>), t1));

            Assert.IsFalse(TypeHelper.MatchType(typeof(Dictionary<int, string>), t1));

            // these are all of there from Is_Assignable_To_Generic_Type
            Assert.IsTrue(TypeHelper.MatchType(typeof(Derived<int>), typeof(Base<>)));
            Assert.IsTrue(TypeHelper.MatchType(typeof(List<int>), typeof(IEnumerable<>)));
            Assert.IsTrue(TypeHelper.MatchType(typeof(Derived<int>), typeof(Derived<>)));
            Assert.IsTrue(TypeHelper.MatchType(typeof(Derived2<int>), typeof(Base<>)));
            Assert.IsTrue(TypeHelper.MatchType(typeof(DerivedI<int>), typeof(IBase<>)));
            Assert.IsTrue(TypeHelper.MatchType(typeof(Derived2<int>), typeof(IBase<>)));
            Assert.IsTrue(TypeHelper.MatchType(typeof(int?), typeof(Nullable<>)));


            Assert.IsTrue(TypeHelper.MatchType(typeof(Derived<int>), typeof(Object)));
            Assert.IsFalse(TypeHelper.MatchType(typeof(Derived<int>), typeof(List<>)));
            Assert.IsFalse(TypeHelper.MatchType(typeof(Derived<int>), typeof(IEnumerable<>)));
            Assert.IsTrue(TypeHelper.MatchType(typeof(Derived<int>), typeof(Base<int>)));
            Assert.IsTrue(TypeHelper.MatchType(typeof(List<int>), typeof(IEnumerable<int>)));
            Assert.IsFalse(TypeHelper.MatchType(typeof(int), typeof(Nullable<>)));

            //This get's the "Type" from the Count extension method on IEnumerable<T>, however the type IEnumerable<T> isn't
            // IEnumerable<> and it is not a generic definition, this attempts to explain that:
            // http://blogs.msdn.com/b/haibo_luo/archive/2006/02/17/534480.aspx

            var genericEnumerableNonGenericDefinition = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
                .Single(x => x.Name == "Count" && x.GetParameters().Count() == 1)
                .GetParameters()
                .Single()
                .ParameterType;

            Assert.IsTrue(TypeHelper.MatchType(typeof(List<int>), genericEnumerableNonGenericDefinition));

        }


        [Test]
        public void CreateOpenGenericTypes()
        {
            // readings
            // http://stackoverflow.com/questions/13466078/create-open-constructed-type-from-string
            // http://stackoverflow.com/questions/6704722/c-sharp-language-how-to-get-type-of-bound-but-open-generic-class

            // note that FullName returns "The fully qualified name of the type, including its namespace but not its
            // assembly; or null if the current instance represents a generic type parameter, an array type, pointer
            // type, or byref type based on a type parameter, or a generic type that is not a generic type definition
            // but contains unresolved type parameters."

            var t = Type.GetType("System.Collections.Generic.IList`1");
            Assert.IsNotNull(t);
            Assert.IsTrue(t.IsGenericTypeDefinition);
            Assert.AreEqual("IList`1", t.Name);
            Assert.AreEqual("System.Collections.Generic.IList`1", t.FullName);
            Assert.AreEqual("System.Collections.Generic.IList`1[T]", t.ToString());

            t = typeof (IList<>);
            Assert.IsTrue(t.IsGenericTypeDefinition);
            Assert.AreEqual("IList`1", t.Name);
            Assert.AreEqual("System.Collections.Generic.IList`1", t.FullName);
            Assert.AreEqual("System.Collections.Generic.IList`1[T]", t.ToString());

            t = typeof(IDictionary<,>);
            Assert.IsTrue(t.IsGenericTypeDefinition);
            Assert.AreEqual("IDictionary`2", t.Name);
            Assert.AreEqual("System.Collections.Generic.IDictionary`2", t.FullName);
            Assert.AreEqual("System.Collections.Generic.IDictionary`2[TKey,TValue]", t.ToString());

            t = typeof(IDictionary<,>);
            t = t.MakeGenericType(t.GetGenericArguments()[0], t.GetGenericArguments()[1]); 
            Assert.AreEqual("IDictionary`2", t.Name);
            Assert.AreEqual("System.Collections.Generic.IDictionary`2", t.FullName);
            Assert.AreEqual("System.Collections.Generic.IDictionary`2[TKey,TValue]", t.ToString());

            t = typeof(IDictionary<,>);
            t = t.MakeGenericType(t.GetGenericArguments()[0], t.GetGenericArguments()[0]);
            Assert.IsFalse(t.IsGenericTypeDefinition); // not anymore
            Assert.AreEqual("IDictionary`2", t.Name);
            Assert.IsNull(t.FullName); // see note above
            Assert.AreEqual("System.Collections.Generic.IDictionary`2[TKey,TKey]", t.ToString());

            t = typeof(IList<>);
            Assert.IsTrue(t.IsGenericTypeDefinition);
            t = t.MakeGenericType(t.GetGenericArguments()[0]);
            Assert.AreEqual("IList`1", t.Name);
            Assert.AreEqual("System.Collections.Generic.IList`1", t.FullName);
            Assert.AreEqual("System.Collections.Generic.IList`1[T]", t.ToString());

            t = typeof(IList<int>);
            Assert.AreEqual("System.Collections.Generic.IList`1[System.Int32]", t.ToString());

            t = typeof (IDictionary<,>);
            t = t.MakeGenericType(typeof(int), t.GetGenericArguments()[1]);
            Assert.IsFalse(t.IsGenericTypeDefinition); // not anymore
            Assert.AreEqual("IDictionary`2", t.Name);
            Assert.IsNull(t.FullName); // see note above
            Assert.AreEqual("System.Collections.Generic.IDictionary`2[System.Int32,TValue]", t.ToString());
        }
    }
}