#include "signetdev_priv.h"
#include "signetdev_unix.h"
#include "signetdev.h"

#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <sys/inotify.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "../common/signetdev_common.h"
#include "../common/signetdev_common_priv.h"

extern signetdev_conn_err_t g_error_handler;
extern void *g_error_handler_param;

static int g_opening_connection;
static int g_emulating;
static int g_poll_fd = -1;
static int g_inotify_fd = -1;

struct signetdev_connection {
	int fd;
	struct send_message_req *tail_message;
	struct send_message_req *head_message;
	struct send_message_req *tail_cancel_message;
	struct send_message_req *head_cancel_message;

	struct tx_message_state tx_state;
	struct rx_message_state rx_state;
};

struct signetdev_connection g_connection;

static struct send_message_req **pending_message()
{
	struct signetdev_connection *conn = &g_connection;
	struct send_message_req **msg = NULL;
	if (conn->rx_state.message) {
		msg = &conn->rx_state.message;
	} else if (conn->tx_state.message && !conn->tx_state.message->interrupt) {
		msg = &conn->tx_state.message;
	}
	return msg;
}

void signetdev_priv_handle_error()
{
	struct signetdev_connection *conn = &g_connection;
	if (conn->fd != -1) {
		close(conn->fd);
		conn->fd  = -1;
	}
	if (g_error_handler) {
		g_error_handler(g_error_handler_param);
	}
}

static int command_response(int rc)
{
	char resp = rc;
	return write(g_command_resp_pipe[1], &resp, 1);
}

static void handle_exit(void *arg)
{
	struct signetdev_connection *conn = &g_connection;
	(void)arg;
	struct send_message_req **msg = pending_message();
	if (msg) {
		signetdev_priv_finalize_message(msg, SIGNET_ERROR_QUIT);
	}
	if (conn->fd != -1)
		close(conn->fd);
	if (g_poll_fd != -1)
		close(g_poll_fd);
	if (g_inotify_fd != -1)
		close(g_inotify_fd);
	g_poll_fd = -1;
	g_inotify_fd = -1;
}

static void handle_error()
{
	struct signetdev_connection *conn = &g_connection;
	if (conn->fd >= 0) {
		close(conn->fd);
		conn->fd = -1;
		g_device_type = SIGNETDEV_DEVICE_NONE;
	}
	if (g_error_handler) {
		g_error_handler(g_error_handler_param);
	}
	struct send_message_req **msg = pending_message();
	if (msg) {
		struct send_message_req *temp = *msg;
		conn->tx_state.message = NULL;
		conn->rx_state.message = NULL;
		signetdev_priv_finalize_message(&temp, SIGNET_ERROR_DISCONNECT);
	}
}

/**
 * Attempt to send a message to the device. If it is the last message in a
 * sequence, and a response is expected, this will trigger a read from the
 * device to get that response.
 */
static int attempt_raw_hid_write()
{
	struct signetdev_connection *conn = &g_connection;
	if (!conn->tx_state.message)
		return 1;

	if (conn->tx_state.msg_packet_seq == conn->tx_state.msg_packet_count) {
		// we have already sent the last message in the sequence
		if (!conn->tx_state.message->resp) {
			// we expect a response
			signetdev_priv_finalize_message(&conn->tx_state.message, conn->tx_state.msg_size);
		} else {
			conn->tx_state.message = NULL;
		}
		return 0;
	}
	// if this was not the last packet, move onto the next one in the queue
	signetdev_priv_advance_message_state(&conn->tx_state);
	// send the packet to the device
	int rc = write(conn->fd, conn->tx_state.packet_buf, signetdev_priv_hid_packet_size() + 1);
	if (rc == -1 && errno == EAGAIN) {
		return 1;
	} else if (rc == -1) {
		handle_error();
	}
	return 0;
}

/**
 * Check for message from the device and process them if they are found.
 *
 * @returns 0 if there may be more messages to read, 1 if the messages
 *          have all been read or there was an error (in which case the device
 *          type will be set to NONE as a side effect)
 */
