#ifndef MODDING_HPP
#define MODDING_HPP

#include <dlfcn.h>
#include <open_engine.hpp>
#include <unistd.h>

#include "/usr/share/dotnet/packs/Microsoft.NETCore.App.Host.arch-x64/10.0.3/runtimes/arch-x64/native/nethost.h"
#include "/usr/share/dotnet/packs/Microsoft.NETCore.App.Host.arch-x64/10.0.3/runtimes/arch-x64/native/hostfxr.h"
#include "/usr/share/dotnet/packs/Microsoft.NETCore.App.Host.arch-x64/10.0.3/runtimes/arch-x64/native/coreclr_delegates.h"

class Modding : public OpenEngine::Layer
{
    public:
        Modding()
            : OpenEngine::Layer("modding")
        {
        };
        ~Modding()
        {
        };

        void* get_export(void* sample_lib, const char* name) {
            // dlsym is the Linux version of GetProcAddress
            void* f = dlsym(sample_lib, name);
            if (!f) {
                OE_ERROR("Failed to find symbol: {} Error: {}", name, dlerror());
            }
            return f;
        };

        // Using the nethost library, discover the location of hostfxr and get exports
        bool load_hostfxr()
        {
            // Pre-allocate a large buffer for the path to hostfxr
            char_t buffer[250];
            size_t buffer_size = sizeof(buffer) / sizeof(char_t);
            int rc = get_hostfxr_path(buffer, &buffer_size, nullptr);
            if (rc != 0)
                return false;

            // Load hostfxr and get desired exports
            auto* lib = dlopen(buffer, RTLD_NOW);
            init_fptr = (hostfxr_initialize_for_runtime_config_fn)get_export(lib, "hostfxr_initialize_for_runtime_config");
            get_delegate_fptr = (hostfxr_get_runtime_delegate_fn)get_export(lib, "hostfxr_get_runtime_delegate");
            close_fptr = (hostfxr_close_fn)get_export(lib, "hostfxr_close");

            return (init_fptr && get_delegate_fptr && close_fptr);
        };

        load_assembly_and_get_function_pointer_fn get_dotnet_load_assembly(const char_t *config_path)
        {
            // Load .NET Core
            void *load_assembly_and_get_function_pointer = nullptr;
            hostfxr_handle cxt = nullptr;
            int rc = init_fptr(config_path, nullptr, &cxt);
            if (rc != 0 || cxt == nullptr)
            {
                OE_ERROR("Init failed");
                close_fptr(cxt);
                return nullptr;
            }

            // Get the load assembly function pointer
            rc = get_delegate_fptr(
                    cxt,
                    hdt_load_assembly_and_get_function_pointer,
                    &load_assembly_and_get_function_pointer);
            if (rc != 0 || load_assembly_and_get_function_pointer == nullptr)
                OE_ERROR("Get delegate failed");

            close_fptr(cxt);
            return (load_assembly_and_get_function_pointer_fn)load_assembly_and_get_function_pointer;
        }

        component_entry_point_fn LoadMethod(const char* method)
        {
            component_entry_point_fn method_ptr;

            int rc = load_assembly_and_get_function_pointer(
                    dotnet_lib_path,
                    dotnet_type,
                    method,
                    UNMANAGEDCALLERSONLY_METHOD /*delegate_type_name*/,
                    nullptr,
                    (void**)&method_ptr);

            if (rc != 0) {
                OE_ERROR("Failed to load method {}", method);
                return nullptr;
            }

            return method_ptr;
        }

        void OnAttach() override
        {
            load_hostfxr();

            load_assembly_and_get_function_pointer = get_dotnet_load_assembly("assets/scripts/mod_loader.runtimeconfig.json");
            component_entry_point_fn init = LoadMethod("Init"); 

            init(nullptr, 0);

            hello = LoadMethod("Hello");

            load_mod = LoadMethod("LoadMod");
            load_mod(nullptr, 0);
        }

        void OnDetach() override
        {
        }

        void OnUpdate() override
        {
            static float time = 0;

            time += OpenEngine::Time::DeltaTime();

            if (time >= 1) {
                hello(nullptr, 0);
                time = 0;
            }
        }

        void OnEvent(OpenEngine::Event& event) override
        {
            //OpenEngine::EventDispatcher dispatcher(event);

            //dispatcher.Dispatch<OpenEngine::KeyPressedEvent>(BIND_EVENT_FN(Sandbox2DLayer::StopRunning));
        }

        void OnImGuiRender() override
        {
        }

    private:
        hostfxr_initialize_for_runtime_config_fn init_fptr;
        hostfxr_get_runtime_delegate_fn get_delegate_fptr;
        hostfxr_close_fn close_fptr;
        load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer;

        component_entry_point_fn hello = nullptr;
        component_entry_point_fn load_mod = nullptr;

        const char_t* dotnet_lib_path = "assets/scripts/mod_loader.dll";
        const char_t* dotnet_type = "OpenEngine.ModLoader, mod_loader";
        const char_t* dotnet_type_method = "Init";
};

#endif // MODDING_HPP
