// ----------------------------------------------------------------------------
// Author: Kaynn, Yeo Wen Qin
// https://github.com/Kaynn-Cahya
// Date:   26/02/2019
// ----------------------------------------------------------------------------

using System;
using Jovian.Utilities;
using UnityEngine;
using Object = UnityEngine.Object;

namespace Jovian.InspectorTools {
    [AttributeUsage(AttributeTargets.Method)]
    public class ButtonMethodAttribute : PropertyAttribute {
        public readonly ButtonMethodDrawOrder DrawOrder;
        public readonly PlayModeVisibility Visibility;

        public ButtonMethodAttribute(PlayModeVisibility visibility, ButtonMethodDrawOrder drawOrder = ButtonMethodDrawOrder.AfterInspector) {
            Visibility = visibility;
            DrawOrder = drawOrder;
        }

        public ButtonMethodAttribute(ButtonMethodDrawOrder drawOrder = ButtonMethodDrawOrder.AfterInspector) {
            DrawOrder = drawOrder;
            Visibility = PlayModeVisibility.Always;
        }
    }

    public enum ButtonMethodDrawOrder {
        BeforeInspector,
        AfterInspector
    }
}

#if UNITY_EDITOR
namespace Jovian.InspectorTools.Internal {
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using UnityEditor;

    public class ButtonMethodHandler {

        public class MethodData {
            public MethodInfo methodInfo;
            public ParameterData[] parameterDatas;
            public string niceName;
            public PlayModeVisibility visibility;
            public ButtonMethodDrawOrder order;
        }

        public class ParameterData {
            public ParameterInfo parameterInfo;
            public string niceName;
            public object value;
        }

        // TODO - replace with MethodDatas;
        public readonly List<(MethodInfo Method, string Name, PlayModeVisibility visibility, ButtonMethodDrawOrder order)> TargetMethods;
        public int Amount => TargetMethods?.Count ?? 0;

        public readonly List<MethodData> MethodDatas;

        public bool HasMethods => MethodDatas?.Count > 0;

        private readonly Object _target;

        public Object Target => _target;

        public ButtonMethodHandler(Object target) {
            _target = target;

            Type type = target.GetType();
            BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
            IEnumerable<MemberInfo> members = type.GetMembers(bindings).Where(IsButtonMethod);

            foreach(MemberInfo member in members) {
                MethodInfo method = member as MethodInfo;
                if(method == null) continue;

                if(IsValidMember(method, member)) {
                    ButtonMethodAttribute attribute = (ButtonMethodAttribute)Attribute.GetCustomAttribute(method, typeof(ButtonMethodAttribute));
                    TargetMethods ??= new List<(MethodInfo, string, PlayModeVisibility, ButtonMethodDrawOrder)>();
                    TargetMethods.Add((method, ObjectNames.NicifyVariableName(method.Name), attribute.Visibility, attribute.DrawOrder));
                    MethodDatas ??= new();

                    ParameterInfo[] parameterInfos = method.GetParameters();
                    ParameterData[] parameterDatas = new ParameterData[parameterInfos.Length];
                    for(int i = 0; i < parameterInfos.Length; i++) {
                        ParameterInfo info = parameterInfos[i];

                        object value;
                        if(info.HasDefaultValue) {
                            value = info.DefaultValue;
                        }
                        else if(info.ParameterType == typeof(string)) {
                            value = "String";
                        }
                        else if(info.ParameterType == typeof(Object) || info.ParameterType.IsSubclassOf(typeof(Object))) {
                            value = null;
                        }
                        else {
                            value = Activator.CreateInstance(info.ParameterType);
                        }

                        parameterDatas[i] = new ParameterData() {
                            niceName = ObjectNames.NicifyVariableName(info.Name),
                            parameterInfo = info,
                            value = value
                        };
                    }

                    MethodData methodData = new MethodData() {
                        methodInfo = method,
                        niceName = ObjectNames.NicifyVariableName(method.Name),
                        visibility = attribute.Visibility,
                        order = attribute.DrawOrder,
                        parameterDatas = parameterDatas
                    };
                    MethodDatas.Add(methodData);
                }
            }
        }

        public bool HasAnyVisibleMethods() {
            if(MethodDatas == null || MethodDatas.Count == 0) return false;
            foreach (MethodData methodData in MethodDatas) {
                if (methodData.visibility.IsVisible()) {
                    return true;
                }
            }
            return false;
        }

        public void OnBeforeInspectorGUI() {
            if(MethodDatas == null || MethodDatas.Count == 0) return;

            DrawButtonMethods(ButtonMethodDrawOrder.BeforeInspector);
        }

        private void DrawButtonMethods(ButtonMethodDrawOrder drawOrder) {
            foreach(MethodData method in MethodDatas) {
                if(method.order != drawOrder) continue;
                if(!method.visibility.IsVisible()) continue;

                if(method.parameterDatas.Length > 0) {
                    GUILayout.BeginVertical(EditorStyles.helpBox);
                }

                if(GUILayout.Button(method.niceName)) {
                    object result = method.methodInfo.Invoke(_target, method.parameterDatas.Select(p=>p.value).ToArray());
                    if(result != null) {
                        Debug.Log($"{method.niceName} => {result}");
                    }
                }

                foreach (ParameterData p in method.parameterDatas) {
                    p.value = InspectorGUIUtility.DrawField(p.niceName, p.parameterInfo.ParameterType, p.value);
                }

                if(method.parameterDatas.Length > 0) {
                    GUILayout.EndVertical();
                }
            }
        }

        public void OnAfterInspectorGUI() {
            if(MethodDatas == null || MethodDatas.Count == 0) return;
            DrawButtonMethods(ButtonMethodDrawOrder.AfterInspector);
        }

        public void Invoke(MethodInfo method) => InvokeMethod(_target, method);


        private void InvokeMethod(Object target, MethodInfo method) {
            object result = method.Invoke(target, null);

            if(result != null) {
                string message = $"{result} \nResult of Method '{method.Name}' invocation on object {target.name}";
                Debug.Log(message, target);
            }
        }

        private bool IsButtonMethod(MemberInfo memberInfo) {
            return Attribute.IsDefined(memberInfo, typeof(ButtonMethodAttribute));
        }

        private bool IsValidMember(MethodInfo method, MemberInfo member) {
            if(method == null) {
                Debug.LogWarning(
                    $"Property <color=brown>{member.Name}</color>.Reason: Member is not a method but has EditorButtonAttribute!");
                return false;
            }
            return true;
        }
    }
}
#endif
