/*
 * EnumOps.cs --
 *
 * Copyright (c) 2007-2012 by Joe Mistachkin.  All rights reserved.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: $
 */

using System;

#if NET_40
using System.Collections.Generic;
#else
using System.Collections;
#endif

using System.Globalization;
using System.Reflection;
using Eagle._Attributes;
using Eagle._Components.Public;
using Eagle._Constants;

#if !NET_40
using Eagle._Containers.Private;
#endif

using Eagle._Containers.Public;

namespace Eagle._Components.Private
{
    [ObjectId("32db1eb0-d7c8-4a31-82bf-215ae3d9086d")]
    internal static class EnumOps
    {
        #region Private Constants
#if NET_40
        private static readonly string TryParseMethodName = "TryParse";
#endif

        internal const char AddFlagOperator = Characters.PlusSign;
        internal const char RemoveFlagOperator = Characters.MinusSign;
        internal const char SetFlagOperator = Characters.EqualSign;
        internal const char SetAddFlagOperator = Characters.Colon;

        internal static readonly char DefaultFlagOperator = SetAddFlagOperator;
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Private Data
#if NET_40
        private static object enumTryParseSyncRoot = new object();
        private static MethodInfo enumTryParse = null;
        private static Dictionary<Type, MethodInfo> enumTryParseCache;
#else
        private static object enumCacheSyncRoot = new object();
        private static TypePairDictionary<StringList, UlongList> enumCache = null;
#endif
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Public Methods
        public static bool IsFlagsEnum(
            Type enumType
            )
        {
            return (enumType != null) &&
                   enumType.IsEnum &&
                   enumType.IsDefined(typeof(FlagsAttribute), false);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

#if ISOLATED_PLUGINS
        public static object ConvertToTypeCodeValue(
            Type enumType,
            object value,
            ref TypeCode typeCode,
            ref Result error
            )
        {
            if (enumType != null)
            {
                object enumValue = TryGetEnum(enumType, value, ref error);

                if (enumValue != null)
                {
                    IConvertible convertible = enumValue as IConvertible;

                    if (convertible != null)
                    {
                        try
                        {
                            typeCode = Convert.GetTypeCode(enumValue);

                            switch (typeCode)
                            {
                                case TypeCode.Byte:
                                    return convertible.ToByte(null);
                                case TypeCode.SByte:
                                    return convertible.ToSByte(null);
                                case TypeCode.Int16:
                                    return convertible.ToInt16(null);
                                case TypeCode.UInt16:
                                    return convertible.ToUInt16(null);
                                case TypeCode.Int32:
                                    return convertible.ToInt32(null);
                                case TypeCode.UInt32:
                                    return convertible.ToUInt32(null);
                                case TypeCode.Int64:
                                    return convertible.ToInt64(null);
                                case TypeCode.UInt64:
                                    return convertible.ToUInt64(null);
                                default:
                                    {
                                        error = String.Format(
                                            "enumerated type \"{0}\" value " +
                                            "\"{1}\" has unsupported type " +
                                            "code \"{2}\"", enumType, enumValue,
                                            typeCode);

                                        break;
                                    }
                            }
                        }
                        catch (Exception e)
                        {
                            error = e;
                        }
                    }
                    else
                    {
                        error = String.Format(
                            "enumerated type \"{0}\" value " +
                            "is not convertible", enumType);
                    }
                }
            }

            return null;
        }
#endif

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static object TryGetEnum(
            Type enumType,
            object value,
            ref Result error
            )
        {
            if (enumType != null)
            {
                if (enumType.IsEnum)
                {
                    try
                    {
                        //
                        // NOTE: Try to get the value as the specified enum
                        //       type.
                        //
                        return Enum.ToObject(enumType, value);
                    }
                    catch (Exception e)
                    {
                        error = e;
                    }
                }
                else
                {
                    error = String.Format(
                        "type \"{0}\" is not an enumeration",
                        enumType.FullName);
                }
            }
            else
            {
                error = "invalid type";
            }

            return null;
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Private TryParseEnum Methods
        private static object TryParseWideIntegerEnum(
            Type enumType,
            string value,
            CultureInfo cultureInfo,
            ref Result error
            )
        {
            if (enumType != null)
            {
                if (enumType.IsEnum)
                {
                    try
                    {
                        if (cultureInfo == null)
                            cultureInfo = Value.DefaultCulture;

                        ulong ulongValue = 0;

                        if (Value.GetUnsignedWideInteger2(
                                value, ValueFlags.AnyWideInteger, cultureInfo,
                                ref ulongValue, ref error) == ReturnCode.Ok)
                        {
                            object enumValue = TryGetEnum(enumType, ulongValue, ref error);

                            //
                            // BUGFIX: Verify that the returned enum value is numerically
                            //         identical to the provided unsigned long integer.  This
                            //         will make things a bit slower; however, it is necessary.
                            //
                            if ((enumValue != null) &&
                                (enumValue.GetType() == enumType) &&
                                (ulongValue == ToULong(enumType, enumValue as Enum, cultureInfo)))
                            {
                                return enumValue;
                            }
                        }
                        else
                        {
                            long longValue = 0;

                            if (Value.GetWideInteger2(
                                    value, ValueFlags.AnyWideInteger, cultureInfo,
                                    ref longValue, ref error) == ReturnCode.Ok)
                            {
                                object enumValue = TryGetEnum(enumType, longValue, ref error);

                                //
                                // BUGFIX: Verify that the returned enum value is numerically
                                //         identical to the provided signed long integer.  This
                                //         will make things a bit slower; however, it is necessary.
                                //
                                if ((enumValue != null) &&
                                    (enumValue.GetType() == enumType) &&
                                    (longValue == ToLong(enumType, enumValue as Enum, cultureInfo)))
                                {
                                    return enumValue;
                                }
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        error = e;
                    }
                }
                else
                {
                    error = String.Format(
                        "type \"{0}\" is not an enumeration",
                        enumType.FullName);
                }
            }
            else
            {
                error = "invalid type";
            }

            return null;
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Public TryParseEnum Methods
        public static object TryParseEnum(
            Type enumType,
            string value,
            bool noCase
            )
        {
            Result error = null;

            return TryParseEnum(enumType, value, noCase, ref error);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static object TryParseEnum(
            Type enumType,
            string value,
            bool noCase,
            ref Result error
            )
        {
#if NET_40
            return TryParseEnumBuiltIn(enumType, value, true, true, noCase, ref error);
#else
            return TryParseEnumFast(enumType, value, true, true, noCase, ref error);
            // return TryParseEnumSlow(enumType, value, true, true, noCase, ref error);
#endif
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Private TryParseEnumBuiltIn Methods (.NET Framework 4.0)
#if NET_40
        private static MethodInfo GetEnumTryParseMethodInfo(
            ref Result error
            )
        {
            try
            {
                lock (enumTryParseSyncRoot)
                {
                    if (enumTryParse != null)
                    {
                        //
                        // NOTE: Return the existing (cached) method object.
                        //
                        return enumTryParse;
                    }
                    else
                    {
                        MethodInfo[] methodInfo = typeof(Enum).GetMethods(
                            MarshalOps.PublicStaticMethodBindingFlags);

                        if (methodInfo != null)
                        {
                            for (int index = 0; index < methodInfo.Length; index++)
                            {
                                if (methodInfo[index] != null)
                                {
                                    if (String.Compare(methodInfo[index].Name,
                                            TryParseMethodName,
                                            StringOps.SystemStringComparisonType) == 0)
                                    {
                                        ParameterInfo[] parameterInfo =
                                            methodInfo[index].GetParameters();

                                        if (parameterInfo != null)
                                        {
                                            //
                                            // NOTE: We expect there to be exactly 3 parameters.
                                            //       The first is a string, the second is a boolean,
                                            //       and the final one is a reference to a generic
                                            //       ValueType constrainted type.
                                            //
                                            if (parameterInfo.Length == 3)
                                            {
                                                if ((parameterInfo[0].ParameterType == typeof(string)) &&
                                                    (parameterInfo[1].ParameterType == typeof(bool)) &&
                                                    parameterInfo[2].ParameterType.IsByRef &&
                                                    parameterInfo[2].ParameterType.ContainsGenericParameters)
                                                {
                                                    enumTryParse = methodInfo[index];

                                                    return enumTryParse;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }

                        error = String.Format(
                            "cannot find \"{0}\" method of \"{1}\" type",
                            TryParseMethodName, typeof(Enum));
                    }
                }
            }
            catch (Exception e)
            {
                error = e;
            }

            return null;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static MethodInfo GetEnumTryParseMethodInfo(
            Type enumType,
            ref Result error
            )
        {
            if (enumType != null)
            {
                if (enumType.IsEnum)
                {
                    try
                    {
                        lock (enumTryParseSyncRoot)
                        {
                            if (enumTryParseCache == null)
                                enumTryParseCache = new Dictionary<Type, MethodInfo>();

                            MethodInfo methodInfo;

                            if (enumTryParseCache.TryGetValue(enumType, out methodInfo) &&
                                (methodInfo != null))
                            {
                                //
                                // NOTE: Return the cached TryParse method for this enumeration.
                                //
                                return methodInfo;
                            }
                            else
                            {
                                methodInfo = GetEnumTryParseMethodInfo(ref error);

                                if (methodInfo != null)
                                {
                                    //
                                    // NOTE: Construct the TryParse method with the enumeration
                                    //       type we have been passed.
                                    //
                                    methodInfo = methodInfo.MakeGenericMethod(enumType);

                                    if (methodInfo != null)
                                    {
                                        //
                                        // NOTE: Cache this TryParse method for later and then
                                        //       return it.
                                        //
                                        enumTryParseCache.Add(enumType, methodInfo);

                                        return methodInfo;
                                    }
                                    else
                                    {
                                        error = String.Format(
                                            "cannot use type \"{0}\" to make generic \"{1}\" method",
                                            enumType.FullName, TryParseMethodName);
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        error = e;
                    }
                }
                else
                {
                    error = String.Format(
                        "type \"{0}\" is not an enumeration",
                        enumType.FullName);
                }
            }
            else
            {
                error = "invalid type";
            }

            return null;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static object TryParseEnumBuiltIn(
            Type enumType,
            string value,
            bool ignoreLeading,
            bool strict, /* NOT USED */
            bool noCase,
            ref Result error
            )
        {
            object result = null;

            if (enumType != null)
            {
                if (enumType.IsEnum)
                {
                    if (!String.IsNullOrEmpty(value))
                    {
                        //
                        // HACK: This call assumes that no "field" of an enumerated type
                        //       can begin with a digit, plus sign, or minus sign.  This
                        //       is always true in C# because the fields are identifiers
                        //       and no identifier in C# may begin with anything except
                        //       a letter or underscore.  However, this may not be true
                        //       for other languages built on the CLR.
                        //
                        if (Parser.IsInteger(value[0], true))
                            result = TryParseWideIntegerEnum(enumType, value, null, ref error);

                        if (result == null)
                        {
                            MethodInfo methodInfo = GetEnumTryParseMethodInfo(enumType, ref error);

                            if (methodInfo != null)
                            {
                                try
                                {
                                    //
                                    // NOTE: Try to parse the name without the leading
                                    //       character if it is not an identifier
                                    //       character.
                                    //
                                    if (ignoreLeading && !Parser.IsIdentifier(value[0]))
                                        value = value.Substring(1);

                                    //
                                    // NOTE: We expect the [generic] Enum.TryParse method to
                                    //       accept exactly 3 arguments.  The first argument
                                    //       must be a string.  The second argument must be
                                    //       a boolean.  The third argument is supposed to
                                    //       be a ByRef generic enumerated type.
                                    //
                                    object[] args = new object[] { value, noCase, null };

                                    //
                                    // NOTE: Attempt to invoke the TryParse method that has
                                    //       been fully constructed for the proper enumeration
                                    //       type.
                                    //
                                    bool? success = methodInfo.Invoke(null, args) as bool?;

                                    //
                                    // NOTE: Make sure the method returned a boolean and that it
                                    //       has a non-zero value.
                                    //
                                    if ((success != null) && (bool)success)
                                        //
                                        // NOTE: Success, extract the third argument as the
                                        //       enumeration valued parsed result.
                                        //
                                        result = args[2]; /* cannot fail */
                                    else
                                        error = ScriptOps.BadValue(
                                            null, String.Format("\"{0}\" value", enumType.FullName),
                                            value, Enum.GetNames(enumType), null, null);
                                }
                                catch (Exception e)
                                {
                                    error = e;
                                }
                            }
                        }
                    }
                    else
                    {
                        error = String.Format(
                            "invalid \"{0}\" value",
                            enumType.FullName);
                    }
                }
                else
                {
                    error = String.Format(
                        "type \"{0}\" is not an enumeration",
                        enumType.FullName);
                }
            }
            else
            {
                error = "invalid type";
            }

            return result;
        }
#endif
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Private TryParseEnumFast Methods (.NET Framework 2.0, 3.5, and Mono)
#if !NET_40
        private static ReturnCode GetEnumNamesAndValues(
            Type enumType,
            ref StringList enumNames,
            ref UlongList enumValues,
            ref Result error
            )
        {
#if !MONO
            if (!CommonOps.Runtime.IsMono())
                return GetEnumNamesAndValuesFast(enumType, ref enumNames, ref enumValues, ref error);
            else
#endif
                return GetEnumNamesAndValuesSlow(enumType, ref enumNames, ref enumValues, ref error);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

#if !MONO
        private static ReturnCode GetEnumNamesAndValuesFast(
            Type enumType,
            ref StringList enumNames,
            ref UlongList enumValues,
            ref Result error
            )
        {
            if (enumType != null)
            {
                if (enumType.IsEnum)
                {
                    #region Dead Code
#if NET_20_FAST_ENUM
                    int retry = 0;

                fallback:
                    if (!CommonOps.Runtime.IsMono() &&
                        (retry == 0) &&
                        (enumType.BaseType == typeof(Enum)))
                    {
                        try
                        {
                        retry:
                            object[] args = new object[] { enumType, null, null };

                            //
                            // HACK: This method is private in the .NET Framework and we
                            //       need to use it; therefore, we use reflection to
                            //       invoke it.
                            //
                            // BUGBUG: Something inside this method occasionally throws
                            //         System.ExecutionEngineException.  It appears to be
                            //         some kind of race condition with the GC.
                            //
                            typeof(Enum).InvokeMember(MarshalOps.GetEnumValuesMethodName,
                                MarshalOps.PrivateStaticMethodBindingFlags, null,
                                null, args); /* throw */

                            //
                            // BUGBUG: Why is this required here?  The above call, seems to
                            //         intermittently fail on our custom "Boolean" enumeration
                            //         type; however, the operation succeeds upon being retried?
                            //
                            if ((retry < 1) && ((args == null) || (args[1] == null) ||
                                (args[2] == null)))
                            {
                                retry++;

                                goto retry;
                            }

                            //
                            // HACK: If we still fail, fallback on the known-good method.
                            //
                            if ((args == null) || (args[1] == null) || (args[2] == null))
                            {
                                retry++;

                                goto fallback;
                            }

                            enumNames = new StringList((string[])args[2]); /* throw */
                            enumValues = new UlongList((ulong[])args[1]); /* throw */

                            return ReturnCode.Ok;
                        }
                        catch (Exception e)
                        {
                            error = e;
                        }
                    }
                    else
#endif
                    #endregion
                    {
                        //
                        // NOTE: *FALLBACK* In case the fast method does not work.  Even
                        //       though the InternalGetEnumValues does fail on occasion,
                        //       it does not seem to do so repeatedly; therefore, this
                        //       code path should be rarely used when not running on Mono.
                        //
                        return GetEnumNamesAndValuesSlow(enumType,
                            ref enumNames, ref enumValues, ref error);
                    }
                }
                else
                {
                    error = String.Format(
                        "type \"{0}\" is not an enumeration",
                        enumType.FullName);
                }
            }
            else
            {
                error = "invalid type";
            }

            return ReturnCode.Error;
        }
#endif

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static ReturnCode GetEnumNamesAndValuesSlow(
            Type enumType,
            ref StringList enumNames,
            ref UlongList enumValues,
            ref Result error
            )
        {
            try
            {
                lock (enumCacheSyncRoot)
                {
                    if (enumCache == null)
                        enumCache = new TypePairDictionary<StringList, UlongList>();

                retry:
                    if (enumCache.ContainsKey(enumType))
                    {
                        enumNames = enumCache[enumType].X;
                        enumValues = enumCache[enumType].Y;

                        if ((enumNames == null) || (enumValues == null))
                        {
                            //
                            // NOTE: This cache entry is corrupted?  Try to remove
                            //       it from the cache and just fetch the names/values
                            //       for this enum type again.
                            //
                            enumCache.Remove(enumType);

                            goto retry;
                        }
                    }
                    else
                    {
                        PairList<object> pairs = new PairList<object>();

                        //
                        // NOTE: Get all the static public fields, these are
                        //       the values.
                        //
                        FieldInfo[] fieldInfo = enumType.GetFields(
                            MarshalOps.EnumFieldBindingFlags);

                        if (fieldInfo != null)
                        {
                            foreach (FieldInfo thisFieldInfo in fieldInfo)
                            {
                                //
                                // NOTE: Add the name and the value itself to the
                                //       list (via our Pair object).
                                //
                                pairs.Add(new ObjectPair(thisFieldInfo.Name,
                                    thisFieldInfo.GetValue(null)));
                            }
                        }

                        //
                        // NOTE: Sort the list based on the underlying integral
                        //       values.
                        //
                        pairs.Sort(new _Comparers.Pair<object>(
                            PairComparison.LYRY, true)); /* throw */

                        //
                        // NOTE: Populate the result lists.
                        //
                        StringList names = new StringList();
                        UlongList values = new UlongList();

                        foreach (Pair<object> pair in pairs)
                        {
                            names.Add((string)pair.X); /* throw */
                            values.Add(ToULong(enumType, (Enum)pair.Y, null)); /* throw */
                        }

                        //
                        // NOTE: Commit changes to the variables provided by the
                        //       caller.
                        //
                        enumNames = names;
                        enumValues = values;

                        //
                        // NOTE: Save in the cache for the next usage (this is
                        //       especially important for commonly used enum types
                        //       like Boolean).
                        //
                        enumCache.Add(enumType,
                            new AnyPair<StringList, UlongList>(enumNames, enumValues));
                    }

                    return ReturnCode.Ok;
                }
            }
            catch (Exception e)
            {
                error = e;
            }

            return ReturnCode.Error;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static object TryParseEnumFast(
            Type enumType,
            string value,
            bool ignoreLeading,
            bool strict,
            bool noCase,
            ref Result error
            )
        {
            object result = null;

            if (enumType != null)
            {
                if (enumType.IsEnum)
                {
                    if (!String.IsNullOrEmpty(value))
                    {
                        //
                        // HACK: This call assumes that no "field" of an enumerated type
                        //       can begin with a digit, plus sign, or minus sign.  This
                        //       is always true in C# because the fields are identifiers
                        //       and no identifier in C# may begin with anything except
                        //       a letter or underscore.  However, this may not be true
                        //       for other languages built on the CLR.
                        //
                        if (Parser.IsInteger(value[0], true))
                            result = TryParseWideIntegerEnum(enumType, value, null, ref error);

                        if (result == null)
                        {
                            //
                            // NOTE: Get the list of possible values for this enumeration
                            //       type.
                            //
                            StringList enumNames = null;
                            UlongList enumValues = null;

                            if (GetEnumNamesAndValues(enumType,
                                    ref enumNames, ref enumValues, ref error) == ReturnCode.Ok)
                            {
                                ulong newValue = 0;

                                //
                                // NOTE: Break the string into multiple values, if
                                //       necessary.
                                //
                                string[] values = value.Split(Characters.Comma);

                                //
                                // NOTE: Figure out how we would like to compare name
                                //       strings.
                                //
                                if (values != null)
                                {
                                    StringComparison comparisonType = noCase ?
                                        StringOps.SystemNoCaseStringComparisonType :
                                        StringOps.SystemStringComparisonType;

                                    for (int index = 0; index < values.Length; index++)
                                    {
                                        //
                                        // NOTE: Grab the current item and clean it.
                                        //
                                        string item = values[index].Trim();

                                        //
                                        // NOTE: Skip over empty entries.
                                        //
                                        if (!String.IsNullOrEmpty(item))
                                        {
                                            //
                                            // NOTE: Try to find the name in the list of valid
                                            //       ones for this enumerated type.
                                            //
                                            int index2 = enumNames.IndexOf(item, 0, comparisonType);

                                            //
                                            // NOTE: Try to find the name without the leading
                                            //       character if it is not an identifier
                                            //       character.
                                            //
                                            if (ignoreLeading && (index2 == Index.Invalid) &&
                                                !Parser.IsIdentifier(item[0]))
                                            {
                                                index2 = enumNames.IndexOf(
                                                    item.Substring(1), 0, comparisonType);
                                            }

                                            //
                                            // NOTE: Did we find the name in the list of valid
                                            //       ones for this enumerated type?
                                            //
                                            if (index2 != Index.Invalid)
                                            {
                                                //
                                                // NOTE: Found it.  Combine the underlying value
                                                //       with our result so far.
                                                //
                                                newValue |= enumValues[index2];
                                            }
                                            else if (strict)
                                            {
                                                error = ScriptOps.BadValue(
                                                    null, String.Format("\"{0}\" value", enumType.FullName),
                                                    item, Enum.GetNames(enumType), null, null);

                                                return null;
                                            }
                                        }
                                    }
                                }

                                result = TryGetEnum(enumType, newValue, ref error);
                            }
                        }
                    }
                    else
                    {
                        error = String.Format(
                            "invalid \"{0}\" value",
                            enumType.FullName);
                    }
                }
                else
                {
                    error = String.Format(
                        "type \"{0}\" is not an enumeration",
                        enumType.FullName);
                }
            }
            else
            {
                error = "invalid type";
            }

            return result;
        }
#endif
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Dead Code
        #region Private TryParseEnumSlow Methods (Obsolete)
#if DEAD_CODE
        [Obsolete()]
        private static object TryParseEnumSlow( /* NOT USED */
            Type enumType,
            string value,
            bool ignoreLeading,
            bool strict, /* NOT USED */
            bool noCase,
            ref Result error
            )
        {
            if (enumType != null)
            {
                if (enumType.IsEnum)
                {
                    if (!String.IsNullOrEmpty(value))
                    {
                        try
                        {
                            //
                            // NOTE: First, try for an exact match.
                            //
                            // NOTE: No TryParse for enumerations, eh?
                            //
                            return Enum.Parse(enumType, value, noCase); /* throw */
                        }
                        catch
                        {
                            if (ignoreLeading && !Parser.IsIdentifier(value[0]))
                            {
                                try
                                {
                                    //
                                    // NOTE: Ok, now try to remove a leading non-identifier
                                    //       character.
                                    //
                                    // NOTE: No TryParse for enumerations, eh?
                                    //
                                    return Enum.Parse(
                                        enumType, value.Substring(1), noCase); /* throw */
                                }
                                catch
                                {
                                    // do nothing.
                                }
                            }
                        }
                    }

                    error = ScriptOps.BadValue(
                        null, null, value,
                        Enum.GetNames(enumType), null, null);
                }
                else
                {
                    error = String.Format(
                        "type \"{0}\" is not an enumeration",
                        enumType.FullName);
                }
            }
            else
            {
                error = "invalid type";
            }

            return null;
        }
#endif
        #endregion
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Private ToULong Methods
#if !MONO
        private static long ToLongFast(
            Type enumType,
            Enum value,
            CultureInfo cultureInfo
            ) /* SAFE */
        {
            return ConversionOps.ToLong(ToULongFast(
                enumType, value, cultureInfo)); /* throw */
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static ulong ToULongFast(
            Type enumType,
            Enum value,
            CultureInfo cultureInfo
            ) /* SAFE */
        {
            //
            // HACK: This method is private in the .NET Framework and we
            //       need to use it; therefore, we use reflection to
            //       invoke it.
            //
            return (ulong)typeof(Enum).InvokeMember("ToUInt64",
                MarshalOps.PrivateStaticMethodBindingFlags,
                null, null, new object[] { value }); /* throw */
        }
#endif

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static long ToLongSlow(
            Type enumType,
            Enum value,
            CultureInfo cultureInfo
            ) /* SAFE */
        {
            //
            // NOTE: Use the routine for converting to an unsigned long integer
            //       and then safely convert it to a signed long integer.
            //
            return ConversionOps.ToLong(ToULongSlow(enumType, value, cultureInfo));
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static ulong ToULongSlow(
            Type enumType,
            Enum value,
            CultureInfo cultureInfo
            ) /* SAFE */
        {
            if (cultureInfo == null)
                cultureInfo = Value.DefaultCulture;

            TypeCode typeCode = Convert.GetTypeCode(value);

            switch (typeCode)
            {
                case TypeCode.Boolean: /* signed, based on int */
                case TypeCode.SByte:
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                    {
                        //
                        // NOTE: Signed value, convert via int64.
                        //
                        return ConversionOps.ToULong(Convert.ToInt64(
                            value, cultureInfo));
                    }
                case TypeCode.Byte:
                case TypeCode.Char:
                case TypeCode.UInt16:
                case TypeCode.UInt32:
                case TypeCode.UInt64:
                    {
                        //
                        // NOTE: Unsigned value, convert directly to uint64.
                        //
                        return Convert.ToUInt64(value, cultureInfo);
                    }
                default:
                    {
                        //
                        // NOTE: We have no idea what this is, punt.
                        //
                        throw new ScriptException(String.Format(
                            "enum type mismatch, type code \"{0}\" is not supported",
                            typeCode));
                    }
            }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static long ToLong(
            Type enumType,
            Enum value,
            CultureInfo cultureInfo
            ) /* SAFE */
        {
#if !MONO
            if (!CommonOps.Runtime.IsMono())
                return ToLongFast(enumType, value, cultureInfo);
            else
#endif
                return ToLongSlow(enumType, value, cultureInfo);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static ulong ToULong(
            Type enumType,
            Enum value,
            CultureInfo cultureInfo
            ) /* SAFE */
        {
#if !MONO
            if (!CommonOps.Runtime.IsMono())
                return ToULongFast(enumType, value, cultureInfo);
            else
#endif
                return ToULongSlow(enumType, value, cultureInfo);
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Public TryParseFlagsEnum Methods
        public static object TryParseFlagsEnum(
            Interpreter interpreter,
            Type enumType,
            string oldValue,
            string newValue,
            CultureInfo cultureInfo,
            bool strict,
            bool noCase
            )
        {
            Result error = null;

            return TryParseFlagsEnum(
                interpreter, enumType, oldValue, newValue, cultureInfo,
                strict, noCase, ref error);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static object TryParseFlagsEnum(
            Interpreter interpreter,
            Type enumType,
            string oldValue,
            string newValue,
            CultureInfo cultureInfo,
            bool strict,
            bool noCase,
            ref Result error
            )
        {
            if (enumType != null)
            {
                if (enumType.IsEnum)
                {
                    if (!String.IsNullOrEmpty(newValue))
                    {
                        //
                        // HACK: Transform common flag delimiters to space so that we
                        //       can try to parse the value as a list.  This treatment
                        //       of delimiters may be too liberal; however, it does
                        //       make enumerations easier to use.
                        //
                        newValue = newValue.Replace(Characters.Comma, Characters.Space);
                        newValue = newValue.Replace(Characters.Pipe, Characters.Space);
                        newValue = newValue.Replace(Characters.SemiColon, Characters.Space);

                        //
                        // NOTE: Parse value into a list of enum values and try to parse
                        //       each one.
                        //
                        StringList list = null;

                        if (Parser.SplitList(
                                interpreter, newValue, 0, Length.Invalid, true,
                                ref list, ref error) == ReturnCode.Ok)
                        {
                            Result localError = null;
                            object enumValue;

                            if (!String.IsNullOrEmpty(oldValue))
                                enumValue = TryParseEnum(enumType, oldValue, noCase);
                            else
                                enumValue = TryGetEnum(enumType, 0, ref localError);

                            if (enumValue != null)
                            {
                                //
                                // NOTE: Some of the Enum related operations within may throw
                                //       exceptions.  We need to catch them and return an
                                //       appropriate error.
                                //
                                try
                                {
                                    //
                                    // NOTE: The initial default operator is ":"; however, this
                                    //       may be changed from within the loop for subsequent
                                    //       flagging operations.
                                    //
                                    char @operator = DefaultFlagOperator;

                                    for (int index = 0; index < list.Count; index++)
                                    {
                                        string item = list[index];

                                        //
                                        // NOTE: We simply ignore invalid and empty list items.
                                        //
                                        if (!String.IsNullOrEmpty(item))
                                        {
                                            //
                                            // NOTE: This used to check if the character
                                            //       was a letter or an underscore (i.e.
                                            //       but not a digit) because it was
                                            //       assumed that numeric values would
                                            //       not be used for flag values; however,
                                            //       this behavior has now been changed.
                                            //
                                            char character = item[0];

                                            if (!Parser.IsIdentifier(character))
                                            {
                                                //
                                                // NOTE: Must be some kind of operator
                                                //       (this will be validated below).
                                                //
                                                @operator = character;

                                                //
                                                // NOTE: Skip over the leading operator
                                                //       character and get the rest of
                                                //       the name.
                                                //
                                                item = item.Substring(1);
                                            }

                                            object itemEnumValue = TryParseEnum(
                                                enumType, item, noCase);

                                            if (itemEnumValue != null)
                                            {
                                                switch (@operator)
                                                {
                                                    case AddFlagOperator:
                                                        {
                                                            //
                                                            // NOTE: Add the specified flag bits.
                                                            //
                                                            enumValue = TryGetEnum(enumType,
                                                                ToULong(enumType,
                                                                    (Enum)enumValue, cultureInfo) |
                                                                ToULong(enumType,
                                                                    (Enum)itemEnumValue, cultureInfo),
                                                                ref error);

                                                            break;
                                                        }
                                                    case RemoveFlagOperator:
                                                        {
                                                            //
                                                            // NOTE: Remove the specified flag
                                                            //       bits.
                                                            //
                                                            enumValue = TryGetEnum(enumType,
                                                                ToULong(enumType,
                                                                    (Enum)enumValue, cultureInfo) &
                                                                ~ToULong(enumType,
                                                                    (Enum)itemEnumValue, cultureInfo),
                                                                ref error);

                                                            break;
                                                        }
                                                    case SetFlagOperator:
                                                        {
                                                            //
                                                            // NOTE: Set the overall value equal
                                                            //       to the current value.  This
                                                            //       should be used only very
                                                            //       rarely and is supported
                                                            //       primarily for completeness.
                                                            //
                                                            enumValue = itemEnumValue;

                                                            break;
                                                        }
                                                    case SetAddFlagOperator:
                                                        {
                                                            //
                                                            // NOTE: Set the overall value equal
                                                            //       to the current value and then
                                                            //       reset the operator to add.
                                                            //
                                                            enumValue = itemEnumValue;
                                                            @operator = AddFlagOperator;

                                                            break;
                                                        }
                                                    default:
                                                        {
                                                            //
                                                            // NOTE: Any other operator character
                                                            //       is invalid.
                                                            //
                                                            error = String.Format(
                                                                "bad flags operator \"{0}\", must " +
                                                                "be \"{1}\", \"{2}\", \"{3}\", or " +
                                                                "\"{4}\"", @operator, AddFlagOperator,
                                                                RemoveFlagOperator, SetFlagOperator,
                                                                SetAddFlagOperator);

                                                            enumValue = null;
                                                            break;
                                                        }
                                                }
                                            }
                                            else
                                            {
                                                //
                                                // NOTE: The current list item did not parse as a
                                                //       valid enum value for the specified enum
                                                //       type.
                                                //
                                                error = ScriptOps.BadValue(null, String.Format(
                                                        "\"{0}\" value", enumType.FullName), item,
                                                    Enum.GetNames(enumType), null, null);

                                                enumValue = null;
                                            }
                                        }

                                        //
                                        // NOTE: If we failed anything above, break out of the
                                        //       processing loop now.
                                        //
                                        if (enumValue == null)
                                            break;
                                    }

                                    //
                                    // NOTE: Return the calculated enum value (this may be null if
                                    //       we failed an operation inside the processing loop).
                                    //
                                    return enumValue;
                                }
                                catch (Exception e)
                                {
                                    error = e;
                                }
                            }
                            else
                            {
                                if (localError != null)
                                    error = localError;
                                else
                                    error = String.Format(
                                        "invalid \"{0}\" old value \"{1}\"",
                                        enumType.FullName, oldValue);
                            }
                        }
                    }
                    else if (strict)
                    {
                        error = String.Format(
                            "invalid \"{0}\" new value",
                            enumType.FullName);
                    }
                    else
                    {
                        Result localError = null;
                        object enumValue;

                        if (!String.IsNullOrEmpty(oldValue))
                            enumValue = TryParseEnum(enumType, oldValue, noCase);
                        else
                            enumValue = TryGetEnum(enumType, 0, ref localError);

                        if (enumValue != null)
                        {
                            return enumValue;
                        }
                        else
                        {
                            if (localError != null)
                                error = localError;
                            else
                                error = String.Format(
                                    "invalid \"{0}\" old value \"{1}\"",
                                    enumType.FullName, oldValue);
                        }
                    }
                }
                else
                {
                    error = String.Format(
                        "type \"{0}\" is not an enumeration",
                        enumType.FullName);
                }
            }
            else
            {
                error = "invalid type";
            }

            return null;
        }
        #endregion
    }
}
