context: using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Diagnostics.CodeAnalysis;
  using System.Globalization;
  using System.Linq;
  using System.Reflection;
  
  namespace PaySheetGenerator.Areas.HelpPage
  {
      /// <summary>
      /// This class will create an object of a given type and populate it with sample data.
      /// </summary>
      public class ObjectGenerator
      {
          internal const int DefaultCollectionSize = 2;
          private readonly SimpleTypeObjectGenerator SimpleObjectGenerator = new SimpleTypeObjectGenerator();
  
          /// <summary>
          /// Generates an object for a given type. The type needs to be public, have a public default constructor and settable public properties/fields. Currently it supports the following types:
          /// Simple types: <see cref="int"/>, <see cref="string"/>, <see cref="Enum"/>, <see cref="DateTime"/>, <see cref="Uri"/>, etc.
          /// Complex types: POCO types.
          /// Nullables: <see cref="Nullable{T}"/>.
          /// Arrays: arrays of simple types or complex types.
          /// Key value pairs: <see cref="KeyValuePair{TKey,TValue}"/>
          /// Tuples: <see cref="Tuple{T1}"/>, <see cref="Tuple{T1,T2}"/>, etc
          /// Dictionaries: <see cref="IDictionary{TKey,TValue}"/> or anything deriving from <see cref="IDictionary{TKey,TValue}"/>.
          /// Collections: <see cref="IList{T}"/>, <see cref="IEnumerable{T}"/>, <see cref="ICollection{T}"/>, <see cref="IList"/>, <see cref="IEnumerable"/>, <see cref="ICollection"/> or anything deriving from <see cref="ICollection{T}"/> or <see cref="IList"/>.
          /// Queryables: <see cref="IQueryable"/>, <see cref="IQueryable{T}"/>.
          /// </summary>
          /// <param name="type">The type.</param>
          /// <returns>An object of the given type.</returns>
          public object GenerateObject(Type type)
          {
              return GenerateObject(type, new Dictionary<Type, object>());
          }
  
          [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Here we just want to return null if anything goes wrong.")]
          private object GenerateObject(Type type, Dictionary<Type, object> createdObjectReferences)
          {
              try
              {
                  if (SimpleTypeObjectGenerator.CanGenerateObject(type))
                  {
                      return SimpleObjectGenerator.GenerateObject(type);
                  }
  
                  if (type.IsArray)
                  {
                      return GenerateArray(type, DefaultCollectionSize, createdObjectReferences);
                  }
  
                  if (type.IsGenericType)
                  {
                      return GenerateGenericType(type, DefaultCollectionSize, createdObjectReferences);
                  }
  
                  if (type == typeof(IDictionary))
                  {
                      return GenerateDictionary(typeof(Hashtable), DefaultCollectionSize, createdObjectReferences);
                  }
  
                  if (typeof(IDictionary).IsAssignableFrom(type))
                  {
                      return GenerateDictionary(type, DefaultCollectionSize, createdObjectReferences);
                  }
  
                  if (type == typeof(IList) ||
                      type == typeof(IEnumerable) ||
                      type == typeof(ICollection))
                  {
                      return GenerateCollection(typeof(ArrayList), DefaultCollectionSize, createdObjectReferences);
                  }
  
                  if (typeof(IList).IsAssignableFrom(type))
                  {
                      return GenerateCollection(type, DefaultCollectionSize, createdObjectReferences);
                  }
  
                  if (type == typeof(IQueryable))
                  {
                      return GenerateQueryable(type, DefaultCollectionSize, createdObjectReferences);
                  }
  
                  if (type.IsEnum)
                  {
                      return GenerateEnum(type);
                  }
  
                  if (type.IsPublic || type.IsNestedPublic)
                  {
                      return GenerateComplexObject(type, createdObjectReferences);
                  }
              }
              catch
              {
                  // Returns null if anything fails
                  return null;
              }
  
              return null;
          }
  
          private static object GenerateGenericType(Type type, int collectionSize, Dictionary<Type, object> createdObjectReferences)
          {
              Type genericTypeDefinition = type.GetGenericTypeDefinition();
              if (genericTypeDefinition == typeof(Nullable<>))
              {
                  return GenerateNullable(type, createdObjectReferences);
              }
  
              if (genericTypeDefinition == typeof(KeyValuePair<,>))
              {
                  return GenerateKeyValuePair(type, createdObjectReferences);
              }
  
              if (IsTuple(genericTypeDefinition))
              {
                  return GenerateTuple(type, createdObjectReferences);
              }
  
              Type[] genericArguments = type.GetGenericArguments();
              if (genericArguments.Length == 1)
              {
                  if (genericTypeDefinition == typeof(IList<>) ||
                      genericTypeDefinition == typeof(IEnumerable<>) ||
                      genericTypeDefinition == typeof(ICollection<>))
                  {
                      Type collectionType = typeof(List<>).MakeGenericType(genericArguments);
                      return GenerateCollection(collectionType, collectionSize, createdObjectReferences);
                  }
  
                  if (genericTypeDefinition == typeof(IQueryable<>))
                  {
                      return GenerateQueryable(type, collectionSize, createdObjectReferences);
                  }
  
                  Type closedCollectionType = typeof(ICollection<>).MakeGenericType(genericArguments[0]);
                  if (closedCollectionType.IsAssignableFrom(type))
                  {
                      return GenerateCollection(type, collectionSize, createdObjectReferences);
                  }
              }
  
              if (genericArguments.Length == 2)
              {
                  if (genericTypeDefinition == typeof(IDictionary<,>))
                  {
                      Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(genericArguments);
                      return GenerateDictionary(dictionaryType, collectionSize, createdObjectReferences);
                  }
  
                  Type closedDictionaryType = typeof(IDictionary<,>).MakeGenericType(genericArguments[0], genericArguments[1]);
                  if (closedDictionaryType.IsAssignableFrom(type))
                  {
                      return GenerateDictionary(type, collectionSize, createdObjectReferences);
                  }
              }
  
              if (type.IsPublic || type.IsNestedPublic)
              {
                  return GenerateComplexObject(type, createdObjectReferences);
              }
  
              return null;
          }
  
          private static object GenerateTuple(Type type, Dictionary<Type, object> createdObjectReferences)
          {
              Type[] genericArgs = type.GetGenericArguments();
              object[] parameterValues = new object[genericArgs.Length];
              bool failedToCreateTuple = true;
              ObjectGenerator objectGenerator = new ObjectGenerator();
              for (int i = 0; i < genericArgs.Length; i++)
              {
                  parameterValues[i] = objectGenerator.GenerateObject(genericArgs[i], createdObjectReferences);
                  failedToCreateTuple &= parameterValues[i] == null;
              }
              if (failedToCreateTuple)
              {
                  return null;
              }
              object result = Activator.CreateInstance(type, parameterValues);
              return result;
          }
  
          private static bool IsTuple(Type genericTypeDefinition)
          {
              return genericTypeDefinition == typeof(Tuple<>) ||
                  genericTypeDefinition == typeof(Tuple<,>) ||
                  genericTypeDefinition == typeof(Tuple<,,>) ||
                  genericTypeDefinition == typeof(Tuple<,,,>) ||
                  genericTypeDefinition == typeof(Tuple<,,,,>) ||
                  genericTypeDefinition == typeof(Tuple<,,,,,>) ||
                  genericTypeDefinition == typeof(Tuple<,,,,,,>) ||
                  genericTypeDefinition == typeof(Tuple<,,,,,,,>);
          }
  
          private static object GenerateKeyValuePair(Type keyValuePairType, Dictionary<Type, object> createdObjectReferences)
          {
              Type[] genericArgs = keyValuePairType.GetGenericArguments();
              Type typeK = genericArgs[0];
              Type typeV = genericArgs[1];
              ObjectGenerator objectGenerator = new ObjectGenerator();
              object keyObject = objectGenerator.GenerateObject(typeK, createdObjectReferences);
              object valueObject = objectGenerator.GenerateObject(typeV, createdObjectReferences);
              if (keyObject == null && valueObject == null)
              {
                  // Failed to create key and values
                  return null;
              }
              object result = Activator.CreateInstance(keyValuePairType, keyObject, valueObject);
              return result;
          }
  
          private static object GenerateArray(Type arrayType, int size, Dictionary<Type, object> createdObjectReferences)
          {
              Type type = arrayType.GetElementType();
              Array result = Array.CreateInstance(type, size);
              bool areAllElementsNull = true;
              ObjectGenerator objectGenerator = new ObjectGenerator();
              for (int i = 0; i < size; i++)
              {
                  object element = objectGenerator.GenerateObject(type, createdObjectReferences);
                  result.SetValue(element, i);
                  areAllElementsNull &= element == null;
              }
  
              if (areAllElementsNull)
              {
                  return null;
              }
  
              return result;
          }
  
          private static object GenerateDictionary(Type dictionaryType, int size, Dictionary<Type, object> createdObjectReferences)
          {
              Type typeK = typeof(object);
              Type typeV = typeof(object);
              if (dictionaryType.IsGenericType)
              {
                  Type[] genericArgs = dictionaryType.GetGenericArguments();
                  typeK = genericArgs[0];
                  typeV = genericArgs[1];
              }
  
              object result = Activator.CreateInstance(dictionaryType);
              MethodInfo addMethod = dictionaryType.GetMethod("Add") ?? dictionaryType.GetMethod("TryAdd");
              MethodInfo containsMethod = dictionaryType.GetMethod("Contains") ?? dictionaryType.GetMethod("ContainsKey");
              ObjectGenerator objectGenerator = new ObjectGenerator();
              for (int i = 0; i < size; i++)
              {
                  object newKey = objectGenerator.GenerateObject(typeK, createdObjectReferences);
                  if (newKey == null)
                  {
                      // Cannot generate a valid key
                      return null;
                  }
  
                  bool containsKey = (bool)containsMethod.Invoke(result, new object[] { newKey });
                  if (!containsKey)
                  {
                      object newValue = objectGenerator.GenerateObject(typeV, createdObjectReferences);
                      addMethod.Invoke(result, new object[] { newKey, newValue });
                  }
              }
  
              return result;
          }
  
          private static object GenerateEnum(Type enumType)
          {
              Array possibleValues = Enum.GetValues(enumType);
              if (possibleValues.Length > 0)
              {
                  return possibleValues.GetValue(0);
              }
              return null;
          }
  
          private static object GenerateQueryable(Type queryableType, int size, Dictionary<Type, object> createdObjectReferences)
          {
              bool isGeneric = queryableType.IsGenericType;
              object list;
              if (isGeneric)
              {
                  Type listType = typeof(List<>).MakeGenericType(queryableType.GetGenericArguments());
                  list = GenerateCollection(listType, size, createdObjectReferences);
              }
              else
              {
                  list = GenerateArray(typeof(object[]), size, createdObjectReferences);
              }
              if (list == null)
              {
                  return null;
              }
              if (isGeneric)
              {
                  Type argumentType = typeof(IEnumerable<>).MakeGenericType(queryableType.GetGenericArguments());
                  MethodInfo asQueryableMethod = typeof(Queryable).GetMethod("AsQueryable", new[] { argumentType });
                  return asQueryableMethod.Invoke(null, new[] { list });
              }
  
              return Queryable.AsQueryable((IEnumerable)list);
          }
  
          private static object GenerateCollection(Type collectionType, int size, Dictionary<Type, object> createdObjectReferences)
          {
              Type type = collectionType.IsGenericType ?
                  collectionType.GetGenericArguments()[0] :
                  typeof(object);
              object result = Activator.CreateInstance(collectionType);
              MethodInfo addMethod = collectionType.GetMethod("Add");
              bool areAllElementsNull = true;
  
gt:             ObjectGenerator objectGenerator = new ObjectGenerator();