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

namespace Unity.VisualScripting
{
    [InitializeAfterPlugins]
    public static class Icons
    {
        static Icons()
        {
            EditorGUIUtility_GetScriptObjectFromClass = typeof(EditorGUIUtility).GetMethod("GetScript", BindingFlags.Static | BindingFlags.NonPublic);
            EditorGUIUtility_GetIconForObject = typeof(EditorGUIUtility).GetMethod("GetIconForObject", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);

            Load();
        }

        private static readonly MethodInfo EditorGUIUtility_GetScriptObjectFromClass; // UnityObject EditorGUIUtility.GetScript(string scriptClass);

        private static readonly MethodInfo EditorGUIUtility_GetIconForObject; // Texture2D EditorGUIUtility.GetIconForObject(UnityObject obj);

        private static readonly Dictionary<Type, EditorTexture> typeIcons = new Dictionary<Type, EditorTexture>();

        private static readonly Dictionary<Namespace, EditorTexture> namespaceIcons = new Dictionary<Namespace, EditorTexture>();

        private static readonly Dictionary<Enum, EditorTexture> enumIcons = new Dictionary<Enum, EditorTexture>();

        private static readonly Dictionary<string, EditorTexture> resourcesTypeIcons = new Dictionary<string, EditorTexture>();

        public static void Load()
        {
            using (ProfilingUtility.SampleBlock("Load Icons"))
            {
                Language.Load();
            }
        }

        internal static void Clear()
        {
            typeIcons.Clear();
            namespaceIcons.Clear();
            enumIcons.Clear();
            resourcesTypeIcons.Clear();
        }

        public static EditorTexture Icon(this Type type)
        {
            return Type(type);
        }

        public static EditorTexture Icon(this MemberInfo member, ActionDirection direction = ActionDirection.Any)
        {
            return Member(member, direction);
        }

        public static EditorTexture Icon(this Enum @enum)
        {
            return Enum(@enum);
        }

        public static EditorTexture Icon(this Namespace @namespace)
        {
            return Namespace(@namespace);
        }

        public static EditorTexture Type(Type type)
        {
            if (type == null)
            {
                return BoltCore.Icons.@null;
            }

            if (!typeIcons.ContainsKey(type))
            {
                var icon = GetCustomTypeIcon(type);

                if (icon == null)
                {
                    if (type.IsClass)
                    {
                        icon = GetTypeVisibilityIcon(type, Language.@class);
                    }
                    else if (type.IsInterface)
                    {
                        icon = GetTypeVisibilityIcon(type, Language.@interface);
                    }
                    else if (type.IsPrimitive)
                    {
                        icon = GetTypeVisibilityIcon(type, Language.primitive);
                    }
                    else if (type.IsEnum)
                    {
                        icon = GetTypeVisibilityIcon(type, Language.@enum);
                    }
                    else if (type.IsValueType)
                    {
                        icon = GetTypeVisibilityIcon(type, Language.@struct);
                    }
                }

                typeIcons.Add(type, icon);
            }

            return typeIcons[type];
        }

