<commit_before>﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Runtime.Serialization;

namespace ValveKeyValue
{
    static class ObjectCopier
    {
        public static TObject MakeObject<TObject>(KVObject keyValueObject)
            => MakeObject<TObject>(keyValueObject, new DefaultObjectReflector());

        public static object MakeObject(Type objectType, KVObject keyValueObject, IObjectReflector reflector)
            => InvokeGeneric(nameof(MakeObject), objectType, new object[] { keyValueObject, reflector });

        public static TObject MakeObject<TObject>(KVObject keyValueObject, IObjectReflector reflector)
        {
            Require.NotNull(keyValueObject, nameof(keyValueObject));
            Require.NotNull(reflector, nameof(reflector));

            if (keyValueObject.Value.ValueType == KVValueType.Collection)
            {
                object[] enumerableValues;
                Type lookupValueType;
                object enumerable;
                if (IsLookupWithStringKey(typeof(TObject), out lookupValueType))
                {
                    return (TObject)MakeLookup(lookupValueType, keyValueObject);
                }
                else if (IsDictionary(typeof(TObject)))
                {
                    return (TObject)MakeDictionary(typeof(TObject), keyValueObject);
                }
                else if (IsArray(keyValueObject, out enumerableValues) && ConstructTypedEnumerable(typeof(TObject), enumerableValues, out enumerable))
                {
                    return (TObject)enumerable;
                }
                else if (IsConstructibleEnumerableType(typeof(TObject)))
                {
                    throw new InvalidOperationException($"Cannot deserialize a non-array value to type \"{typeof(TObject).Namespace}.{typeof(TObject).Name}\".");
                }

                var typedObject = (TObject)FormatterServices.GetSafeUninitializedObject(typeof(TObject));
                CopyObject(keyValueObject, typedObject, reflector);
                return typedObject;
            }
            else if (CanConvertValueTo(typeof(TObject)))
            {
                return (TObject)Convert.ChangeType(keyValueObject.Value, typeof(TObject));
            }
            else
            {
                throw new NotSupportedException(typeof(TObject).Name);
            }
        }

        public static KVObject FromObject<TObject>(TObject managedObject, string topLevelName)
            => FromObjectCore(managedObject, topLevelName, new DefaultObjectReflector(), new HashSet<object>());

        static KVObject FromObjectCore<TObject>(TObject managedObject, string topLevelName, IObjectReflector reflector, HashSet<object> visitedObjects)
        {
            if (managedObject == null)
            {
                throw new ArgumentNullException(nameof(managedObject));
            }

            Require.NotNull(topLevelName, nameof(topLevelName));
            Require.NotNull(reflector, nameof(reflector));
            Require.NotNull(visitedObjects, nameof(visitedObjects));

            if (!visitedObjects.Add(managedObject))
            {
                throw new KeyValueException("Serialization failed - circular object reference detected.");
            }

            var childObjects = new List<KVObject>();

            if (typeof(IDictionary).IsAssignableFrom(typeof(TObject)))
            {
                var dictionary = (IDictionary)managedObject;
                var enumerator = dictionary.GetEnumerator();
                while (enumerator.MoveNext())
                {
                    var entry = enumerator.Entry;
                    childObjects.Add(new KVObject(entry.Key.ToString(), entry.Value.ToString()));
                }
            }
            else if (typeof(TObject).IsArray || typeof(IEnumerable).IsAssignableFrom(typeof(TObject)))
            {
                var counter = 0;
                foreach (var child in (IEnumerable)managedObject)
                {
                    var childKVObject = CopyObject(child, counter.ToString(), reflector, visitedObjects);
                    childObjects.Add(childKVObject);

                    counter++;
                }
            }
            else
            {
                foreach (var member in reflector.GetMembers(managedObject).OrderBy(p => p.Name))
                {
                    var name = member.Name;
                    if (!member.IsExplicitName && name.Length > 0 && char.IsUpper(name[0]))
                    {
                        name = char.ToLower(name[0]) + name.Substring(1);
                    }

                    if (typeof(IConvertible).IsAssignableFrom(member.MemberType))
                    {
                        childObjects.Add(new KVObject(name, (string)Convert.ChangeType(member.Value, typeof(string))));
                    }
                    else
                    {
                        childObjects.Add(CopyObject(member.Value, member.Name, reflector, visitedObjects));
                    }
                }
            }

            return new KVObject(topLevelName, childObjects);
        }

