#include "il2cpp-config.h"
#include "il2cpp-runtime-stats.h"
#include "os/Atomic.h"
#include "os/Mutex.h"
#include "vm/Class.h"
#include "vm/GenericClass.h"
#include "vm/Image.h"
#include "vm/MetadataLock.h"
#include "vm/Method.h"
#include "vm/Type.h"
#include "metadata/ArrayMetadata.h"
#include "metadata/GenericMetadata.h"
#include "metadata/GenericMethod.h"
#include "metadata/Il2CppGenericClassHash.h"
#include "metadata/Il2CppGenericClassCompare.h"
#include "metadata/Il2CppGenericInstCompare.h"
#include "metadata/Il2CppGenericInstHash.h"
#include "metadata/Il2CppTypeCompare.h"
#include "metadata/Il2CppTypeHash.h"
#include "vm/MetadataAlloc.h"
#include "vm/MetadataCache.h"
#include "vm/Runtime.h"
#include "utils/Memory.h"
#include "utils/Il2CppHashMap.h"
#include "utils/StringUtils.h"
#include "il2cpp-class-internals.h"
#include "il2cpp-tabledefs.h"
#include <vector>
#include <limits>

using namespace il2cpp::vm;
using il2cpp::os::FastAutoLock;
using il2cpp::utils::StringUtils;
using il2cpp::vm::MetadataCache;

using il2cpp::vm::MetadataCalloc;
using il2cpp::vm::MetadataMalloc;

using std::vector;
using std::pair;


static size_t ImplilcitArrayInterfaceCount()
{
    size_t count = 0;
    if (il2cpp_defaults.generic_ilist_class) count++;
    if (il2cpp_defaults.generic_icollection_class) count++;
    if (il2cpp_defaults.generic_ienumerable_class) count++;
    if (il2cpp_defaults.generic_ireadonlylist_class) count++;
    if (il2cpp_defaults.generic_ireadonlycollection_class) count++;

    return count;
}

namespace il2cpp
{
namespace metadata
{
    static const char* GetArrayName(const char* elementClassName, uint32_t rank, bool bounded)
    {
        std::string name;
        name += elementClassName;
        name += "[";
        for (uint32_t i = 1; i < rank; i++)
            name += ",";
        if (bounded)
            name += "*";
        name += "]";

        return StringUtils::StringDuplicate(name.c_str());
    }

    static MethodInfo* ConstructArrayMethod(Il2CppClass* declaringType, const char* name, const Il2CppType* returnType, uint8_t parameterCount, const Il2CppType** parameterTypes)
    {
        MethodInfo* method = (MethodInfo*)MetadataCalloc(1, sizeof(MethodInfo));
        method->klass = declaringType;
        method->flags = METHOD_ATTRIBUTE_PUBLIC;
        method->iflags = METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL;
        method->name = name;
        method->slot = kInvalidIl2CppMethodSlot;
        method->return_type = returnType;
        method->parameters_count = parameterCount;
        const Il2CppType** parameters = (const Il2CppType**)MetadataCalloc(parameterCount, sizeof(Il2CppType*));
        for (uint8_t i = 0; i < parameterCount; i++)
        {
            parameters[i] = parameterTypes[i];
        }
        method->parameters = parameters;

        if (!strcmp(".ctor", name))
        {
            method->flags |= METHOD_ATTRIBUTE_RT_SPECIAL_NAME | METHOD_ATTRIBUTE_SPECIAL_NAME;
        }
        else
        {
            method->iflags |= METHOD_IMPL_ATTRIBUTE_RUNTIME;
        }

        ++il2cpp_runtime_stats.method_count;

        return method;
    }

    struct GenericArrayMethod
    {
        const char* name;
        const MethodInfo* method;
        const MethodInfo* interfaceMethodDefinition;
    };

    typedef vector<GenericArrayMethod> GenericArrayMethods;
    static GenericArrayMethods s_GenericArrayMethods;

    static size_t GetArrayGenericMethodsCount();
    static void PopulateArrayGenericMethods(Il2CppClass* klass, const MethodInfo** methodTable, uint16_t offset);

    static void CollectImplicitArrayInterfaces(Il2CppClass* elementClass, ::std::vector<Il2CppClass*>& interfaces);

