#include "twn_filewatch_c.h"
#include "twn_util.h"
#include "twn_engine_context_c.h"

#define DMON_IMPL
#include <dmon.h>
#include <stb_ds.h>
#include <SDL2/SDL.h>


struct FilewatchEntry {
    char *path;
    FilewatchCallback callback;
    enum FilewatchAction *actions_pending;
};

static struct FilewatchEntry *filewatch_directories;
static struct FilewatchEntry *filewatch_files;
/* note: it gets rebuilt on every addition, as api is such */
/* TODO: test whether it's possible to miss on update while watcher is being rebuilt */
/*       we might have to enumerate things, just to make sure */
static SDL_mutex *filewatcher_lock;
static bool filewatcher_initialized;

static void filewatch_callback(dmon_watch_id watch_id,
                               dmon_action action,
                               const char* rootdir,
                               const char* filepath,
                               const char* oldfilepath,
                               void* user)
{
    (void)watch_id; (void)rootdir; (void)filepath; (void)oldfilepath;

    enum FilewatchAction faction;

    switch (action) {
    case DMON_ACTION_CREATE:
        faction = FILEWATCH_ACTION_FILE_CREATED;
        break;
    case DMON_ACTION_DELETE:
        faction = FILEWATCH_ACTION_FILE_DELETED;
        break;
    case DMON_ACTION_MODIFY:
        faction = FILEWATCH_ACTION_FILE_MODIFIED;
        break;
    case DMON_ACTION_MOVE:
    default:
        return;
    }

    SDL_LockMutex(filewatcher_lock);

    intptr_t const context = (intptr_t)user;
    struct FilewatchEntry *p;

    if (context < 0)
        p = &filewatch_files[-context - 1];
    else
        p = &filewatch_directories[context];

    arrpush(p->actions_pending, faction);
    SDL_UnlockMutex(filewatcher_lock);
}


bool filewatch_add_directory(char const *dir, FilewatchCallback callback) {
    SDL_assert(dir && callback);

    if (!filewatcher_initialized) {
        dmon_init();
        filewatcher_initialized = true;
    }

    struct FilewatchEntry const w = {
        .callback = callback,
        .path = SDL_strdup(dir), /* TODO: free */
    };
    arrpush(filewatch_directories, w);
    dmon_watch(dir, filewatch_callback, DMON_WATCHFLAGS_RECURSIVE, (void *)(intptr_t)(arrlen(filewatch_directories) - 1));

    return true;
}


bool filewatch_add_file(char const *filepath, FilewatchCallback callback) {
    SDL_assert(filepath && callback);

    if (!filewatcher_initialized) {
        dmon_init();
        filewatcher_initialized = true;
    }

    struct FilewatchEntry const f = {
        .callback = callback,
        .path = SDL_strdup(filepath), /* TODO: free */
    };
    arrpush(filewatch_files, f);
    dmon_watch("./", filewatch_callback, 0, (void *)(intptr_t)(-arrlen(filewatch_files)));

    return true;
}


void filewatch_poll(void) {
    SDL_LockMutex(filewatcher_lock);

    for (int i = 0; i < arrlen(filewatch_directories); ++i) {
        for (int u = 0; u < arrlen(filewatch_directories[i].actions_pending); ++u)
            filewatch_directories[i].callback(filewatch_directories[i].path, filewatch_directories[i].actions_pending[u]);
        arrfree(filewatch_directories[i].actions_pending);
    }

    for (int i = 0; i < arrlen(filewatch_files); ++i) {
        for (int u = 0; u < arrlen(filewatch_files[i].actions_pending); ++u)
            filewatch_files[i].callback(filewatch_files[i].path, filewatch_files[i].actions_pending[u]);
        arrfree(filewatch_files[i].actions_pending);
    }

    SDL_UnlockMutex(filewatcher_lock);
}