        static KVObject CopyObject(object @object, string name, IObjectReflector reflector, HashSet<object> visitedObjects)
        {
            try
            {
                var keyValueRepresentation = (KVObject)typeof(ObjectCopier)
                    .GetMethod(nameof(FromObjectCore), BindingFlags.NonPublic | BindingFlags.Static)
                    .MakeGenericMethod(@object.GetType())
                    .Invoke(null, new[] { @object, name, reflector, visitedObjects });

                return keyValueRepresentation;
            }
            catch (TargetInvocationException ex)
            {
                ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
                return default(KVObject); // Unreachable.
            }
        }

        static void CopyObject<TObject>(KVObject kv, TObject obj, IObjectReflector reflector)
        {
            Require.NotNull(kv, nameof(kv));

            // Cannot use Require.NotNull here because TObject might be a struct.
            if (obj == null)
            {
                throw new ArgumentNullException(nameof(obj));
            }

            Require.NotNull(reflector, nameof(reflector));

            var members = reflector.GetMembers(obj).ToDictionary(m => m.Name, m => m, StringComparer.OrdinalIgnoreCase);

            foreach (var item in kv.Children)
            {
                IObjectMember member;
                if (!members.TryGetValue(item.Name, out member))
                {
                    continue;
                }

                member.Value = MakeObject(member.MemberType, item, reflector);
            }
        }

        static bool IsArray(KVObject obj, out object[] values)
        {
            values = null;

            if (obj.Value.ValueType != KVValueType.Collection)
            {
                return false;
            }

            if (obj.Children.Any(i => !IsNumeric(i.Name)))
            {
                return false;
            }

            var items = obj.Children
                .Select(i => new { Index = int.Parse(i.Name), i.Value })
                .OrderBy(i => i.Index)
                .ToArray();

            for (int i = 0; i < items.Length; i++)
            {
                if (i != items[i].Index)
                {
                    return false;
                }
            }

            values = items.Select(i => i.Value).ToArray();
            return true;
        }