    static const MethodInfo** CreateArrayMethods(Il2CppClass* arrayClass)
    {
        size_t methodIndex = 0;
        uint8_t rank = arrayClass->rank;
        ::std::vector<Il2CppClass*> interfaces;
        CollectImplicitArrayInterfaces(arrayClass, interfaces);

        size_t methodCount = 3 + (rank > 1 ? 2 : 1) + interfaces.size() * GetArrayGenericMethodsCount();
        IL2CPP_ASSERT(methodCount <= std::numeric_limits<uint16_t>::max());
        arrayClass->method_count = static_cast<uint16_t>(methodCount);

        const MethodInfo** methodTable = (const MethodInfo**)MetadataCalloc(methodCount, sizeof(MethodInfo*));

        const Il2CppType** parameters = (const Il2CppType**)alloca(rank * sizeof(Il2CppType*));
        for (uint8_t i = 0; i < rank; i++)
            parameters[i] = &il2cpp_defaults.int32_class->byval_arg;
        methodTable[methodIndex++] = ConstructArrayMethod(arrayClass, ".ctor", &il2cpp_defaults.void_class->byval_arg, rank, parameters);

        if (rank > 1)
        {
            parameters = (const Il2CppType**)alloca(2 * rank * sizeof(Il2CppType*));
            for (uint8_t i = 0; i < 2 * rank; i++)
                parameters[i] = &il2cpp_defaults.int32_class->byval_arg;
            methodTable[methodIndex++] = ConstructArrayMethod(arrayClass, ".ctor", &il2cpp_defaults.void_class->byval_arg, 2 * rank, parameters);
        }

        parameters = (const Il2CppType**)alloca((rank + 1) * sizeof(Il2CppType*));
        for (uint8_t i = 0; i < rank; i++)
            parameters[i] = &il2cpp_defaults.int32_class->byval_arg;
        parameters[rank] = &arrayClass->element_class->byval_arg;
        MethodInfo* setMethod = ConstructArrayMethod(arrayClass, "Set", &il2cpp_defaults.void_class->byval_arg, rank + 1, parameters);
        setMethod->invoker_method = vm::Runtime::GetArraySetInvoker();
        methodTable[methodIndex++] = setMethod;

        parameters = (const Il2CppType**)alloca(rank * sizeof(Il2CppType*));
        for (uint8_t i = 0; i < rank; i++)
            parameters[i] = &il2cpp_defaults.int32_class->byval_arg;
        MethodInfo* addressMethod = ConstructArrayMethod(arrayClass, "Address", &arrayClass->element_class->this_arg, rank, parameters);
        addressMethod->invoker_method = vm::Runtime::GetMissingMethodInvoker();
        methodTable[methodIndex++] = addressMethod;

        parameters = (const Il2CppType**)alloca(rank * sizeof(Il2CppType*));
        for (uint8_t i = 0; i < rank; i++)
            parameters[i] = &il2cpp_defaults.int32_class->byval_arg;
        MethodInfo* getMethod = ConstructArrayMethod(arrayClass, "Get", &arrayClass->element_class->byval_arg, rank, parameters);
        getMethod->invoker_method = vm::Runtime::GetArrayGetInvoker();
        methodTable[methodIndex++] = getMethod;

        IL2CPP_ASSERT(methodIndex <= std::numeric_limits<uint16_t>::max());
        PopulateArrayGenericMethods(arrayClass, methodTable, static_cast<uint16_t>(methodIndex));

        return methodTable;
    }

