/******************************************************************************
 \file drv/linux/test/firmdown.c

  Firmware downloader for ASI2xxx network products

  Copied from apps repo @ 477504e159 to break dependency of drv on apps repo.

 Copyright (C) 1997-2017 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 <string.h>
#include <stdlib.h>


#include "hpi.h"
#include "hpinet.h"
#include "hpifirmware.h"
#include <getopt.h>

#ifdef _MSC_VER
     #define snprintf _snprintf
#endif

#define MAX_FILES 4
#define MAC_STRING_SIZE 12

#ifndef MAX_PATH
#define MAX_PATH 260
#endif

struct fw_info {
	enum eHpiFirmwareId type;
	char path[MAX_PATH];
	char name[12];
};

static struct fw_info fw[MAX_FILES] = {
	{0,"","INVALID"},
	{HPI_FW_UPDATE, "", "Update"},
	{HPI_FW_FACTORY, "", "Factory"},
	{HPI_FW_BOOTLOADER, "", "Bootloader"}
};

/* Option variables */
static int num_files = 0;
static int shown_help = 0;
static int list = 0;
static int query = 0;
static int family = 0;
static int adapter_specified = 0;
static int no_restart = 0;
static int force_restart = 0;
static uint16_t wAdapterIndex = 0;
static uint16_t wAdapterFamily = 0;
static char szAdapterName[64];

static struct option long_options[] = {
	{"list", no_argument, 0, 'l'},
	{"adapter", required_argument, 0, 'a'},
	{"family", required_argument, 0, 'F'},
	{"factory", required_argument, 0, 'f'},
	{"update", required_argument, 0, 'u'},
	{"bootloader", required_argument, 0, 'b'},
	{"all", required_argument, 0, 'A'},
	{"query", no_argument, 0, 'q'},
	{"no-restart", no_argument, 0 , 'n'},
	{"restart", no_argument, 0 , 'r'},
	{"help", no_argument, 0, 'h'},
	{0, 0, 0, 0}
};

static const char *short_options = "la:f:F:u:b:A:qnrh?";

static const char *option_help[] = {
	"List adapters found on the network",
	"[index] Adapter index",
	"[Adapter .bin file family]",
	"[factory image filename] Download factory firmware (Danger!)",
	"[update image filename] Download update firmware",
	"[bootloader image filename ] Download bootloader firmware (Danger!)",
	"[factory and update image filename] Download BOOT2400.BIN, and both images with given file",
	"Query current hardware/firmware versions",
	"Don't restart after downloading update image",
	"Restart adapter unconditionally",
	"Show this text.\n\n"
	"For example:\n"
	" firmdown -l, will find the adapters on the network, listing them by <index>\n"
	" firmdown -a<index> -udsp2400.bin, load dsp2416 to an ASI2416\n"
	" firmdown -a<index> -F1200 -bboot1200_10200.ais, load bootloader image to an ASI2300\n"
};

static void help(char *progname)
{
	int i = 0;
	printf("\nUsage - %s [options]\n",
		progname);
	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");
}

static void parse_options(int argc, char *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':
			wAdapterIndex = atoi(optarg);
			adapter_specified = 1;
			break;
		case 'F':
			family = 1;
			sscanf(optarg, "%hx", &wAdapterFamily);
			break;
		case 'u':
			strcpy(fw[HPI_FW_UPDATE].path, optarg);
			break;
		case 'f':
			strcpy(fw[HPI_FW_FACTORY].path, optarg);
			break;
		case 'b':
			strcpy(fw[HPI_FW_BOOTLOADER].path, optarg);
			break;
		case 'A':
			// program all (b,u,f) in one go
			strcpy(fw[HPI_FW_BOOTLOADER].path, "BOOT2400.BIN");	//assume this file is present in local dir
			strcpy(fw[HPI_FW_FACTORY].path, optarg);
			strcpy(fw[HPI_FW_UPDATE].path, optarg);
			printf("Programming with:\n b=%s\n f=%s\n u=%s\n",
				fw[HPI_FW_BOOTLOADER].path,
				fw[HPI_FW_FACTORY].path,
				fw[HPI_FW_UPDATE].path);
			break;
		case 'q':
			query = 1;
			break;
		case 'l':
			list = 1;
			break;
		case 'n':
			no_restart = 1;
			break;
		case 'r':
			force_restart = 1;
			break;
		case '?':
		case 'h':
			shown_help = 1;
			help(argv[0]);
			exit(0);
			break;

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

	if (optind < argc) {
		// printf ("non-option ARGV-elements: ");
		while ((optind < argc) && (num_files < MAX_FILES)) {
			strncpy(fw[num_files].path, argv[optind++],
				MAX_PATH - 1);
			printf("%s image is %s\n", fw[num_files].name,
				fw[num_files].path);
			num_files++;
		}
	}
}

