// SPDX-License-Identifier: MIT

#define _GNU_SOURCE

#include <getopt.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netdb.h>
#include <unistd.h>

#include "jitterdebugger.h"

static void read_online_cpus(struct jd_samples_info *info)
{
	FILE *fd;
	int n;

	fd = jd_fopen(info->dir, "cpus_online", "r");
	if (!fd)
		err_handler(errno, "Could not read %s/cpus_online\n", info->dir);
	n = fscanf(fd, "%d\n", &info->cpus_online);
	if (n == EOF) {
		if (ferror(fd)) {
			err_handler(errno, "fscanf()");
			perror("fscanf");
		} else {
			fprintf(stderr, "cpus_online: No matching characters, no matching failure\n");
			exit(1);
		}
	} else if (n != 1) {
		fprintf(stderr, "fscan() read more then one element\n");
		exit(1);
	}
	fclose(fd);

	if (info->cpus_online < 1) {
		fprintf(stderr, "invalid input from cpus_online\n");
		exit(1);
	}
}

static void dump_samples(const char *port)
{
	struct addrinfo hints, *res, *tmp;
	struct latency_sample sp[SAMPLES_PER_PACKET];
	ssize_t len;
	size_t wlen;
	int err, sk;

	bzero(&hints, sizeof(struct addrinfo));
	hints.ai_flags = AI_PASSIVE;
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_protocol = IPPROTO_UDP;

	err = getaddrinfo(NULL, port, &hints, &res);
	if (err < 0)
		err_handler(errno, "getaddrinfo()");

	tmp = res;
	do {
		sk = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
		if (sk < 0)
			continue;
		err = bind(sk, res->ai_addr, res->ai_addrlen);
		if (err == 0)
			break;

		close(sk);
		res = res->ai_next;
	} while(res);

	freeaddrinfo(tmp);

	while (1) {
		len = recvfrom(sk, sp, sizeof(sp), 0, NULL, NULL);
		if (len != sizeof(sp)) {
			warn_handler("UDP packet has wrong size\n");
			continue;
		}
		wlen = fwrite(sp, sizeof(sp), 1, stdout);
		if (wlen != sizeof(sp))
			err_handler(errno, "fwrite()");
	}

	close(sk);
}

struct jd_slist jd_samples_plugins = {
	NULL,
};

int jd_samples_register(struct jd_samples_ops *ops)
{
	jd_slist_append(&jd_samples_plugins, ops);

	return 0;
}

void jd_samples_unregister(struct jd_samples_ops *ops)
{
	jd_slist_remove(&jd_samples_plugins, ops);
}

static struct option long_options[] = {
	{ "help",	no_argument,		0,	'h' },
	{ "version",	no_argument,		0,	 0  },
	{ "format",	required_argument,	0,	'f' },
	{ "listen",	required_argument,	0,	'l' },
	{ 0, },
};

static void __attribute__((noreturn)) usage(int status)
{
	printf("jittersamples [options] [DIR]\n");
	printf("  DIR			Directory generated by jitterdebugger --output\n");
	printf("\n");
	printf("Usage:\n");
	printf("  -h, --help		Print this help\n");
	printf("      --version		Print version of jittersamples\n");
	printf("  -f, --format FMT	Exporting samples in format [csv, hdf5]\n");
	printf("  -l, --listen PORT	Listen on PORT, dump samples to stdout\n");

	exit(status);
}

int main(int argc, char *argv[])
{
	FILE *input;
	int c, long_idx;
	char *format = "csv";
	char *port = NULL;
	struct jd_samples_info info;
	struct jd_slist *list;

	while (1) {
		c = getopt_long(argc, argv, "hf:l:", long_options, &long_idx);
		if (c < 0)
			break;

		switch (c) {
		case 0:
			if (!strcmp(long_options[long_idx].name, "version")) {
				printf("jittersamples %s\n",
					JD_VERSION);
				exit(0);
			}
			break;
		case 'h':
			usage(0);
		case 'f':
			format = optarg;
			break;
		case 'l':
			port = optarg;
			break;
		default:
			printf("unknown option\n");
			usage(1);
		}
	}

	if (port) {
		dump_samples(port);
		exit(0);
	}

	if (optind == argc) {
		fprintf(stderr, "Missing input DIR\n");
		usage(1);
	}
	info.dir = argv[optind];

	read_online_cpus(&info);

	__jd_plugin_init();

	input = jd_fopen(info.dir, "samples.raw", "r");
	if (!input)
		err_handler(errno, "Could not open '%s/samples.raw' for reading", info.dir);

	for (list = jd_samples_plugins.next; list; list = list->next) {
		struct jd_samples_ops *plugin = list->data;

		if (strcmp(plugin->format, format))
			continue;

		plugin->output(&info, input);
		break;
	}

	fclose(input);

	__jd_plugin_cleanup();

	if (!list) {
		fprintf(stderr, "Unsupported file format \"%s\"\n", format);
		exit(1);
	}

	return 0;
}