    static void CollectImplicitArrayInterfacesFromElementClass(Il2CppClass* elementClass, ::std::vector<Il2CppClass*>& interfaces)
    {
        while (elementClass != NULL)
        {
            interfaces.push_back(elementClass);
            if (!elementClass->byval_arg.valuetype && elementClass != il2cpp_defaults.value_type_class && elementClass != il2cpp_defaults.enum_class)
            {
                void* iter = NULL;
                while (Il2CppClass* itf = Class::GetInterfaces(elementClass, &iter))
                    interfaces.push_back(itf);
            }

            if (elementClass->rank == 1)
            {
                ::std::vector<Il2CppClass*> elementInterfaces;
                CollectImplicitArrayInterfacesFromElementClass(elementClass->element_class, elementInterfaces);

                for (::std::vector<Il2CppClass*>::iterator iter = elementInterfaces.begin(); iter != elementInterfaces.end(); ++iter)
                {
                    const Il2CppType* genericArgument = &(*iter)->byval_arg;

                    if (il2cpp_defaults.generic_ilist_class) interfaces.push_back(Class::GetInflatedGenericInstanceClass(il2cpp_defaults.generic_ilist_class, &genericArgument, 1));
                    if (il2cpp_defaults.generic_icollection_class) interfaces.push_back(Class::GetInflatedGenericInstanceClass(il2cpp_defaults.generic_icollection_class, &genericArgument, 1));
                    if (il2cpp_defaults.generic_ienumerable_class) interfaces.push_back(Class::GetInflatedGenericInstanceClass(il2cpp_defaults.generic_ienumerable_class, &genericArgument, 1));
                    if (il2cpp_defaults.generic_ireadonlylist_class) interfaces.push_back(Class::GetInflatedGenericInstanceClass(il2cpp_defaults.generic_ireadonlylist_class, &genericArgument, 1));
                    if (il2cpp_defaults.generic_ireadonlycollection_class) interfaces.push_back(Class::GetInflatedGenericInstanceClass(il2cpp_defaults.generic_ireadonlycollection_class, &genericArgument, 1));
                }
            }

            elementClass = Class::GetParent(elementClass);
            if (elementClass != NULL && (elementClass->byval_arg.valuetype || elementClass == il2cpp_defaults.value_type_class || elementClass == il2cpp_defaults.enum_class))
                break;
        }
    }

    static void CollectImplicitArrayInterfaces(Il2CppClass* arrayClass, ::std::vector<Il2CppClass*>& interfaces)
    {
        if (arrayClass->byval_arg.type != IL2CPP_TYPE_SZARRAY)
            return;

        CollectImplicitArrayInterfacesFromElementClass(arrayClass->element_class, interfaces);
    }

// note assuming list is ordered as IList, ICollection, IEnumerable

    static void CollectGenericArrayMethods()
    {
        const size_t kNumGenericArrayMethods = 13;
        s_GenericArrayMethods.reserve(kNumGenericArrayMethods);

        void* iter = NULL;
        while (const MethodInfo* method = Class::GetMethods(il2cpp_defaults.array_class, &iter))
        {
            if (strncmp(method->name, "InternalArray__", 15))
                continue;

            std::string name;
            std::string methodName;

            Il2CppClass* implementingInterface = NULL;
            if (!strncmp(method->name, "InternalArray__ICollection_", 27))
            {
                implementingInterface = il2cpp_defaults.generic_icollection_class;
                methodName = method->name + 27;
                name = StringUtils::Printf("System.Collections.Generic.ICollection`1.%s", method->name + 27);
            }
            else if (!strncmp(method->name, "InternalArray__IEnumerable_", 27))
            {
                implementingInterface = il2cpp_defaults.generic_ienumerable_class;
                methodName = method->name + 27;
                name = StringUtils::Printf("System.Collections.Generic.IEnumerable`1.%s", method->name + 27);
            }
            else if (!strncmp(method->name, "InternalArray__IReadOnlyList_", 29))
            {
                implementingInterface = il2cpp_defaults.generic_ireadonlylist_class;
                methodName = method->name + 29;
                name = StringUtils::Printf("System.Collections.Generic.IReadOnlyList`1.%s", method->name + 29);
            }
            else if (!strncmp(method->name, "InternalArray__IReadOnlyCollection_", 35))
            {
                implementingInterface = il2cpp_defaults.generic_ireadonlycollection_class;
                methodName = method->name + 35;
                name = StringUtils::Printf("System.Collections.Generic.IReadOnlyCollection`1.%s", method->name + 35);
            }
            else
            {
                implementingInterface = il2cpp_defaults.generic_ilist_class;
                methodName = method->name + 15;
                name = StringUtils::Printf("System.Collections.Generic.IList`1.%s", method->name + 15);
            }

            Class::Init(implementingInterface);

            const MethodInfo* matchingInterfacesMethod = NULL;
            for (int methodIndex = 0; methodIndex < implementingInterface->method_count; methodIndex++)
            {
                const MethodInfo* interfaceMethod = implementingInterface->methods[methodIndex];
                if (methodName == interfaceMethod->name)
                    matchingInterfacesMethod = interfaceMethod;
            }

            if (matchingInterfacesMethod != NULL)
            {
                GenericArrayMethod genericArrayMethod = { StringUtils::StringDuplicate(name.c_str()), method, matchingInterfacesMethod };
                s_GenericArrayMethods.push_back(genericArrayMethod);
            }
        }
    }

