#pragma once
#include "../deps/UnityResolve/UnityResolve.hpp"
#include "Log.h"
#include <memory>

namespace Il2cppUtils {
    using namespace GakumasLocal;

    struct Il2CppClassHead {
        // The following fields are always valid for a Il2CppClass structure
        const void* image;
        void* gc_desc;
        const char* name;
        const char* namespaze;
    };

    struct MethodInfo {
        uintptr_t methodPointer;
        uintptr_t invoker_method;
        const char* name;
        uintptr_t klass;
        //const Il2CppType* return_type;
        //const ParameterInfo* parameters;
        const void* return_type;
        const void* parameters;
        uintptr_t methodDefinition;
        uintptr_t genericContainer;
        uint32_t token;
        uint16_t flags;
        uint16_t iflags;
        uint16_t slot;
        uint8_t parameters_count;
        uint8_t is_generic : 1;
        uint8_t is_inflated : 1;
        uint8_t wrapper_type : 1;
        uint8_t is_marshaled_from_native : 1;
    };

    struct Il2CppObject
    {
        union
        {
            void* klass;
            void* vtable;
        };
        void* monitor;
    };

    enum Il2CppTypeEnum
    {
        IL2CPP_TYPE_END = 0x00,       /* End of List */
        IL2CPP_TYPE_VOID = 0x01,
        IL2CPP_TYPE_BOOLEAN = 0x02,
        IL2CPP_TYPE_CHAR = 0x03,
        IL2CPP_TYPE_I1 = 0x04,
        IL2CPP_TYPE_U1 = 0x05,
        IL2CPP_TYPE_I2 = 0x06,
        IL2CPP_TYPE_U2 = 0x07,
        IL2CPP_TYPE_I4 = 0x08,
        IL2CPP_TYPE_U4 = 0x09,
        IL2CPP_TYPE_I8 = 0x0a,
        IL2CPP_TYPE_U8 = 0x0b,
        IL2CPP_TYPE_R4 = 0x0c,
        IL2CPP_TYPE_R8 = 0x0d,
        IL2CPP_TYPE_STRING = 0x0e,
        IL2CPP_TYPE_PTR = 0x0f,
        IL2CPP_TYPE_BYREF = 0x10,
        IL2CPP_TYPE_VALUETYPE = 0x11,
        IL2CPP_TYPE_CLASS = 0x12,
        IL2CPP_TYPE_VAR = 0x13,
        IL2CPP_TYPE_ARRAY = 0x14,
        IL2CPP_TYPE_GENERICINST = 0x15,
        IL2CPP_TYPE_TYPEDBYREF = 0x16,
        IL2CPP_TYPE_I = 0x18,
        IL2CPP_TYPE_U = 0x19,
        IL2CPP_TYPE_FNPTR = 0x1b,
        IL2CPP_TYPE_OBJECT = 0x1c,
        IL2CPP_TYPE_SZARRAY = 0x1d,
        IL2CPP_TYPE_MVAR = 0x1e,
        IL2CPP_TYPE_CMOD_REQD = 0x1f,
        IL2CPP_TYPE_CMOD_OPT = 0x20,
        IL2CPP_TYPE_INTERNAL = 0x21,

        IL2CPP_TYPE_MODIFIER = 0x40,
        IL2CPP_TYPE_SENTINEL = 0x41,
        IL2CPP_TYPE_PINNED = 0x45,

        IL2CPP_TYPE_ENUM = 0x55
    };

    typedef struct Il2CppType
    {
        void* dummy;
        unsigned int attrs : 16;
        Il2CppTypeEnum type : 8;
        unsigned int num_mods : 6;
        unsigned int byref : 1;
        unsigned int pinned : 1;
    } Il2CppType;

    struct Il2CppReflectionType
    {
        Il2CppObject object;
        const Il2CppType* type;
    };

    struct Resolution_t {
        int width;
        int height;
        int herz;
    };

