using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityObject = UnityEngine.Object;

namespace Unity.VisualScripting
{
    public static class NameUtility
    {
        private static readonly Dictionary<Type, string> humanPrimitiveNames = new Dictionary<Type, string>
        {
            { typeof(byte), "Byte" },
            { typeof(sbyte), "Signed Byte" },
            { typeof(short), "Short" },
            { typeof(ushort), "Unsigned Short" },
            { typeof(int), "Integer" },
            { typeof(uint), "Unsigned Integer" },
            { typeof(long), "Long" },
            { typeof(ulong), "Unsigned Long" },
            { typeof(float), "Float" },
            { typeof(double), "Double" },
            { typeof(decimal), "Decimal" },
            { typeof(string), "String" },
            { typeof(char), "Character" },
            { typeof(bool), "Boolean" },
            { typeof(void), "Void" },
            { typeof(object), "Object" },
        };

        public static readonly Dictionary<string, string> humanOperatorNames = new Dictionary<string, string>
        {
            { "op_Addition", "Add" },
            { "op_Subtraction", "Subtract" },
            { "op_Multiply", "Multiply" },
            { "op_Division", "Divide" },
            { "op_Modulus", "Modulo" },
            { "op_ExclusiveOr", "Exclusive Or" },
            { "op_BitwiseAnd", "Bitwise And" },
            { "op_BitwiseOr", "Bitwise Or" },
            { "op_LogicalAnd", "Logical And" },
            { "op_LogicalOr", "Logical Or" },
            { "op_Assign", "Assign" },
            { "op_LeftShift", "Left Shift" },
            { "op_RightShift", "Right Shift" },
            { "op_Equality", "Equals" },
            { "op_GreaterThan", "Greater Than" },
            { "op_LessThan", "Less Than" },
            { "op_Inequality", "Not Equals" },
            { "op_GreaterThanOrEqual", "Greater Than Or Equals" },
            { "op_LessThanOrEqual", "Less Than Or Equals" },
            { "op_MultiplicationAssignment", "Multiplication Assignment" },
            { "op_SubtractionAssignment", "Subtraction Assignment" },
            { "op_ExclusiveOrAssignment", "Exclusive Or Assignment" },
            { "op_LeftShiftAssignment", "Left Shift Assignment" },
            { "op_ModulusAssignment", "Modulus Assignment" },
            { "op_AdditionAssignment", "Addition Assignment" },
            { "op_BitwiseAndAssignment", "Bitwise And Assignment" },
            { "op_BitwiseOrAssignment", "Bitwise Or Assignment" },
            { "op_Comma", "Comma" },
            { "op_DivisionAssignment", "Division Assignment" },
            { "op_Decrement", "Decrement" },
            { "op_Increment", "Increment" },
            { "op_UnaryNegation", "Negate" },
            { "op_UnaryPlus", "Positive" },
            { "op_OnesComplement", "One's Complement" },
        };

        private static readonly HashSet<string> booleanVerbs = new HashSet<string>
        {
            "Is",
            "Can",
            "Has",
            "Are",
            "Will",
            "Was",
            "Had",
            "Were"
        };

        public static string SelectedName(this Type type, bool human, bool includeGenericParameters = true)
        {
            return human ? type.HumanName(includeGenericParameters) : type.CSharpName(includeGenericParameters);
        }

        public static string SelectedName(this MemberInfo member, bool human, ActionDirection direction = ActionDirection.Any, bool expectingBoolean = false)
        {
            return human ? member.HumanName(direction) : member.CSharpName(direction);
        }

        public static string SelectedName(this ParameterInfo parameter, bool human)
        {
            return human ? parameter.HumanName() : parameter.Name;
        }

        public static string SelectedName(this Exception exception, bool human)
        {
            return human ? exception.HumanName() : exception.GetType().CSharpName(false);
        }

        public static string SelectedName(this Enum @enum, bool human)
        {
            return human ? HumanName(@enum) : @enum.ToString();
        }

        public static string SelectedName(this Namespace @namespace, bool human, bool full = true)
        {
            return human ? @namespace.HumanName(full) : @namespace.CSharpName(full);
        }

        public static string SelectedParameterString(this MethodBase methodBase, Type targetType, bool human)
        {
            return string.Join(", ", methodBase.GetInvocationParameters(targetType).Select(p => p.SelectedName(human)).ToArray());
        }

        public static string DisplayName(this Type type, bool includeGenericParameters = true)
        {
            return SelectedName(type, BoltCore.Configuration.humanNaming, includeGenericParameters);
        }

        public static string DisplayName(this MemberInfo member, ActionDirection direction = ActionDirection.Any, bool expectingBoolean = false)
        {
            return SelectedName(member, BoltCore.Configuration.humanNaming, direction, expectingBoolean);
        }

        public static string DisplayName(this ParameterInfo parameter)
        {
            return SelectedName(parameter, BoltCore.Configuration.humanNaming);
        }

        public static string DisplayName(this Exception exception)
        {
            return SelectedName(exception, BoltCore.Configuration.humanNaming);
        }

        public static string DisplayName(this Enum @enum)
        {
            return SelectedName(@enum, BoltCore.Configuration.humanNaming);
        }

        public static string DisplayName(this Namespace @namespace, bool full = true)
        {
            return SelectedName(@namespace, BoltCore.Configuration.humanNaming, full);
        }

        public static string DisplayParameterString(this MethodBase methodBase, Type targetType)
        {
            return SelectedParameterString(methodBase, targetType, BoltCore.Configuration.humanNaming);
        }

