#include <functional>
#include <utility>

// Information Interface
template <typename Ret, typename... Args>
class __FunctionInfo {
    typedef Ret (*type)(Args...);
public:
    [[nodiscard]] virtual bool can_overwrite() const = 0;
    [[nodiscard]] virtual type get() const = 0;
    [[nodiscard]] virtual type *get_addr() const = 0;
    virtual void update(type new_func) = 0;
    virtual ~__FunctionInfo() = default;
};

// Thunks
typedef void *(*thunk_enabler_t)(void *target, void *thunk);
extern thunk_enabler_t thunk_enabler;

// Function
template <unsigned int, typename T>
class __Function;
template <unsigned int discriminator, typename Ret, typename... Args>
class __Function<discriminator, Ret(Args...)> final {
    // Prevent Copying
    __PREVENT_COPY(__Function);
    __PREVENT_DESTRUCTION(__Function);

    // Instance
    static __Function *instance;

    // Current Function
    typedef __FunctionInfo<Ret, Args...> *func_t;
    const func_t func;

public:
    // Types
    typedef Ret (*ptr_type)(Args...);
    typedef std::function<Ret(Args...)> type;
    typedef std::function<Ret(const type &, Args...)> overwrite_type;

    // State
    const bool enabled;
    const char *const name;

    // Backup Of Original Function Pointer
    const ptr_type backup;

#ifdef {{ BUILDING_SYMBOLS_GUARD }}
    // Constructor
    __Function(const char *const name_, const func_t func_):
        func(func_),
        enabled(func->can_overwrite()),
        name(name_),
        backup(func->get())
    {
        instance = this;
    }
#else
    // Prevent Construction
    __PREVENT_JUST_CONSTRUCTION(__Function);
#endif

    // Overwrite Function
    [[nodiscard]] bool overwrite(const overwrite_type &target) {
        // Check If Enabled
        if (!enabled) {
            return false;
        }
        // Enable Thunk
        enable_thunk();
        // Overwrite
        type original = get_thunk_target();
        thunk_target = [original, target](Args... args) {
            return target(original, std::forward<Args>(args)...);
        };
        return true;
    }

    // Getters
    [[nodiscard]] ptr_type get(const bool result_will_be_stored) {
        if (!enabled) {
            return nullptr;
        } else {
            if (result_will_be_stored) {
                enable_thunk();
            }
            return func->get();
        }
    }
    [[nodiscard]] ptr_type *get_vtable_addr() const {
        return func->get_addr();
    }

private:
    // Thunk
    [[nodiscard]] type get_thunk_target() const {
        if (thunk_target) {
            return thunk_target;
        } else {
            return backup;
        }
    }
    static Ret thunk(Args... args) {
        return instance->get_thunk_target()(std::forward<Args>(args)...);
    }
    // Enable Thunk
    type thunk_target;
    bool thunk_enabled = false;
    void enable_thunk() {
        if (!thunk_enabled && enabled) {
            ptr_type real_thunk = (ptr_type) thunk_enabler((void *) backup, (void *) thunk);
            func->update(real_thunk);
            thunk_enabled = true;
        }
    }
};