static int attempt_raw_hid_read()
{
	struct signetdev_connection *conn = &g_connection;
	u8 rx_packet_buf[MAX_HID_PACKET_SIZE];
	int rc = read(conn->fd, rx_packet_buf, signetdev_priv_hid_packet_size());
	if (rc == -1 && errno == EAGAIN) {  // Nothing to do right now
		return 1;
	} else if (rc != signetdev_priv_hid_packet_size()) {
		handle_error();
		return 1;
	} else { // deal with whatever the device sent to the host
		signetdev_priv_process_rx_packet(&conn->rx_state, rx_packet_buf);
	}
	return 0;  // we found data and dealt with it
}

/**
 * Open an handle to /dev/signet and add it to the list of handles that we are
 * polling for data. If it can't be opened, the device type is set to NONE.
 */
static int attempt_open_connection()
{
	struct signetdev_connection *conn = &g_connection;
	if (conn->fd >= 0) {
		g_opening_connection = 0;
		g_device_type = SIGNETDEV_DEVICE_NONE;
		return g_device_type;
	}
	int is_hc = 0;
	int fd = open("/dev/signet-hc", O_RDWR | O_NONBLOCK);
	if (fd >= 0) {
		is_hc = 1;
	} else {
		is_hc = 0;
		#ifndef QT_NO_DEBUG
		fprintf(stderr, "[attempt_open_connection] attempting to open /dev/signet\n");
		#endif
		fd = open("/dev/signet", O_RDWR | O_NONBLOCK);
		#ifndef QT_NO_DEBUG
		fprintf(stderr, "[attempt_open_connection] fd = %d\n", fd);
		#endif
	}
	if (fd >= 0) {
		memset(conn, 0, sizeof(g_connection));
		conn->fd = fd;
		g_device_type = is_hc ? SIGNETDEV_DEVICE_HC : SIGNETDEV_DEVICE_ORIGINAL;
		struct epoll_event ev;
		ev.events = EPOLLIN | EPOLLOUT | EPOLLET;
		ev.data.fd = conn->fd;
		int rc = epoll_ctl(g_poll_fd, EPOLL_CTL_ADD, conn->fd, &ev);
		if (rc)
			pthread_exit(NULL);
		g_opening_connection = 0;
		return g_device_type;
	} else {
		#ifndef QT_NO_DEBUG
		fprintf(stderr, "[attempt_open_connection] failed to open device. errorno = %s\n", strerror(errno));
		#endif
		g_opening_connection = 1;
		conn->fd = -1;
		g_device_type = SIGNETDEV_DEVICE_NONE;
		return g_device_type;
	}
}

/**
 * Linux-specific implementation of handing commands that the client is trying
 * to do (which may or may not result in a message being passed to the device).
 */
static void handle_command(int command, void *p)
{
	int rc __attribute__((unused)); // TODO add error handling
	struct signetdev_connection *conn = &g_connection;
	switch (command) {
	case SIGNETDEV_CMD_EMULATE_BEGIN:
		if (!g_opening_connection && conn->fd < 0) {
			g_emulating = 1;
			rc = command_response(1);
		} else {
			rc = command_response(0);
		}
		break;
	case SIGNETDEV_CMD_EMULATE_END:
		g_emulating = 0;
		break;
	case SIGNETDEV_CMD_OPEN:
		g_device_type = attempt_open_connection();
		rc = command_response(g_device_type);
		break;
	case SIGNETDEV_CMD_CLOSE:
		g_opening_connection = 0;
		if (conn->fd >= 0) {
			epoll_ctl(g_poll_fd, EPOLL_CTL_DEL, conn->fd, NULL);
			close(conn->fd);
			conn->fd = -1;
			g_device_type = SIGNETDEV_DEVICE_NONE;
		}
		break;
	case SIGNETDEV_CMD_MESSAGE: {
		struct signetdev_connection *conn = &g_connection;
		struct send_message_req *msg = (struct send_message_req *)p;
		if (g_emulating) {
			signetdev_emulate_handle_message_priv(msg);
		} else {
			msg->next = NULL;
			if (!conn->head_message) {
				conn->head_message = msg;
			}
			if (conn->tail_message)
				conn->tail_message->next = msg;
			conn->tail_message = msg;
		}
	} break;
	case SIGNETDEV_CMD_QUIT: {
		pthread_exit(NULL);
	} break;
	case SIGNETDEV_CMD_CANCEL_MESSAGE: {
		struct send_message_req *msg = (struct send_message_req *)p;
		if (g_emulating) {
			signetdev_emulate_handle_message_priv(msg);
		} else {
			msg->next = NULL;
			if (!conn->head_cancel_message) {
				conn->head_cancel_message = msg;
			}
			if (conn->tail_cancel_message)
				conn->tail_cancel_message->next = msg;
			conn->tail_cancel_message = msg;
		}
	} break;
	}
}