    static size_t GetArrayGenericMethodsCount()
    {
        if (s_GenericArrayMethods.size() == 0)
            CollectGenericArrayMethods();

        return s_GenericArrayMethods.size();
    }

    static MethodInfo* ConstructGenericArrayMethod(const GenericArrayMethod& genericArrayMethod, Il2CppClass* klass, Il2CppGenericContext* context)
    {
        MethodInfo* inflatedMethod;
        if (genericArrayMethod.method->is_generic)
        {
            inflatedMethod = GenericMethod::AllocateNewMethodInfo(genericArrayMethod.method, context->class_inst, context->method_inst);
        }
        else
        {
            inflatedMethod = (MethodInfo*)MetadataCalloc(1, sizeof(MethodInfo));
            memcpy(inflatedMethod, genericArrayMethod.method, sizeof(MethodInfo));
        }

        inflatedMethod->name = genericArrayMethod.name;

        // The array methods are owned by the specific array instance, but they do not exist in metadata
        // Ensure that the metadata token is zero (and not copied from the method definition) so any
        // metadata lookup (e.g. custom attributes) will not find anything
        inflatedMethod->klass = klass;
        inflatedMethod->token = 0;

        return inflatedMethod;
    }

    static void PopulateArrayGenericMethods(Il2CppClass* klass, const MethodInfo** methodTable, uint16_t offset)
    {
        for (int i = 0; i < klass->interface_offsets_count; i++)
        {
            Il2CppClass* interfaceType = klass->interfaceOffsets[i].interfaceType;
            if (!interfaceType->generic_class)
                continue;

            Il2CppClass* interfaceDefinition = GenericClass::GetTypeDefinition(interfaceType->generic_class);

            Il2CppGenericContext context = { 0 };
            context.method_inst = MetadataCache::GetGenericInst(&interfaceType->generic_class->context.class_inst->type_argv[0], 1);

            for (GenericArrayMethods::const_iterator iter = s_GenericArrayMethods.begin(); iter != s_GenericArrayMethods.end(); ++iter)
            {
                if (iter->interfaceMethodDefinition->klass != interfaceDefinition)
                    continue;

                MethodInfo* arrayMethod = ConstructGenericArrayMethod(*iter, klass, &context);
                methodTable[offset++] = arrayMethod;

                size_t vtableIndex = klass->interfaceOffsets[i].offset + iter->interfaceMethodDefinition->slot;
                klass->vtable[vtableIndex].method = arrayMethod;
                klass->vtable[vtableIndex].methodPtr = arrayMethod->virtualMethodPointer;
            }
        }
    }

