#include "twn_game_object_c.h"
#include "twn_engine_context_c.h"
#include "twn_util_c.h"
#include "twn_util.h"

#include <x-watcher.h>
#include <SDL2/SDL.h>

#include <dlfcn.h>


#define GAME_OBJECT_NAME "libgame.so"
#define MODIFIED_TICKS_MERGED 10


static void (*game_tick_callback)(void);
static void (*game_end_callback)(void);

static x_watcher *watcher;
static void *handle = NULL;

static uint64_t last_tick_modified;
static bool loaded_after_modification = true;
static SDL_mutex *lock;

static char *game_object_path;

static void load_game_object(void) {
    /* needs to be closed otherwise symbols aren't resolved again */
    if (handle) {
        dlclose(handle);
        handle = NULL;
        game_tick_callback = NULL;
        game_end_callback = NULL;
    }

    void *new_handle = dlopen(game_object_path, RTLD_LAZY);
    if (!new_handle) {
        log_critical("Hot Reload Error: Cannot open game code shared object (%s)", dlerror());
        goto ERR_OPENING_SO;
    }

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"

    game_tick_callback = (void (*)(void))dlsym(new_handle, "game_tick");
    if (!game_tick_callback) {
        CRY("Hot Reload Error", "game_tick_callback() symbol wasn't found");
        goto ERR_GETTING_PROC;
    }

    game_end_callback = (void (*)(void))dlsym(new_handle, "game_end");
    if (!game_end_callback) {
        CRY("Hot Reload Error", "game_end_callback() symbol wasn't found");
        goto ERR_GETTING_PROC;
    }

#pragma GCC diagnostic pop

    handle = new_handle;

    if (ctx.game.frame_number != 0)
        log_info("Game object was reloaded\n");

    return;

ERR_GETTING_PROC:
    dlclose(new_handle);
    game_tick_callback = NULL;
    game_end_callback = NULL;

ERR_OPENING_SO:
    SDL_UnlockMutex(lock);

}

static void watcher_callback(XWATCHER_FILE_EVENT event,
                             const char *path,
                             int context,
                             void *data)
{
    (void)context;
    (void)path;
    (void)data;

    switch(event) {
        case XWATCHER_FILE_CREATED:
        case XWATCHER_FILE_MODIFIED:
            SDL_LockMutex(lock);
            last_tick_modified = ctx.game.frame_number;
            loaded_after_modification = false;
            SDL_UnlockMutex(lock);
            break;

        case XWATCHER_FILE_UNSPECIFIED:
        case XWATCHER_FILE_REMOVED:
        case XWATCHER_FILE_OPENED:
        case XWATCHER_FILE_ATTRIBUTES_CHANGED:
        case XWATCHER_FILE_NONE:
        case XWATCHER_FILE_RENAMED:
        default:
            break;
    }
}


void game_object_load(void) {
    SDL_asprintf(&game_object_path, "%s%s", ctx.base_dir, GAME_OBJECT_NAME);

    watcher = xWatcher_create();

    xWatcher_reference dir;
    dir.path = game_object_path;
    dir.callback_func = watcher_callback;

    xWatcher_appendFile(watcher, &dir);
    xWatcher_start(watcher);

    lock = SDL_CreateMutex();
    load_game_object();
}


void game_object_unload(void) {
    game_end_callback();
    xWatcher_destroy(watcher);
    watcher = NULL;
    dlclose(handle);
    handle = NULL;
    game_tick_callback = NULL;
    game_end_callback = NULL;
    SDL_DestroyMutex(lock);
}


bool game_object_try_reloading(void) {
    bool result = false;

    /* only load the modified library after some time, as compilers make a lot of modifications */
    SDL_LockMutex(lock);
    if (ctx.game.frame_number - last_tick_modified > MODIFIED_TICKS_MERGED &&
            !loaded_after_modification) {
        load_game_object();
        loaded_after_modification = true;
        result = true;
    } SDL_UnlockMutex(lock);

    return result;
}


void game_object_tick(void) {
    game_tick_callback();
}

void *game_object_get_game_tick_address(void) {
#pragma GCC diagnostic push 
#pragma GCC diagnostic ignored "-Wpedantic"
    return (void *)&game_tick_callback;
#pragma GCC diagnostic pop
}