    UnityResolve::Class* GetClass(const std::string& assemblyName, const std::string& nameSpaceName,
                   const std::string& className) {
        const auto assembly = UnityResolve::Get(assemblyName);
        if (!assembly) {
            Log::ErrorFmt("GetMethodPointer error: assembly %s not found.", assemblyName.c_str());
            return nullptr;
        }
        const auto pClass = assembly->Get(className, nameSpaceName);
        if (!pClass) {
            Log::ErrorFmt("GetMethodPointer error: Class %s::%s not found.", nameSpaceName.c_str(), className.c_str());
            return nullptr;
        }
        return pClass;
    }
    /*
    UnityResolve::Method* GetMethodIl2cpp(const char* assemblyName, const char* nameSpaceName,
                                    const char* className, const char* methodName, const int argsCount) {
        auto domain = UnityResolve::Invoke<void*>("il2cpp_domain_get");
        UnityResolve::Invoke<void*>("il2cpp_thread_attach", domain);
        auto image = UnityResolve::Invoke<void*>("il2cpp_assembly_get_image", domain);
        if (!image) {
            Log::ErrorFmt("GetMethodIl2cpp error: assembly %s not found.", assemblyName);
            return nullptr;
        }
        Log::Debug("GetMethodIl2cpp 1");
        auto klass = UnityResolve::Invoke<void*>("il2cpp_class_from_name", image, nameSpaceName, className);
        if (!klass) {
            Log::ErrorFmt("GetMethodIl2cpp error: Class %s::%s not found.", nameSpaceName, className);
            return nullptr;
        }
        Log::Debug("GetMethodIl2cpp 2");
        auto ret = UnityResolve::Invoke<UnityResolve::Method*>("il2cpp_class_get_method_from_name", klass, methodName, argsCount);
        if (!ret) {
            Log::ErrorFmt("GetMethodIl2cpp error: method %s::%s.%s not found.", nameSpaceName, className, methodName);
            return nullptr;
        }
        return ret;
    }*/

    UnityResolve::Method* GetMethod(const std::string& assemblyName, const std::string& nameSpaceName,
                           const std::string& className, const std::string& methodName, const std::vector<std::string>& args = {}) {
        const auto assembly = UnityResolve::Get(assemblyName);
        if (!assembly) {
            Log::ErrorFmt("GetMethod error: assembly %s not found.", assemblyName.c_str());
            return nullptr;
        }
        const auto pClass = assembly->Get(className, nameSpaceName);
        if (!pClass) {
            Log::ErrorFmt("GetMethod error: Class %s::%s not found.", nameSpaceName.c_str(), className.c_str());
            return nullptr;
        }
        auto method = pClass->Get<UnityResolve::Method>(methodName, args);
        if (!method) {
            /*
            method = GetMethodIl2cpp(assemblyName.c_str(), nameSpaceName.c_str(), className.c_str(),
                                     methodName.c_str(), args.size() == 0 ? -1 : args.size());
            if (!method) {
                Log::ErrorFmt("GetMethod error: method %s::%s.%s not found.", nameSpaceName.c_str(), className.c_str(), methodName.c_str());
                return nullptr;
            }*/
            Log::ErrorFmt("GetMethod error: method %s::%s.%s not found.", nameSpaceName.c_str(), className.c_str(), methodName.c_str());
            return nullptr;
        }
        return method;
    }

    void* GetMethodPointer(const std::string& assemblyName, const std::string& nameSpaceName,
                           const std::string& className, const std::string& methodName, const std::vector<std::string>& args = {}) {
        auto method = GetMethod(assemblyName, nameSpaceName, className, methodName, args);
        if (method) {
            return method->function;
        }
        return nullptr;
    }

    void* il2cpp_resolve_icall(const char* s) {
        return UnityResolve::Invoke<void*>("il2cpp_resolve_icall", s);
    }

    Il2CppClassHead* get_class_from_instance(const void* instance) {
        return static_cast<Il2CppClassHead*>(*static_cast<void* const*>(std::assume_aligned<alignof(void*)>(instance)));
    }

    MethodInfo* il2cpp_class_get_method_from_name(void* klass, const char* name, int argsCount) {
        return UnityResolve::Invoke<MethodInfo*>("il2cpp_class_get_method_from_name", klass, name, argsCount);
    }

    void* find_nested_class(void* klass, std::predicate<void*> auto&& predicate)
    {
        void* iter{};
        while (const auto curNestedClass = UnityResolve::Invoke<void*>("il2cpp_class_get_nested_types", klass, &iter))
        {
            if (static_cast<decltype(predicate)>(predicate)(curNestedClass))
            {
                return curNestedClass;
            }
        }

        return nullptr;
    }

