#nullable enable
using IPA.Config.Stores.Converters;
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

namespace IPA.Config.Stores.Attributes
{
    /// <summary>
    /// Indicates that the generated subclass of the attribute's target should implement <see cref="INotifyPropertyChanged"/>.
    /// If the type this is applied to already inherits it, this is implied.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
    public sealed class NotifyPropertyChangesAttribute : Attribute { }

    /// <summary>
    /// Causes a field or property in an object being wrapped by <see cref="GeneratedStore.Generated{T}(Config, bool)"/> to be
    /// ignored during serialization and deserialization.
    /// </summary>
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
    public sealed class IgnoreAttribute : Attribute { }

    /// <summary>
    /// Indicates that a field or property in an object being wrapped by <see cref="GeneratedStore.Generated{T}(Config, bool)"/>
    /// that would otherwise be nullable (i.e. a reference type or a <see cref="Nullable{T}"/> type) should never be null, and the
    /// member will be ignored if the deserialized value is <see langword="null"/>.
    /// </summary>
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
    public sealed class NonNullableAttribute : Attribute { }

    /// <summary>
    /// Indicates that a given field or property in an object being wrapped by <see cref="GeneratedStore.Generated{T}(Config, bool)"/>
    /// should be serialized and deserialized using the provided converter instead of the default mechanism.
    /// </summary>
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
    public sealed class UseConverterAttribute : Attribute
    {
        /// <summary>
        /// Gets whether or not to use the default converter for the member type instead of the specified type.
        /// </summary>
        [MemberNotNullWhen(false, nameof(ConverterType))]
        public bool UseDefaultConverterForType { get; }

        /// <summary>
        /// Gets the type of the converter to use.
        /// </summary>
        public Type? ConverterType { get; }

        /// <summary>
        /// Gets the target type of the converter if it is avaliable at instantiation time, otherwise
        /// <see langword="null"/>.
        /// </summary>
        public Type? ConverterTargetType { get; }

        /// <summary>
        /// Gets whether or not this converter is a generic <see cref="ValueConverter{T}"/>.
        /// </summary>
        [MemberNotNullWhen(true, nameof(ConverterTargetType))]
        public bool IsGenericConverter => ConverterTargetType is not null;

        /// <summary>
        /// Creates a new <see cref="UseConverterAttribute"/> specifying to use the default converter type for the target member.
        /// </summary>
        public UseConverterAttribute()
            => UseDefaultConverterForType = true;

        /// <summary>
        /// Creates a new <see cref="UseConverterAttribute"/> with a  given <see cref="ConverterType"/>.
        /// </summary>
        /// <param name="converterType">the type to assign to <see cref="ConverterType"/></param>
        public UseConverterAttribute(Type converterType)
        {
            if (converterType is null)
                throw new ArgumentNullException(nameof(converterType));

            UseDefaultConverterForType = false;
            ConverterType = converterType;

            if (converterType.IsValueType)
                throw new ArgumentException("Type is not a value converter!");

            var baseT = ConverterType.BaseType;
            while (baseT != typeof(object) &&
                (!baseT.IsGenericType || baseT.GetGenericTypeDefinition() != typeof(ValueConverter<>)))
                baseT = baseT.BaseType;
            if (baseT == typeof(object)) ConverterTargetType = null;
            else ConverterTargetType = baseT.GetGenericArguments()[0];

            var implInterface = ConverterType.GetInterfaces().Contains(typeof(IValueConverter));

            if (ConverterTargetType == null && !implInterface) throw new ArgumentException("Type is not a value converter!");
        }
    }

    /// <summary>
    /// Specifies a name for the serialized field or property in an object being wrapped by
    /// <see cref="GeneratedStore.Generated{T}(Config, bool)"/> that is different from the member name itself.
    /// </summary>
    /// <example>
    /// <para>
    /// When serializing the following object, we might get the JSON that follows.
    /// <code>
    /// public class PluginConfig
    /// {
    ///     public virtual bool BooleanField { get; set; } = true;
    /// }
    /// </code>
    /// <code>
    /// {
    ///     "BooleanField": true
    /// }
    /// </code>
    /// </para>
    /// <para>
    /// However, if we were to add a <see cref="SerializedNameAttribute"/> to that field, we would get the following.
    /// <code>
    /// public class PluginConfig
    /// {
    ///     [SerializedName("bool")]
    ///     public virtual bool BooleanField { get; set; } = true;
    /// }
    /// </code>
    /// <code>
    /// {
    ///     "bool": true
    /// }
    /// </code>
    /// </para>
    /// </example>
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
    public sealed class SerializedNameAttribute : Attribute
    {
        /// <summary>
        /// Gets the name to replace the member name with.
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// Creates a new <see cref="SerializedNameAttribute"/> with the given <see cref="Name"/>.
        /// </summary>
        /// <param name="name">the value to assign to <see cref="Name"/></param>
        public SerializedNameAttribute(string name)
        {
            Name = name;
        }
    }
}