#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#include <json_object.h>
#include <json_tokener.h>

#include "conn.h"
#include "vali.h"

static bool buffer_grow(struct vali_buffer *buf, size_t n) {
	if (buf->cap >= buf->size + n) {
		return true;
	}

	size_t new_cap = buf->cap;
	if (new_cap == 0) {
		new_cap = 4096;
	}
	while (new_cap < buf->size + n) {
		new_cap *= 2;
	}

	char *new_data = realloc(buf->data, new_cap);
	if (new_data == NULL) {
		return false;
	}

	buf->data = new_data;
	buf->cap = new_cap;
	return true;
}

static void buffer_shift(struct vali_buffer *buf, size_t n) {
	if (n == buf->size) {
		buf->size = 0;
	} else {
		memmove(buf->data, &buf->data[n], buf->size - n);
		buf->size -= n;
	}
}

void conn_init(struct vali_conn *conn, int fd) {
	*conn = (struct vali_conn){ .fd = fd };
}

void conn_finish(struct vali_conn *conn) {
	close(conn->fd);
	free(conn->in.data);
	free(conn->out.data);
}

bool conn_enqueue(struct vali_conn *conn, struct json_object *obj) {
	const char *raw = json_object_to_json_string(obj);
	size_t raw_size = strlen(raw) + 1;
	if (!buffer_grow(&conn->out, raw_size)) {
		return false;
	}
	memcpy(&conn->out.data[conn->out.size], raw, raw_size);
	conn->out.size += raw_size;
	json_object_put(obj);
	return true;
}

bool conn_flush(struct vali_conn *conn) {
	size_t n_written = 0;
	bool ok = true;
	while (n_written < conn->out.size) {
		ssize_t n = write(conn->fd, &conn->out.data[n_written], conn->out.size - n_written);
		if (n < 0) {
			if (errno == EINTR) {
				continue;
			} else if (errno == EAGAIN) {
				break;
			}
			ok = false;
			break;
		}
		n_written += (size_t)n;
	}
	buffer_shift(&conn->out, n_written);
	return ok;
}

static ssize_t conn_find_in_buf_end(struct vali_conn *conn, size_t offset, size_t len) {
	for (size_t i = offset; i < offset + len; i++) {
		if (conn->in.data[i] == '\0') {
			return (ssize_t)i;
		}
	}
	return -1;
}

bool conn_receive(struct vali_conn *conn, bool *eof) {
	*eof = false;

	if (conn_find_in_buf_end(conn, 0, conn->in.size) >= 0) {
		return true;
	}

	while (true) {
		if (!buffer_grow(&conn->in, 4096)) {
			return false;
		}
		size_t max = conn->in.cap - conn->in.size;
		ssize_t n = read(conn->fd, &conn->in.data[conn->in.size], max);
		if (n < 0) {
			if (errno == EINTR) {
				continue;
			} else if (errno == EAGAIN) {
				break;
			}
			return false;
		} else if (n == 0) {
			*eof = true;
			break;
		}

		conn->in.size += (size_t)n;

		if (conn_find_in_buf_end(conn, conn->in.size - (size_t)n, (size_t)n) >= 0) {
			break;
		}
	}

	return true;
}

bool conn_dequeue(struct vali_conn *conn, struct json_object **out) {
	*out = NULL;

	ssize_t end = conn_find_in_buf_end(conn, 0, conn->in.size);
	if (end < 0) {
		return true;
	}

	struct json_object *obj = json_tokener_parse(conn->in.data);

	size_t msg_size = (size_t)end + 1;
	buffer_shift(&conn->in, msg_size);

	*out = obj;
	return true;
}

bool set_sockaddr_un(struct sockaddr_un *addr, const char *path) {
	*addr = (struct sockaddr_un){ .sun_family = AF_UNIX };
	size_t path_len = strlen(path);
	if (path_len >= sizeof(addr->sun_path)) {
		return false;
	}
	memcpy(addr->sun_path, path, path_len + 1);
	return true;
}

bool set_cloexec(int fd) {
	int flags = fcntl(fd, F_GETFD, 0);
	if (flags < 0) {
		return false;
	}
	flags |= FD_CLOEXEC;
	return fcntl(fd, F_SETFD, flags) != -1;
}

bool set_nonblock(int fd) {
	int flags = fcntl(fd, F_GETFL, 0);
	if (flags < 0) {
		return false;
	}
	flags |= O_NONBLOCK;
	return fcntl(fd, F_SETFL, flags) != -1;
}