    void* find_nested_class_from_name(void* klass, const char* name)
    {
        return find_nested_class(klass, [name = std::string_view(name)](void* nestedClass) {
            return static_cast<Il2CppClassHead*>(nestedClass)->name == name;
        });
    }

    template <typename RType>
    auto ClassGetFieldValue(void* obj, UnityResolve::Field* field) -> RType {
        return *reinterpret_cast<RType*>(reinterpret_cast<uintptr_t>(obj) + field->offset);
    }

    template <typename RType>
    auto ClassSetFieldValue(void* obj, UnityResolve::Field* field, RType value) -> void {
        *reinterpret_cast<RType*>(reinterpret_cast<uintptr_t>(obj) + field->offset) = value;
    }

    void* get_system_class_from_reflection_type_str(const char* typeStr, const char* assemblyName = "mscorlib") {
        using Il2CppString = UnityResolve::UnityType::String;

        static auto assemblyLoad = reinterpret_cast<void* (*)(Il2CppString*)>(
                GetMethodPointer("mscorlib.dll", "System.Reflection",
                                 "Assembly", "Load", {"*"})
        );
        static auto assemblyGetType = reinterpret_cast<Il2CppReflectionType * (*)(void*, Il2CppString*)>(
                GetMethodPointer("mscorlib.dll", "System.Reflection",
                                 "Assembly", "GetType", {"*"})
        );

        static auto reflectionAssembly = assemblyLoad(Il2CppString::New(assemblyName));
        auto reflectionType = assemblyGetType(reflectionAssembly, Il2CppString::New(typeStr));
        return UnityResolve::Invoke<void*>("il2cpp_class_from_system_type", reflectionType);
    }

    namespace Tools {

        template <typename T = void*>
        class CSListEditor {
        public:
            CSListEditor(void* list) {
                list_klass = get_class_from_instance(list);
                lst = list;

                lst_get_Count_method = il2cpp_class_get_method_from_name(list_klass, "get_Count", 0);
                lst_get_Item_method = il2cpp_class_get_method_from_name(list_klass, "get_Item", 1);
                lst_set_Item_method = il2cpp_class_get_method_from_name(list_klass, "set_Item", 2);
                lst_Add_method = il2cpp_class_get_method_from_name(list_klass, "Add", 1);

                lst_get_Count = reinterpret_cast<lst_get_Count_t>(lst_get_Count_method->methodPointer);
                lst_get_Item = reinterpret_cast<lst_get_Item_t>(lst_get_Item_method->methodPointer);
                lst_set_Item = reinterpret_cast<lst_set_Item_t>(lst_set_Item_method->methodPointer);
                lst_Add = reinterpret_cast<lst_Add_t>(lst_Add_method->methodPointer);
            }

            void Add(T value) {
                lst_Add(lst, value, lst_Add_method);
            }

            T get_Item(int index) {
                return lst_get_Item(lst, index, lst_get_Item_method);
            }

            void set_Item(int index, T value) {
                return lst_set_Item(lst, index, value, lst_set_Item_method);
            }

            int get_Count() {
                return lst_get_Count(lst, lst_get_Count_method);
            }

            T operator[] (int key) {
                return get_Item(key);
            }

            class Iterator {
            public:
                Iterator(CSListEditor<T>* editor, int index) : editor(editor), index(index) {}

                T operator*() const {
                    return editor->get_Item(index);
                }

                Iterator& operator++() {
                    ++index;
                    return *this;
                }

                bool operator!=(const Iterator& other) const {
                    return index != other.index;
                }

            private:
                CSListEditor<T>* editor;
                int index;
            };

            Iterator begin() {
                return Iterator(this, 0);
            }

            Iterator end() {
                return Iterator(this, get_Count());
            }

            void* lst;
            void* list_klass;
        private:
            typedef T(*lst_get_Item_t)(void*, int, void* mtd);
            typedef void(*lst_Add_t)(void*, T, void* mtd);
            typedef void(*lst_set_Item_t)(void*, int, T, void* mtd);
            typedef int(*lst_get_Count_t)(void*, void* mtd);

            MethodInfo* lst_get_Item_method;
            MethodInfo* lst_Add_method;
            MethodInfo* lst_get_Count_method;
            MethodInfo* lst_set_Item_method;

            lst_get_Item_t lst_get_Item;
            lst_set_Item_t lst_set_Item;
            lst_Add_t lst_Add;
            lst_get_Count_t lst_get_Count;
        };

    }
}
