using System;
using System.Linq;
using System.Reflection;

namespace InABox.Core
{
    public static class EditorUtils
    {
        /// <summary>
        /// Returns a default editor for the given type, e.g., a <see cref="CheckBoxEditor"/> for <see cref="bool"/>.
        /// If the type does not have a sensible editor, returns <see langword="null"/>.
        /// </summary>
        /// <param name="type"></param>
        /// <returns>A new <see cref="BaseEditor"/>, or <see langword="null"/> if no sensible default.</returns>
        public static BaseEditor? GetEditor(Type type)
        {
            BaseEditor? editor = null;

            if (type == typeof(bool))
                editor = new CheckBoxEditor();

            else if (type == typeof(DateTime))
                editor = new DateTimeEditor();

            else if (type == typeof(string))
                editor = new TextBoxEditor();

            else if (type.IsFloatingPoint())
                editor = new DoubleEditor();

            else if (type.IsOrdinal())
                editor = new IntegerEditor();

            else if (typeof(IPackable).IsAssignableFrom(type))
                editor = new NullEditor();

            return editor;
        }

        public static BaseEditor? GetEditor(object value)
        {
            if (value != null)
                return GetEditor(value.GetType());
            return new NullEditor();
        }

        /// <summary>
        /// Gets the editor for the specified property. If the property has a <see cref="BaseEditor"/> defined, then returns that.<br/>
        /// Otherwise, gets a default for the property type. (<see cref="GetEditor(Type)"/>), which can be <see langword="null"/>.
        /// </summary>
        /// <param name="prop"></param>
        /// <returns></returns>
        public static BaseEditor? GetEditor(this PropertyInfo prop)
        {
            var attribute = prop.GetCustomAttributes<BaseEditor>(true).FirstOrDefault();
            var editor = attribute ?? GetEditor(prop.PropertyType);
            if (editor != null && !prop.CanWrite)
                editor.Editable = Editable.Hidden;
            return editor;
        }

        public static BaseEditor GetPropertyEditor(Type type, IProperty property, BaseEditor? defaultEditor = null)
        {
            BaseEditor editor = new NullEditor();

            try
            {
                var parts = property.Name.Split('.');
                var caption = "";
                var page = "";

                for (var i = 0; i < parts.Length; i++)
                {
                    var column = string.Join(".", parts.Take(i + 1));
                    var prop = CoreUtils.GetProperty(type, column);
                    if (column.Equals(property.Name))
                    {
                        editor = defaultEditor ?? property.Editor;
                    }
                    else
                    {
                        var pedit = prop.GetEditor();
                        if (pedit is NullEditor) return pedit;
                    }

                    editor = editor == null ? new NullEditor() : (editor.Clone() as BaseEditor)!;

                    var capattr = prop.GetCustomAttribute<Caption>();
                    var subcap = capattr != null ? capattr.Text : parts[i];
                    var path = capattr != null ? capattr.IncludePath : true;
                    if (!string.IsNullOrWhiteSpace(subcap))
                        caption = string.IsNullOrWhiteSpace(caption) || path == false ? subcap : string.Format("{0} {1}", caption, subcap);

                    if (string.IsNullOrWhiteSpace(page))
                    {
                        var pageattr = prop.GetCustomAttribute<EditorSequence>();
                        if (pageattr != null)
                            page = pageattr.Page;
                    }
                }

                editor.Caption = caption;
                editor.Page = page;
            }
            catch (Exception)
            {
                if (property is CustomProperty)
                {
                    editor = (property.Editor.Clone() as BaseEditor)!;
                    editor.Caption = property.Caption;
                    editor.Page = string.IsNullOrWhiteSpace(property.Page) ? "Custom Fields" : property.Page;
                }
            }
            return editor;
        }

        public static string GetCaption(this PropertyInfo prop)
        {
            var attribute = prop.GetCustomAttribute<Caption>();
            var result = attribute != null ? attribute.Text : prop.Name;
            return result;
        }

        public static int GetSequence(this PropertyInfo prop)
        {
            var attribute = prop.GetCustomAttribute<EditorSequence>();
            var result = attribute != null ? attribute.Sequence : 999;
            return result;
        }

        public static bool IsCalculated(this PropertyInfo prop)
        {
            return prop.GetCustomAttribute<AggregateAttribute>() != null
                || prop.GetCustomAttribute<FormulaAttribute>() != null
                || prop.GetCustomAttribute<ConditionAttribute>() != null
                || prop.GetCustomAttribute<ComplexFormulaAttribute>() != null;
        }
    }
}