    static void SetupArrayVTableAndInterfaceOffsets(Il2CppClass* klass)
    {
        Il2CppClass* arrayClass = Class::GetParent(klass);
        size_t arrayInterfacesCount = arrayClass->interface_offsets_count;

        ::std::vector<Il2CppClass*> interfaces;

        if (klass->byval_arg.type == IL2CPP_TYPE_SZARRAY)
        {
            CollectImplicitArrayInterfaces(klass, interfaces);
        }

        Il2CppRuntimeInterfaceOffsetPair* newInterfaceOffsets = (Il2CppRuntimeInterfaceOffsetPair*)MetadataMalloc((arrayInterfacesCount + ImplilcitArrayInterfaceCount() * interfaces.size()) * sizeof(Il2CppRuntimeInterfaceOffsetPair));
        memcpy(newInterfaceOffsets, arrayClass->interfaceOffsets, (arrayInterfacesCount) * sizeof(Il2CppRuntimeInterfaceOffsetPair));

        int32_t arrayVTableSlot = arrayClass->vtable_count;

        size_t implicitIntefaceMethodCount = 0;
        if (il2cpp_defaults.generic_ilist_class)               implicitIntefaceMethodCount += il2cpp_defaults.generic_ilist_class->method_count;
        if (il2cpp_defaults.generic_icollection_class)         implicitIntefaceMethodCount += il2cpp_defaults.generic_icollection_class->method_count;
        if (il2cpp_defaults.generic_ienumerable_class)         implicitIntefaceMethodCount += il2cpp_defaults.generic_ienumerable_class->method_count;
        if (il2cpp_defaults.generic_ireadonlylist_class)       implicitIntefaceMethodCount += il2cpp_defaults.generic_ireadonlylist_class->method_count;
        if (il2cpp_defaults.generic_ireadonlycollection_class) implicitIntefaceMethodCount += il2cpp_defaults.generic_ireadonlycollection_class->method_count;

        size_t slots = arrayVTableSlot + interfaces.size() * implicitIntefaceMethodCount;

        memcpy(klass->vtable, arrayClass->vtable, arrayVTableSlot * sizeof(VirtualInvokeData));

        size_t index = arrayInterfacesCount;
        int32_t vtableSlot = arrayVTableSlot;
        for (::std::vector<Il2CppClass*>::iterator iter = interfaces.begin(); iter != interfaces.end(); iter++, index += ImplilcitArrayInterfaceCount())
        {
            const Il2CppType* genericArgument = &(*iter)->byval_arg;

            size_t interfaceIndex = index;

            if (il2cpp_defaults.generic_ilist_class)
            {
                newInterfaceOffsets[interfaceIndex].interfaceType = Class::GetInflatedGenericInstanceClass(il2cpp_defaults.generic_ilist_class, &genericArgument, 1);
                newInterfaceOffsets[interfaceIndex].offset = vtableSlot;
                vtableSlot += newInterfaceOffsets[interfaceIndex].interfaceType->method_count;
                interfaceIndex++;
            }

            if (il2cpp_defaults.generic_icollection_class)
            {
                newInterfaceOffsets[interfaceIndex].interfaceType = Class::GetInflatedGenericInstanceClass(il2cpp_defaults.generic_icollection_class, &genericArgument, 1);
                newInterfaceOffsets[interfaceIndex].offset = vtableSlot;
                vtableSlot += newInterfaceOffsets[interfaceIndex].interfaceType->method_count;
                interfaceIndex++;
            }

            if (il2cpp_defaults.generic_ienumerable_class)
            {
                newInterfaceOffsets[interfaceIndex].interfaceType = Class::GetInflatedGenericInstanceClass(il2cpp_defaults.generic_ienumerable_class, &genericArgument, 1);
                newInterfaceOffsets[interfaceIndex].offset = vtableSlot;
                vtableSlot += newInterfaceOffsets[interfaceIndex].interfaceType->method_count;
                interfaceIndex++;
            }

            if (il2cpp_defaults.generic_ireadonlylist_class)
            {
                newInterfaceOffsets[interfaceIndex].interfaceType = Class::GetInflatedGenericInstanceClass(il2cpp_defaults.generic_ireadonlylist_class, &genericArgument, 1);
                newInterfaceOffsets[interfaceIndex].offset = vtableSlot;
                vtableSlot += newInterfaceOffsets[interfaceIndex].interfaceType->method_count;
                interfaceIndex++;
            }

            if (il2cpp_defaults.generic_ireadonlycollection_class)
            {
                newInterfaceOffsets[interfaceIndex].interfaceType = Class::GetInflatedGenericInstanceClass(il2cpp_defaults.generic_ireadonlycollection_class, &genericArgument, 1);
                newInterfaceOffsets[interfaceIndex].offset = vtableSlot;
                vtableSlot += newInterfaceOffsets[interfaceIndex].interfaceType->method_count;
                interfaceIndex++;
            }
        }

        size_t interfaceOffsetsCount = arrayInterfacesCount + ImplilcitArrayInterfaceCount() * interfaces.size();
        IL2CPP_ASSERT(interfaceOffsetsCount <= std::numeric_limits<uint16_t>::max());
        klass->interface_offsets_count = static_cast<uint16_t>(interfaceOffsetsCount);
        klass->interfaceOffsets = newInterfaceOffsets;
    }

