// Copyright(c) 2017-2020, Intel Corporation
//
// Redistribution  and  use  in source  and  binary  forms,  with  or  without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of  source code  must retain the  above copyright notice,
//   this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
// * Neither the name  of Intel Corporation  nor the names of its contributors
//   may be used to  endorse or promote  products derived  from this  software
//   without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,  BUT NOT LIMITED TO,  THE
// IMPLIED WARRANTIES OF  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT  SHALL THE COPYRIGHT OWNER  OR CONTRIBUTORS BE
// LIABLE  FOR  ANY  DIRECT,  INDIRECT,  INCIDENTAL,  SPECIAL,  EXEMPLARY,  OR
// CONSEQUENTIAL  DAMAGES  (INCLUDING,  BUT  NOT LIMITED  TO,  PROCUREMENT  OF
// SUBSTITUTE GOODS OR SERVICES;  LOSS OF USE,  DATA, OR PROFITS;  OR BUSINESS
// INTERRUPTION)  HOWEVER CAUSED  AND ON ANY THEORY  OF LIABILITY,  WHETHER IN
// CONTRACT,  STRICT LIABILITY,  OR TORT  (INCLUDING NEGLIGENCE  OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,  EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif // HAVE_CONFIG_H
#include <errno.h>
#include <stdbool.h>
#include <malloc.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <getopt.h>
#include <time.h>
#include <limits.h>

#include <opae/fpga.h>



#define GETOPT_STRING ":hB:D:F:S:H:L:v"

struct option longopts[] = {
	{ "help",      no_argument,       NULL, 'h' },
	{ "segment",   required_argument, NULL, 0xe },
	{ "bus",       required_argument, NULL, 'B' },
	{ "device",    required_argument, NULL, 'D' },
	{ "function",  required_argument, NULL, 'F' },
	{ "socket-id", required_argument, NULL, 'S' },
	{ "freq-high", required_argument, NULL, 'H' },
	{ "freq-low",  required_argument, NULL, 'L' },
	{ "version",   no_argument,       NULL, 'v' },
	{ NULL, 0, NULL, 0 }
};

// User clock Command line struct
struct UserClkCommandLine {
	int      segment;
	int      bus;
	int      device;
	int      function;
	int      socket;
	int      freq_high;
	int      freq_low;

};

struct UserClkCommandLine userclkCmdLine = { -1, -1, -1, -1, -1, -1, -1 };

// User clock Command line input help
void UserClkAppShowHelp(void)
{
	printf("Usage:\n");
	printf("userclk\n");
	printf("<Segment>             --segment=<SEGMENT NUMBER>\n");
	printf("<Bus>                 --bus=<BUS NUMBER>           OR  -B=<BUS NUMBER>\n");
	printf("<Device>              --device=<DEVICE NUMBER>     OR  -D=<DEVICE NUMBER>\n");
	printf("<Function>            --function=<FUNCTION NUMBER> OR  -F=<FUNCTION NUMBER>\n");
	printf("<Socket-id>           --socket-id=<socket NUMBER>  OR  -S=<SOCKET NUMBER>\n");
	printf("<freq high>           --freq-high                  OR  -H=<User clock high>\n");
	printf("<freq low>            --freq-low                   OR  -L=<User clock low>\n");
	printf("<version>             -v,--version\n");
	printf("\n");

}

/*
 * macro to check return codes, print error message, and goto cleanup label
 * NOTE: this changes the program flow (uses goto)!
 */
#define ON_ERR_GOTO(res, label, desc)                    \
		do {                                       \
			if ((res) != FPGA_OK) {            \
				print_err((desc), (res));  \
				goto label;                \
			}                                  \
		} while (0)

void print_err(const char *s, fpga_result res)
{
	fprintf(stderr, "Error %s: %s\n", s, fpgaErrStr(res));
}

int ParseCmds(struct UserClkCommandLine *userclkCmdLine, int argc, char *argv[]);

