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

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

This software is provided 'as-is', without any express or implied warranty.
In no event will AudioScience Inc. be held liable for any damages arising
from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not
   claim that you wrote the original software. If you use this software
   in a product, an acknowledgment in the product documentation would be
   appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
   misrepresented as being the original software.
3. This copyright notice and list of conditions may not be altered or removed
   from any source distribution.

AudioScience, Inc. <support@audioscience.com>

( This license is GPL compatible see http://www.gnu.org/licenses/license-list.html#GPLCompatibleLicenses )

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

#include <stdbool.h>
#include <unistd.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <getopt.h>

#include <hpi.h>
#include <hpirds.h>
#include <hpidebug.h>

#define MAX_RES_SZ (256U)

static struct {
	bool enable_proc;
	int repeat_count;
	int repeat_interval_seconds;
	uint16_t adapter_index;
	uint16_t tuner_index;
	uint8_t cmd_buf[16];
	uint8_t cmd_sz;
	size_t res_sz;
} options;

static struct option long_options[] = {
	{"adapter", required_argument, 0, 'a'},
	{"tuner", required_argument, 0, 't'},
	{"proc-enable", required_argument, 0, 'p'},
	{"repeat-count", required_argument, 0, 'r'},
	{"repeat-interval", required_argument, 0, 'i'},
	{"response-size", required_argument, 0, 's'},
	{"help", no_argument, 0, 'h'},
	{0, 0, 0, 0}
};

static const char *short_options = "a:t:i:r:s:p:h";

static const char *option_help[] = {
	"<adapter number> to test, default is 0.",
	"<tuner index> to query or set, default is 0.",
	"Enable tuner background processing on exit (defaults to true).",
	"Repeat count for command/response, defaults to 1.",
	"Repeat interval in seconds, defaults to 1.",
	"Expected response size in bytes, defaults to 256.",
	"Show this text."
};

static void display_help(void) {
	int i = 0;
	printf("\nUsage - asihpi_si4688 [options] [command_bytes]\n");
	printf("\n[options]:\n");
	while (long_options[i].name != 0) {
		printf("--%s -%c %s\n",
			long_options[i].name,
			(char)(long_options[i].val), option_help[i]);
		i++;
	}
	printf("\n[command_bytes], if present, is a space separated list of byte values (decimal or hex).\n");
	printf("When [command_bytes] is not present the RD_REPLY is sent and the response shown.\n");
	printf("Example: `asihpi_si4688 0x92 0x00` sends an HD_DIGRAD_STATUS command and displays the reply\n");
	printf("\n");
	exit(1);
}

static void parse_options(const int argc, char * const argv[])
{
	int c, i;
	/*********** Parse the command line options ***************/
	while (1) {
		// int this_option_optind = optind ? optind : 1;
		int option_index = 0;

		c = getopt_long(argc, argv, short_options,
			long_options, &option_index);
		if (c == -1)
			break;

		switch (c) {
		case 0:
			printf("option %s", long_options[option_index].name);
			if (optarg)
				printf(" with arg %s", optarg);
			printf("\n");
			break;
		case 'a':
			options.adapter_index = atoi(optarg);
			break;
		case 'r':
			options.repeat_count = atoi(optarg);
			break;
		case 'i':
			options.repeat_interval_seconds = atoi(optarg);
			break;
		case 't':
			options.tuner_index = atoi(optarg);
			break;
		case 's':
			options.res_sz = atoi(optarg);
			if (options.res_sz > MAX_RES_SZ) {
				options.res_sz = MAX_RES_SZ;
			}
			break;
		case 'p':
			options.enable_proc = !!atoi(optarg);
			break;
		case '?':
		case 'h':
			display_help();
			break;

		default:
			printf("?? getopt returned character code 0%o ??\n",c);
		}
	}

	for (i = 0; optind < argc; optind++,i++) {
		options.cmd_buf[i] = strtol(argv[optind], NULL, 0);
		options.cmd_sz = i+1;
	}
}

void print_retval(const char* szMsg, hpi_err_t err) {
	if (err) {
		char szError[256];
		HPI_GetErrorText(err, szError);
		printf("E:%s failed (error %s)\n", szMsg, szError);
	}
}

void print_string_literal(const char *s)
{
	printf("\"");
	while (*s) {
		if (iscntrl(*s) || (*s == '"') || ((unsigned char)*s > 127)) {
			printf("\\x%02hhx", (unsigned char)*s);
		} else if (*s == '\\') {
			printf("\\\\");
		} else {
			printf("%c", *s);
		}
		s++;
	}
	printf("\"");
}

void print_hex_literal(const uint8_t *s, const size_t sz)
{
	size_t i;

	for (i = 0; i < sz; i++) {
		if (i && !(i % 16)) {
			printf("\n");
		}

		if (!(i % 16)) {
			printf("%04lX: ", i);
		} else if (i && !(i % 8)) {
			printf(" ");
		}

		printf("%02hhX ", s[i]);
	}
	printf("\n");
}

int query_status(const int nAdapterIndex, const int nTunerIndex) {
	hpi_err_t err;
	hpi_handle_t hMixer = 0, hTuner;
	hpi_hsubsys_t* hSubSys = HPI_SubSysCreate();

	err = HPI_MixerOpen(hSubSys, nAdapterIndex, &hMixer);
	print_retval("HPI_MixerOpen()", err);
	if (err) {
		return err;
	}

	err = HPI_MixerGetControl(hSubSys, hMixer, HPI_SOURCENODE_TUNER, nTunerIndex, 0, 0, HPI_CONTROL_TUNER, &hTuner);
	print_retval("HPI_MixerGetControl(TUNER)", err);
	if (err) {
		return err;
	}

	err = HPI_Tuner_SetBackgroundProc(hSubSys, hTuner, 0);
	print_retval("HPI_Tuner_SetBackgroundProc()", err);
	if (err) {
		return err;
	}

	while (options.repeat_count == -1 || options.repeat_count--) {
		uint8_t buf[MAX_RES_SZ];
		memset(buf, 0, sizeof(buf));

		if (options.cmd_sz) {
			err = HPI_Tuner_SendMsg(hSubSys, hTuner, options.cmd_buf, options.cmd_sz, 0);
			if (err) {
				return err;
			}

			printf("cmd:\n");
			print_hex_literal(options.cmd_buf, options.cmd_sz);
		}

		usleep(100000L);
		err = HPI_Tuner_SendMsg(hSubSys, hTuner, buf, 0, sizeof(buf));
		if (err) {
			return err;
		}

		printf("res:\n");
		print_hex_literal(buf, options.res_sz);

		if (options.repeat_count) {
			sleep(options.repeat_interval_seconds);
		}
	}

	err = HPI_Tuner_SetBackgroundProc(hSubSys, hTuner, options.enable_proc);
	print_retval("HPI_Tuner_SetBackgroundProc()", err);
	if (err) {
		return err;
	}

	err = HPI_MixerClose(hSubSys, hMixer);
	print_retval("HPI_MixerClose()", err);
	if (err) {
		return err;
	}

	HPI_SubSysFree(hSubSys);

	return 0;
}

int main(const int argc, char * const argv[]) {
	memset(&options, 0, sizeof(options));

	/* Set all non-zero defaults */
	options.repeat_count = 1;
	options.repeat_interval_seconds = 1;
	options.cmd_sz = 1; /* cmd buffer is zeroed on startup so  */
	options.res_sz = MAX_RES_SZ;
	options.enable_proc = true;

	parse_options(argc, argv);

	return query_status(options.adapter_index, options.tuner_index);
}
