/****************************************************************************  
 | 
**  
 | 
** Copyright (C) 2020 The Qt Company Ltd.  
 | 
** Contact: https://www.qt.io/licensing/  
 | 
**  
 | 
** This file is part of the Qt VS Tools.  
 | 
**  
 | 
** $QT_BEGIN_LICENSE:GPL-EXCEPT$  
 | 
** Commercial License Usage  
 | 
** Licensees holding valid commercial Qt licenses may use this file in  
 | 
** accordance with the commercial license agreement provided with the  
 | 
** Software or, alternatively, in accordance with the terms contained in  
 | 
** a written agreement between you and The Qt Company. For licensing terms  
 | 
** and conditions see https://www.qt.io/terms-conditions. For further  
 | 
** information use the contact form at https://www.qt.io/contact-us.  
 | 
**  
 | 
** GNU General Public License Usage  
 | 
** Alternatively, this file may be used under the terms of the GNU  
 | 
** General Public License version 3 as published by the Free Software  
 | 
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT  
 | 
** included in the packaging of this file. Please review the following  
 | 
** information to ensure the GNU General Public License requirements will  
 | 
** be met: https://www.gnu.org/licenses/gpl-3.0.html.  
 | 
**  
 | 
** $QT_END_LICENSE$  
 | 
**  
 | 
****************************************************************************/  
 | 
  
 | 
using System;  
 | 
using System.Collections.Generic;  
 | 
using System.Diagnostics;  
 | 
using System.Linq;  
 | 
using System.Reflection;  
 | 
  
 | 
