#region License
// Copyright (c) 2007 James Newton-King
//
// 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.
#endregion

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Collections;
using System.Linq;
using System.Globalization;
using System.Runtime.Serialization.Formatters;

namespace Newtonsoft.Json.Utilities
{
  internal static class ReflectionUtils
  {
    public static Type GetObjectType(object v)
    {
      return (v != null) ? v.GetType() : null;
    }

    public static string GetTypeName(Type t, FormatterAssemblyStyle assemblyFormat)
    {
      switch (assemblyFormat)
      {
        case FormatterAssemblyStyle.Simple:
          return t.FullName + ", " + t.Assembly.GetName().Name;
        case FormatterAssemblyStyle.Full:
          return t.AssemblyQualifiedName;
        default:
          throw new ArgumentOutOfRangeException();
      }
    }

    public static bool IsInstantiatableType(Type t)
    {
      ValidationUtils.ArgumentNotNull(t, "t");

      if (t.IsAbstract || t.IsInterface || t.IsArray || t.IsGenericTypeDefinition || t == typeof(void))
        return false;

      if (!HasDefaultConstructor(t))
        return false;

      return true;
    }

    public static bool HasDefaultConstructor(Type t)
    {
      return HasDefaultConstructor(t, false);
    }

    public static bool HasDefaultConstructor(Type t, bool nonPublic)
    {
      ValidationUtils.ArgumentNotNull(t, "t");

      if (t.IsValueType)
        return true;

      return (GetDefaultConstructor(t, nonPublic) != null);
    }

    public static ConstructorInfo GetDefaultConstructor(Type t)
    {
      return GetDefaultConstructor(t, false);
    }

    public static ConstructorInfo GetDefaultConstructor(Type t, bool nonPublic)
    {
      BindingFlags accessModifier = BindingFlags.Public;
      
      if (nonPublic)
        accessModifier = accessModifier | BindingFlags.NonPublic;

      return t.GetConstructor(accessModifier | BindingFlags.Instance, null, new Type[0], null);
    }

    public static bool IsNullable(Type t)
    {
      ValidationUtils.ArgumentNotNull(t, "t");

      if (t.IsValueType)
        return IsNullableType(t);

      return true;
    }

    public static bool IsNullableType(Type t)
    {
      ValidationUtils.ArgumentNotNull(t, "t");

      return (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>));
    }

    //public static bool IsValueTypeUnitializedValue(ValueType value)
    //{
    //  if (value == null)
    //    return true;

    //  return value.Equals(CreateUnitializedValue(value.GetType()));
    //}

    public static bool IsUnitializedValue(object value)
    {
      if (value == null)
      {
        return true;
      }
      else
      {
        object unitializedValue = CreateUnitializedValue(value.GetType());
        return value.Equals(unitializedValue);
      }
    }

    public static object CreateUnitializedValue(Type type)
    {
      ValidationUtils.ArgumentNotNull(type, "type");

      if (type.IsGenericTypeDefinition)
        throw new ArgumentException("Type {0} is a generic type definition and cannot be instantiated.".FormatWith(CultureInfo.InvariantCulture, type), "type");

      if (type.IsClass || type.IsInterface || type == typeof(void))
        return null;
      else if (type.IsValueType)
        return Activator.CreateInstance(type);
      else
        throw new ArgumentException("Type {0} cannot be instantiated.".FormatWith(CultureInfo.InvariantCulture, type), "type");
    }

    public static bool IsPropertyIndexed(PropertyInfo property)
    {
      ValidationUtils.ArgumentNotNull(property, "property");

      return !CollectionUtils.IsNullOrEmpty<ParameterInfo>(property.GetIndexParameters());
    }

    public static bool ImplementsGenericDefinition(Type type, Type genericInterfaceDefinition)
    {
      Type implementingType;
      return ImplementsGenericDefinition(type, genericInterfaceDefinition, out implementingType);
    }