        static bool IsLookupWithStringKey(Type type, out Type valueType)
        {
            valueType = null;

            if (!type.IsGenericType)
            {<commit_msg>Remove unreachable code (guaranteed by caller)<commit_after>﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Runtime.Serialization;

namespace ValveKeyValue
{
    static class ObjectCopier
    {
        public static TObject MakeObject<TObject>(KVObject keyValueObject)
            => MakeObject<TObject>(keyValueObject, new DefaultObjectReflector());

        public static object MakeObject(Type objectType, KVObject keyValueObject, IObjectReflector reflector)
            => InvokeGeneric(nameof(MakeObject), objectType, new object[] { keyValueObject, reflector });

        public static TObject MakeObject<TObject>(KVObject keyValueObject, IObjectReflector reflector)
        {
            Require.NotNull(keyValueObject, nameof(keyValueObject));
            Require.NotNull(reflector, nameof(reflector));

            if (keyValueObject.Value.ValueType == KVValueType.Collection)
            {
                object[] enumerableValues;
                Type lookupValueType;
                object enumerable;
                if (IsLookupWithStringKey(typeof(TObject), out lookupValueType))
                {
                    return (TObject)MakeLookup(lookupValueType, keyValueObject);
                }
                else if (IsDictionary(typeof(TObject)))
                {
                    return (TObject)MakeDictionary(typeof(TObject), keyValueObject);
                }
                else if (IsArray(keyValueObject, out enumerableValues) && ConstructTypedEnumerable(typeof(TObject), enumerableValues, out enumerable))
                {
                    return (TObject)enumerable;
                }
                else if (IsConstructibleEnumerableType(typeof(TObject)))
                {
                    throw new InvalidOperationException($"Cannot deserialize a non-array value to type \"{typeof(TObject).Namespace}.{typeof(TObject).Name}\".");
                }

                var typedObject = (TObject)FormatterServices.GetSafeUninitializedObject(typeof(TObject));
                CopyObject(keyValueObject, typedObject, reflector);
                return typedObject;
            }
            else if (CanConvertValueTo(typeof(TObject)))
            {
                return (TObject)Convert.ChangeType(keyValueObject.Value, typeof(TObject));
            }
            else
            {
                throw new NotSupportedException(typeof(TObject).Name);
            }
        }

        public static KVObject FromObject<TObject>(TObject managedObject, string topLevelName)
            => FromObjectCore(managedObject, topLevelName, new DefaultObjectReflector(), new HashSet<object>());

        static KVObject FromObjectCore<TObject>(TObject managedObject, string topLevelName, IObjectReflector reflector, HashSet<object> visitedObjects)
        {
            if (managedObject == null)
            {
                throw new ArgumentNullException(nameof(managedObject));
            }

            Require.NotNull(topLevelName, nameof(topLevelName));
            Require.NotNull(reflector, nameof(reflector));
            Require.NotNull(visitedObjects, nameof(visitedObjects));

            if (!visitedObjects.Add(managedObject))
            {
                throw new KeyValueException("Serialization failed - circular object reference detected.");
            }

            var childObjects = new List<KVObject>();

            if (typeof(IDictionary).IsAssignableFrom(typeof(TObject)))
            {
                var dictionary = (IDictionary)managedObject;
                var enumerator = dictionary.GetEnumerator();
                while (enumerator.MoveNext())
                {
                    var entry = enumerator.Entry;
                    childObjects.Add(new KVObject(entry.Key.ToString(), entry.Value.ToString()));
                }
            }
            else if (typeof(TObject).IsArray || typeof(IEnumerable).IsAssignableFrom(typeof(TObject)))
            {
                var counter = 0;
                foreach (var child in (IEnumerable)managedObject)
                {
                    var childKVObject = CopyObject(child, counter.ToString(), reflector, visitedObjects);
                    childObjects.Add(childKVObject);

                    counter++;
                }
            }
            else
            {
                foreach (var member in reflector.GetMembers(managedObject).OrderBy(p => p.Name))
                {
                    var name = member.Name;
                    if (!member.IsExplicitName && name.Length > 0 && char.IsUpper(name[0]))
                    {
                        name = char.ToLower(name[0]) + name.Substring(1);
                    }

                    if (typeof(IConvertible).IsAssignableFrom(member.MemberType))
                    {
                        childObjects.Add(new KVObject(name, (string)Convert.ChangeType(member.Value, typeof(string))));
                    }
                    else
                    {
                        childObjects.Add(CopyObject(member.Value, member.Name, reflector, visitedObjects));
                    }
                }
            }

            return new KVObject(topLevelName, childObjects);
        }

        static KVObject CopyObject(object @object, string name, IObjectReflector reflector, HashSet<object> visitedObjects)
        {
            try
            {
                var keyValueRepresentation = (KVObject)typeof(ObjectCopier)
                    .GetMethod(nameof(FromObjectCore), BindingFlags.NonPublic | BindingFlags.Static)
                    .MakeGenericMethod(@object.GetType())
                    .Invoke(null, new[] { @object, name, reflector, visitedObjects });

                return keyValueRepresentation;
            }
            catch (TargetInvocationException ex)
            {
                ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
                return default(KVObject); // Unreachable.
            }
        }

        static void CopyObject<TObject>(KVObject kv, TObject obj, IObjectReflector reflector)
        {
            Require.NotNull(kv, nameof(kv));

            // Cannot use Require.NotNull here because TObject might be a struct.
            if (obj == null)
            {
                throw new ArgumentNullException(nameof(obj));
            }

            Require.NotNull(reflector, nameof(reflector));

            var members = reflector.GetMembers(obj).ToDictionary(m => m.Name, m => m, StringComparer.OrdinalIgnoreCase);

            foreach (var item in kv.Children)
            {
                IObjectMember member;
                if (!members.TryGetValue(item.Name, out member))
                {
                    continue;
                }

                member.Value = MakeObject(member.MemberType, item, reflector);
            }
        }

        static bool IsArray(KVObject obj, out object[] values)
        {
            values = null;

            if (obj.Children.Any(i => !IsNumeric(i.Name)))
            {
                return false;
            }

            var items = obj.Children
                .Select(i => new { Index = int.Parse(i.Name), i.Value })
                .OrderBy(i => i.Index)
                .ToArray();

            for (int i = 0; i < items.Length; i++)
            {
                if (i != items[i].Index)
                {
                    return false;
                }
            }

            values = items.Select(i => i.Value).ToArray();
            return true;
        }

        static bool IsLookupWithStringKey(Type type, out Type valueType)
        {
            valueType = null;

            if (!type.IsGenericType)
            {
{'fraction_non_alphanumeric': 0.09040835707502375, 'fraction_numerical': 0.0007597340930674264, 'mean_word_length': 1.6763808878346322, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1448626', 'n_tokens_mistral': 3927, 'n_tokens_neox': 3707, 'n_words': 760}