#include "virt_mouse.h"

int virt_mouse_init(virt_mouse_t *const mouse) {
    int ret = -EINVAL;

    mouse->status_recv = 0;

    int fd = open("/dev/uinput", O_RDWR);
	if(fd < 0) {
        ret = errno;
        goto virt_mouse_init_err;
    }

    ioctl(fd, UI_SET_EVBIT, EV_REL);
    ioctl(fd, UI_SET_EVBIT, EV_KEY);
    ioctl(fd, UI_SET_EVBIT, EV_MSC);
    ioctl(fd, UI_SET_EVBIT, EV_SYN);
    ioctl(fd, UI_SET_MSCBIT, MSC_TIMESTAMP);
    ioctl(fd, UI_SET_KEYBIT, BTN_LEFT);
    ioctl(fd, UI_SET_KEYBIT, BTN_MIDDLE);
    ioctl(fd, UI_SET_KEYBIT, BTN_RIGHT);
    ioctl(fd, UI_SET_RELBIT, REL_X);
    ioctl(fd, UI_SET_RELBIT, REL_Y);
    ioctl(fd, UI_SET_RELBIT, REL_WHEEL);
    ioctl(fd, UI_SET_RELBIT, REL_WHEEL_HI_RES);

    struct uinput_setup dev = {0};
	strncpy(dev.name, VIRT_MOUSE_DEV_NAME, UINPUT_MAX_NAME_SIZE-1);
    dev.id.bustype = BUS_VIRTUAL;
    dev.id.vendor = VIRT_MOUSE_DEV_VENDOR_ID;
	dev.id.product = VIRT_MOUSE_DEV_PRODUCT_ID;
    dev.id.version = VIRT_MOUSE_DEV_VERSION;

    if(ioctl(fd, UI_DEV_SETUP, &dev) < 0) {
        ret = errno > 0 ? errno : -1 * errno;
        ret = ret == 0 ? -EIO : ret;
        goto virt_mouse_init_err;
    }

    if(ioctl(fd, UI_DEV_CREATE) < 0) {
        ret = errno > 0 ? errno : -1 * errno;
        ret = ret == 0 ? -EIO : ret;
        goto virt_mouse_init_err;
    }

    // initialization ok
    mouse->prev_btn_left = 0;
    mouse->prev_btn_right = 0;
    mouse->prev_btn_middle = 0;
    mouse->fd = fd;
    ret = 0;

virt_mouse_init_err:
    if (ret != 0) {
        mouse->fd = -1;
        close(fd);
    }

    return ret;
}

int virt_mouse_get_fd(virt_mouse_t *const mouse) {
    return mouse->fd;
}

int virt_mouse_send(virt_mouse_t *const mouse, mouse_status_t *const status, struct timeval *const now) {
    int res = 0;
    
    struct timeval syn_time;
    gettimeofday(&syn_time, NULL);

    struct input_event tmp_ev;
    gettimeofday(&tmp_ev.time, NULL);
    if (now != NULL) {
        tmp_ev.time = *now;
    }

    tmp_ev.type = EV_REL;

    if (status->x != 0) {
        tmp_ev.code = REL_X;
        tmp_ev.value = status->x;
        if (write(mouse->fd, &tmp_ev, sizeof(tmp_ev)) != sizeof(struct input_event)) {
            res = errno < 0 ? errno : -1 * errno;
            goto virt_mouse_send_err;
        }
    }

    if (status->y != 0) {
        tmp_ev.code = REL_Y;
        tmp_ev.value = status->y;
        if (write(mouse->fd, &tmp_ev, sizeof(tmp_ev)) != sizeof(struct input_event)) {
            res = errno < 0 ? errno : -1 * errno;
            goto virt_mouse_send_err;
        }
    }

    tmp_ev.type = EV_KEY;

    if (status->btn_left != mouse->prev_btn_left) {
        mouse->prev_btn_left = status->btn_left;
        tmp_ev.code = BTN_LEFT;
        tmp_ev.value = status->btn_left;
        if (write(mouse->fd, &tmp_ev, sizeof(tmp_ev)) != sizeof(struct input_event)) {
            res = errno < 0 ? errno : -1 * errno;
            goto virt_mouse_send_err;
        }
    }

    if (status->btn_middle != mouse->prev_btn_middle) {
        mouse->prev_btn_middle = status->btn_middle;
        tmp_ev.code = BTN_MIDDLE;
        tmp_ev.value = status->btn_middle;
        if (write(mouse->fd, &tmp_ev, sizeof(tmp_ev)) != sizeof(struct input_event)) {
            res = errno < 0 ? errno : -1 * errno;
            goto virt_mouse_send_err;
        }
    }

    if (status->btn_right != mouse->prev_btn_right) {
        mouse->prev_btn_right = status->btn_right;
        tmp_ev.code = BTN_RIGHT;
        tmp_ev.value = status->btn_right;
        if (write(mouse->fd, &tmp_ev, sizeof(tmp_ev)) != sizeof(struct input_event)) {
            res = errno < 0 ? errno : -1 * errno;
            goto virt_mouse_send_err;
        }
    }

    #if 0
	const struct input_event timestamp_ev = {
		.code = MSC_TIMESTAMP,
		.type = EV_MSC,
		.value = (now.tv_sec - secAtInit)*1000000 + (now.tv_usec - usecAtInit),
		.time = now,
	};
	const ssize_t timestamp_written = write(fd, (void*)&timestamp_ev, sizeof(timestamp_ev));
	if (timestamp_written != sizeof(timestamp_ev)) {
		fprintf(stderr, "Error in sync: written %ld bytes out of %ld\n", timestamp_written, sizeof(timestamp_ev));
	}
    #endif

    syn_time.tv_usec += 1;
	const struct input_event syn_ev = {
		.code = SYN_REPORT,
		.type = EV_SYN,
		.value = 0,
		.time = syn_time,
	};
	const ssize_t sync_written = write(mouse->fd, (void*)&syn_ev, sizeof(syn_ev));
	if (sync_written != sizeof(syn_ev)) {
		fprintf(stderr, "Error in sync: written %ld bytes out of %ld\n", sync_written, sizeof(syn_ev));
	}

virt_mouse_send_err:
    return res;
}

void virt_mouse_close(virt_mouse_t *const mouse) {
    close(mouse->fd);
}