        private static EditorTexture GetCustomTypeIcon(Type type, bool inherit = true)
        {
            var attribute = type.GetAttribute<TypeIconAttribute>();

            if (attribute != null)
            {
                type = attribute.type;
            }

            var resourcesIcon = GetResourcesTypeIcon(type.CSharpFileName(true, true)) ??
                GetResourcesTypeIcon(type.CSharpFileName(true, false)) ??
                GetResourcesTypeIcon(type.CSharpFileName(false, true)) ??
                GetResourcesTypeIcon(type.CSharpFileName(false, false));

            if (resourcesIcon != null)
            {
                return resourcesIcon;
            }

            if (typeof(UnityObject).IsAssignableFrom(type))
            {
                var unityIcon = GetBuiltInUnityTypeIcon(type);

                if (unityIcon != null)
                {
                    return unityIcon;
                }
            }

            if (type.IsEnum || type == typeof(Enum))
            {
                return GetResourcesTypeIcon("enum");
            }

            if (type.IsGenericType)
            {
                var definition = type.GetGenericTypeDefinition();
                var isList = definition == typeof(IList<>);
                var isEnumerable = definition == typeof(IEnumerable<>);

                if (isList || isEnumerable)
                {
                    var argument = type.GetGenericArguments()[0];

                    if (argument.IsEnum || argument == typeof(Enum))
                    {
                        if (isList)
                        {
                            return GetResourcesTypeIcon("System.Collections.Generic.IList_enum");
                        }

                        return GetResourcesTypeIcon("System.Collections.Generic.IEnumerable_enum");
                    }
                }
            }

            if (typeof(MonoBehaviour).IsAssignableFrom(type))
            {
                var scriptIcon = GetScriptTypeIcon(type.Name);

                if (scriptIcon != null)
                {
                    return scriptIcon;
                }
            }

            if (inherit)
            {
                foreach (var inherited in type.BaseTypeAndInterfaces(false).OrderBy(InterfaceImplementationPriority))
                {
                    if (inherited == typeof(object))
                    {
                        continue;
                    }

                    var inheritedIcon = GetCustomTypeIcon(inherited, false);

                    if (inheritedIcon != null)
                    {
                        return inheritedIcon;
                    }
                }

                if (type.BaseType != null && type.BaseType != typeof(object))
                {
                    var baseTypeIcon = GetCustomTypeIcon(type.BaseType, true);

                    if (baseTypeIcon != null)
                    {
                        return baseTypeIcon;
                    }
                }
            }

            if (typeof(UnityObject).IsAssignableFrom(type))
            {
                return GetBuiltInUnityTypeIcon(typeof(UnityObject));
            }

            return null;
        }

        private static int InterfaceImplementationPriority(Type type)
        {
            // Quick and dirty method to determine interface implementation priority.

            /* Desired order is:
             *
             * IList<>
             * IList
             * ICollection<>
             * ICollection
             * IEnumerable<>
             * IEnumerable
             */

            var priority = type.GetAttribute<TypeIconPriorityAttribute>()?.priority;

            if (priority != null)
            {
                return priority.Value;
            }

            if (type.IsGenericType)
            {
                var definition = type.GetGenericTypeDefinition();

                if (definition == typeof(IList<>))
                {
                    return 0;
                }

                if (definition == typeof(ICollection<>))
                {
                    return 2;
                }

                if (definition == typeof(IEnumerable<>))
                {
                    return 4;
                }

                return int.MaxValue - 1;
            }

            if (type == typeof(IList))
            {
                return 1;
            }

            if (type == typeof(ICollection))
            {
                return 3;
            }

            if (type == typeof(IEnumerable))
            {
                return 5;
            }

            return int.MaxValue;
        }

        public static EditorTexture Icon(this UnityObject obj)
        {
            var icon = (Texture2D)EditorGUIUtility.ObjectContent(obj, obj?.GetType()).image;

            if (icon != null)
            {
                return EditorTexture.Single(icon);
            }

            return null;
        }

        private static EditorTexture GetResourcesTypeIcon(string fileName)
        {
            if (!resourcesTypeIcons.TryGetValue(fileName, out var icon))
            {
                icon = PluginResources.LoadSharedIcon($"{fileName}.png", false);
                resourcesTypeIcons.Add(fileName, icon);
            }

            return icon;
        }

        private static EditorTexture GetBuiltInUnityTypeIcon(Type type)
        {
            var icon = AssetPreview.GetMiniTypeThumbnail(type);

            // Change the blank file icon to a Unity-logo file icon
            if (icon == EditorGUIUtility.FindTexture("DefaultAsset Icon"))
            {
                icon = EditorGUIUtility.FindTexture("ScriptableObject Icon");
            }

            if (icon != null)
            {
                return EditorTexture.Single(icon);
            }

            return null;
        }

