#include <errno.h>
#include <fcntl.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <time.h>
#include <unistd.h>

#include <linux/input.h>
#include <sys/epoll.h>

#define MAX_EVENTS 16
#define CONTENT_BUFFER_SIZE 32

constexpr std::string_view KEYBOARD = "k";
constexpr std::string_view MOUSE = "m";

int dump_event(struct timespec* start, struct input_event* event, const std::string_view& type);

int event_content_keyboard(char* buffer, int buffer_size, struct input_event* event);
int event_content_mouse(char* buffer, int buffer_size, struct input_event* event);

int stream_events(char* mouse_path, char* keyboard_path, int hotkey_scancode);

static inline void timespec_diff(struct timespec* a, struct timespec* b, struct timespec* result) {
	result->tv_sec = a->tv_sec - b->tv_sec;
	result->tv_nsec = a->tv_nsec - b->tv_nsec;
	if (result->tv_nsec < 0) {
		--result->tv_sec;
		result->tv_nsec += 1000000000L;
	}
}

static inline void timeval_diff(struct timeval* a, struct timeval* b, struct timeval* result) {
	result->tv_sec = a->tv_sec - b->tv_sec;
	result->tv_usec = a->tv_usec - b->tv_usec;
	if (result->tv_usec < 0) {
		--result->tv_sec;
		result->tv_usec += 1000000L;
	}
}

static inline void time_diff(struct timeval* a, struct timespec* b, struct timeval* result) {
	result->tv_sec = a->tv_sec - b->tv_sec;
	result->tv_usec = a->tv_usec - (b->tv_nsec / 1000);
	if (result->tv_usec < 0) {
		--result->tv_sec;
		result->tv_usec += 1000000L;
	}
}
	
int dump_event(struct timespec* start, struct input_event* event, const std::string_view& type) {
	struct timespec now;
	struct timespec diff;
	char content_buffer[CONTENT_BUFFER_SIZE];

	clock_gettime(CLOCK_MONOTONIC, &now);
	timespec_diff(&now, start, &diff);
	// time_diff(&(event.time), start, &diff);

	if(type == MOUSE) {
		if(event_content_mouse(content_buffer, CONTENT_BUFFER_SIZE, event)) {
			return 1;
		}
	} else if(type == KEYBOARD) {
		// Ignore all but EV_KEY events on keyboard, they have no useful content.
		if(event->type != EV_KEY) {
			 return 0;
		}
		if(event_content_keyboard(content_buffer, CONTENT_BUFFER_SIZE, event)) {
			return 1;
		}
	} else {
		fprintf(stderr, "Unknown event type.\n");
		return 1;
	}
	start->tv_sec = now.tv_sec;
	start->tv_nsec = now.tv_nsec;

	std::cout <<
		diff.tv_sec << " " <<
		diff.tv_nsec << "," <<
		type << "," <<
		content_buffer << std::endl;
		
	fflush(stdout);
	return 0;
}

int event_content_keyboard(char* buffer, int buffer_size, struct input_event* event) {
	sprintf(buffer, "%d,%d,%d",
		event->type,
		event->code,
		event->value);
	return 0;
}

int event_content_mouse(char* buffer, int buffer_size, struct input_event* event) {
    unsigned char button, bLeft, bMiddle, bRight;
	unsigned char *ptr = (unsigned char*)event;
	int i;
    char x, y;
	button = ptr[0];
	bLeft = button & 0x1;
	bMiddle = ( button & 0x4 ) > 0;
	bRight = ( button & 0x2 ) > 0;
   	x=(char) ptr[1];
	y=(char) ptr[2];

	int chars = sprintf(buffer, "l%d,m%d,r%d,x%d,y%d",
		bLeft, bMiddle, bRight, x, y);
	return chars < 0;
}

int main(int argc, char* argv[]) {
	int hotkey_scancode;
	if(argc < 2) {
		fprintf(stderr, "You must specify a mouse input to track like /dev/input/mouse1.");
		exit(EXIT_FAILURE);
	}
	if(argc < 3) {
		fprintf(stderr, "You must specify a keyboard input to track like /dev/input/event3. If you're not sure which to use read through /proc/bus/input/devices and look for 'Handlers=eventX'");
		exit(EXIT_FAILURE);
	}
	if(argc < 4) {
		fprintf(stderr, "You must specify a character to indicate when to start and stop capture. 53 for 'z'.\n");
		exit(EXIT_FAILURE);
	}
	int matched = sscanf(argv[3], "%d", &hotkey_scancode);
	if(matched != 1) {
		fprintf(stderr, "Failed to read hotkey scancode.\n");
		exit(EXIT_FAILURE);
	}
	int result = stream_events(argv[1], argv[2], hotkey_scancode);
	exit(result);
}

int stream_events(char* mouse_path, char* keyboard_path, int hotkey_scancode) {
    int keyboard_fd, mouse_fd;
	struct timespec start;
	struct epoll_event e_event, events[MAX_EVENTS];

	int has_seen_hotkey = 0;
	int running = 1;
	int epoll_fd = epoll_create1(0);
	if(epoll_fd < 0) {
		fprintf(stderr, "Failed to initialize epoll fd: %d", errno);
		return 1;
	}
    if((mouse_fd = open(mouse_path, O_RDONLY)) == -1) {
        fprintf(stderr, "Failed to open %s. Are you running as root?\n", mouse_path);
		return 1;
    }
    else {
        fprintf(stderr, "%s open OK\n", mouse_path);
    }
    if((keyboard_fd = open(keyboard_path, O_RDONLY)) == -1) {
        fprintf(stderr, "Failed to open %s. Are you running as root?\n", keyboard_path);
		return 1;
    }
    else {
        fprintf(stderr, "%s open OK\n", keyboard_path);
    }
	e_event.events = EPOLLIN;

	e_event.data.fd = keyboard_fd;
	if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, keyboard_fd, &e_event)) {
		fprintf(stderr, "Failed to add keyboard file descriptor\n");
		close(epoll_fd);
		return 1;
	}
	e_event.data.fd = mouse_fd;
	if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, mouse_fd, &e_event)) {
		fprintf(stderr, "Failed to add mouse file descriptor\n");
		close(epoll_fd);
		return 1;
	}

	fprintf(stderr, "Waiting for hotkey\n");
    struct input_event i_event;
	while(running) {
		int event_count = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
		std::string_view type;
		for(int i = 0; i < event_count; i++) {
			int result = read(events[i].data.fd, &i_event, sizeof(struct input_event));
			if(result < 0) {
				perror("Failed to read an event");
				return 1;
			}

			if(events[i].data.fd == keyboard_fd) {
				type = KEYBOARD;
				if(i_event.type == EV_KEY && i_event.code == hotkey_scancode && i_event.value == 1) {
					if(has_seen_hotkey) {
						fprintf(stderr, "Stop capture\n");
						return 0;
					} else {
						has_seen_hotkey = 1;
						fprintf(stderr, "Start capture\n");
						clock_gettime(CLOCK_MONOTONIC, &start);
						continue;
					}
				} else if(i_event.value == 2) {
					continue;
				}
			} else if (events[i].data.fd == mouse_fd) {
				type = MOUSE;
			} else {
				fprintf(stderr, "Unknown fd");
				return 1;
			}
			// Wait for the hotkey to start capture
			if(has_seen_hotkey && dump_event(&start, &i_event, type)) {
				return 1;
			}
		}
	}
	return 0;
}