    void SetupCastClass(Il2CppClass *arrayType)
    {
        Il2CppClass *elementType = arrayType->element_class;

        arrayType->castClass = ArrayMetadata::GetArrayVarianceReducedType(elementType);
        arrayType->has_references = Type::IsReference(&elementType->byval_arg) || elementType->has_references;
    }

    Il2CppClass** ArrayMetadata::CreateArrayInterfacesLocked(Il2CppClass* klass, const FastAutoLock& lock)
    {
        Il2CppClass** implementedInterfaces = NULL;

        if (klass->byval_arg.type == IL2CPP_TYPE_SZARRAY)
        {
            IL2CPP_ASSERT(klass->implementedInterfaces == NULL);

            const Il2CppType* genericArguments = &klass->element_class->byval_arg;

            IL2CPP_ASSERT(klass->interfaces_count == ImplilcitArrayInterfaceCount());
            implementedInterfaces = (Il2CppClass**)MetadataMalloc(klass->interfaces_count * sizeof(Il2CppClass*));

            size_t interfaceIndex = 0;

            if (il2cpp_defaults.generic_ilist_class)
            {
                implementedInterfaces[interfaceIndex] = Class::GetInflatedGenericInstanceClass(il2cpp_defaults.generic_ilist_class, &genericArguments, 1);
                IL2CPP_ASSERT(implementedInterfaces[interfaceIndex]);
                interfaceIndex++;
            }
            if (il2cpp_defaults.generic_icollection_class)
            {
                implementedInterfaces[interfaceIndex] = Class::GetInflatedGenericInstanceClass(il2cpp_defaults.generic_icollection_class, &genericArguments, 1);
                IL2CPP_ASSERT(implementedInterfaces[interfaceIndex]);
                interfaceIndex++;
            }
            if (il2cpp_defaults.generic_ienumerable_class)
            {
                implementedInterfaces[interfaceIndex] = Class::GetInflatedGenericInstanceClass(il2cpp_defaults.generic_ienumerable_class, &genericArguments, 1);
                IL2CPP_ASSERT(implementedInterfaces[interfaceIndex]);
                interfaceIndex++;
            }
            if (il2cpp_defaults.generic_ireadonlylist_class)
            {
                implementedInterfaces[interfaceIndex] = Class::GetInflatedGenericInstanceClass(il2cpp_defaults.generic_ireadonlylist_class, &genericArguments, 1);
                IL2CPP_ASSERT(implementedInterfaces[interfaceIndex]);
                interfaceIndex++;
            }
            if (il2cpp_defaults.generic_ireadonlycollection_class)
            {
                implementedInterfaces[interfaceIndex] = Class::GetInflatedGenericInstanceClass(il2cpp_defaults.generic_ireadonlycollection_class, &genericArguments, 1);
                IL2CPP_ASSERT(implementedInterfaces[interfaceIndex]);
                interfaceIndex++;
            }
        }

        return implementedInterfaces;
    }

    void ArrayMetadata::SetupArrayVTable(Il2CppClass* klass, const FastAutoLock& lock)
    {
        // we assume we are being called as part of Class::Init and that the element class has already been initialized
        IL2CPP_ASSERT(klass->element_class->initialized);

        SetupCastClass(klass);
        SetupArrayVTableAndInterfaceOffsets(klass);
        il2cpp::os::Atomic::PublishPointer(&klass->methods, CreateArrayMethods(klass));
    }

    struct SZArrayClassHash
    {
        size_t operator()(const Il2CppClass* arrayClass) const
        {
            return Il2CppTypeHash::Hash(&arrayClass->byval_arg);
        }
    };

    struct SZArrayClassCompare
    {
        bool operator()(const Il2CppClass* arrayClass1, const Il2CppClass* arrayClass2) const
        {
            return Il2CppTypeEqualityComparer::AreEqual(&arrayClass1->byval_arg, &arrayClass2->byval_arg);
        }
    };

    struct ArrayClassHash
    {
        size_t operator()(const std::pair<Il2CppClass*, uint32_t>& arrayClass) const
        {
            return Il2CppTypeHash::Hash(&arrayClass.first->byval_arg) * arrayClass.second;
        }
    };

