// --------------------------------------------------------------------------------------------------------------------------
        /// <summary>
        /// Evaluates the input objects for nulls + handles the various conditions.
        /// </summary>
        /// <returns>A boolean value indicating whether the caller should continue evaluation or not.</returns>
        private static bool EvaluateNulls(object source, object comp, string name, InspectionReport report)
        {
            if (source == null)
            {
                if (comp == null)
                {
                    // The objects are both null, so we are OK.
                    report.Pass();
                    return(false);
                }
                else
                {
                    report.Fail(string.Format("The comparison object is null on member '{0}'!", name));
                    return(false);
                }
            }
            else if (comp == null)
            {
                report.Fail(string.Format("The comparison object is null on member '{0}'!", name));
                return(false);
            }

            // Neither object is null...
            return(true);
        }
        // --------------------------------------------------------------------------------------------------------------------------
        private void CompareArrays(Type type, object source, object comp, InspectionReport report)
        {
            Type arrayType  = type.GetElementType();
            var  listMethod = ArrayCompare.MakeGenericMethod(new[] { arrayType });

            var listComp = (Tuple <bool, string>)listMethod.Invoke(this, new[] { (object)source, (object)comp, null });

            bool match = listComp.Item1;

            if (!match)
            {
                report.Fail(null);
            }
            else
            {
                report.Pass();
                report.Message = listComp.Item2;
            }
        }
        // --------------------------------------------------------------------------------------------------------------------------
        private void CompareLists(Type type, object source, object comp, InspectionReport report)
        {
#if !NETFX_CORE
            Type listType = type.GetGenericArguments()[0];
#else
            Type listType = type.GetTypeInfo().GenericTypeArguments[0];
#endif

            var listMethod = ListCompare.MakeGenericMethod(new[] { listType });

            var listComp = (Tuple <bool, string>)listMethod.Invoke(this, new[] { (object)source, (object)comp, null });

            bool match = listComp.Item1;
            if (!match)
            {
                report.Fail(null);
            }
            else
            {
                report.Pass();
                report.Message = listComp.Item2;
            }
        }
        // --------------------------------------------------------------------------------------------------------------------------
        private void CompareDictionaries(Type type, object source, object comp, InspectionReport report)
        {
            // Also, handle it...
#if !NETFX_CORE
            Type[] args = type.GetGenericArguments();
#else
            Type[] args = type.GetTypeInfo().GenericTypeArguments;
#endif
            var compMethod = DictionaryCompare.MakeGenericMethod(args);

            var dictComp = (Tuple <bool, string>)compMethod.Invoke(this, new[] { source, comp, null, null });

            bool match = dictComp.Item1;
            if (!match)
            {
                report.Fail(null);
            }
            else
            {
                report.Pass();
                report.Message = dictComp.Item2;
            }
        }
        // --------------------------------------------------------------------------------------------------------------------------
        internal InspectionReport InternalCompare(Type type, object source, object comp, string name, bool throwOnFail = false)
        {
            InspectionReport report = new InspectionReport(name, throwOnFail)
            {
                SourceObject = source,
                CompObject   = comp,
            };

            // TODO: We really need to clean this method up!
            // In particular, it should be broken into sub methods, and we should really reduce the number
            // of exit points as they can make this very confusing indeed!!

            if (IgnoredTypes.Contains(type))
            {
                string skipMessage = "The type '{0}' is set to be ignored, and so the member '{1}' has been excluded from the inspection";
                report.Skip(string.Format(skipMessage, type, name));
                return(report);
            }

            // One or more nulls require some special handling...
            bool continueEval = EvaluateNulls(source, comp, name, report);

            if (!continueEval)
            {
                return(report);
            }

            if (!TypesMatch(source, comp, report))
            {
                return(report);
            }


            // Since both items are null, we can see if we have already cached this data....
            if (CachedReports.ContainsKey(source, comp))
            {
                return(CachedReports[source, comp]);
            }
            CachedReports.Add(source, comp, report);



            if (Comparers.ContainsKey(type))
            {
                bool match = (bool)Comparers[type].DynamicInvoke(source, comp);
                if (!match)
                {
                    report.Fail(null);
                }

                report.Pass();
                return(report);
            }

            if (ReflectionTools.IsSimpleType(type))
            {
                // NOTE: 3.14.2013 --> Technically strings are reference types, but I can't think of a way to get compare
                // their references in a reasonable way at this time.  I am sure that it is possible, but
                // I am still going to treat them as 'primitve' items.

                bool match = source.Equals(comp);
                if (!match)
                {
                    report.Fail(null);
                }
                else
                {
                    report.Pass();
                }

                return(report);
            }


            // Do the reference comparison.
            bool graphOK = CheckReferenceGraph(source, comp);

            if (!graphOK)
            {
                report.Fail(string.Format("Invalid reference at item '{0}'!", name));
                return(report);
            }


            if (IsArray(type))
            {
                CompareArrays(type, source, comp, report);
                return(report);
            }

            if (IsIList(type))
            {
                CompareLists(type, source, comp, report);
                return(report);
            }


            if (IsIDictionary(type))
            {
                CompareDictionaries(type, source, comp, report);
                return(report);
            }


            // We will look at each property and determine if it is a value type, or string.
            // If it is, then we can just do a normal equality test.
            // If not, we will continue to decompose the object.
            // Same goes for the fields.
            List <PropertyInfo> props  = ResolveProperties(type);
            List <FieldInfo>    fields = ResolveFields(type);


            // NOTE: In the WinRT version, we simply get all of the non public members in the above calls.  We should probably port the Win32 code to do the same thing.
            List <TypeMember> nonPublics = ResolvePrivateMembers(type);
            List <TypeMember> allMembers = (from x in props select new TypeMember(x)).Concat(
                (from x in fields select new TypeMember(x))).ToList();

            foreach (var item in nonPublics)
            {
                allMembers.Add(item);
            }

            int memberCount = allMembers.Count;

            for (int i = 0; i < memberCount; i++)
            {
                string memberName = allMembers[i].Name;

                if (allMembers[i].IsIndexer)
                {
                    InspectionReport memberReport = new InspectionReport(memberName, throwOnFail);
                    memberReport.Skip(string.Format("The member '{0}' is an indexer property and will be ignored.", memberName));
                    report.AddMemberReport(memberReport);
                    continue;
                }

                object srcVal  = allMembers[i].GetValue(source);
                object compVal = allMembers[i].GetValue(comp);

                try
                {
                    InspectionReport memberReport = null;

                    object res    = null;
                    var    caller = RecursiveCompare;
                    try
                    {
                        res = caller.Invoke(this, new[] { allMembers[i].Type, srcVal, compVal, memberName, throwOnFail });
                    }
                    catch (TargetInvocationException ex)
                    {
                        // Unwrap and rethrow basically.
                        if (ex.InnerException.GetType() == typeof(ObjectInspectorException))
                        {
                            throw ex.InnerException;
                        }
                        throw;
                    }

                    memberReport = (InspectionReport)res;

                    report.AddMemberReport(memberReport);

                    // NOTE: This was the old condition.  It turned out that sometimes, during recursive compares,
                    // we would have non-matching objects, though their evaluation wasn't complete.  This made it
                    // seem as though the items did not match at all....
                    //if (memberReport.ObjectsMatch == false)

                    // So I changed it to this.  This seems like the right thing to do.  Instead of bailing right away,
                    // let it finish doing its thing....
                    if (memberReport.ObjectsMatch == false) // && memberReport.InspectionComplete)
                    {
                        string errMsg = "The member values for '{0}' on type '{1}' do not match!\r\n{2}";
                        report.Fail(string.Format(errMsg, memberName, type, memberReport.Message));
                        return(report);
                    }
                }
                catch (Exception ex)
                {
                    throw;

                    string errMsg = "The inspection of the member '{0}' on Type '{1}' failed!\r\n" +
                                    "Message:\r\n{2}";
                    report.Fail(string.Format(errMsg, memberName, type, ex.Message));
                    return(report);
                }
            }

            report.Pass();

            return(report);
        }