using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace EasyDevCore.Common
{
    /// <summary>
    /// 
    /// </summary>
    public static class TypeHelper
    {
        /// <summary>
        /// Determines whether [is base system type].
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>
        ///   <c>true</c> if [is base system type] [the specified value]; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsBaseSystemType(this object value)
        {
            if (value is bool? || value is bool)
            {
                return true;
            }
            else if (value is byte? || value is byte)
            {
                return true;
            }
            else if (value is char? || value is char)
            {
                return true;
            }
            else if (value is string)
            {
                return true;
            }
            else if (value is DateTime? || value is DateTime)
            {
                return true;
            }
            else if (value is decimal? || value is decimal)
            {
                return true;
            }
            else if (value is double? || value is double)
            {
                return true;
            }
            else if (value is Guid? || value is Guid)
            {
                return true;
            }
            else if (value is short? || value is short)
            {
                return true;
            }
            else if (value is int? || value is int)
            {
                return true;
            }
            else if (value is long? || value is long)
            {
                return true;
            }
            else if (value is sbyte? || value is sbyte)
            {
                return true;
            }
            else if (value is float? || value is float)
            {
                return true;
            }
            else if (value is TimeSpan? || value is TimeSpan)
            {
                return true;
            }
            else if (value is ushort? || value is ushort)
            {
                return true;
            }
            else if (value is uint? || value is uint)
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// Determines whether this instance is list.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>
        ///   <c>true</c> if the specified value is list; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsList(this object value)
        {
            if (value == null) return false;
            return value is IList &&
                   value.GetType().IsGenericType &&
                   value.GetType().GetGenericTypeDefinition().IsAssignableFrom(typeof(List<>));
        }

        /// <summary>
        /// Determines whether this instance is dictionary.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>
        ///   <c>true</c> if the specified value is dictionary; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsDictionary(this object value)
        {
            if (value == null) return false;
            return value is IDictionary &&
                   value.GetType().IsGenericType &&
                   value.GetType().GetGenericTypeDefinition().IsAssignableFrom(typeof(Dictionary<,>));
        }

        /// <summary>
        /// Determines whether the specified value is nullable.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value">The value.</param>
        /// <returns>
        ///   <c>true</c> if the specified value is nullable; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsNullable<T>(this T value)
        {
            if (!typeof(T).IsGenericType || typeof(T).IsGenericTypeDefinition)
                return false;

            return typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>);
        }

        /// <summary>
        /// Determines whether this instance is anonymous.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns>
        ///   <c>true</c> if the specified type is anonymous; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsAnonymous(this Type type)
        {
            // HACK: The only way to detect anonymous types right now.
            return Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false)
                && type.IsGenericType && type.Name.Contains("AnonymousType")
                && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$"))
                && type.Attributes.HasFlag(TypeAttributes.NotPublic);
        }

        /// <summary>
        /// Determines whether [is numeric type] [the specified type].
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns>
        ///   <c>true</c> if [is numeric type] [the specified type]; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsNumericType(this Type type)
        {
            TypeCode typeCode = Type.GetTypeCode(type);
            //The TypeCode of numerical types are between SByte (5) and Decimal (15).
            return (int)typeCode >= 5 && (int)typeCode <= 15;
        }

        /// <summary>
        /// Determines whether this instance is numberic.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>
        ///   <c>true</c> if the specified value is numberic; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsNumberic(this object value)
        {
            return value != null && value.GetType().IsNumericType();
        }

        /// <summary>
        /// Determines whether [is expando object] [the specified object value].
        /// </summary>
        /// <param name="objectValue">The object value.</param>
        /// <returns>
        ///   <c>true</c> if [is expando object] [the specified object value]; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsExpandoObject(object objectValue)
        {
            if (objectValue == null)
                return false;

            if (IsDynamicObject(objectValue.GetType()))
            {
                IDictionary<string, object> expandoPropertyValues = objectValue as IDictionary<string, object>;
                return expandoPropertyValues != null;
            }

            return false;
        }

        /// <summary>
        /// Determines whether [is dynamic object].
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns>
        ///   <c>true</c> if [is dynamic object] [the specified type]; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsDynamicObject(this Type type)
        {
            return typeof(IDynamicMetaObjectProvider).IsAssignableFrom(type);
        }

        /// <summary>
        /// Determines whether [is inherited from] [the specified obj].
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="types">The types.</param>
        /// <returns>
        ///   <c>true</c> if [is inherited from] [the specified obj]; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsInheritedFrom(this object obj, params Type[] types)
        {
            return types.Any(t => t.IsAssignableFrom(obj.GetType()));
        }

        /// <summary>
        /// Finds the interface.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="interfaceType">Type of the interface.</param>
        /// <returns></returns>
        public static Type FindInterface(this Type type, Type interfaceType)
        {
            return type.GetInterfaces().SingleOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType);
        }

        /// <summary>
        /// Finds the interface.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="type">The type.</param>
        /// <returns></returns>
        public static Type FindInterface<T>(this Type type)
        {
            return FindInterface(type, typeof(T));
        }

        /// <summary>
        /// Determines whether [has generict argument type] [the specified argument index].
        /// </summary>
        /// <typeparam name="TType">The type of the type.</typeparam>
        /// <param name="value">The value.</param>
        /// <param name="index">Index of the argument (-1 means check any).</param>
        /// <returns>
        ///   <c>true</c> if [has generict argument type] [the specified argument index]; otherwise, <c>false</c>.
        /// </returns>
        public static bool HasGenerictArgumentType<TType>(this object value, int index = -1)
        {
            Type[] arguments = value.GetType().GetGenericArguments();
            if (index >= 0 && arguments.Length - 1 < index) return false;
            if (index == -1)
            {
                return arguments.Any(x => typeof(TType).IsAssignableFrom(x));
            }
            return typeof(TType).IsAssignableFrom(arguments[index]);
        }

        /// <summary>
        /// Gets the type of the generict argument.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="index">The index.</param>
        /// <returns></returns>
        public static Type GetGenerictArgumentType(this object value, int index)
        {
            var list = value.GetType().GetGenericArguments();
            return index >= list.Length - 1 ? list[index] : null;
        }

        /// <summary>
        /// Determines whether this instance has interface.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="interfaceType">Type of the interface.</param>
        /// <returns>
        ///   <c>true</c> if the specified value has interface; otherwise, <c>false</c>.
        /// </returns>
        public static bool HasInterface(this object value, Type interfaceType)
        {
            return value == null ? false : HasInterface(value.GetType(), interfaceType);
        }

        /// <summary>
        /// Determines whether the specified type has interface.
        /// </summary>
        /// <typeparam name="TInterface">The type of the interface.</typeparam>
        /// <param name="type">The type.</param>
        /// <returns>
        ///   <c>true</c> if the specified type has interface; otherwise, <c>false</c>.
        /// </returns>
        public static bool HasInterface<TInterface>(this Type type)
        {
            return HasInterface(type, typeof(TInterface));
        }

        /// <summary>
        /// Determines whether the specified interface type has interface.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="interfaceType">Type of the interface.</param>
        /// <returns>
        ///   <c>true</c> if the specified interface type has interface; otherwise, <c>false</c>.
        /// </returns>
        public static bool HasInterface(this Type type, Type interfaceType)
        {
            if (type == null) return false;
            return type.FindInterface(interfaceType) != null;            
        }

        /// <summary>
        /// Gets underlying the type.
        /// </summary>
        /// <param name="propertyType">Type of the property.</param>
        /// <returns></returns>
        public static Type GetUnderlyingType(this Type propertyType)
        {
            Type type = propertyType;
            bool isNullable = type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Nullable<>));
            if (isNullable)
                return Nullable.GetUnderlyingType(type);
            return type;
        }

        /// <summary>
        /// Gets the name of the type.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <returns></returns>
        public static string GetTypeName(this object obj)
        {
            return obj.GetType().Name;
        }

        /// <summary>
        /// Gets the full name of the type.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <returns></returns>
        public static string GetTypeFullName(this object obj)
        {
            return obj == null ? null : obj.GetType().FullName;
        }


    }
}
