/*
 * BlueALSA - hcitop.c
 * Copyright (c) 2016-2023 Arkadiusz Bokowy
 *
 * This file is a part of bluez-alsa.
 *
 * This project is licensed under the terms of the MIT license.
 *
 */

#if HAVE_CONFIG_H
# include <config.h>
#endif

#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <curses.h>
#include <bsd/stdlib.h>

#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>

static const struct {
	unsigned int bit;
	char flag;
} hci_flags_map[] = {
	{ HCI_UP, 'U' },
	{ HCI_INIT, 'N' },
	{ HCI_RUNNING, 'R' },
	{ HCI_PSCAN, 'P' },
	{ HCI_ISCAN, 'I' },
	{ HCI_AUTH, 'A' },
	{ HCI_ENCRYPT, 'E' },
	{ HCI_INQUIRY, 'Q' },
	{ HCI_RAW, 'X' },
};

static int get_devinfo(struct hci_dev_info di[HCI_MAX_DEV]) {

	int i, num;

	for (i = num = 0; i < HCI_MAX_DEV; i++)
		if (hci_devinfo(i, &di[num]) == 0)
			num++;

	return num;
}

static unsigned int get_average_rate(unsigned int *array, size_t size) {

	/* at least two points are required */
	if (size < 2)
		return 0;

	unsigned int x = 0;
	unsigned int y = 0;
	size_t i;

	size--;
	i = size;

	while (i--) {
		int b = (array[i] - array[i + 1]) % size;
		x += (array[i] - array[i + 1]) / size;
		if (y >= size - b) {
			y -= size - b;
			x++;
		}
		else {
			y += b;
		}
	}

	return x + y / size;
}

static void sprint_hci_flags(char *str, unsigned int flags) {

	size_t i;

	for (i = 0; i < sizeof(hci_flags_map) / sizeof(*hci_flags_map); i++)
		str[i] = hci_test_bit(hci_flags_map[i].bit, &flags) ? hci_flags_map[i].flag : ' ';

	str[i] = '\0';
}

int main(int argc, char *argv[]) {

	int opt;
	const char *opts = "hVd:";
	const struct option longopts[] = {
		{ "help", no_argument, NULL, 'h' },
		{ "version", no_argument, NULL, 'V' },
		{ "delay", required_argument, NULL, 'd' },
		{ 0, 0, 0, 0 },
	};

	int delay_sec = 1;
	int delay_msec = 0;

	while ((opt = getopt_long(argc, argv, opts, longopts, NULL)) != -1)
		switch (opt) {
		case 'h' /* --help */ :
			printf("usage: %s [ -d sec ]\n"
					"  -h, --help\t\tprint this help and exit\n"
					"  -V, --version\t\tprint version and exit\n"
					"  -d, --delay=SEC\tdelay time interval\n",
					argv[0]);
			return EXIT_SUCCESS;

		case 'V' /* --version */ :
			printf("%s\n", PACKAGE_VERSION);
			return EXIT_SUCCESS;

		case 'd' /* --delay=SEC */ :
			delay_sec = atoi(optarg);
			delay_msec = (int)((atof(optarg) - delay_sec) * 10) * 100;
			if (delay_sec < 0 || delay_msec < 0 || (delay_sec == 0 && delay_msec == 0)) {
				fprintf(stderr, "%s: -d requires positive argument (max precision: 0.1)\n", argv[0]);
				return EXIT_FAILURE;
			}
			break;

		default:
			fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]);
			return EXIT_FAILURE;
		}

	struct hci_dev_info devices[HCI_MAX_DEV];
	unsigned int byte_rx[HCI_MAX_DEV][3];
	unsigned int byte_tx[HCI_MAX_DEV][3];
	size_t ii;

	memset(byte_rx, 0, sizeof(byte_rx));
	memset(byte_tx, 0, sizeof(byte_tx));

	initscr();
	cbreak();
	noecho();
	curs_set(0);

	for (ii = 1;; ii++) {

		const char *template_top = "%-5s %4s %-17s %-9s %8s %8s %8s %8s";
		const char *template_row = "%-5s %4s %17s %9s %8s %8s %8s %8s";
		int i, count;

		attron(A_REVERSE);
		mvprintw(0, 0, template_top, "HCI", "BUS", "ADDR", "FLAGS", "RX", "TX", "RX/s", "TX/s");
		attroff(A_REVERSE);

		count = get_devinfo(devices);
		for (i = 0; i < HCI_MAX_DEV; i++) {

			/* shift historic data to the right by one sample */
			memmove(&byte_rx[i][1], &byte_rx[i][0], sizeof(*byte_rx) - sizeof(**byte_rx));
			memmove(&byte_tx[i][1], &byte_tx[i][0], sizeof(*byte_tx) - sizeof(**byte_tx));

			if (i >= count)
				continue;

			char addr[18];
			ba2str(&devices[i].bdaddr, addr);

			char *bus = hci_dtypetostr(devices[i].type);

			char flags[sizeof(hci_flags_map) / sizeof(*hci_flags_map) + 1];
			sprint_hci_flags(flags, devices[i].flags);

			byte_rx[i][0] = devices[i].stat.byte_rx;
			byte_tx[i][0] = devices[i].stat.byte_tx;

			const size_t ii_max = sizeof(*byte_rx) / sizeof(**byte_rx);
			const size_t samples = ii < ii_max ? ii : ii_max;
			unsigned int rate_rx = get_average_rate(byte_rx[i], samples);
			unsigned int rate_tx = get_average_rate(byte_tx[i], samples);

			rate_rx = rate_rx * 10 / (delay_sec * 10 + delay_msec / 100);
			rate_tx = rate_tx * 10 / (delay_sec * 10 + delay_msec / 100);

			char rx[7], rx_rate[9];
			char tx[7], tx_rate[9];

			humanize_number(rx, sizeof(rx), byte_rx[i][0], "B", HN_AUTOSCALE, 0);
			humanize_number(tx, sizeof(tx), byte_tx[i][0], "B", HN_AUTOSCALE, 0);
			humanize_number(rx_rate, sizeof(rx_rate), rate_rx, "B", HN_AUTOSCALE, 0);
			humanize_number(tx_rate, sizeof(tx_rate), rate_tx, "B", HN_AUTOSCALE, 0);

			mvprintw(i + 1, 0, template_row,
					devices[i].name, bus, addr, flags, rx, tx, rx_rate, tx_rate);

		}

		timeout(delay_sec * 1000 + delay_msec);
		if (getch() == 'q')
			break;

	}

	endwin();
	return EXIT_SUCCESS;
}