        private static EditorTexture GetScriptTypeIcon(string scriptName)
        {
            var scriptObject = (UnityObject)EditorGUIUtility_GetScriptObjectFromClass.Invoke(null, new object[] { scriptName });

            if (scriptObject != null)
            {
                var scriptIcon = (Texture2D)EditorGUIUtility_GetIconForObject.Invoke(null, new object[] { scriptObject });

                if (scriptIcon != null)
                {
                    return EditorTexture.Single(scriptIcon);
                }
            }

            var scriptPath = AssetDatabase.GetAssetPath(scriptObject);

            if (scriptPath != null)
            {
                switch (Path.GetExtension(scriptPath))
                {
                    case ".js":

                        return EditorTexture.Single((Texture2D)EditorGUIUtility.IconContent("js Script Icon").image);
                    case ".cs":

                        return EditorTexture.Single((Texture2D)EditorGUIUtility.IconContent("cs Script Icon").image);
                    case ".boo":

                        return EditorTexture.Single((Texture2D)EditorGUIUtility.IconContent("boo Script Icon").image);
                }
            }

            return null;
        }

        private static EditorTexture GetTypeVisibilityIcon(Type type, LanguageIconSet languageIcon)
        {
            if (languageIcon == null)
            {
                return null;
            }

            if (type.IsNested)
            {
                if (type.IsNestedPrivate)
                {
                    return languageIcon.@private;
                }

                if (type.IsNestedFamily)
                {
                    return languageIcon.@protected;
                }

                if (type.IsNestedAssembly)
                {
                    return languageIcon.@internal;
                }

                if (type.IsNestedPublic)
                {
                    return languageIcon.@public;
                }
            }
            else
            {
                if (type.IsPublic)
                {
                    return languageIcon.@public;
                }

                return languageIcon.@internal;
            }

            return null;
        }

        public static EditorTexture Member(MemberInfo member, ActionDirection direction)
        {
            Ensure.That(nameof(member)).IsNotNull(member);

            var method = member as MethodInfo;
            var field = member as FieldInfo;
            var property = member as PropertyInfo;
            var constructor = member as ConstructorInfo;

            if (method != null)
            {
                if (method.IsExtension())
                {
                    return Language.extensionMethod.@public;
                }

                if (method.IsPrivate)
                {
                    return Language.method.@private;
                }

                if (method.IsFamily)
                {
                    return Language.method.@protected;
                }

                if (method.IsAssembly)
                {
                    return Language.method.@internal;
                }

                if (method.IsPublic)
                {
                    return Language.method.@public;
                }
            }
            else if (constructor != null)
            {
                if (constructor.IsPrivate)
                {
                    return Language.constructor.@private;
                }

                if (constructor.IsFamily)
                {
                    return Language.constructor.@protected;
                }

                if (constructor.IsAssembly)
                {
                    return Language.constructor.@internal;
                }

                if (constructor.IsPublic)
                {
                    return Language.constructor.@public;
                }
            }
            else if (field != null)
            {
                if (field.IsLiteral)
                {
                    if (field.IsPrivate)
                    {
                        return Language.method.@private;
                    }

                    if (field.IsFamily)
                    {
                        return Language.method.@protected;
                    }

                    if (field.IsAssembly)
                    {
                        return Language.method.@internal;
                    }

                    if (field.IsPublic)
                    {
                        return Language.method.@public;
                    }
                }
                else
                {
                    if (field.IsPrivate)
                    {
                        return Language.method.@private;
                    }

                    if (field.IsFamily)
                    {
                        return Language.method.@protected;
                    }

                    if (field.IsAssembly)
                    {
                        return Language.method.@internal;
                    }

                    if (field.IsPublic)
                    {
                        return Language.method.@public;
                    }
                }
            }
            else if (property != null)
            {
                var accessors = property.GetAccessors(true);
                var getter = accessors.FirstOrDefault(accessor => accessor.ReturnType != typeof(void));
                var setter = accessors.FirstOrDefault(accessor => accessor.ReturnType == typeof(void));

                bool isPrivate, isProtected, isInternal, isPublic;

                if (direction == ActionDirection.Any)
                {
                    isPrivate = getter == null || getter.IsPrivate || setter == null || setter.IsPrivate;

                    if (isPrivate)
                    {
                        isProtected = false;
                        isInternal = false;
                        isPublic = false;
                    }
                    else
                    {
                        isProtected = getter.IsFamily || setter.IsFamily;
                        isInternal = getter.IsAssembly || setter.IsAssembly;
                        isPublic = getter.IsPublic && setter.IsPublic;
                    }
                }
                else if (direction == ActionDirection.Get && getter != null)
                {
                    isPrivate = getter.IsPrivate;
                    isProtected = getter.IsFamily;
                    isInternal = getter.IsAssembly;
                    isPublic = getter.IsPublic;
                }
                else if (direction == ActionDirection.Set && setter != null)
                {
                    isPrivate = setter.IsPrivate;
                    isProtected = setter.IsFamily;
                    isInternal = setter.IsAssembly;
                    isPublic = setter.IsPublic;
                }
                else
                {
                    return null;
                }

                if (isPrivate)
                {
                    return Language.property.@private;
                }

                if (isProtected)
                {
                    return Language.property.@protected;
                }

                if (isInternal)
                {
                    return Language.property.@internal;
                }

                if (isPublic)
                {
                    return Language.property.@public;
                }
            }

            return null;
        }