/**
 * Read commands from the command pipe and send them to the handle_command()
 * function to be dealt with.
 */
static void command_pipe_io_iter()
{
	int done = 0;
	while (!done) {
		intptr_t v[2];
		int rc = read(g_command_pipe[0], v, sizeof(intptr_t) * 2);
		if (rc == sizeof(intptr_t) * 2) {
			handle_command(v[0], (void *)v[1]);
		} else if (rc == -1 && errno == EAGAIN) {
			done = 1;
		} else {
			handle_error();
		}
	}
}

/**
 * Read and/or write non-blocking until EAGAIN is returned for both directions.
 */
static int raw_hid_io(struct signetdev_connection *conn)
{
	if (!conn->tx_state.message && (conn->head_message || conn->head_cancel_message)) {
		if (conn->head_cancel_message) {
			conn->tx_state.message = conn->head_cancel_message;
			conn->head_cancel_message = conn->head_cancel_message->next;
			if (!conn->head_cancel_message) {
				conn->tail_cancel_message = NULL;
			}
		} else if (!conn->rx_state.message) {
			conn->tx_state.message = conn->head_message;
			if (conn->head_message->resp) {
				conn->rx_state.message = conn->head_message;
			}
			conn->head_message = conn->head_message->next;
			if (!conn->head_message) {
				conn->tail_message = NULL;
			}
		}
		if (conn->tx_state.message) {
			signetdev_priv_prepare_message_state(&conn->tx_state,
					 conn->tx_state.message->dev_cmd,
					 conn->tx_state.message->messages_remaining,
					 conn->tx_state.message->payload,
					 conn->tx_state.message->payload_size);
		}
	}

	return attempt_raw_hid_read() && attempt_raw_hid_write();
}

/**
 * Read all messages from the device, and then send all messages to the device.
 */
static void raw_hid_io_iter()
{
	//Read and/or write non-blocking until EAGAIN is returned
	struct signetdev_connection *conn = &g_connection;
	while (!raw_hid_io(conn));
}

/**
 * This function is called each time a device in /dev appears. It looks to see
 * if the change was a signet being connected and if so, triggers an attempt to
 * open a connection to it.
 */
static void inotify_fd_readable()
{
	while (g_opening_connection) {
		u8 buf[4096];
		int rc = read(g_inotify_fd, buf, 4096);
		if (rc == -1 && errno == EINTR)
			break;
		if (rc == -1)
			break;
		int idx = 0;
		while (idx < rc) {
			struct inotify_event *ev = (struct inotify_event *)(buf + idx);
			int attempt_open = 0;
			if (!strcmp(ev->name, "signet")) {
				attempt_open = 1;
			} else if (!strcmp(ev->name, "signet-hc")) {
				attempt_open = 1;
			} else if (strstr(ev->name, "signet")) {
				// Compensates for when the notification has .#signetHEX
				// instead of just showing up as "signet" !signet-client#41
				attempt_open = 1;
			}
			#ifndef QT_NO_DEBUG
			fprintf(stderr, "[inotify_fd_readable] notification about /dev/%s\n", ev->name);
			#endif
			if (attempt_open) {
				g_device_type = attempt_open_connection();
				if (g_device_type != SIGNETDEV_DEVICE_NONE) {
					g_device_opened_cb(g_device_type, g_device_opened_cb_param);
					break;
				}
			}
			idx += sizeof(struct inotify_event) + ev->len;
		}
		break;
	}
}

/**
 * A thread is started with this fuction as the entry point to setup the
 * command pipeline for the client and then eternally read from it and
 * process any commands that are sent to this pipe.
 */
