context: //
  // System.Enum.cs
  //
  // Authors:
  //   Miguel de Icaza (miguel@ximian.com)
  //   Nick Drochak (ndrochak@gol.com)
  //   Gonzalo Paniagua Javier (gonzalo@ximian.com)
  //
  // (C) Ximian, Inc.  http://www.ximian.com
  //
  //
  
  //
  // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
  //
  // Permission is hereby granted, free of charge, to any person obtaining
  // a copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to
  // permit persons to whom the Software is furnished to do so, subject to
  // the following conditions:
  // 
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  // 
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  //
  
  using System.Collections;
  using System.Collections.Generic;
  using System.Globalization;
  using System.Runtime.CompilerServices;
  using System.Runtime.InteropServices;
  
  namespace System
  {
  	internal struct MonoEnumInfo
  	{
  		internal Type utype;
  		internal Array values;
  		internal string[] names;
  		internal Dictionary<string, int> name_hash;
  		[ThreadStatic]
  		static Dictionary<Type, MonoEnumInfo> cache;
  		static readonly Dictionary<Type, MonoEnumInfo> global_cache = new Dictionary<Type, MonoEnumInfo> ();
  		static object global_cache_monitor = new object ();
  		
  		[MethodImplAttribute (MethodImplOptions.InternalCall)]
  		private static extern void get_enum_info (Type enumType, out MonoEnumInfo info);
  
  		//
  		// These comparers are needed because enumerations must be compared
  		// using unsigned values so that negative numbers can be looked up
  		// See bug: #371559
  		//
  		internal static SByteComparer  sbyte_comparer = new SByteComparer ();
  		internal static ShortComparer short_comparer = new ShortComparer ();
  		internal static IntComparer   int_comparer = new IntComparer ();
  		internal static LongComparer  long_comparer = new LongComparer ();
  		
  		internal class SByteComparer : IComparer, System.Collections.Generic.IComparer<sbyte>
  		{
  			public int Compare (object x, object y)
  			{
  				sbyte ix = (sbyte) x;
  				sbyte iy = (sbyte) y;
  				
  				return ((byte) ix) - ((byte) iy);
  			}
  
  			public int Compare (sbyte ix, sbyte iy)
  			{
  				return ((byte) ix) - ((byte) iy);
  			}
  		}
  		
  		internal class ShortComparer : IComparer, System.Collections.Generic.IComparer<short>
  	  	{
  			public int Compare (object x, object y)
  			{
  				short ix = (short) x;
  				short iy = (short) y;
  				
  				return ((ushort) ix) - ((ushort) iy);
  			}
  
  			public int Compare (short ix, short iy)
  			{
  				return ((ushort) ix) - ((ushort) iy);
  			}
  		}
  		
  		internal class IntComparer : IComparer, System.Collections.Generic.IComparer<int>
  		  {
  			public int Compare (object x, object y)
  			{
  				int ix = (int) x;
  				int iy = (int) y;
  
  				if (ix == iy)
  					return 0;
  
  				if (((uint) ix) < ((uint) iy))
  					return -1;
  				return 1;
  			}
  
  			public int Compare (int ix, int iy)
  			{
  				if (ix == iy)
  					return 0;
  
  				if (((uint) ix) < ((uint) iy))
  					return -1;
  				return 1;
  			}
  		}
  
  		internal class LongComparer : IComparer, System.Collections.Generic.IComparer<long>
  		{
  			public int Compare (object x, object y)
  			{
  				long ix = (long) x;
  				long iy = (long) y;
  				
  				if (ix == iy)
  					return 0;
  				if (((ulong) ix) < ((ulong) iy))
  					return -1;
  				return 1;
  			}
  
  			public int Compare (long ix, long iy)
  			{
  				if (ix == iy)
  					return 0;
  				if (((ulong) ix) < ((ulong) iy))
  					return -1;
  				return 1;
  			}
  		}
  
  		private MonoEnumInfo (MonoEnumInfo other)
  		{
  			utype = other.utype;
  			values = other.values;
  			names = other.names;
  			name_hash = other.name_hash;
  		}
  		
  		internal static void GetInfo (Type enumType, out MonoEnumInfo info)
  		{
  			/* First check the thread-local cache without locking */
  			if (cache != null && cache.TryGetValue (enumType, out info)) {
  				return;
  			}
  
  			/* Threads could die, so keep a global cache too */
  			lock (global_cache_monitor) {
  				if (global_cache.TryGetValue (enumType, out info)) {
  					if (cache == null)
  						cache = new Dictionary<Type, MonoEnumInfo> ();
  
  					cache [enumType] = info;
  					return;
  				}
  			}
  
  			get_enum_info (enumType, out info);
  
  			Type et = Enum.GetUnderlyingType (enumType);
  			SortEnums (et, info.values, info.names);
  			
  			if (info.names.Length > 50) {
  				info.name_hash = new Dictionary<string, int> (info.names.Length);
  				for (int i = 0; i <  info.names.Length; ++i)
  					info.name_hash [info.names [i]] = i;
  			}
  			MonoEnumInfo cached = new MonoEnumInfo (info);
  			lock (global_cache_monitor) {
  				global_cache [enumType] = cached;
  			}
  		}
  		
  		internal static void SortEnums (Type et, Array values, Array names)
  		{
  			IComparer ic = null;
  			if (et == typeof (int))
  				ic = int_comparer;
  			else if (et == typeof (short))
  				ic = short_comparer;
  			else if (et == typeof (sbyte))
  				ic = sbyte_comparer;
  			else if (et == typeof (long))
  				ic = long_comparer;
  			
  			Array.Sort (values, names, ic);
  		}
  	};
  
  	[Serializable]
  	[ComVisible (true)]
  	public abstract class Enum : ValueType, IComparable, IConvertible, IFormattable
  	{
  		protected Enum ()
  		{
  		}
  
  		// IConvertible methods Start -->
  		public TypeCode GetTypeCode ()
  		{
  			return Type.GetTypeCode (GetUnderlyingType (this.GetType ()));
  		}
  
  		bool IConvertible.ToBoolean (IFormatProvider provider)
  		{
  			return Convert.ToBoolean (Value, provider);
  		}
  
  		byte IConvertible.ToByte (IFormatProvider provider)
  		{
  			return Convert.ToByte (Value, provider);
  		}
  
  		char IConvertible.ToChar (IFormatProvider provider)
  		{
  			return Convert.ToChar (Value, provider);
  		}
  
  		DateTime IConvertible.ToDateTime (IFormatProvider provider)
  		{
  			return Convert.ToDateTime (Value, provider);
  		}
  
  		decimal IConvertible.ToDecimal (IFormatProvider provider)
  		{	
  			return Convert.ToDecimal (Value, provider);
  		}
  
  		double IConvertible.ToDouble (IFormatProvider provider)
  		{	
  			return Convert.ToDouble (Value, provider);
  		}
  
  		short IConvertible.ToInt16 (IFormatProvider provider)
  		{
  			return Convert.ToInt16 (Value, provider);
  		}
  
  		int IConvertible.ToInt32 (IFormatProvider provider)
  		{
  			return Convert.ToInt32 (Value, provider);
  		}
  
  		long IConvertible.ToInt64 (IFormatProvider provider)
  		{
  			return Convert.ToInt64 (Value, provider);
  		}
  
  		sbyte IConvertible.ToSByte (IFormatProvider provider)
  		{
  			return Convert.ToSByte (Value, provider);
  		}
  
  		float IConvertible.ToSingle (IFormatProvider provider)
  		{
  			return Convert.ToSingle (Value, provider);
  		}
  
  		object IConvertible.ToType (Type targetType, IFormatProvider provider)
  		{
  			if (targetType == null)
  				throw new ArgumentNullException ("targetType");
  
  			return Convert.ToType (this, targetType, provider, false);
  		}
  
  		ushort IConvertible.ToUInt16 (IFormatProvider provider)
  		{
  			return Convert.ToUInt16 (Value, provider);
  		}
  
  		uint IConvertible.ToUInt32 (IFormatProvider provider)
  		{
  			return Convert.ToUInt32 (Value, provider);
  		}
  
  		ulong IConvertible.ToUInt64 (IFormatProvider provider)
  		{
  			return Convert.ToUInt64 (Value, provider);
  		}
  
  		// <-- End IConvertible methods
  
  		[MethodImplAttribute (MethodImplOptions.InternalCall)]
  		private extern object get_value ();
  
  		// wrap the icall into a property so we don't hav to use the icall everywhere
  		private object Value {
  			get { return get_value (); }
  		}
  
  		[ComVisible (true)]
  		public static Array GetValues (Type enumType)
  		{
  			if (enumType == null)
  				throw new ArgumentNullException ("enumType");
  
  			if (!enumType.IsEnum)
  				throw new ArgumentException ("enumType is not an Enum type.", "enumType");
  
  			MonoEnumInfo info;
  			MonoEnumInfo.GetInfo (enumType, out info);
  			return (Array) info.values.Clone ();
  		}
  
  		[ComVisible (true)]
  		public static string[] GetNames (Type enumType)
  		{
  			if (enumType == null)
  				throw new ArgumentNullException ("enumType");
  
  			if (!enumType.IsEnum)
  				throw new ArgumentException ("enumType is not an Enum type.");
  
  			MonoEnumInfo info;
  			MonoEnumInfo.GetInfo (enumType, out info);
  			return (string []) info.names.Clone ();
  		}
  
  		//
  		// The faster, non-boxing version.   It must use the special MonoEnumInfo.xxx_comparers
  		// to ensure that we are perfoming bitwise compares, and not signed compares.
  		//
  		// It also tries to use the non-boxing version of the various Array.BinarySearch methods
  		//
  		static int FindPosition (Type enumType, object value, Array values)
  		{
  			switch (Type.GetTypeCode (GetUnderlyingType (enumType))) {
  			case TypeCode.SByte:
  				sbyte [] sbyte_array = values as sbyte [];
  				return Array.BinarySearch (sbyte_array, (sbyte) value,  MonoEnumInfo.sbyte_comparer);
  
  			case TypeCode.Byte:
  				byte [] byte_array = values as byte [];
  				return Array.BinarySearch (byte_array, (byte) value);
  
  			case TypeCode.Int16:
  				short [] short_array = values as short [];
  				return Array.BinarySearch (short_array, (short)value, MonoEnumInfo.short_comparer);
  
  			case TypeCode.UInt16:
  				ushort [] ushort_array = values as ushort [];
  				return Array.BinarySearch (ushort_array, (ushort)value);
  		
  			case TypeCode.Int32:
  				int[] int_array = values as int[];
  				return Array.BinarySearch (int_array, (int)value, MonoEnumInfo.int_comparer);
  
  			case TypeCode.UInt32:
  				uint[] uint_array = values as uint [];
  				return Array.BinarySearch (uint_array, (uint)value);			
  			
  			case TypeCode.Int64:
  				long [] long_array = values as long [];
  				return Array.BinarySearch (long_array, (long) value,  MonoEnumInfo.long_comparer);
  
  			case TypeCode.UInt64:
  				ulong [] ulong_array = values as ulong [];
  				return Array.BinarySearch (ulong_array, (ulong) value);
  
  			default:
  				// This should never happen
  				throw new NotSupportedException ("Unknown underlying enum type");
  			}
  		}
  	
  		[ComVisible (true)]
  		public static string GetName (Type enumType, object value)
  		{
  			if (enumType == null)
  				throw new ArgumentNullException ("enumType");
  			if (value == null)
  				throw new ArgumentNullException ("value");
  
  			if (!enumType.IsEnum)
  				throw new ArgumentException ("enumType is not an Enum type.", "enumType");
  
  			MonoEnumInfo info;
  			value = ToObject (enumType, value);
  			MonoEnumInfo.GetInfo (enumType, out info);
  
  			int i = FindPosition (enumType, value, info.values);
  			return (i >= 0) ? info.names [i] : null;
  		}
  
  		[ComVisible (true)]
  		public static bool IsDefined (Type enumType, object value)
  		{
  			if (enumType == null)
  				throw new ArgumentNullException ("enumType");
  			if (value == null)
  				throw new ArgumentNullException ("value");
  
  			if (!enumType.IsEnum)
  				throw new ArgumentException ("enumType is not an Enum type.", "enumType");
  
  			MonoEnumInfo info;
  			MonoEnumInfo.GetInfo (enumType, out info);
  
  			Type vType = value.GetType ();
  			if (vType == typeof(String)) {
  				return ((IList)(info.names)).Contains (value);
  			} else if ((vType == info.utype) || (vType == enumType)) {
  				value = ToObject (enumType, value);
  				MonoEnumInfo.GetInfo (enumType, out info);
  
  				return FindPosition (enumType, value, info.values) >= 0;
  			} else {
  				throw new ArgumentException("The value parameter is not the correct type. "
  					+ "It must be type String or the same type as the underlying type "
  					+ "of the Enum.");
  			}
  		}
  
  		[MethodImplAttribute (MethodImplOptions.InternalCall)]
  		private static extern Type get_underlying_type (Type enumType);
  
  		[ComVisible (true)]
  		public static Type GetUnderlyingType (Type enumType)
  		{
  			if (enumType == null)
  				throw new ArgumentNullException ("enumType");
  
  			if (!enumType.IsEnum)
  				throw new ArgumentException ("enumType is not an Enum type.", "enumType");
  
  			return get_underlying_type (enumType);
  		}
  
  		[ComVisible (true)]
  		public static object Parse (Type enumType, string value)
  		{
  			// Note: Parameters are checked in the other overload
  			return Parse (enumType, value, false);
  		}
  
  		private static int FindName (IDictionary<string, int> name_hash, string [] names, string name,  bool ignoreCase)
  		{
  			if (!ignoreCase) {
  				/* For enums with many values, use a hash table */
  				if (name_hash != null) {
  					int val;
  					if (name_hash.TryGetValue (name, out val))
  						return val;
  				} else {
  					for (int i = 0; i < names.Length; ++i) {
  						if (name == names [i])
  							return i;
  					}
  				}
  			} else {
  				for (int i = 0; i < names.Length; ++i) {
  					if (String.Compare (name, names [i], ignoreCase, CultureInfo.InvariantCulture) == 0)
  						return i;
  				}
  			}
  			return -1;
  		}
  
  		// Helper function for dealing with [Flags]-style enums.
  		private static ulong GetValue (object value, TypeCode typeCode)
  		{
  			switch (typeCode) {
  			case TypeCode.Byte:
  				return (byte) value;
  			case TypeCode.SByte:
  				return (byte) ((sbyte) value);
  			case TypeCode.Int16:
  				return (ushort) ((short) value);
  			case TypeCode.Int32:
  				return (uint) ((int) value);
  			case TypeCode.Int64:
  				return (ulong) ((long) value);
  			case TypeCode.UInt16:
  				return (ushort) value;
  			case TypeCode.UInt32:
  				return (uint) value;
  			case TypeCode.UInt64:
  				return (ulong) value;
  			}
  			throw new ArgumentException ("typeCode is not a valid type code for an Enum");
  		}
  
  		[ComVisible(true)]
  		public static object Parse (Type enumType, string value, bool ignoreCase)
  		{
  			if (enumType == null)
  				throw new ArgumentNullException ("enumType");
  
  			if (value == null)
  				throw new ArgumentNullException ("value");
  
  			if (!enumType.IsEnum)
  				throw new ArgumentException ("enumType is not an Enum type.", "enumType");
  
  			value = value.Trim ();
  			if (value.Length == 0)
  				throw new ArgumentException ("An empty string is not considered a valid value.");
  
  			object result;
  			if (!Parse (enumType, value, ignoreCase, out result))
  				throw new ArgumentException (String.Format ("The requested value '{0}' was not found.", value));
  
  			return result;
  		}
  
  		static char [] split_char;
  
  		static bool Parse<TEnum> (Type enumType, string value, bool ignoreCase, out TEnum result)
  		{
  			result = default (TEnum);
  
  			MonoEnumInfo info;
  			MonoEnumInfo.GetInfo (enumType, out info);
  
  			// is 'value' a named constant?
  			int loc = FindName (info.name_hash, info.names, value, ignoreCase);
  			if (loc >= 0) {
  				result = (TEnum) info.values.GetValue (loc);
  				return true;
  			}
  
  			TypeCode typeCode = ((Enum) info.values.GetValue (0)).GetTypeCode ();
  
  			// is 'value' a list of named constants?
  			if (value.IndexOf (',') != -1) {
  				if (split_char == null)
  					split_char = new [] { ',' };
  				string [] names = value.Split (split_char);
  				ulong retVal = 0;
  				for (int i = 0; i < names.Length; ++i) {
  					loc = FindName (info.name_hash, info.names, names [i].Trim (), ignoreCase);
  					if (loc < 0)
  						return false;
  
  					retVal |= GetValue (info.values.GetValue (loc), typeCode);
  				}
  				result = (TEnum) ToObject (enumType, retVal);
  				return true;
  			}
  
  			// is 'value' a number?
  			switch (typeCode) {
  			case TypeCode.SByte:
  				sbyte sb;
  				if (!SByte.TryParse (value, out sb))
  					return false;
  				result = (TEnum) ToObject (enumType, sb);
  				break;
  			case TypeCode.Byte:
  				byte b;
  				if (!Byte.TryParse (value, out b))
  					return false;
  				result = (TEnum) ToObject (enumType, b);
  				break;
  			case TypeCode.Int16:
  				short i16;
  				if (!Int16.TryParse (value, out i16))
  					return false;
  				result = (TEnum) ToObject (enumType, i16);
  				break;
  			case TypeCode.UInt16:
  				ushort u16;
  				if (!UInt16.TryParse (value, out u16))
  					return false;
  
gt: 				result = (TEnum) ToObject (enumType, u16);