/******************************************************************************

Copyright (C) 2026 AudioScience, Inc. All rights reserved.

Print approximate DSP utilization from the adapter firmware profile (idle-STS).

Permission is granted to use and redistribute this software per the same terms
as sibling utilities in this directory.

AudioScience, Inc. <support@audioscience.com>

******************************************************************************/

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <getopt.h>

#include <hpi.h>

struct options {
	uint16_t adapter_index;
	uint16_t profile_dsp_index;
	unsigned int interval_sec;
	bool machine_readable;
};

static struct options g_opt;

static struct option long_options[] = {
	{"adapter", required_argument, 0, 'a'},
	{"dsp", required_argument, 0, 'd'},
	{"interval", required_argument, 0, 'i'},
	{"machine", no_argument, 0, 'm'},
	{"help", no_argument, 0, 'h'},
	{0, 0, 0, 0}
};

static const char *short_options = "a:d:i:mh";

static void display_help(void)
{
	printf("\nUsage: asihpidspusage [options]\n\n");
	printf("  -a, --adapter N   Adapter index (default 0)\n");
	printf("  -d, --dsp N       Profile / DSP bin index passed to HPI_ProfileOpenAll (default 0)\n");
	printf("  -i, --interval N  Repeat every N seconds (0 = once, default)\n");
	printf("  -m, --machine     Print a single numeric utilization line as percent (%%.2f) only\n");
	printf("  -h, --help        This help\n");
	printf("\nUtilization is reported as %% busy (0–100). The firmware updates the value\n");
	printf("roughly once per second; very short -i intervals may repeat the same sample.\n\n");
	exit(1);
}

static void parse_options(int argc, char *const argv[])
{
	int c;
	int option_index = 0;

	g_opt.adapter_index = 0;
	g_opt.profile_dsp_index = 0;
	g_opt.interval_sec = 0;
	g_opt.machine_readable = false;

	while (1) {
		c = getopt_long(argc, argv, short_options, long_options, &option_index);
		if (c == -1)
			break;
		switch (c) {
		case 'a':
			g_opt.adapter_index = (uint16_t)atoi(optarg);
			break;
		case 'd':
			g_opt.profile_dsp_index = (uint16_t)atoi(optarg);
			break;
		case 'i':
			g_opt.interval_sec = (unsigned int)atoi(optarg);
			break;
		case 'm':
			g_opt.machine_readable = true;
			break;
		case 'h':
		case '?':
			display_help();
			break;
		default:
			display_help();
			break;
		}
	}
}

int main(int argc, char *const argv[])
{
	hpi_hsubsys_t *h_sub;
	hpi_handle_t h_profile;
	uint16_t max_profiles;
	hpi_err_t err;
	uint32_t util_centi_percent;

	parse_options(argc, argv);

	h_sub = HPI_SubSysCreate();
	if (!h_sub) {
		fprintf(stderr, "asihpidspusage: HPI_SubSysCreate failed\n");
		return 1;
	}

	err = HPI_AdapterOpen(h_sub, g_opt.adapter_index);
	if (err) {
		char buf[256];

		HPI_GetErrorText(err, buf);
		fprintf(stderr, "asihpidspusage: adapter %u: %s\n",
			g_opt.adapter_index, buf);
		HPI_SubSysFree(h_sub);
		return 1;
	}

	err = HPI_ProfileOpenAll(h_sub, g_opt.adapter_index,
		g_opt.profile_dsp_index, &h_profile, &max_profiles);
	if (err) {
		char buf[256];

		HPI_GetErrorText(err, buf);
		fprintf(stderr,
			"asihpidspusage: HPI_ProfileOpenAll failed (%s). "
			"Profiling may be absent in this firmware build.\n",
			buf);
		HPI_SubSysFree(h_sub);
		return 1;
	}

	do {
		err = HPI_ProfileGetUtilization(h_sub, h_profile, &util_centi_percent);
		if (err) {
			char buf[256];

			HPI_GetErrorText(err, buf);
			fprintf(stderr, "asihpidspusage: HPI_ProfileGetUtilization: %s\n",
				buf);
			HPI_SubSysFree(h_sub);
			return 1;
		}

		/*
		 * Firmware stores (utilization fraction) * 10000; one hundredth of
		 * a percent per unit (see axprof.c gwUtilization).
		 */
		if (g_opt.machine_readable)
			printf("%.2f\n", util_centi_percent / 100.0);
		else {
			printf("adapter %u dsp_profile %u: DSP utilization %.2f%% (max_profiles %u)\n",
				(unsigned)g_opt.adapter_index,
				(unsigned)g_opt.profile_dsp_index,
				util_centi_percent / 100.0,
				(unsigned)max_profiles);
		}
		fflush(stdout);

		if (g_opt.interval_sec > 0)
			sleep((unsigned int)g_opt.interval_sec);
	} while (g_opt.interval_sec > 0);

	HPI_SubSysFree(h_sub);
	return 0;
}