    struct ArrayClassCompare
    {
        bool operator()(const std::pair<Il2CppClass*, uint32_t>& arrayClass1, const std::pair<Il2CppClass*, uint32_t>& arrayClass2) const
        {
            return Il2CppTypeEqualityComparer::AreEqual(&arrayClass1.first->byval_arg, &arrayClass2.first->byval_arg) && arrayClass1.second == arrayClass2.second;
        }
    };

    typedef Il2CppReaderWriterLockedHashMap<Il2CppClass*, Il2CppClass*, SZArrayClassHash, SZArrayClassCompare> SZArrayClassMap;
    typedef Il2CppReaderWriterLockedHashMap<std::pair<Il2CppClass*, uint32_t>, Il2CppClass*, ArrayClassHash, ArrayClassCompare> ArrayClassMap;

    SZArrayClassMap s_SZArrayClassMap;
    ArrayClassMap s_ArrayClassMap;

    void ArrayMetadata::Clear()
    {
        WalkArrays([](Il2CppClass* arrayClass, void* ctx) { IL2CPP_FREE(const_cast<char*>(arrayClass->name)); }, NULL);
        WalkSZArrays([](Il2CppClass* arrayClass, void* ctx) { IL2CPP_FREE(const_cast<char*>(arrayClass->name)); }, NULL);

        for (auto genericArrayMethod : s_GenericArrayMethods)
            IL2CPP_FREE((void*)genericArrayMethod.name);

        s_SZArrayClassMap.Clear();
        s_ArrayClassMap.Clear();
        s_GenericArrayMethods.clear();
    }

    static Il2CppClass* FindBoundedArrayClass(Il2CppClass* elementClass, uint32_t rank, bool bounded)
    {
        Il2CppClass* arrayClass = NULL;
        if (rank > 1 || bounded)
        {
            if (s_ArrayClassMap.TryGet(std::make_pair(elementClass, rank), &arrayClass))
                return arrayClass;
        }
        else
        {
            if (s_SZArrayClassMap.TryGet(elementClass, &arrayClass))
                return arrayClass;
        }

        return NULL;
    }