int main(int argc, char *argv[])
{
	fpga_properties filter	= NULL;
	uint32_t num_matches	= 1;
	fpga_result result	= FPGA_OK;
	fpga_result res		= FPGA_OK;
	uint64_t userclk_high	= 0;
	uint64_t userclk_low	= 0;
	fpga_token accel_token	= NULL;
	int high		= 0;
	int low			= 0;
	fpga_handle		accelerator_handle;

	// Parse command line
	if (argc < 2) {
		UserClkAppShowHelp();
		return 1;
	} else if (0 != ParseCmds(&userclkCmdLine, argc, argv)) {
		return 2;
	}

	printf(" ------- Command line Input START ----\n \n");

	printf(" Segment               : %d\n", userclkCmdLine.segment);
	printf(" Bus                   : %d\n", userclkCmdLine.bus);
	printf(" Device                : %d\n", userclkCmdLine.device);
	printf(" Function              : %d\n", userclkCmdLine.function);
	printf(" Socket-id             : %d\n", userclkCmdLine.socket);
	printf(" Freq High             : %d\n", userclkCmdLine.freq_high);
	printf(" Freq Low              : %d\n", userclkCmdLine.freq_low);

	printf(" ------- Command line Input END   ----\n\n");

	result = fpgaInitialize(NULL);
	ON_ERR_GOTO(result, out_exit, "Failed to initilize ");

	// Enum FPGA device
	result = fpgaGetProperties(NULL, &filter);
	ON_ERR_GOTO(result, out_exit, "creating properties object");

	result = fpgaPropertiesSetObjectType(filter, FPGA_ACCELERATOR);
	ON_ERR_GOTO(result, out_destroy_prop, "setting object type");

	if (-1 != userclkCmdLine.segment) {
		result = fpgaPropertiesSetSegment(filter, userclkCmdLine.segment);
		ON_ERR_GOTO(result, out_destroy_prop, "setting segment");
	}

	if (-1 != userclkCmdLine.bus) {
		result = fpgaPropertiesSetBus(filter, userclkCmdLine.bus);
		ON_ERR_GOTO(result, out_destroy_prop, "setting bus");
	}

	if (-1 != userclkCmdLine.device) {
		result = fpgaPropertiesSetDevice(filter, userclkCmdLine.device);
		ON_ERR_GOTO(result, out_destroy_prop, "setting device");
	}

	if (-1 != userclkCmdLine.function) {
		result = fpgaPropertiesSetFunction(filter, userclkCmdLine.function);
		ON_ERR_GOTO(result, out_destroy_prop, "setting function");
	}

	if (-1 != userclkCmdLine.socket) {
		result = fpgaPropertiesSetSocketID(filter, userclkCmdLine.socket);
		ON_ERR_GOTO(result, out_destroy_prop, "setting socket");
	}

	result = fpgaEnumerate(&filter, 1, &accel_token, 1, &num_matches);
	ON_ERR_GOTO(result, out_destroy_prop, "enumerating FPGAs");

	if (num_matches < 1) {
		OPAE_ERR("FPGA Resource not found.");
		res = FPGA_NOT_FOUND;
		goto out_destroy_prop;
	}
	printf("AFU Resource found.\n");

	result = fpgaOpen(accel_token, &accelerator_handle, 0);
	ON_ERR_GOTO(result, out_destroy_prop, "opening accelerator");

	res = fpgaGetUserClock(accelerator_handle, &userclk_high, &userclk_low, 0);
	ON_ERR_GOTO(res, out_close, "Failed to get user clock");

	printf("\nApproximate frequency:\n"
		"High clock = %5.1f MHz\n"
		"Low clock  = %5.1f MHz\n \n",
		userclk_high / 1.0e6, userclk_low / 1.0e6);

	if (userclkCmdLine.freq_high > 0 || userclkCmdLine.freq_low > 0) {
		high = userclkCmdLine.freq_high;
		low = userclkCmdLine.freq_low;
		if (low <= 0) {
			low = userclkCmdLine.freq_high / 2;
		} else if (high <= 0) {
			high = userclkCmdLine.freq_low * 2;
		} else if ((abs(high - (2 * low))) > 1) {
			res = FPGA_INVALID_PARAM;
			OPAE_ERR("High freq must be ~ (2 * Low freq)");
			goto out_close;
		}
	} else {
		res = FPGA_INVALID_PARAM;
		OPAE_ERR("Please specify one or both of -H and -L");
		goto out_close;
	}

	res = fpgaSetUserClock(accelerator_handle, high, low, 0);
	ON_ERR_GOTO(res, out_close, "Failed to set user clock");

	res = fpgaGetUserClock(accelerator_handle, &userclk_high, &userclk_low, 0);
	ON_ERR_GOTO(res, out_close, "Failed to get user clock");

	printf("\nApproximate frequency:\n"
		"High clock = %5.1f MHz\n"
		"Low clock  = %5.1f MHz\n \n",
		userclk_high / 1.0e6, userclk_low / 1.0e6);

out_close:
	result = fpgaClose(accelerator_handle);
	ON_ERR_GOTO(result, out_destroy_tok, "closing accelerator");

out_destroy_tok:
	result = fpgaDestroyToken(&accel_token);
	ON_ERR_GOTO(result, out_destroy_prop, "destroying token object");

	/* Destroy properties object */
out_destroy_prop:
	result = fpgaDestroyProperties(&filter);
	ON_ERR_GOTO(result, out_exit, "destroying properties object");

out_exit:
	return (res != FPGA_OK) ? res : result;
}