    public static bool ImplementsGenericDefinition(Type type, Type genericInterfaceDefinition, out Type implementingType)
    {
      ValidationUtils.ArgumentNotNull(type, "type");
      ValidationUtils.ArgumentNotNull(genericInterfaceDefinition, "genericInterfaceDefinition");

      if (!genericInterfaceDefinition.IsInterface || !genericInterfaceDefinition.IsGenericTypeDefinition)
        throw new ArgumentNullException("'{0}' is not a generic interface definition.".FormatWith(CultureInfo.InvariantCulture, genericInterfaceDefinition));

      if (type.IsInterface)
      {
        if (type.IsGenericType)
        {
          Type interfaceDefinition = type.GetGenericTypeDefinition();

          if (genericInterfaceDefinition == interfaceDefinition)
          {
            implementingType = type;
            return true;
          }
        }
      }

      foreach (Type i in type.GetInterfaces())
      {
        if (i.IsGenericType)
        {
          Type interfaceDefinition = i.GetGenericTypeDefinition();

          if (genericInterfaceDefinition == interfaceDefinition)
          {
            implementingType = i;
            return true;
          }
        }
      }

      implementingType = null;
      return false;
    }

    public static bool AssignableToTypeName(this Type type, string fullTypeName, out Type match)
    {
      Type current = type;

      while (current != null)
      {
        if (string.Equals(current.FullName, fullTypeName, StringComparison.Ordinal))
        {
          match = current;
          return true;
        }

        current = current.BaseType;
      }

      foreach (Type i in type.GetInterfaces())
      {
        if (string.Equals(i.Name, fullTypeName, StringComparison.Ordinal))
        {
          match = type;
          return true;
        }
      }

      match = null;
      return false;
    }

    public static bool AssignableToTypeName(this Type type, string fullTypeName)
    {
      Type match;
      return type.AssignableToTypeName(fullTypeName, out match);
    }

    public static bool InheritsGenericDefinition(Type type, Type genericClassDefinition)
    {
      Type implementingType;
      return InheritsGenericDefinition(type, genericClassDefinition, out implementingType);
    }

    public static bool InheritsGenericDefinition(Type type, Type genericClassDefinition, out Type implementingType)
    {
      ValidationUtils.ArgumentNotNull(type, "type");
      ValidationUtils.ArgumentNotNull(genericClassDefinition, "genericClassDefinition");

      if (!genericClassDefinition.IsClass || !genericClassDefinition.IsGenericTypeDefinition)
        throw new ArgumentNullException("'{0}' is not a generic class definition.".FormatWith(CultureInfo.InvariantCulture, genericClassDefinition));

      return InheritsGenericDefinitionInternal(type, genericClassDefinition, out implementingType);
    }

    private static bool InheritsGenericDefinitionInternal(Type currentType, Type genericClassDefinition, out Type implementingType)
    {
      if (currentType.IsGenericType)
      {
        Type currentGenericClassDefinition = currentType.GetGenericTypeDefinition();

        if (genericClassDefinition == currentGenericClassDefinition)
        {
          implementingType = currentType;
          return true;
        }
      }

      if (currentType.BaseType == null)
      {
        implementingType = null;
        return false;
      }

      return InheritsGenericDefinitionInternal(currentType.BaseType, genericClassDefinition, out implementingType);
    }

    /// <summary>
    /// Gets the type of the typed collection's items.
    /// </summary>
    /// <param name="type">The type.</param>
    /// <returns>The type of the typed collection's items.</returns>
    public static Type GetCollectionItemType(Type type)
    {
      ValidationUtils.ArgumentNotNull(type, "type");
      Type genericListType;

      if (type.IsArray)
      {
        return type.GetElementType();
      }
      else if (ImplementsGenericDefinition(type, typeof(IEnumerable<>), out genericListType))
      {
        if (genericListType.IsGenericTypeDefinition)
          throw new Exception("Type {0} is not a collection.".FormatWith(CultureInfo.InvariantCulture, type));

        return genericListType.GetGenericArguments()[0];
      }
      else if (typeof(IEnumerable).IsAssignableFrom(type))
      {
        return null;
      }
      else
      {
        throw new Exception("Type {0} is not a collection.".FormatWith(CultureInfo.InvariantCulture, type));
      }
    }