        public static EditorTexture Enum(Enum @enum)
        {
            Ensure.That(nameof(@enum)).IsNotNull(@enum);

            if (!enumIcons.ContainsKey(@enum))
            {
                var enumType = @enum.GetType();

                if (!enumType.IsEnum)
                {
                    throw new ArgumentException(nameof(@enum));
                }

                var namespaced = PluginResources.LoadSharedIcon($"{enumType.CSharpFileName(true)}/{@enum}.png", false);
                var nonNamespaced = PluginResources.LoadSharedIcon($"{enumType.CSharpFileName(false)}/{@enum}.png", false);

                enumIcons.Add(@enum, namespaced ?? nonNamespaced);
            }

            return enumIcons[@enum];
        }

        public static EditorTexture Namespace(Namespace @namespace)
        {
            Ensure.That(nameof(@namespace)).IsNotNull(@namespace);

            if (@namespace.IsGlobal)
            {
                return Language.@namespace.@public;
            }

            if (!namespaceIcons.ContainsKey(@namespace))
            {
                var path = $"{@namespace}.png";

                namespaceIcons.Add(@namespace, PluginResources.LoadSharedIcon(path, false) ?? Language.@namespace.@public);
            }

            return namespaceIcons[@namespace];
        }

        public static class Language
        {
            public static LanguageIconSet method;

            public static LanguageIconSet @namespace { get; private set; }

            public static LanguageIconSet @class { get; private set; }

            public static LanguageIconSet @interface { get; private set; }

            public static LanguageIconSet @struct { get; private set; }

            public static LanguageIconSet @enum { get; private set; }

            public static LanguageIconSet primitive { get; private set; }

            public static LanguageIconSet field { get; private set; }

            public static LanguageIconSet property { get; private set; }

            public static LanguageIconSet extensionMethod { get; private set; }

            public static LanguageIconSet constructor { get; private set; }

            public static LanguageIconSet @const { get; private set; }

            public static LanguageIconSet favorite { get; private set; }

            internal static void Load()
            {
                @namespace = LanguageIconSet.Load("Namespace");
                @interface = LanguageIconSet.Load("Interface");
                @class = LanguageIconSet.Load("Class");
                @struct = LanguageIconSet.Load("Struct");
                @enum = LanguageIconSet.Load("Enum");
                primitive = LanguageIconSet.Load("Primitive");
                field = LanguageIconSet.Load("Field");
                property = LanguageIconSet.Load("Property");
                method = LanguageIconSet.Load("Method");
                extensionMethod = LanguageIconSet.Load("ExtensionMethod");
                constructor = LanguageIconSet.Load("Constructor");
                @const = LanguageIconSet.Load("Const");
                favorite = LanguageIconSet.Load("Favorite");
            }
        }
    }
}