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

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>


static struct {
	bool decode_stdin;
	uint16_t adapter_index;
	uint16_t tuner_index;
	short rds_mode;
} options;

static struct option long_options[] = {
	{"adapter", required_argument, 0, 'a'},
	{"rds-mode", required_argument, 0, 'm'},
	{"tuner", required_argument, 0, 't'},
	{"decode-stdin", no_argument, 0, 'p'},
	{"help", no_argument, 0, 'h'},
	{0, 0, 0, 0}
};

static const char *short_options = "a:m:t:ph";

static const char *option_help[] = {
	"<adapter number> to test, default is 0.",
	"<mode> Set tuner RDS mode.",
	"<tuner index> to query or set, default is 0.",
	"Decode stdin input to output only",
	"Show this text."
};

static void display_help(void) {
	int i = 0;
	printf("\nUsage - asihpirds [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");
	exit(1);
}

static void parse_options(const int argc, char * const argv[])
{
	int c;
	/*********** 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 'm':
			options.rds_mode = atoi(optarg);
			break;
		case 'p':
			options.decode_stdin = true;
			break;
		case 't':
			options.tuner_index = atoi(optarg);
			break;
		case '?':
		case 'h':
			display_help();
			break;

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

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);
	}
}

enum eHPI_RDS_errors analyze_datagroup(HPI_RDS_HANDLE hRDS, const char *raw_rds_datagroup, const size_t raw_rds_datagroup_len)
{
	enum eHPI_RDS_errors eRDSerror = HPI_RDS_AnalyzeGroup(hRDS, raw_rds_datagroup, raw_rds_datagroup_len);
	if (eRDSerror != HPI_RDS_ERROR_NOERROR)
			printf("E:HPIRDS error #%d\n", (int)eRDSerror);
	return eRDSerror;
}

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 log_decoder_state(HPI_RDS_HANDLE hRDS)
{
	/* Get raw RDS data groups and decode them */
	int nGroupType;
	char cGroupVersion;
	bool ps_ready, rt_ready;
	unsigned short nPI,nPTY;
	const char *szPTY;
	const char *szPS;
	const char *szRT;

	// Retrieve current RDS settings.
	nGroupType = HPI_RDS_Get_GroupType(hRDS);
	cGroupVersion = HPI_RDS_Get_GroupVersion(hRDS);
	ps_ready = HPI_RDS_Get_PS_Ready(hRDS);
	szPS = HPI_RDS_Get_PS(hRDS);
	rt_ready = HPI_RDS_Get_RT_Ready(hRDS);
	szRT = HPI_RDS_Get_RT(hRDS);
	nPI = HPI_RDS_Get_PI(hRDS);
	nPTY = HPI_RDS_Get_PTY(hRDS);
	szPTY = HPI_RDS_Get_PTY_Text(hRDS);

	#define str_or_null(ptr) ((ptr) ? (ptr) : "(null)")
	#define bool_to_str(v) ((v) ? "True " : "False")
	printf("A:%d,\"%c\",%hu,%hu,\"%s\",%s,", nGroupType, cGroupVersion,
		nPI, nPTY, str_or_null(szPTY), bool_to_str(ps_ready));
	print_string_literal(szPS);
	printf(",%s,", bool_to_str(rt_ready));
	print_string_literal(szRT);
	printf("\n");
}

int analyze_stdin(const enum eHPI_RDS_type rds_bitstream_type) {
	HPI_RDS_HANDLE hRDS = HPI_RDS_Create(rds_bitstream_type);
	char *line = NULL;
	size_t len = 0;
	ssize_t read;

	while ((read = getline(&line, &len, stdin)) != -1) {
		unsigned char rds_data[12];

		if (line[0] == '#') {
			/* pass comments through */
			printf("%s", line);
		} else if (line[0] == 'R') {
			/* pass raw data lines through */
			printf("%s", line);
			/* parse raw data lines */
			sscanf(line, "R:0x%02hhX,0x%02hhX,0x%02hhX,0x%02hhX,"
						"0x%02hhX,0x%02hhX,0x%02hhX,0x%02hhX,"
						"0x%02hhX,0x%02hhX,0x%02hhX,0x%02hhX",
						&rds_data[0], &rds_data[1], &rds_data[2], &rds_data[3],
						&rds_data[4], &rds_data[5], &rds_data[6], &rds_data[7],
						&rds_data[8], &rds_data[9], &rds_data[10], &rds_data[11]);

			analyze_datagroup(hRDS, (char *)rds_data, sizeof(rds_data));
			log_decoder_state(hRDS);
		}
	}
	free(line);

	HPI_RDS_Delete(hRDS);
	return 0;
}

