#pragma once

#include <ISXDK.h>
#include <memory>
#include <tuple>
#include <vector>
#include <string>
#include <sstream>
#include <functional>
#include <type_traits>
#include <unordered_map>

#include "../isxeq2/ExtensionTLOs.h"

// Helper to get the number of arguments of a method
template <typename T>
struct function_traits;

template <typename R, typename C, typename... Args>
struct function_traits<R (C::*)(Args...)>
{
    static constexpr std::size_t arity = sizeof...(Args);
    using arg_tuple = std::tuple<Args...>;
};

template <typename R, typename C, typename... Args>
struct function_traits<R (C::*)(Args...) const>
{
    static constexpr std::size_t arity = sizeof...(Args);
    using arg_tuple = std::tuple<Args...>;
};

// A helper function to convert an array of C-strings to a tuple of strings.
template <typename Tuple, std::size_t... I>
Tuple array_to_tuple_impl(char *argv[], std::index_sequence<I...>)
{
    return Tuple{std::string(argv[I])...};
}

template <typename Tuple>
Tuple array_to_tuple(char *argv[])
{
    return array_to_tuple_impl<Tuple>(argv, std::make_index_sequence<std::tuple_size<Tuple>::value>{});
}

#define ADD_METHOD_TO_MAP(EnumType, methodName)                                                                                    \
    AddMethod(static_cast<DWORD>(EnumType::methodName), const_cast<char *>(#methodName));                                          \
    methodMap[EnumType::methodName] = [this](int argc, char *argv[]) {                                                             \
        constexpr auto expectedArgc = function_traits<decltype(&std::remove_pointer_t<decltype(this)>::methodName)>::arity;        \
        if (argc != expectedArgc)                                                                                                  \
        {                                                                                                                          \
            logw << "MrBotApiType::GetMethod: " #methodName ": Expected " << expectedArgc                                          \
                 << " argument(s), got " << argc << std::endl;                                                                     \
            return false;                                                                                                          \
        }                                                                                                                          \
        log << "MrBotApiType::GetMethod: " #methodName << std::endl;                                                               \
        using ArgsTuple = typename function_traits<decltype(&std::remove_pointer_t<decltype(this)>::methodName)>::arg_tuple;       \
        auto args_tuple = array_to_tuple<ArgsTuple>(argv);                                                                         \
        return std::apply([this](auto &&...args) { return this->methodName(std::forward<decltype(args)>(args)...); }, args_tuple); \
    }

#define ADD_MEMBER_TO_MAP(EnumType, memberName, expectedArgc)                             \
    AddMember(static_cast<DWORD>(EnumType::memberName), const_cast<char *>(#memberName)); \
    memberMap[EnumType::memberName] = [this](int argc, char *argv[], LSOBJECT &dest) {    \
        if (argc != expectedArgc)                                                         \
        {                                                                                 \
            logw << "MrBotApiType::GetMember: " #memberName ": Expected " << expectedArgc \
                 << " argument(s), got " << argc << std::endl;                            \
            return false;                                                                 \
        }                                                                                 \
        log << "MrBotApiType::GetMember: " #memberName << std::endl;                      \
        std::vector<std::string> args(argv, argv + argc);                                 \
        return memberName(args, dest);                                                    \
    }

class MrBotApiType : public LSTypeDefinition
{
public:
    enum class MrBotApiTypeMembers
    {
        Version,
    };

    enum class MrBotApiTypeMethods
    {
        ClickActorByName,
        ClickActorById,
        ApplyVerb
    };

    MrBotApiType() : LSTypeDefinition(const_cast<char *>("mrbotapi"))
    {
        ADD_MEMBER_TO_MAP(MrBotApiTypeMembers, Version, 0);
        ADD_METHOD_TO_MAP(MrBotApiTypeMethods, ClickActorByName);
        ADD_METHOD_TO_MAP(MrBotApiTypeMethods, ClickActorById);
        ADD_METHOD_TO_MAP(MrBotApiTypeMethods, ApplyVerb);
    }

    virtual bool GetMember(LSOBJECTDATA ObjectData, PLSTYPEMEMBER pMember, int argc, char *argv[], LSOBJECT &Dest);
    virtual bool GetMethod(LSOBJECTDATA &ObjectData, PLSTYPEMEMBER pMethod, int argc, char *argv[]);
    virtual bool ToText(LSOBJECTDATA ObjectData, char *buf, unsigned int buflen);

    static void RegisterType(ISInterface *pISInterface)
    {
        pISInterface->AddLSType(*pMrBotApi);
        pMrBotApi->SetInheritance(nullptr);
    }

    static void RemoveType(ISInterface *pISInterface)
    {
        pISInterface->RemoveLSType(*pMrBotApi);
    }

    static bool __cdecl TLO_MrBotApiType(int argc, char *argv[], LSOBJECT &Dest)
    {
        Dest.DWord = 1;
        Dest.Type = pMrBotApi.get();
        return true;
    }

    static void RegisterTLO(ISInterface *pISInterface)
    {
        pISInterface->AddTopLevelObject(const_cast<char *>("MrBotApi"), MrBotApiType::TLO_MrBotApiType);
    }

    static void RemoveTLO(ISInterface *pISInterface)
    {
        pISInterface->RemoveTopLevelObject(const_cast<char *>("MrBotApi"));
    }

private:
    // Helpers
    unsigned long GetActorIdByName(const std::string &actorName, bool exactMatch = false);
    // Members
    bool Version(const std::vector<std::string> &args, LSOBJECT &dest);
    // Methods
    bool ClickActorByName(const string name);
    bool ClickActorById(const string id);
    bool ApplyVerb(const string actorName, const string verb);
    bool ConversationBubble(const string option);

    static unique_ptr<MrBotApiType> pMrBotApi;
    using MethodFunc = std::function<bool(int argc, char *argv[])>;
    std::map<MrBotApiTypeMethods, MethodFunc> methodMap;
    using MemberFunc = std::function<bool(int argc, char *argv[], LSOBJECT &dest)>;
    std::map<MrBotApiTypeMembers, MemberFunc> memberMap;
};

#undef ADD_MEMBER_TO_MAP
#undef ADD_METHOD_TO_MAP
