#include "il2cpp-config.h"
#include <stddef.h>
#include "icalls/mscorlib/System.Reflection/MethodBase.h"
#include "il2cpp-class-internals.h"
#include "il2cpp-api.h"
#include "vm/Class.h"
#include "vm/Exception.h"
#include "vm/GenericClass.h"
#include "vm/Reflection.h"
#include "vm/StackTrace.h"
#include "vm/MetadataCache.h"
#include "metadata/GenericMetadata.h"

namespace il2cpp
{
namespace icalls
{
namespace mscorlib
{
namespace System
{
namespace Reflection
{
    static Il2CppClass* il2cpp_class_get_generic_type_definition(Il2CppClass *klass)
    {
        return klass->generic_class ? il2cpp::vm::GenericClass::GetTypeDefinition(klass->generic_class) : klass;
    }

    Il2CppReflectionMethod * MethodBase::GetMethodFromHandleInternalType(intptr_t method, intptr_t type)
    {
        Il2CppClass *klass = NULL;
        MethodInfo* methodInfo = (MethodInfo*)method;
        if (type)
        {
            klass = vm::Class::FromIl2CppType((Il2CppType*)type);
            if (il2cpp_class_get_generic_type_definition(methodInfo->klass) != il2cpp_class_get_generic_type_definition(klass))
                return NULL;

            // See the VerifyTwoArgumentGetMethodFromHandleWithGenericType for the failing test. Once we have support for inflating methods
            // we can implement this case as well and make that test pass.
            if (methodInfo->klass != klass)
                IL2CPP_NOT_IMPLEMENTED_ICALL(MethodBase::GetMethodFromHandleInternalType);
        }
        else
        {
            klass = methodInfo->klass;
        }
        return il2cpp::vm::Reflection::GetMethodObject(methodInfo, klass);
    }

    Il2CppReflectionMethod* MethodBase::GetCurrentMethod()
    {
        NOT_SUPPORTED_IL2CPP(MethodBase::GetCurrentMethod, "This icall is not supported by il2cpp. Use the il2cpp_codegen_get_method_object intrinsic instead.");

        return NULL;
    }

    void* /* System.Reflection.MethodBody */ MethodBase::GetMethodBodyInternal(intptr_t handle)
    {
        NOT_SUPPORTED_IL2CPP(MethodBase::GetMethodBodyInternal, "This icall is not supported by il2cpp.");

        return 0;
    }

    static const MethodInfo* il2cpp_method_get_equivalent_method(const MethodInfo *method, Il2CppClass *klass)
    {
        int offset = -1, i;

        if (method->is_inflated)
        {
            const Il2CppGenericContext *context = il2cpp::vm::MetadataCache::GetMethodGenericContext(method);
            if (context->method_inst)
            {
                const MethodInfo *result;

                //MonoMethodInflated *inflated = (MonoMethodInflated*)method;
                //method is inflated, we should inflate it on the other class
                Il2CppGenericContext newCtx;
                newCtx.method_inst = context->method_inst;
                newCtx.class_inst = context->class_inst;
                if (klass->generic_class)
                {
                    newCtx.class_inst = klass->generic_class->context.class_inst;
                }
                else if (klass->genericContainerIndex != kGenericContainerIndexInvalid)
                {
                    IL2CPP_NOT_IMPLEMENTED(il2cpp_method_get_equivalent_method: generic_container_case);
                    //const Il2CppGenericContainer *genericContainer = il2cpp::vm::MetadataCache::GetGenericContainerFromIndex(klass->genericContainerIndex);
                    //newCtx.class_inst = genericContainer->context.class_inst;
                }

                result = il2cpp::metadata::GenericMetadata::Inflate(method, &newCtx);
                return result;
            }
        }

        il2cpp::vm::Class::SetupMethods(method->klass);

        for (i = 0; i < method->klass->method_count; ++i)
        {
            if (method->klass->methods[i] == method)
            {
                offset = i;
                break;
            }
        }

        il2cpp::vm::Class::SetupMethods(klass);

        IL2CPP_ASSERT(offset >= 0 && offset < klass->method_count);
        return klass->methods[offset];
    }

    Il2CppReflectionMethod* MethodBase::GetMethodFromHandleInternalType_native(intptr_t method_handle, intptr_t type_handle, bool genericCheck)
    {
        Il2CppReflectionMethod *res = NULL;
        Il2CppClass *klass;
        const MethodInfo *method = (const MethodInfo*)method_handle;
        if (type_handle && genericCheck)
        {
            klass = il2cpp_class_from_il2cpp_type((Il2CppType*)type_handle);
            if (il2cpp_class_get_generic_type_definition(method->klass) != il2cpp_class_get_generic_type_definition(klass))
                return NULL;

            if (method->klass != klass)
            {
                method = il2cpp_method_get_equivalent_method(method, klass);
                if (!method)
                    return NULL;
            }
        }
        else if (type_handle)
            klass = il2cpp_class_from_il2cpp_type((Il2CppType*)type_handle);
        else
            klass = method->klass;

        res = il2cpp_method_get_object(method, klass);
        return res;
    }
} /* namespace Reflection */
} /* namespace System */
} /* namespace mscorlib */
} /* namespace icalls */
} /* namespace il2cpp */