    public static void GetDictionaryKeyValueTypes(Type dictionaryType, out Type keyType, out Type valueType)
    {
      ValidationUtils.ArgumentNotNull(dictionaryType, "type");

      Type genericDictionaryType;
      if (ImplementsGenericDefinition(dictionaryType, typeof(IDictionary<,>), out genericDictionaryType))
      {
        if (genericDictionaryType.IsGenericTypeDefinition)
          throw new Exception("Type {0} is not a dictionary.".FormatWith(CultureInfo.InvariantCulture, dictionaryType));

        Type[] dictionaryGenericArguments = genericDictionaryType.GetGenericArguments();

        keyType = dictionaryGenericArguments[0];
        valueType = dictionaryGenericArguments[1];
        return;
      }
      else if (typeof(IDictionary).IsAssignableFrom(dictionaryType))
      {
        keyType = null;
        valueType = null;
        return;
      }
      else
      {
        throw new Exception("Type {0} is not a dictionary.".FormatWith(CultureInfo.InvariantCulture, dictionaryType));
      }
    }

    public static Type GetDictionaryValueType(Type dictionaryType)
    {
      Type keyType;
      Type valueType;
      GetDictionaryKeyValueTypes(dictionaryType, out keyType, out valueType);

      return valueType;
    }

    public static Type GetDictionaryKeyType(Type dictionaryType)
    {
      Type keyType;
      Type valueType;
      GetDictionaryKeyValueTypes(dictionaryType, out keyType, out valueType);

      return keyType;
    }

    /// <summary>
    /// Tests whether the list's items are their unitialized value.
    /// </summary>
    /// <param name="list">The list.</param>
    /// <returns>Whether the list's items are their unitialized value</returns>
    public static bool ItemsUnitializedValue<T>(IList<T> list)
    {
      ValidationUtils.ArgumentNotNull(list, "list");

      Type elementType = GetCollectionItemType(list.GetType());

      if (elementType.IsValueType)
      {
        object unitializedValue = CreateUnitializedValue(elementType);

        for (int i = 0; i < list.Count; i++)
        {
          if (!list[i].Equals(unitializedValue))
            return false;
        }
      }
      else if (elementType.IsClass)
      {
        for (int i = 0; i < list.Count; i++)
        {
          object value = list[i];

          if (value != null)
            return false;
        }
      }
      else
      {
        throw new Exception("Type {0} is neither a ValueType or a Class.".FormatWith(CultureInfo.InvariantCulture, elementType));
      }

      return true;
    }

    /// <summary>
    /// Gets the member's underlying type.
    /// </summary>
    /// <param name="member">The member.</param>
    /// <returns>The underlying type of the member.</returns>
    public static Type GetMemberUnderlyingType(MemberInfo member)
    {
      ValidationUtils.ArgumentNotNull(member, "member");

      switch (member.MemberType)
      {
        case MemberTypes.Field:
          return ((FieldInfo)member).FieldType;
        case MemberTypes.Property:
          return ((PropertyInfo)member).PropertyType;
        case MemberTypes.Event:
          return ((EventInfo)member).EventHandlerType;
        default:
          throw new ArgumentException("MemberInfo must be of type FieldInfo, PropertyInfo or EventInfo", "member");
      }
    }

    /// <summary>
    /// Determines whether the member is an indexed property.
    /// </summary>
    /// <param name="member">The member.</param>
    /// <returns>
    /// 	<c>true</c> if the member is an indexed property; otherwise, <c>false</c>.
    /// </returns>
    public static bool IsIndexedProperty(MemberInfo member)
    {
      ValidationUtils.ArgumentNotNull(member, "member");

      PropertyInfo propertyInfo = member as PropertyInfo;

      if (propertyInfo != null)
        return IsIndexedProperty(propertyInfo);
      else
        return false;
    }