void *transaction_thread(void *arg)
{
	struct signetdev_connection *conn = &g_connection;
	g_opening_connection = 0;
	conn->fd = -1;
	g_device_type = SIGNETDEV_DEVICE_NONE;
	pthread_cleanup_push(handle_exit, NULL);

	g_poll_fd = epoll_create1(0);

	struct epoll_event ev_pipe;
	struct epoll_event ev_inotify;
	int rc;

	(void)arg;

	ev_pipe.events = EPOLLIN | EPOLLET;
	ev_pipe.data.fd = g_command_pipe[0];
	rc = epoll_ctl(g_poll_fd, EPOLL_CTL_ADD, g_command_pipe[0], &ev_pipe);
	if (rc)
		pthread_exit(NULL);

	struct epoll_event events[8];

	g_inotify_fd = inotify_init1(O_NONBLOCK);
	inotify_add_watch(g_inotify_fd, "/dev", IN_CREATE | IN_DELETE);
	ev_inotify.events = EPOLLIN | EPOLLET;
	ev_inotify.data.fd = g_inotify_fd;
	rc = epoll_ctl(g_poll_fd, EPOLL_CTL_ADD, g_inotify_fd, &ev_inotify);
	if (rc)
		pthread_exit(NULL);

	while (1) {
		command_pipe_io_iter();
		if (conn->fd != -1) {
			raw_hid_io_iter();
		}
		int nfds = epoll_wait(g_poll_fd, events, 8, -1);
		int i;
		for (i = 0; i < nfds; i++) {
			if (events[i].data.fd == g_inotify_fd) {
				inotify_fd_readable();
			}
			if (events[i].data.fd == conn->fd && (events[i].events & EPOLLERR)) {
				handle_error();
			}
		}
	}
	pthread_cleanup_pop(1);
	pthread_exit(NULL);
	return NULL;
}

/**
 * Send a command to the command pipeline to be processed. This may or may not
 * result in anything being sent to the device. Then it will read the response
 * from the device and return that.
 *
 * @command: the command to execute
 * @p: a pointer to the message to sent to the device
 */
int signetdev_priv_issue_command(int command, void *p)
{
	int err __attribute__((unused)); // TODO add error handling
	intptr_t v[2] = {command, (intptr_t)p};
	#ifndef QT_NO_DEBUG
	fprintf(stderr, "[signetdev_priv_issue_command] client command = %d\n", command);
	if (command == SIGNETDEV_CMD_MESSAGE) {
		struct send_message_req *msg = (struct send_message_req *)p;
		fprintf(stderr, "[signetdev_priv_issue_command] dev_cmd = %d\n", msg->dev_cmd);
		fprintf(stderr, "[signetdev_priv_issue_command] api_cmd = %d\n", msg->api_cmd);
		fprintf(stderr, "[signetdev_priv_issue_command] messages_remaining = %d\n", msg->messages_remaining);
	}
	#endif
	err = write(g_command_pipe[1], v, sizeof(intptr_t) * 2);
	char cmd_resp;
	err = read(g_command_resp_pipe[0], &cmd_resp, 1);
	#ifndef QT_NO_DEBUG
	fprintf(stderr, "[signetdev_priv_issue_command] client response = %d\n", cmd_resp);
	#endif
	return cmd_resp;
}

/**
 * Same as signetdev_priv_issue_command(int command, void *p) except that it
 * does not read any response from the device.
 */
void signetdev_priv_issue_command_no_resp(int command, void *p)
{
	intptr_t v[2] = {command, (intptr_t)p};
	int err __attribute__((unused)); // TODO return int and do error handling in callers
	#ifndef QT_NO_DEBUG
	fprintf(stderr, "[signetdev_priv_issue_command_no_resp] client command = %d\n", command);
	if (command == SIGNETDEV_CMD_MESSAGE) {
		struct send_message_req *msg = (struct send_message_req *)p;
		fprintf(stderr, "[signetdev_priv_issue_command] dev_cmd = %d\n", msg->dev_cmd);
		fprintf(stderr, "[signetdev_priv_issue_command] api_cmd = %d\n", msg->api_cmd);
		fprintf(stderr, "[signetdev_priv_issue_command] messages_remaining = %d\n", msg->messages_remaining);
	}
	#endif
	err = write(g_command_pipe[1], v, sizeof(intptr_t) * 2);
}
