#include "dev_evdev.h"
#include <libevdev-1.0/libevdev/libevdev.h>

static const char *input_path = "/dev/input/";

static pthread_mutex_t input_acquire_mutex = PTHREAD_MUTEX_INITIALIZER;

struct open_resource {
    char* sysfs_path;
    int fd;
};

static struct open_resource open_paths[] = {
    {
        .fd = -1,
        .sysfs_path = NULL
    },
    {
        .fd = -1,
        .sysfs_path = NULL
    },
    {
        .fd = -1,
        .sysfs_path = NULL
    },
    {
        .fd = -1,
        .sysfs_path = NULL
    },
    {
        .fd = -1,
        .sysfs_path = NULL
    },
    {
        .fd = -1,
        .sysfs_path = NULL
    },
    {
        .fd = -1,
        .sysfs_path = NULL
    },
    {
        .fd = -1,
        .sysfs_path = NULL
    },
    {
        .fd = -1,
        .sysfs_path = NULL
    },
    {
        .fd = -1,
        .sysfs_path = NULL
    },
    {
        .fd = -1,
        .sysfs_path = NULL
    },
    {
        .fd = -1,
        .sysfs_path = NULL
    },
    {
        .fd = -1,
        .sysfs_path = NULL
    },
    {
        .fd = -1,
        .sysfs_path = NULL
    },
    {
        .fd = -1,
        .sysfs_path = NULL
    },
    {
        .fd = -1,
        .sysfs_path = NULL
    },
};

static bool ev_matches(
    const uinput_filters_t* const in_filters,
    struct libevdev *in_evdev
) {
    if (in_evdev == NULL) {
        return NULL;
    }

    const char* name = libevdev_get_name(in_evdev);
    if ((name != NULL) && (strcmp(name, in_filters->name) != 0)) {
        return false;
    }

    // TODO: if more filters are implemented write them here

    return true;
}

void dev_evdev_close(struct libevdev* out_evdev) {
    if (out_evdev == NULL) {
        return;
    }

    const int input_acquire_lock_result = pthread_mutex_lock(&input_acquire_mutex);
    if (input_acquire_lock_result != 0) {
        fprintf(stderr, "Cannot lock input mutex: %d -- this will probably have no consequences\n", input_acquire_lock_result);
        return;
    }

    int fd = libevdev_get_fd(out_evdev);
    for (int i = 0; i < sizeof(open_paths) / sizeof(open_paths[0]); ++i) {
        if (open_paths[i].fd == fd) {
            if (open_paths[i].sysfs_path != NULL) {
                free(open_paths[i].sysfs_path);
                open_paths[i].sysfs_path = NULL;
            }
            open_paths[i].fd = -1;
        }
    }

    // free the memory
    libevdev_free(out_evdev);

    pthread_mutex_unlock(&input_acquire_mutex);
}

#define MAX_PATH_LEN 512

int dev_evdev_open(
    const uinput_filters_t *const in_filters,
    struct libevdev* *const out_evdev
) {
    int res = -ENOENT;

    int open_sysfs_idx = -1;

    const int mutex_lock_res = pthread_mutex_lock(&input_acquire_mutex);
    if (mutex_lock_res != 0) {
        fprintf(stderr, "Cannot lock input mutex: %d\n", mutex_lock_res);
        res = mutex_lock_res;
        goto dev_evdev_open_mutex_err;
    }

    char *const path = malloc(MAX_PATH_LEN);
    if (path == NULL) {
        res = -ENOMEM;
        goto dev_evdev_open_err;
    }
    
    DIR *d;
    struct dirent *dir;
    d = opendir(input_path);
    if (d) {
        while ((dir = readdir(d)) != NULL) {
            if (dir->d_name[0] == '.') {
                continue;
            } else if (dir->d_name[0] == 'b') { // by-id
                continue;
            } else if (dir->d_name[0] == 'j') { // js-0
                continue;
            }

            snprintf(path, MAX_PATH_LEN - 1, "%s%s", input_path, dir->d_name);

            //printf("Testing for device %s\n", path);

            // try to open the device, if it cannot be opened to go the next
            int fd = open(path, O_RDWR);
            if (fd < 0) {
                fprintf(stderr, "Cannot open %s, device skipped.\n", path);
                continue;
            }

            // check if that has been already opened
            // open_sysfs
            int skip = 0;
            for (int o = 0; o < (sizeof(open_paths) / sizeof(open_paths[0])); ++o) {
                if ((open_paths[o].fd != -1) && (open_paths[o].sysfs_path != NULL) && (strcmp(open_paths[o].sysfs_path, path) == 0)) {
                    close(fd);
                    skip = 1;
                    break;
                } else if ((open_paths[o].sysfs_path == NULL) && (open_paths[o].fd == -1)) {
                    open_sysfs_idx = o;
                }
            }

            if ((skip) || (open_sysfs_idx == -1)) {
                continue;
            }

            if (libevdev_new_from_fd(fd, out_evdev) != 0) {
                close(fd);
                continue;
            }

            // try to open the device
            if (!ev_matches(in_filters, *out_evdev)) {
                libevdev_free(*out_evdev);
                close(fd);
                continue;
            }

            // register the device as being opened already
            open_paths[open_sysfs_idx].sysfs_path = path;
            open_paths[open_sysfs_idx].fd = fd;

            // the device has been found
            res = 0;
            break;
        }
        closedir(d);
    }

dev_evdev_open_err:
    if ((path != NULL) && (res != 0)) {
        free(path);
    }

    pthread_mutex_unlock(&input_acquire_mutex);

dev_evdev_open_mutex_err:
    return res;
}
