/*
    Copyright 2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com

    All rights reserved.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Il2CppInspector.Reflection
{
    // Class representing a MethodBase.Invoke() method for a specific signature
    // Every IL2CPP invoker has the signature:
    // void* RuntimeInvoker_{RequiresObject (True/False)}{ReturnType}_{ParameterTypes}
    // (Il2CppMethodPointer pointer, const RuntimeMethod* methodMetadata, void* obj, void** args)
    public class MethodInvoker
    {
        // IL2CPP invoker index
        public int Index { get; }

        // Virtual address of the invoker function
        public (ulong Start, ulong End) VirtualAddress { get; }

        // If false, the first argument to the called function pointers must be the object instance
        public bool IsStatic { get; }

        // Return type
        public TypeInfo ReturnType { get; }

        // Argument types
        public TypeInfo[] ParameterTypes { get; }

        // Find the correct method invoker for a method with a specific signature
        public MethodInvoker(MethodBase exampleMethod, int index) {
            var model = exampleMethod.Assembly.Model;
            var package = exampleMethod.Assembly.Model.Package;

            Index = index;
            IsStatic = exampleMethod.IsStatic;
            
            ReturnType = exampleMethod.IsConstructor ? model.TypesByFullName["System.Void"] : mapParameterType(model, ((MethodInfo) exampleMethod).ReturnType);
            ParameterTypes = exampleMethod.DeclaredParameters.Select(p => mapParameterType(model, p.ParameterType)).ToArray();

            var start = package.MethodInvokePointers[Index];
            VirtualAddress = (start & 0xffff_ffff_ffff_fffe, package.FunctionAddresses[start]);
        }

        // The invokers use Object for all reference types, and SByte for booleans
        private TypeInfo mapParameterType(Il2CppModel model, TypeInfo type) => type switch {
            { IsValueType: false }              => model.TypesByFullName["System.Object"],
            { FullName: "System.Boolean" }      => model.TypesByFullName["System.SByte"],
            _                                   => type
        };

        public string Name => $"RunTimeInvoker_{!IsStatic}{ReturnType.BaseName}_" + string.Join("_", ParameterTypes.Select(p => p.BaseName));

        // Display as a C++ method signature
        public string Signature => Name + "(Il2CppMethodPointer pointer, const RuntimeMethod* methodMetadata, void* obj, void** args)";

        public override string ToString() => Signature;
    }
}