/*=================================================================*/
/**

*/
static void HPI_PrintError(
	uint16_t wError,
	uint16_t wSpecificError
)
{
	char errbuf[256];

	if (wError == 0) {

	} else {
		HPI_GetErrorText(wError, errbuf);
		printf(" Error%s, SE=%d\n", errbuf, wSpecificError);
	}
}

/*=================================================================*/
/**
Progress callback
*/

static void HPI_CALLBACK pc(char *state, u_int percent)
{
	if (percent)
		printf("\r%s %d%%", state, percent);
	else
		printf("\r%s", state);
	fflush(stdout);
}

/*=================================================================*/
/**
Map adapter names
*/
static const char *MapAdapterName(const int nAdapterType)
{
	snprintf(szAdapterName,sizeof(szAdapterName),"ASI%x, ",nAdapterType);
	return szAdapterName;
}


/*=================================================================*/
static void list_adapters(void)
{
	hpi_err_t error;
	uint32_t *sorted_indices;
	uint32_t swap;
	int num_adapters;
	int i;

	int bad_adapter_count = 0;

	error = HPI_SubSysGetNumAdapters(NULL, &num_adapters);

	if (error || !num_adapters) {
		printf("ERROR: No adapters found on the network\n");
		return;
	}

	sorted_indices = malloc(num_adapters * sizeof(uint32_t));
	// fill the array with the unsorted adapter indexes
	for (i = 0; i < num_adapters; i++)
	{
		uint16_t wAdapterType=0;

		error = HPI_SubSysGetAdapter(NULL, i,
			&sorted_indices[i], &wAdapterType);

		if (error) {
			printf("ERROR, adapter index %d, error=%d, on HPI_SubSysGetAdapter()\n",
					sorted_indices[i], error);
			sorted_indices[i] = 999999; // sorts to end of array
			bad_adapter_count++;
			continue;
		}

		error = HPI_AdapterOpen(NULL, (uint16_t)sorted_indices[i]);
		if (error) {
			printf("ERROR, adapter index %d, error=%d, on HPI_AdapterOpen()\n",
					sorted_indices[i], error);
			sorted_indices[i] = 999998;
			bad_adapter_count++;
			continue;
		}

	}
	// sort indexes from low to high
	do {
		swap = 0;
		for (i = 0; i < num_adapters - 1; i++)
		{
			if(sorted_indices[i] > sorted_indices[i+1])
			{
				swap = sorted_indices[i];
				sorted_indices[i] = sorted_indices[i+1];
				sorted_indices[i+1] = swap;
			}
		}
	} while (swap);

	// Drop bad adapters now they are sorted to end of list
	num_adapters -= bad_adapter_count;

	printf("Choose one of these adapters using -a<index>:\n\n");
	for (i = 0; i < num_adapters; i++) {
		uint16_t adapter_index;
		uint32_t dwSerialNumber;
		uint16_t wAdapterType;
		uint16_t wNumOutStreams;
		uint16_t wNumInStreams;
		uint16_t wVersion;
		uint16_t wIPmsb;
		uint16_t wIPlsb;
		uint16_t wParam1;
		uint16_t nDspSwVer;
		uint16_t wDspSwVerRelease;
		uint16_t mac[3];

		char szIp[64];
		char cHwVerMajor;
		char cHwVerMinor;

		adapter_index = sorted_indices[i];

		error = HPI_AdapterOpen(NULL, adapter_index);
		if (error) {
			printf("ERROR, adapter index %d, error=%d, on HPI_AdapterOpen()\n",
				adapter_index, error);
			continue;
		}

		error = HPI_AdapterGetInfo(
			     NULL,
			     adapter_index,
			     &wNumOutStreams,
			     &wNumInStreams,
			     &wVersion,
			     &dwSerialNumber,
			     &wAdapterType);

		if (error) {
			printf("ERROR, adapter index %d, error=%d, on HPI_AdapterGetInfo()\n",
				adapter_index,error);
			goto adapter_list_error;
		}
		cHwVerMajor = ((wVersion >> 3) & 0xF) + 'A';
		cHwVerMinor = wVersion & 0x7;
		nDspSwVer = ((wVersion >> 13) * 100) + ((wVersion >> 7) & 0x3F);

		error = HPI_AdapterGetProperty(
				NULL,
				adapter_index,
				HPI_ADAPTER_PROPERTY_SOFTWARE_VERSION,
				&wParam1,
				&wDspSwVerRelease
				);
		if (error) {
			printf("ERROR, adapter index %d, error=%d, on HPI_AdapterGetProperty(SW_VER)\n",
				adapter_index,error);
			goto adapter_list_error;
		}

		error = HPI_AdapterGetProperty(NULL,
				adapter_index,
				HPI_ADAPTER_PROPERTY_IP_ADDRESS,
				&wIPmsb, &wIPlsb );
		if (error) {
			printf("ERROR, adapter index %d, error=%d, on HPI_AdapterGetProperty(IP)\n",
				adapter_index,error);
			goto adapter_list_error;
		}

		sprintf(szIp,"%u.%u.%u.%u",
				(wIPmsb >> 8) & 0xff,
				wIPmsb & 0xff,
				(wIPlsb >> 8) & 0xff,
				wIPlsb & 0xff
				);

				error = HPI_AdapterGetProperty(NULL,
						adapter_index,
						HPI_ADAPTER_PROPERTY_MAC_ADDRESS_MSB,
						&mac[2],
						&mac[1] );

				error = HPI_AdapterGetProperty(NULL,
						adapter_index,
						HPI_ADAPTER_PROPERTY_MAC_ADDRESS_LSB,
						&mac[0],
						NULL );
				if(error) {
					printf("ERROR, adapter index %d, error=%d, on HPI_AdapterGetProperty(IP)\n",
						adapter_index,error);
					goto adapter_list_error;
				}

		if (!error)
			printf("%s Index=%5d, "
				"S/N=%5d, "
				"Ver=%01d.%02d.%02d, "
				"Rev=%c%d, "
				"IP=%s M=%04X:%04X:%04X\n",
			MapAdapterName(wAdapterType),
			adapter_index,
			dwSerialNumber,
			nDspSwVer / 100, nDspSwVer % 100, wDspSwVerRelease,
			cHwVerMajor,cHwVerMinor,
			szIp, mac[2], mac[1], mac[0]);

adapter_list_error:
		error = HPI_AdapterClose(NULL, adapter_index);
		if(error) {
			printf("ERROR, adapter index %d, error=%d, on HPI_AdapterClose()\n",
				adapter_index,error);
			continue;
		}
	}
	printf("\nWhere the following abbreviations are used:\n"
		"Ver = Firmware Version\n"
		"Rev = Hardware Revision\n");
}

