using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace EasyDevCore.Common
{
    /// <summary>
    ///   <br />
    /// </summary>
    public static class EnumHelper
    {
        /// <summary>
        /// Determines whether the specified input has flags.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="args">The args.</param>
        /// <returns>
        ///   <c>true</c> if the specified input has flags; otherwise, <c>false</c>.
        /// </returns>
        public static bool HasAny(this Enum input, params Enum[] args) => args.Any(v => input.HasFlag(v));

        /// <summary>Gets the names.</summary>
        /// <param name="input">The input.</param>
        /// <returns>
        ///   <br />
        /// </returns>
        public static List<string> GetNames(this Enum input) => new List<string>(Enum.GetNames(input.GetType()));

        /// <summary>Gets the values.</summary>
        /// <typeparam name="T"></typeparam>
        /// <returns>
        ///   <br />
        /// </returns>
        public static IEnumerable<T> GetValues<T>() where T : Enum => Enum.GetValues(typeof(T)).Cast<T>();

        /// <summary>
        /// Gets the textual description of the enum if it has one. e.g.
        /// 
        /// <code>
        /// enum UserColors
        /// {
        ///     [Description("Bright Red")]
        ///     BrightRed
        /// }
        /// UserColors.BrightRed.ToDescription();
        /// </code>
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public static string GetDescription(this Enum input)
        {
            return input.GetType().GetField(input.ToString()).GetCustomAttribute<DescriptionAttribute>()?.Description ?? input.ToString();
        }

        /// <summary>Gets the type code.</summary>
        /// <param name="input">The enum.</param>
        /// <returns>
        ///   <br />
        /// </returns>
        public static TypeCode GetTypeCode(this Enum input) => Type.GetTypeCode(Enum.GetUnderlyingType(input.GetType()));

        /// <summary>Determines whether [has] [the specified value].</summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="input">The input.</param>
        /// <param name="value">The value.</param>
        /// <returns>
        ///   <c>true</c> if [has] [the specified value]; otherwise, <c>false</c>.</returns>
        /// <exception cref="NotSupportedException">Enums of type {input.GetType().Name}</exception>
        public static bool Has<T>(this Enum input, T value)
        {
            var typeCode = input.GetTypeCode();
            switch (typeCode)
            {
                case TypeCode.Byte:
                    return (((byte)(object)input & (byte)(object)value) == (byte)(object)value);
                case TypeCode.Int16:
                    return (((short)(object)input & (short)(object)value) == (short)(object)value);
                case TypeCode.Int32:
                    return (((int)(object)input & (int)(object)value) == (int)(object)value);
                case TypeCode.Int64:
                    return (((long)(object)input & (long)(object)value) == (long)(object)value);
                default:
                    throw new NotSupportedException($"Enums of type {input.GetType().Name}");
            }
        }

        /// <summary>Determines whether [is] [the specified value].</summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="input">The input.</param>
        /// <param name="value">The value.</param>
        /// <returns>
        ///   <c>true</c> if [is] [the specified value]; otherwise, <c>false</c>.</returns>
        /// <exception cref="NotSupportedException">Enums of type {input.GetType().Name}</exception>
        public static bool Is<T>(this Enum input, T value)
        {
            var typeCode = input.GetTypeCode();
            switch (typeCode)
            {
                case TypeCode.Byte:
                    return (byte)(object)input == (byte)(object)value;
                case TypeCode.Int16:
                    return (short)(object)input == (short)(object)value;
                case TypeCode.Int32:
                    return (int)(object)input == (int)(object)value;
                case TypeCode.Int64:
                    return (long)(object)input == (long)(object)value;
                default:
                    throw new NotSupportedException($"Enums of type {input.GetType().Name}");
            }
        }

        /// <summary>Adds the specified value.</summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="input">The input.</param>
        /// <param name="value">The value.</param>
        /// <returns>
        ///   <br />
        /// </returns>
        /// <exception cref="NotSupportedException">Enums of type {input.GetType().Name}</exception>
        public static T Add<T>(this Enum input, T value)
        {
            var typeCode = input.GetTypeCode();
            switch (typeCode)
            {
                case TypeCode.Byte:
                    return (T)(object)((byte)(object)input | (byte)(object)value);
                case TypeCode.Int16:
                    return (T)(object)((short)(object)input | (short)(object)value);
                case TypeCode.Int32:
                    return (T)(object)((int)(object)input | (int)(object)value);
                case TypeCode.Int64:
                    return (T)(object)((long)(object)input | (long)(object)value);
                default:
                    throw new NotSupportedException($"Enums of type {input.GetType().Name}");
            }
        }

        /// <summary>Removes the specified value.</summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="input">The input.</param>
        /// <param name="value">The value.</param>
        /// <returns>
        ///   <br />
        /// </returns>
        /// <exception cref="NotSupportedException">Enums of type {input.GetType().Name}</exception>
        public static T Remove<T>(this Enum input, T value)
        {
            var typeCode = input.GetTypeCode();
            switch (typeCode)
            {
                case TypeCode.Byte:
                    return (T)(object)((byte)(object)input & ~(byte)(object)value);
                case TypeCode.Int16:
                    return (T)(object)((short)(object)input & ~(short)(object)value);
                case TypeCode.Int32:
                    return (T)(object)((int)(object)input & ~(int)(object)value);
                case TypeCode.Int64:
                    return (T)(object)((long)(object)input & ~(long)(object)value);
                default:
                    throw new NotSupportedException($"Enums of type {input.GetType().Name}");
            }
        }
    }
}