    Il2CppClass* ArrayMetadata::GetBoundedArrayClass(Il2CppClass* elementClass, uint32_t rank, bool bounded)
    {
        IL2CPP_ASSERT(rank <= 255);

        if (rank > 1)
            bounded = false;

        // Check for a cached array class using the reader lock only
        Il2CppClass* cachedArrayClass = FindBoundedArrayClass(elementClass, rank, bounded);
        if (cachedArrayClass != NULL)
            return cachedArrayClass;

        FastAutoLock lock(&il2cpp::vm::g_MetadataLock);

        // Check if the array class was created while we were waiting for the g_MetadataLock
        cachedArrayClass = FindBoundedArrayClass(elementClass, rank, bounded);
        if (cachedArrayClass != NULL)
            return cachedArrayClass;

        Il2CppClass* arrayClass = il2cpp_defaults.array_class;
        Class::Init(arrayClass);

        //count number of virtual call slots for array class
        ::std::vector<Il2CppClass*> interfaces;

        if (rank <= 1 && !bounded)
            CollectImplicitArrayInterfacesFromElementClass(elementClass, interfaces);

        size_t implicitIntefaceMethodCount = 0;
        if (il2cpp_defaults.generic_ilist_class)               implicitIntefaceMethodCount += il2cpp_defaults.generic_ilist_class->method_count;
        if (il2cpp_defaults.generic_icollection_class)         implicitIntefaceMethodCount += il2cpp_defaults.generic_icollection_class->method_count;
        if (il2cpp_defaults.generic_ienumerable_class)         implicitIntefaceMethodCount += il2cpp_defaults.generic_ienumerable_class->method_count;
        if (il2cpp_defaults.generic_ireadonlylist_class)       implicitIntefaceMethodCount += il2cpp_defaults.generic_ireadonlylist_class->method_count;
        if (il2cpp_defaults.generic_ireadonlycollection_class) implicitIntefaceMethodCount += il2cpp_defaults.generic_ireadonlycollection_class->method_count;

        size_t slots = arrayClass->vtable_count + interfaces.size() * implicitIntefaceMethodCount;

        Il2CppClass* klass = (Il2CppClass*)MetadataCalloc(1, sizeof(Il2CppClass) + (slots * sizeof(VirtualInvokeData)));
        klass->klass = klass;
        klass->image = elementClass->image;
        // can share the const char* since it's immutable
        klass->namespaze = elementClass->namespaze;
        klass->name = GetArrayName(elementClass->name, rank, bounded);

        klass->parent = il2cpp_defaults.array_class;

        klass->flags = TYPE_ATTRIBUTE_AUTO_LAYOUT | TYPE_ATTRIBUTE_ANSI_CLASS | TYPE_ATTRIBUTE_CLASS | TYPE_ATTRIBUTE_PUBLIC | TYPE_ATTRIBUTE_SEALED | TYPE_ATTRIBUTE_SERIALIZABLE;
        klass->rank = rank;

        klass->instance_size = Class::GetInstanceSize(arrayClass);
        klass->stack_slot_size = sizeof(void*);
        klass->vtable_count = static_cast<uint16_t>(slots);

        // need this before we access the size or has_references
        Class::SetupFields(elementClass);

        klass->element_size = Class::GetArrayElementSize(elementClass);
        klass->native_size = klass->thread_static_fields_offset = -1;

        klass->has_references = Type::IsReference(&elementClass->byval_arg) || elementClass->has_references;

        klass->size_inited = true; // set only after instance_size and has_references are set

        klass->element_class = elementClass;
        SetupCastClass(klass);

        if (rank > 1 || bounded)
        {
            Il2CppArrayType *at = (Il2CppArrayType*)MetadataCalloc(1, sizeof(Il2CppArrayType));
            klass->byval_arg.type = IL2CPP_TYPE_ARRAY;
            klass->byval_arg.data.array = at;
            at->etype = &elementClass->byval_arg;
            at->rank = rank;
        }
        else
        {
            klass->byval_arg.type = IL2CPP_TYPE_SZARRAY;
            klass->byval_arg.data.type = &elementClass->byval_arg;
        }

        klass->this_arg = klass->byval_arg;
        klass->this_arg.byref = 1;

        if (rank > 1 || bounded)
        {
            klass->interfaces_count = 0;
        }
        else
        {
            klass->interfaces_count = (uint16_t)ImplilcitArrayInterfaceCount();
        }

        klass->interopData = MetadataCache::GetInteropDataForType(&klass->byval_arg);

        // Insert the new array class while still holding the g_MetadataLock
        // This ensures that the class is only added once
        // And WalkSZArrays and WalkArrays (see below) only take the g_MetadataLock and assume that the maps won't be changed
        // It's not safe to take the reader/writer lock there because those locks aren't re-entrant and those methods take
        // call backs that call arbitrary code

        if (rank > 1 || bounded)
            s_ArrayClassMap.Add(ArrayClassMap::key_type(std::make_pair(klass->element_class, klass->rank)), klass);
        else
            s_SZArrayClassMap.Add(klass->element_class, klass);

        // This is intentionally after adding to the map so debug name can find the cached class
        Class::SetDebugName(klass);

        return klass;
    }

    void ArrayMetadata::WalkSZArrays(ArrayTypeWalkCallback callback, void* context)
    {
        s_SZArrayClassMap.LockShared();

        for (SZArrayClassMap::iterator it = s_SZArrayClassMap.UnlockedBegin(); it != s_SZArrayClassMap.UnlockedEnd(); it++)
        {
            callback(it->second, context);
        }

        s_SZArrayClassMap.ReleaseShared();
    }

    void ArrayMetadata::WalkArrays(ArrayTypeWalkCallback callback, void* context)
    {
        s_ArrayClassMap.LockShared();

        for (ArrayClassMap::iterator it = s_ArrayClassMap.UnlockedBegin(); it != s_ArrayClassMap.UnlockedEnd(); it++)
        {
            callback(it->second, context);
        }

        s_ArrayClassMap.ReleaseShared();
    }

    void ArrayMetadata::AcquireMetadataLocks()
    {
        s_SZArrayClassMap.LockExclusive();
        s_ArrayClassMap.LockExclusive();
    }

    void ArrayMetadata::ReleaseMetadataLocks()
    {
        s_ArrayClassMap.ReleaseExclusive();
        s_SZArrayClassMap.ReleaseExclusive();
    }
} /* namespace vm */
} /* namespace il2cpp */