    /// <summary>
    /// Determines whether the property is an indexed property.
    /// </summary>
    /// <param name="property">The property.</param>
    /// <returns>
    /// 	<c>true</c> if the property is an indexed property; otherwise, <c>false</c>.
    /// </returns>
    public static bool IsIndexedProperty(PropertyInfo property)
    {
      ValidationUtils.ArgumentNotNull(property, "property");

      return (property.GetIndexParameters().Length > 0);
    }

    /// <summary>
    /// Gets the member's value on the object.
    /// </summary>
    /// <param name="member">The member.</param>
    /// <param name="target">The target object.</param>
    /// <returns>The member's value on the object.</returns>
    public static object GetMemberValue(MemberInfo member, object target)
    {
      ValidationUtils.ArgumentNotNull(member, "member");
      ValidationUtils.ArgumentNotNull(target, "target");

      switch (member.MemberType)
      {
        case MemberTypes.Field:
          return ((FieldInfo)member).GetValue(target);
        case MemberTypes.Property:
          try
          {
            return ((PropertyInfo)member).GetValue(target, null);
          }
          catch (TargetParameterCountException e)
          {
            throw new ArgumentException("MemberInfo '{0}' has index parameters".FormatWith(CultureInfo.InvariantCulture, member.Name), e);
          }
        default:
          throw new ArgumentException("MemberInfo '{0}' is not of type FieldInfo or PropertyInfo".FormatWith(CultureInfo.InvariantCulture, CultureInfo.InvariantCulture, member.Name), "member");
      }
    }

    /// <summary>
    /// Sets the member's value on the target object.
    /// </summary>
    /// <param name="member">The member.</param>
    /// <param name="target">The target.</param>
    /// <param name="value">The value.</param>
    public static void SetMemberValue(MemberInfo member, object target, object value)
    {
      ValidationUtils.ArgumentNotNull(member, "member");
      ValidationUtils.ArgumentNotNull(target, "target");

      switch (member.MemberType)
      {
        case MemberTypes.Field:
          ((FieldInfo)member).SetValue(target, value);
          break;
        case MemberTypes.Property:
          ((PropertyInfo)member).SetValue(target, value, null);
          break;
        default:
          throw new ArgumentException("MemberInfo '{0}' must be of type FieldInfo or PropertyInfo".FormatWith(CultureInfo.InvariantCulture, member.Name), "member");
      }
    }

    /// <summary>
    /// Determines whether the specified MemberInfo can be read.
    /// </summary>
    /// <param name="member">The MemberInfo to determine whether can be read.</param>
    /// /// <param name="nonPublic">if set to <c>true</c> then allow the member to be gotten non-publicly.</param>
    /// <returns>
    /// 	<c>true</c> if the specified MemberInfo can be read; otherwise, <c>false</c>.
    /// </returns>
    public static bool CanReadMemberValue(MemberInfo member, bool nonPublic)
    {
      switch (member.MemberType)
      {
        case MemberTypes.Field:
          FieldInfo fieldInfo = (FieldInfo)member;

          if (nonPublic)
            return true;
          else if (fieldInfo.IsPublic)
            return true;
          return false;
        case MemberTypes.Property:
          PropertyInfo propertyInfo = (PropertyInfo) member;

          if (!propertyInfo.CanRead)
            return false;
          if (nonPublic)
            return true;
          return (propertyInfo.GetGetMethod(nonPublic) != null);
        default:
          return false;
      }
    }