/*=================================================================*/
static void printf_info(const char *what, int family, int sel,  int version, int checksum, int length, int max_len)
{
	int maj, min, rel;

	if (version < 51200)  { // decimal versions up to 5.11.99
		maj = version / 10000;
		rel = version % 100;
		min = (version % 10000) / 100;
	} else {// hex version from 0.200.0
		maj = version >> 16;
		min = (version >> 8) & 0xFF;
		rel = version & 0xFF;
	}

	printf("%s %s, family %X, version %d.%02d.%02d, checksum %08x, length %6d, max_len %6d\n\n",
		fw[sel].name,  what, family, maj, min, rel, checksum, length, max_len);
}

/*=================================================================*/
/**

*/
int main(int argc,	char *argv[])
{
	hpi_err_t error = 0;
	enum eHpiFirmwareId sel;
	uint16_t wAdapterType=0;

	printf("Network Firmware Updater (c) AudioScience, Inc. 2011\n\n");
	parse_options(argc, argv);

	if (!shown_help)
		printf("To see full option list, use -h option\n\n");

	if (!HPI_SubSysCreate()) {
		printf("ERROR: SubSysOpen Failed\n");
		return 1;
	}

	if (list) {
		list_adapters();
		exit(0);
	}

	if (!adapter_specified)
		exit(0);

	//========= adapter open ==========
	error = HPI_AdapterOpen(NULL, wAdapterIndex);
	HPI_PrintError(error, 0);
	if (error)
		exit(1);

	{	//========= adapter get info ==========
		uint16_t wNumOutStreams;
		uint16_t wNumInStreams;
		uint16_t wVersion;
		uint32_t dwSerialNumber;

		error = HPI_AdapterGetInfo(NULL, wAdapterIndex,
			&wNumOutStreams, &wNumInStreams,
			&wVersion, &dwSerialNumber, &wAdapterType);
		HPI_PrintError(error, 0);

		printf("Adapter Type=ASI%4X, Index=%5d, S/N=%5d, Hardware version %c%d\n\n", wAdapterType, wAdapterIndex, dwSerialNumber, ((wVersion >> 3) & 0xf) + 'A',	// Hw version major
			wVersion & 0x7	// Hw version minor
			//,((wVersion >> 13) * 100) + ((wVersion >> 7) & 0x3f)	// DSP code version
			);
	}

	if (!family) {
		//========= set adapter family if not on cmd line ==========
		switch (HPI_ADAPTER_FAMILY_ASI(wAdapterType))
		{
			case HPI_ADAPTER_FAMILY_ASI(0x2300):
				wAdapterFamily = wAdapterType & 0xFFF0;
				break;
			default:
				wAdapterFamily = HPI_ADAPTER_FAMILY_ASI(wAdapterType);
				break;
		}
	}

	if (query) {
		for (sel = HPI_FW_UPDATE; sel <= HPI_FW_BOOTLOADER; sel++) {
			unsigned int version, checksum, length, adapter, family, max_len;

			error = HPI_AdapterFirmwareGetInfo2(wAdapterIndex,  sel,
				&version, &checksum, &length, &family, &max_len);

			if (error) {
				printf("Error getting %s image info\n", fw[sel].name);
			} else
				printf_info("image", family, sel, version, checksum, length, max_len);

			if (strlen(fw[sel].path)) {
				error = HPI_FileFirmwareGetInfo(fw[sel].path, wAdapterFamily, 0,
						&adapter, &version, &checksum, &length);

				HPI_PrintError(error, 0);
				if (!error)
					printf_info("file", wAdapterFamily, sel, version, checksum, length, length);
			}

		}
		exit(0);
	}

	//========= program selected images to flash ==========
	for (sel = HPI_FW_UPDATE; sel <= HPI_FW_BOOTLOADER; sel++) {
		if (strlen(fw[sel].path)) {
			unsigned int version, checksum, length, adapter;

			error = HPI_FileFirmwareGetInfo(fw[sel].path, wAdapterFamily, 0,
					&adapter, &version, &checksum, &length);

			HPI_PrintError(error, 0);
			if (!error)
				printf_info("file", wAdapterFamily, sel, version, checksum, length, length);

			error = HPI_AdapterFirmwareDownload(wAdapterIndex,
				fw[sel].type, fw[sel].path,  wAdapterFamily, 0xFF00, pc);
			printf("\n");
			HPI_PrintError(error, 0);
		}
	}

	if (force_restart || (!no_restart && !error && strlen(fw[HPI_FW_UPDATE].path))) {
		printf("Restarting adapter #%d\n", wAdapterIndex);
		error = HPI_AdapterRestart(wAdapterIndex);
		HPI_PrintError(error, 0);
	}

	if (error)
		exit(1);
	exit(0);
}
