using System;
using System.Reflection;

namespace UnityEngine.InputSystem.Utilities
{
    internal static class TypeHelpers
    {
        public static TObject As<TObject>(this object obj)
        {
            // This avoid NREs for value types. For example, trying to do "(Vector3)null" will
            // result in an NRE. Doing "null.As<Vector3>()" will result in "default(Vector3)".
            if (obj == null)
                return default;
            return (TObject)obj;
        }

        public static bool IsInt(this TypeCode type)
        {
            switch (type)
            {
                case TypeCode.Byte: return true;
                case TypeCode.SByte: return true;
                case TypeCode.Int16: return true;
                case TypeCode.Int32: return true;
                case TypeCode.Int64: return true;
                case TypeCode.UInt16: return true;
                case TypeCode.UInt32: return true;
                case TypeCode.UInt64: return true;
            }
            return false;
        }

        public static Type GetValueType(MemberInfo member)
        {
            var field = member as FieldInfo;
            if (field != null)
                return field.FieldType;

            var property = member as PropertyInfo;
            if (property != null)
                return property.PropertyType;

            var method = member as MethodInfo;
            if (method != null)
                return method.ReturnType;

            return null;
        }

        public static string GetNiceTypeName(this Type type)
        {
            if (type.IsPrimitive)
            {
                if (type == typeof(int))
                    return "int";
                if (type == typeof(float))
                    return "float";
                if (type == typeof(char))
                    return "char";
                if (type == typeof(byte))
                    return "byte";
                if (type == typeof(short))
                    return "short";
                if (type == typeof(long))
                    return "long";
                if (type == typeof(double))
                    return "double";
                if (type == typeof(uint))
                    return "uint";
                if (type == typeof(sbyte))
                    return "sbyte";
                if (type == typeof(ushort))
                    return "ushort";
                if (type == typeof(ulong))
                    return "ulong";
            }

            return type.Name;
        }

        public static Type GetGenericTypeArgumentFromHierarchy(Type type, Type genericTypeDefinition, int argumentIndex)
        {
            if (type == null)
                throw new ArgumentNullException(nameof(type));
            if (genericTypeDefinition == null)
                throw new ArgumentNullException(nameof(genericTypeDefinition));
            if (argumentIndex < 0)
                throw new ArgumentOutOfRangeException(nameof(argumentIndex));

            if (genericTypeDefinition.IsInterface)
            {
                // Walk up the chain until we find the generic type def as an interface on a type.
                while (true)
                {
                    var interfaces = type.GetInterfaces();
                    var haveFoundInterface = false;
                    foreach (var element in interfaces)
                    {
                        if (element.IsConstructedGenericType &&
                            element.GetGenericTypeDefinition() == genericTypeDefinition)
                        {
                            type = element;
                            haveFoundInterface = true;
                            break;
                        }

                        // Recurse into interface in case we're looking for a base interface.
                        var typeArgument =
                            GetGenericTypeArgumentFromHierarchy(element, genericTypeDefinition, argumentIndex);
                        if (typeArgument != null)
                            return typeArgument;
                    }

                    if (haveFoundInterface)
                        break;

                    type = type.BaseType;
                    if (type == null || type == typeof(object))
                        return null;
                }
            }
            else
            {
                // Walk up the chain until we find the generic type def.
                while (!type.IsConstructedGenericType || type.GetGenericTypeDefinition() != genericTypeDefinition)
                {
                    type = type.BaseType;
                    if (type == typeof(object))
                        return null;
                }
            }

            return type.GenericTypeArguments[argumentIndex];
        }
    }
}