    /// <summary>
    /// Determines whether the specified MemberInfo can be set.
    /// </summary>
    /// <param name="member">The MemberInfo to determine whether can be set.</param>
    /// <param name="nonPublic">if set to <c>true</c> then allow the member to be set non-publicly.</param>
    /// <returns>
    /// 	<c>true</c> if the specified MemberInfo can be set; otherwise, <c>false</c>.
    /// </returns>
    public static bool CanSetMemberValue(MemberInfo member, bool nonPublic)
    {
      switch (member.MemberType)
      {
        case MemberTypes.Field:
          FieldInfo fieldInfo = (FieldInfo)member;

          if (fieldInfo.IsInitOnly)
            return false;
          if (nonPublic)
            return true;
          else if (fieldInfo.IsPublic)
            return true;
          return false;
        case MemberTypes.Property:
          PropertyInfo propertyInfo = (PropertyInfo)member;

          if (!propertyInfo.CanWrite)
            return false;
          if (nonPublic)
            return true;
          return (propertyInfo.GetSetMethod(nonPublic) != null);
        default:
          return false;
      }
    }

    public static List<MemberInfo> GetFieldsAndProperties<T>(BindingFlags bindingAttr)
    {
      return GetFieldsAndProperties(typeof(T), bindingAttr);
    }

    public static List<MemberInfo> GetFieldsAndProperties(Type type, BindingFlags bindingAttr)
    {
      List<MemberInfo> targetMembers = new List<MemberInfo>();

      targetMembers.AddRange(GetFields(type, bindingAttr));
      targetMembers.AddRange(GetProperties(type, bindingAttr));

      // for some reason .NET returns multiple members when overriding a generic member on a base class
      // http://forums.msdn.microsoft.com/en-US/netfxbcl/thread/b5abbfee-e292-4a64-8907-4e3f0fb90cd9/
      // filter members to only return the override on the topmost class
      // update: I think this is fixed in .NET 3.5 SP1 - leave this in for now...
      List<MemberInfo> distinctMembers = new List<MemberInfo>(targetMembers.Count);

      var groupedMembers = targetMembers.GroupBy(m => m.Name).Select(g => new { Count = g.Count(), Members = g.Cast<MemberInfo>() });
      foreach (var groupedMember in groupedMembers)
      {
        if (groupedMember.Count == 1)
        {
          distinctMembers.Add(groupedMember.Members.First());
        }
        else
        {
          var members = groupedMember.Members.Where(m => !IsOverridenGenericMember(m, bindingAttr) || m.Name == "Item");

          distinctMembers.AddRange(members);
        }
      }

      return distinctMembers;
    }

    private static bool IsOverridenGenericMember(MemberInfo memberInfo, BindingFlags bindingAttr)
    {
      if (memberInfo.MemberType != MemberTypes.Field && memberInfo.MemberType != MemberTypes.Property)
        throw new ArgumentException("Member must be a field or property.");

      Type declaringType = memberInfo.DeclaringType;
      if (!declaringType.IsGenericType)
        return false;
      Type genericTypeDefinition = declaringType.GetGenericTypeDefinition();
      if (genericTypeDefinition == null)
        return false;
      MemberInfo[] members = genericTypeDefinition.GetMember(memberInfo.Name, bindingAttr);
      if (members.Length == 0)
        return false;
      Type memberUnderlyingType = GetMemberUnderlyingType(members[0]);
      if (!memberUnderlyingType.IsGenericParameter)
        return false;

      return true;
    }

    public static T GetAttribute<T>(ICustomAttributeProvider attributeProvider) where T : Attribute
    {
      return GetAttribute<T>(attributeProvider, true);
    }

    public static T GetAttribute<T>(ICustomAttributeProvider attributeProvider, bool inherit) where T : Attribute
    {
      T[] attributes = GetAttributes<T>(attributeProvider, inherit);

      return CollectionUtils.GetSingleItem(attributes, true);
    }

    public static T[] GetAttributes<T>(ICustomAttributeProvider attributeProvider, bool inherit) where T : Attribute
    {
      ValidationUtils.ArgumentNotNull(attributeProvider, "attributeProvider");

      return (T[])attributeProvider.GetCustomAttributes(typeof(T), inherit);
    }

