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

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>
#ifndef HPI_OS_WIN32_USER
#include <unistd.h>
#endif
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <getopt.h>

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

#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
#define expand_mode_enum(e) { e, #e }

static struct {
	int mode_enum_val;
	const char *mode_enum_str;
} modes[] = {
	expand_mode_enum(HPI_ADAPTER_MODE_4OSTREAM),
	expand_mode_enum(HPI_ADAPTER_MODE_6OSTREAM),
	expand_mode_enum(HPI_ADAPTER_MODE_8OSTREAM),
	expand_mode_enum(HPI_ADAPTER_MODE_16OSTREAM),
	expand_mode_enum(HPI_ADAPTER_MODE_1OSTREAM),
	expand_mode_enum(HPI_ADAPTER_MODE_1),
	expand_mode_enum(HPI_ADAPTER_MODE_2),
	expand_mode_enum(HPI_ADAPTER_MODE_3),
	expand_mode_enum(HPI_ADAPTER_MODE_MULTICHANNEL),
	expand_mode_enum(HPI_ADAPTER_MODE_12OSTREAM),
	expand_mode_enum(HPI_ADAPTER_MODE_9OSTREAM),
	expand_mode_enum(HPI_ADAPTER_MODE_MONO),
	expand_mode_enum(HPI_ADAPTER_MODE_LOW_LATENCY),
	expand_mode_enum(HPI_ADAPTER_MODE_24OSTREAM),
	expand_mode_enum(HPI_ADAPTER_MODE_32OSTREAM),
	{-1, "Invalid Mode"},
};

static struct {
	uint16_t adapter_index;
	bool set_mode;
	int target_mode;
} options;

static struct option long_options[] = {
	{"adapter", required_argument, 0, 'a'},
	{"mode", required_argument, 0, 'm'},
	{"help", no_argument, 0, 'h'},
	{0, 0, 0, 0}
};

static const char *short_options = "a:m:h";

static const char *option_help[] = {
	"<adapter number> to test, default is 0.",
	"<mode index> to set.",
	"Show this text."
};

static void display_help(void) {
	int i = 0;
	printf("\nUsage - asihpisetmode [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.target_mode = atoi(optarg);
			options.set_mode = true;
			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);
	}
}

int query_mode(const hpi_hsubsys_t *phSubSys, const uint16_t nAdapterIndex)
{
	hpi_err_t err;
	int idx, current_mode_index = -1;
	int32_t pdwAdapterMode = 0;

	err = HPI_AdapterGetMode(phSubSys, nAdapterIndex, (uint32_t *)&pdwAdapterMode);
	if (err) {
		printf("This adapter does not support multiple modes.\n");
		return -1;
	}
	printf("Supported modes:\n");
	for (idx = 0; idx < ARRAY_SIZE(modes); idx++) {
		if (modes[idx].mode_enum_val == pdwAdapterMode) {
			current_mode_index = idx;
		}
		err = HPI_AdapterSetModeEx(phSubSys, nAdapterIndex, (const uint32_t)modes[idx].mode_enum_val, HPI_ADAPTER_MODE_QUERY);
		if (err) {
			/* this mode is not supoprted by this adapter */
			continue;
		} else {
			printf("\t%02d: %s\n", modes[idx].mode_enum_val, modes[idx].mode_enum_str);
		}
	}
	if (current_mode_index < 0) {
		current_mode_index = ARRAY_SIZE(modes)-1;
	}
	printf("Current mode: %d - %s.\n", modes[current_mode_index].mode_enum_val, modes[current_mode_index].mode_enum_str);
	return 0;
}

int set_mode(const hpi_hsubsys_t *phSubSys, const int nAdapterIndex, const int32_t target_mode)
{
	hpi_err_t err;
	int32_t pdwAdapterMode = 0;

	err = HPI_AdapterGetMode(phSubSys, nAdapterIndex, (uint32_t *)&pdwAdapterMode);
	if (err) {
		printf("This adapter does not support multiple modes.\n");
		return -1;
	}
	err = HPI_AdapterSetModeEx(phSubSys, nAdapterIndex, (const uint32_t)target_mode, HPI_ADAPTER_MODE_SET);
	if (err) {
		printf("Failed to set adapter mode to %d\n", target_mode);
		return -1;
	}
	if (target_mode != pdwAdapterMode) {
		printf("Mode changed to %d, driver reload required to activate.\n", target_mode);
		return 1;
	} else {
		printf("Adapter mode %d did not change.\n", pdwAdapterMode);
	}
	return 0;
}

int main(const int argc, char * const argv[]) {
	int retval = 0;

	memset(&options, 0, sizeof(options));
	parse_options(argc, argv);

	{
		hpi_hsubsys_t* hSubSys = HPI_SubSysCreate();
		hpi_err_t err = HPI_AdapterOpen(hSubSys, options.adapter_index);
		if (err) {
			printf("Adapter index %d not found.\n", options.adapter_index);
			return -1;
		}

		if (options.set_mode) {
			retval = set_mode(hSubSys, options.adapter_index, options.target_mode);
		} else {
			retval = query_mode(hSubSys, options.adapter_index);
		}

		HPI_SubSysFree(hSubSys);
	}

	return retval;
}