// parse Input command line
int ParseCmds(struct UserClkCommandLine *userclkCmdLine, int argc, char *argv[])
{
	int getopt_ret     = 0;
	int option_index   = 0;
	char *endptr       = NULL;

	while (-1 != (getopt_ret = getopt_long(argc, argv, GETOPT_STRING, longopts, &option_index))) {
		const char *tmp_optarg = optarg;

		if ((optarg) && ('=' == *tmp_optarg)) {
			++tmp_optarg;
		}

		if ((!optarg) && (optind < argc) && (NULL != argv[optind]) &&
			('-' != argv[optind][0])) {
			tmp_optarg = argv[optind++];
		}

		switch (getopt_ret) {
		case 'h':
			// Command line help
			UserClkAppShowHelp();
			return -2;
			break;

		case 0xe:
			// segment number
			if (!tmp_optarg)
				return -1;
			endptr = NULL;
			userclkCmdLine->segment = strtol(tmp_optarg, &endptr, 0);
			break;

		case 'B':
			// bus number
			if (!tmp_optarg)
				return -1;
			endptr = NULL;
			userclkCmdLine->bus = strtol(tmp_optarg, &endptr, 0);
			break;

		case 'D':
			// Device number
			if (!tmp_optarg)
				return -1;
			endptr = NULL;
			userclkCmdLine->device = strtol(tmp_optarg, &endptr, 0);
			break;

		case 'F':
			// Function number
			if (!tmp_optarg)
				return -1;
			endptr = NULL;
			userclkCmdLine->function = strtol(tmp_optarg, &endptr, 0);
			break;

		case 'S':
			// Socket number
			if (!tmp_optarg)
				return -1;
			endptr = NULL;
			userclkCmdLine->socket = strtol(tmp_optarg, &endptr, 0);
			break;

		case 'H':
			// User clock High
			if (!tmp_optarg)
				return -1;
			endptr = NULL;
			userclkCmdLine->freq_high = strtol(tmp_optarg, &endptr, 0);
			break;

		case 'L':
			// User clock low
			if (!tmp_optarg)
				return -1;
			endptr = NULL;
			userclkCmdLine->freq_low = strtol(tmp_optarg, &endptr, 0);
			break;

		case 'v':
			printf("userclk %s %s%s\n",
			       OPAE_VERSION,
			       OPAE_GIT_COMMIT_HASH,
			       OPAE_GIT_SRC_TREE_DIRTY ? "*":"");
			return -2;

		case '?':
		default:    /* invalid option */
			printf("Invalid cmdline options.\n");
			return -1;
		}
	}

	return 0;
}