    public static string GetNameAndAssessmblyName(Type t)
    {
      ValidationUtils.ArgumentNotNull(t, "t");

      return t.FullName + ", " + t.Assembly.GetName().Name;
    }

    public static Type MakeGenericType(Type genericTypeDefinition, params Type[] innerTypes)
    {
      ValidationUtils.ArgumentNotNull(genericTypeDefinition, "genericTypeDefinition");
      ValidationUtils.ArgumentNotNullOrEmpty<Type>(innerTypes, "innerTypes");
      ValidationUtils.ArgumentConditionTrue(genericTypeDefinition.IsGenericTypeDefinition, "genericTypeDefinition", "Type {0} is not a generic type definition.".FormatWith(CultureInfo.InvariantCulture, genericTypeDefinition));

      return genericTypeDefinition.MakeGenericType(innerTypes);
    }

    public static object CreateGeneric(Type genericTypeDefinition, Type innerType, params object[] args)
    {
      return CreateGeneric(genericTypeDefinition, new [] { innerType }, args);
    }

    public static object CreateGeneric(Type genericTypeDefinition, IList<Type> innerTypes, params object[] args)
    {
      return CreateGeneric(genericTypeDefinition, innerTypes, (t, a) => CreateInstance(t, a.ToArray()), args);
    }

    public static object CreateGeneric(Type genericTypeDefinition, IList<Type> innerTypes, Func<Type, IList<object>, object> instanceCreator, params object[] args)
    {
      ValidationUtils.ArgumentNotNull(genericTypeDefinition, "genericTypeDefinition");
      ValidationUtils.ArgumentNotNullOrEmpty(innerTypes, "innerTypes");
      ValidationUtils.ArgumentNotNull(instanceCreator, "createInstance");

      Type specificType = MakeGenericType(genericTypeDefinition, innerTypes.ToArray());

      return instanceCreator(specificType, args);
    }

    public static bool IsCompatibleValue(object value, Type type)
    {
      if (value == null)
        return IsNullable(type);

      if (type.IsAssignableFrom(value.GetType()))
        return true;

      return false;
    }

     public static object CreateInstance(Type type, params object[] args)
     {
       ValidationUtils.ArgumentNotNull(type, "type");

#if !PocketPC
       return Activator.CreateInstance(type, args);
#else
       // CF doesn't have a Activator.CreateInstance overload that takes args
       // lame

       if (type.IsValueType && CollectionUtils.IsNullOrEmpty<object>(args))
         return Activator.CreateInstance(type);

       ConstructorInfo[] constructors = type.GetConstructors();
       ConstructorInfo match = constructors.Where(c =>
         {
           ParameterInfo[] parameters = c.GetParameters();
           if (parameters.Length != args.Length)
             return false;

           for (int i = 0; i < parameters.Length; i++)
           {
             ParameterInfo parameter = parameters[i];
             object value = args[i];

             if (!IsCompatibleValue(value, parameter.ParameterType))
               return false;
           }

           return true;
         }).FirstOrDefault();

       if (match == null)
         throw new Exception("Could not create '{0}' with given parameters.".FormatWith(CultureInfo.InvariantCulture, type));

       return match.Invoke(args);
#endif
     }

    public static void SplitFullyQualifiedTypeName(string fullyQualifiedTypeName, out string typeName, out string assemblyName)
    {
      int? assemblyDelimiterIndex = GetAssemblyDelimiterIndex(fullyQualifiedTypeName);

      if (assemblyDelimiterIndex != null)
      {
        typeName = fullyQualifiedTypeName.Substring(0, assemblyDelimiterIndex.Value).Trim();
        assemblyName = fullyQualifiedTypeName.Substring(assemblyDelimiterIndex.Value + 1, fullyQualifiedTypeName.Length - assemblyDelimiterIndex.Value - 1).Trim();
      }
      else
      {
        typeName = fullyQualifiedTypeName;
        assemblyName = null;
      }

    }