namespace QtVsTools.Common  
 | 
{  
 | 
    /// <summary>  
 | 
    /// Extended enum support:  
 | 
    ///   * Customized cast of enum values to arbitrary types  
 | 
    /// </summary>  
 | 
    public static class EnumExt  
 | 
    {  
 | 
        /// <summary>  
 | 
        /// Wrapper for enum cast values.  
 | 
        /// </summary>  
 | 
        /// <typeparam name="T">Type of cast output</typeparam>  
 | 
        /// <remarks>  
 | 
        /// Cast attributes associated with enum values must implement this interface.  
 | 
        /// </remarks>  
 | 
        public interface ICast<T>  
 | 
        {  
 | 
            T Value { get; }  
 | 
        }  
 | 
  
 | 
        /// <summary>  
 | 
        /// String cast attribute associated to an enum value.  
 | 
        /// </summary>  
 | 
        /// <example>  
 | 
        ///     enum Foobar {  
 | 
        ///         Foo,  
 | 
        ///         [EnumExt.String("Bahr")] Bar  
 | 
        ///     }  
 | 
        /// </example>  
 | 
        [AttributeUsage(AttributeTargets.All)]  
 | 
        public sealed class StringAttribute : Attribute, ICast<string>  
 | 
        {  
 | 
            public string Value { get; private set; }  
 | 
            public StringAttribute(string str) { Value = str; }  
 | 
        }  
 | 
  
 | 
        /// <summary>  
 | 
        /// Cast enum value to type T.  
 | 
        /// </summary>  
 | 
        /// <typeparam name="T">Cast output type.</typeparam>  
 | 
        /// <param name="value">Input enum value.</param>  
 | 
        /// <returns>  
 | 
        /// Value of type T associated to the enum value by an Attribute implementing  
 | 
        /// ICast<typeparamref name="T"/>. If no attribute is found, returns a default value.  
 | 
        /// </returns>  
 | 
        /// <example>  
 | 
        ///     enum Foobar  
 | 
        ///     {  
 | 
        ///         Foo,  
 | 
        ///         [EnumExt.String("Bahr")] Bar  
 | 
        ///     }  
 | 
        ///     Foobar foo = Foobar.Foo;  
 | 
        ///     Foobar bar = Foobar.Bar;  
 | 
        ///     string fooCastString = foo.Cast<string>(); // "Foo"  
 | 
        ///     string barCastString = bar.Cast<string>(); // "Bahr"  
 | 
        ///     string fooToString = foo.ToString();       // "Foo"  
 | 
        ///     string barToString = bar.ToString();       // "Bar"  
 | 
        /// </example>  
 | 
        public static T Cast<T>(this Enum value)  
 | 
        {  
 | 
            if (FindCastAttrib<T>(value) is ICast<T> cast)  
 | 
                return cast.Value;  
 | 
            else  
 | 
                return Default<T>(value);  
 | 
        }  
 | 
  
 | 
        /// <summary>  
 | 
        /// Compare enum value with instance/value of type T.  
 | 
        /// </summary>  
 | 
        /// <typeparam name="T">Cast/comparison type.</typeparam>  
 | 
        /// <param name="valueT">Instance/value of type T to compare with.</param>  
 | 
        /// <param name="valueEnum">Enum value to compare with.</param>  
 | 
        /// <returns>true if cast of valueEnum is equal to valueT, false otherwise</returns>  
 | 
        public static bool EqualTo<T>(this T valueT, Enum valueEnum)  
 | 
        {  
 | 
            return valueT.Equals(valueEnum.Cast<T>());  
 | 
        }  
 | 
  
 | 
        /// <summary>  
 | 
        /// Convert type T to enum  
 | 
        /// </summary>  
 | 
        public static bool TryCast<T, TEnum>(this T valueT, out TEnum value) where TEnum : struct  
 | 
        {  
 | 
            value = default(TEnum);  
 | 
            IEnumerable<Enum> enumValues = Enum.GetValues(typeof(TEnum)).OfType<Enum>()  
 | 
                .Where((Enum valueEnum) => valueEnum.Cast<T>().Equals(valueT));  
 | 
            if (enumValues.Any())  
 | 
                value = (TEnum)Enum.ToObject(typeof(TEnum), enumValues.FirstOrDefault());  
 | 
            return enumValues.Any();  
 | 
        }  
 | 
  
 | 
        /// <summary>  
 | 
        /// Convert type T to enum  
 | 
        /// </summary>  
 | 
        public static TEnum Cast<T, TEnum>(this T valueT, TEnum defaultValue) where TEnum : struct  
 | 
        {  
 | 
            TEnum value;  
 | 
            return TryCast(valueT, out value) ? value : defaultValue;  
 | 
        }  
 | 
  
 | 
        /// <summary>  
 | 
        /// Get list of values of enum type  
 | 
        /// </summary>  
 | 
        public static IEnumerable<TEnum> GetValues<TEnum>() where TEnum : struct  
 | 
        {  
 | 
            Debug.Assert(typeof(TEnum).IsEnum);  
 | 
            return Enum.GetValues(typeof(TEnum)).OfType<TEnum>();  
 | 
        }  
 | 
  
 | 
        /// <summary>  
 | 
        /// Get list of values of enum type converted to type T  
 | 
        /// </summary>  
 | 
        public static IEnumerable<T> GetValues<T>(Type enumType)  
 | 
        {  
 | 
            return Enum.GetValues(enumType).OfType<Enum>()  
 | 
                .Select((Enum value) => value.Cast<T>());  
 | 
        }  
 | 
  
 | 
        /// <summary>  
 | 
        /// Default cast of enum value to type T.  
 | 
        /// </summary>  
 | 
        /// <typeparam name="T">Cast output type.</typeparam>  
 | 
        /// <param name="value">Input enum value.</param>  
 | 
        /// <returns>  
 | 
        /// Default value of type T associated with the enum value:  
 | 
        ///   * if T is string: returns the enum value name as string;  
 | 
        ///   * if T is an integer type: returns the underlying enum integer value;  
 | 
        ///   * otherwise: default value for type T (e.g. null for reference types).  
 | 
        /// </returns>  
 | 
        static T Default<T>(Enum value)  
 | 
        {  
 | 
            Type enumType = value.GetType();  
 | 
            Type baseType = Enum.GetUnderlyingType(enumType);  
 | 
            Type outputType = typeof(T);  
 | 
            if (outputType.IsAssignableFrom(enumType) || outputType.IsAssignableFrom(baseType))  
 | 
                return (T)(object)value;  
 | 
            else if (outputType == typeof(string))  
 | 
                return (T)(object)Enum.GetName(value.GetType(), value);  
 | 
            else  
 | 
                return default(T);  
 | 
        }  
 | 
  
 | 
        /// <summary>  
 | 
        /// Find cast attribute.  
 | 
        /// </summary>  
 | 
        /// <typeparam name="T">Cast output type.</typeparam>  
 | 
        /// <param name="value">Input enum value.</param>  
 | 
        /// <returns>  
 | 
        /// First cast attribute of type T found associated with the enum value, or null in case a  
 | 
        /// suitable attribute is not found.  
 | 
        /// </returns>  
 | 
        static ICast<T> FindCastAttrib<T>(Enum value)  
 | 
        {  
 | 
            Type enumType = value.GetType();  
 | 
  
 | 
            string valueName = Enum.GetName(enumType, value);  
 | 
            if (string.IsNullOrEmpty(valueName))  
 | 
                return null;  
 | 
  
 | 
            FieldInfo enumField = enumType.GetField(valueName);  
 | 
            if (enumField == null)  
 | 
                return null;  
 | 
  
 | 
            return CastAttribTypes  
 | 
                .Where(type => typeof(ICast<T>).IsAssignableFrom(type))  
 | 
                .Select(type => Attribute.GetCustomAttribute(enumField, type) as ICast<T>)  
 | 
                .FirstOrDefault();  
 | 
        }  
 | 
  
 | 
        static IEnumerable<Type> _CastAttribTypes;  
 | 
        /// <summary>  
 | 
        /// List of cast attribute types.  
 | 
        /// </summary>  
 | 
        /// <remarks>  
 | 
        /// Future cast attribute types need to be added to this list.  
 | 
        /// </remarks>  
 | 
        static IEnumerable<Type> CastAttribTypes => _CastAttribTypes  
 | 
            ?? (_CastAttribTypes = new[]  
 | 
            {  
 | 
                typeof(StringAttribute)  
 | 
            });  
 | 
  
 | 
    }  
 | 
}  
 |