        public static string HumanName(this Type type, bool includeGenericParameters = true)
        {
            if (type == typeof(UnityObject))
            {
                return "Unity Object";
            }

            if (humanPrimitiveNames.ContainsKey(type))
            {
                return humanPrimitiveNames[type];
            }
            else if (type.IsGenericParameter)
            {
                var genericParameterName = type.Name.Prettify();

                if (genericParameterName == "T")
                {
                    return "Generic";
                }
                else if (genericParameterName.StartsWith("T "))
                {
                    return genericParameterName.Substring(2).Prettify() + " Generic";
                }
                else
                {
                    return genericParameterName.Prettify();
                }
            }
            else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                var nonNullable = Nullable.GetUnderlyingType(type);

                var underlyingName = nonNullable.HumanName(includeGenericParameters);

                return "Nullable " + underlyingName;
            }
            else
            {
                string name;

                if (Attribute.GetCustomAttribute(type, typeof(DisplayNameAttribute)) is DisplayNameAttribute displayNameAttribute)
                    name = displayNameAttribute.DisplayName;
                else
                    name = type.Name.Prettify();

                if (type.IsInterface && name.StartsWith("I "))
                {
                    name = name.Substring(2) + " Interface";
                }

                if (type.IsArray && name.Contains("[]"))
                {
                    name = name.Replace("[]", " Array");
                }

                if (type.IsGenericType && name.Contains('`'))
                {
                    name = name.Substring(0, name.IndexOf('`'));
                }

                var genericArguments = (IEnumerable<Type>)type.GetGenericArguments();

                if (type.IsNested)
                {
                    name += " of " + type.DeclaringType.HumanName(includeGenericParameters);

                    if (type.DeclaringType.IsGenericType)
                    {
                        genericArguments.Skip(type.DeclaringType.GetGenericArguments().Length);
                    }
                }

                if (genericArguments.Any())
                {
                    if (type.ContainsGenericParameters)
                    {
                        name = "Generic " + name;

                        var count = genericArguments.Count();

                        if (count > 1)
                        {
                            name += " (" + genericArguments.Count() + " parameters)";
                        }
                    }
                    else
                    {
                        name += " of ";
                        name += string.Join(" and ", genericArguments.Select(t => t.HumanName(includeGenericParameters)).ToArray());
                    }
                }

                return name;
            }
        }

        public static string HumanName(this MemberInfo member, ActionDirection direction = ActionDirection.Any, bool expectingBoolean = false)
        {
            var words = member.Name.Prettify();

            if (member is MethodInfo)
            {
                if (((MethodInfo)member).IsOperator())
                {
                    return humanOperatorNames[member.Name];
                }
                else
                {
                    return words;
                }
            }
            else if (member is FieldInfo || member is PropertyInfo)
            {
                if (direction == ActionDirection.Any)
                {
                    return words;
                }

                var type = member is FieldInfo ? ((FieldInfo)member).FieldType : ((PropertyInfo)member).PropertyType;

                // Fix for Unity's object-to-boolean implicit null-check operators
                if (direction == ActionDirection.Get && typeof(UnityObject).IsAssignableFrom(type) && expectingBoolean)
                {
                    return words + " Is Not Null";
                }

                string verb;

                switch (direction)
                {
                    case ActionDirection.Get:
                        verb = "Get";
                        break;

                    case ActionDirection.Set:
                        verb = "Set";
                        break;

                    default:
                        throw new UnexpectedEnumValueException<ActionDirection>(direction);
                }

                if (type == typeof(bool))
                {
                    // Check for boolean verbs like IsReady, HasChildren, etc.
                    if (words.Contains(' ') && booleanVerbs.Contains(words.Split(' ')[0]))
                    {
                        // Return them as-is for gets
                        if (direction == ActionDirection.Get)
                        {
                            return words;
                        }
                        // Skip them for sets
                        else if (direction == ActionDirection.Set)
                        {
                            return verb + " " + words.Substring(words.IndexOf(' ') + 1);
                        }
                        else
                        {
                            throw new UnexpectedEnumValueException<ActionDirection>(direction);
                        }
                    }
                    else
                    {
                        return verb + " " + words;
                    }
                }
                // Otherwise, add get/set the verb prefix
                else
                {
                    return verb + " " + words;
                }
            }
            else if (member is ConstructorInfo)
            {
                return "Create " + member.DeclaringType.HumanName();
            }
            else
            {
                throw new UnexpectedEnumValueException<ActionDirection>(direction);
            }
        }

        public static string HumanName(this ParameterInfo parameter)
        {
            return parameter.Name.Prettify();
        }

        public static string HumanName(this Exception exception)
        {
            return exception.GetType().CSharpName(false).Prettify().Replace(" Exception", "");
        }

        public static string HumanName(this Enum @enum)
        {
            return @enum.ToString().Prettify();
        }

        public static string CSharpName(this Namespace @namespace, bool full = true)
        {
            return @namespace.IsGlobal ? "(global)" : (full ? @namespace.FullName : @namespace.Name);
        }

        public static string HumanName(this Namespace @namespace, bool full = true)
        {
            return @namespace.IsGlobal ? "(Global Namespace)" : (full ? @namespace.FullName.Replace(".", "/").Prettify() : @namespace.Name.Prettify());
        }

        public static string ToSummaryString(this Exception ex)
        {
            return $"{ex.GetType().DisplayName()}: {ex.Message}";
        }
    }
}