int capture_and_analyze(const int nAdapterIndex, const int nTunerIndex, const enum eHPI_RDS_type rds_bitstream_type) {
	hpi_err_t err;
	hpi_handle_t hMixer = 0, hPad, 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_MixerGetControl(hSubSys, hMixer, HPI_SOURCENODE_TUNER, nTunerIndex, 0, 0, HPI_CONTROL_PAD, &hPad);
	print_retval("HPI_MixerGetControl(PAD)", err);
	if (err) {
		return err;
	}

	HPI_RDS_HANDLE hRDS = HPI_RDS_Create(rds_bitstream_type);

	while (true) {
		{
			unsigned char rds_data[12];

			/* run the following loop until all data has been read */
			while(true) {
				int i;
				err = HPI_Tuner_GetRDS(hSubSys, hTuner, (char *)rds_data);
				if (err == HPI_ERROR_INVALID_DATASIZE) {
					/* break out of the loop if there is no data available */
					break;
				}
				/* dump raw RDS data */
				printf("R:");
				for (i=0;i<sizeof(rds_data);i++) {
					printf("0x%02hhX", rds_data[i]);
					if (i+1 < sizeof(rds_data)) {
						printf(",");
					}
				}
				printf("\n");

				analyze_datagroup(hRDS, (char *)rds_data, sizeof(rds_data));
				log_decoder_state(hRDS);
			}
		}

		{
			/* Retrieve decoded RDS info using the HPI_PAD_* APIs */
			uint32_t nPI = 0;
			uint32_t nPTY = 0;
			char szArtist[HPI_PAD_ARTIST_LEN];
			char szTitle[HPI_PAD_TITLE_LEN];
			char szPTY[HPI_PAD_TITLE_LEN];

			szArtist[0] = 0;
			szTitle[0] = 0;
			szPTY[0] = 0;

			err = HPI_PAD_GetArtist(hSubSys, hPad, szArtist, sizeof(szArtist));
			print_retval("HPI_PAD_GetArtist()", err);
			err = HPI_PAD_GetTitle(hSubSys, hPad, szTitle, sizeof(szTitle));
			print_retval("HPI_PAD_GetTitle()", err);
			err = HPI_PAD_GetRdsPI(hSubSys, hPad, &nPI);
			print_retval("HPI_PAD_GetRdsPI()", err);
			err = HPI_PAD_GetProgramType(hSubSys, hPad, &nPTY);
			print_retval("HPI_PAD_GetProgramType()", err);
			err = HPI_PAD_GetProgramTypeString(hSubSys, hPad, HPI_RDS_DATATYPE_RDS, nPTY, szPTY, sizeof(szPTY));
			print_retval("HPI_PAD_GetProgramTypeString()", err);

			printf("T:0,'-',%u,%u,'%s',False,'%s',False,'%s'\n", nPI, nPTY, szPTY, szTitle, szArtist);
		}

		sleep(1);
	}

	HPI_RDS_Delete(hRDS);

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

	HPI_SubSysFree(hSubSys);
}

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

	memset(&options, 0, sizeof(options));
	options.rds_mode = HPI_RDS_DATATYPE_RDS;

	parse_options(argc, argv);

	if (options.decode_stdin) {
		return analyze_stdin(options.rds_mode);
	} else {
		return capture_and_analyze(options.adapter_index, options.tuner_index, options.rds_mode);
	}
}