    private static int? GetAssemblyDelimiterIndex(string fullyQualifiedTypeName)
    {
      // we need to get the first comma following all surrounded in brackets because of generic types
      // e.g. System.Collections.Generic.Dictionary`2[[System.String, mscorlib,Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
      int scope = 0;
      for (int i = 0; i < fullyQualifiedTypeName.Length; i++)
      {
        char current = fullyQualifiedTypeName[i];
        switch (current)
        {
          case '[':
            scope++;
            break;
          case ']':
            scope--;
            break;
          case ',':
            if (scope == 0)
              return i;
            break;
        }
      }

      return null;
    }

    public static IEnumerable<FieldInfo> GetFields(Type targetType, BindingFlags bindingAttr)
    {
      ValidationUtils.ArgumentNotNull(targetType, "targetType");

      List<MemberInfo> fieldInfos = new List<MemberInfo>(targetType.GetFields(bindingAttr));
      // Type.GetFields doesn't return inherited private fields
      // manually find private fields from base class
      GetChildPrivateFields(fieldInfos, targetType, bindingAttr);

      return fieldInfos.Cast<FieldInfo>();
    }

    private static void GetChildPrivateFields(IList<MemberInfo> initialFields, Type targetType, BindingFlags bindingAttr)
    {
      // fix weirdness with private FieldInfos only being returned for the current Type
      // find base type fields and add them to result
      if ((bindingAttr & BindingFlags.NonPublic) != 0)
      {
        // modify flags to not search for public fields
        BindingFlags nonPublicBindingAttr = bindingAttr.RemoveFlag(BindingFlags.Public);

        while ((targetType = targetType.BaseType) != null)
        {
          // filter out protected fields
          IEnumerable<MemberInfo> childPrivateFields =
            targetType.GetFields(nonPublicBindingAttr).Where(f => f.IsPrivate).Cast<MemberInfo>();

          initialFields.AddRange(childPrivateFields);
        }
      }
    }

    public static IEnumerable<PropertyInfo> GetProperties(Type targetType, BindingFlags bindingAttr)
    {
      ValidationUtils.ArgumentNotNull(targetType, "targetType");

      List<MemberInfo> propertyInfos = new List<MemberInfo>(targetType.GetProperties(bindingAttr));
      GetChildPrivateProperties(propertyInfos, targetType, bindingAttr);

      return propertyInfos.Cast<PropertyInfo>();
    }

    public static BindingFlags RemoveFlag(this BindingFlags bindingAttr, BindingFlags flag)
    {
      return ((bindingAttr & flag) == flag)
        ? bindingAttr ^ flag
        : bindingAttr;
    }

    private static void GetChildPrivateProperties(IList<MemberInfo> initialProperties, Type targetType, BindingFlags bindingAttr)
    {
      // fix weirdness with private PropertyInfos only being returned for the current Type
      // find base type properties and add them to result
      if ((bindingAttr & BindingFlags.NonPublic) != 0)
      {
        // modify flags to not search for public fields
        BindingFlags nonPublicBindingAttr = bindingAttr.RemoveFlag(BindingFlags.Public);

        while ((targetType = targetType.BaseType) != null)
        {
          foreach (PropertyInfo propertyInfo in targetType.GetProperties(nonPublicBindingAttr))
          {
            PropertyInfo nonPublicProperty = propertyInfo;

            // have to test on name rather than reference because instances are different
            // depending on the type that GetProperties was called on
            int index = initialProperties.IndexOf(p => p.Name == nonPublicProperty.Name);
            if (index == -1)
            {
              initialProperties.Add(nonPublicProperty);
            }
            else
            {
              // replace nonpublic properties for a child, but gotten from
              // the parent with the one from the child
              // the property gotten from the child will have access to private getter/setter
              initialProperties[index] = nonPublicProperty;
            }
          }
        }
      }
    }
  }
}