#include "tb_cli.h"
#include "tb_constants.h"
#include <iostream>

static const char *help_str =
"Usage: tb [--bin x.bin] [--port n] [--vpi-port n] [--vcd x.vcd] [--dump start end] \\\n"
"          [--cycles n] [--cpuret] [--jtagdump x] [--jtagreplay x] [--vpi-poll n] \\\n"
"          [--gdb-port n] [--uart0-port n] [--uart1-port n]\n"
"          [--vpi-clk-per-tck n]\n"
"\n"
"    --bin x.bin      : Flat binary file loaded to address 0x0 in RAM\n"
"    --vcd x.vcd      : Path to dump waveforms to\n"
"    --dump start end : Print out memory contents from start to end (exclusive)\n"
"                       after execution finishes. Can be passed multiple times.\n"
"    --cycles n       : Maximum number of cycles to run before exiting.\n"
"                       Default is 0 (no maximum).\n"
"    --port n         : Port number to listen for openocd remote bitbang. Sim\n"
"                       runs in lockstep with JTAG bitbang, not free-running.\n"
"    --vpi-port n     : Port number to listen for openocd jtag_vpi. Faster than\n"
"                       remote bitbang, and sim is free-running.\n"
"    --vpi-poll n     : When using --vpi-port, back off up to n core cycles\n"
"                       between socket polls when OpenOCD is idle (0 = every cycle).\n"
"    --gdb-port n     : Listen for GDB RSP directly (no OpenOCD/JTAG).\n"
"    --uart0-port n   : Expose testbench UART0 as a TCP stream.\n"
"    --uart1-port n   : Expose testbench UART1 as a TCP stream.\n"
"    --vpi-clk-per-tck n : When using --vpi-port, run n core clock cycles per\n"
"                       JTAG TCK cycle during OpenOCD TMS sequences, so DMI/APB\n"
"                       CDC can make progress without huge BUSY delays.\n"
"    --cpuret         : Testbench's return code is the return code written to\n"
"                       IO_EXIT by the CPU, or -1 if timed out.\n"
"    --jtagdump       : Dump OpenOCD JTAG bitbang commands to a file so they\n"
"                       can be replayed. (Lower perf impact than VCD dumping)\n"
"    --jtagreplay     : Play back some dumped OpenOCD JTAG bitbang commands\n"
"    --logfile path   : File to write testbench stdout\n"
"    --sigfile path   : File to write only the data from --dump commands\n"
"                       (hex, 32 bits per line, same as riscv-arch-test)\n"
#ifdef CXXRTL_DEBUG_AGENT
"    --debug          : Run CXXRTL debugger\n"
#endif
;

static void exit_help(std::string errtext = "") {
	std::cerr << errtext << help_str;
	exit(-1);
}

void tb_parse_args(int argc, char **argv, tb_cli_args &args) {
	for (int i = 1; i < argc; ++i) {
		std::string s(argv[i]);
		if (s.substr(0, 11) == "+verilator+") {
			// Skip arguments passed directly to verilator context
			i += 1;
		} else if (s.rfind("--", 0) != 0) {
			std::cerr << "Unexpected positional argument " << s << "\n";
			exit_help("");
		} else if (s == "--bin") {
			if (argc - i < 2)
				exit_help("Option --bin requires an argument\n");
			args.load_bin = true;
			args.bin_path = argv[i + 1];
			i += 1;
		} else if (s == "--vcd") {
			if (argc - i < 2)
				exit_help("Option --vcd requires an argument\n");
			args.dump_waves = true;
			args.waves_path = argv[i + 1];
			i += 1;
		} else if (s == "--logfile") {
			if (argc - i < 2)
				exit_help("Option --logfile requires an argument\n");
			args.log_path = argv[i + 1];
			i += 1;
		} else if (s == "--sigfile") {
			if (argc - i < 2)
				exit_help("Option --sigfile requires an argument\n");
			args.sig_path = argv[i + 1];
			i += 1;
		} else if (s == "--jtagdump") {
			if (argc - i < 2)
				exit_help("Option --jtagdump requires an argument\n");
			args.dump_jtag = true;
			args.jtag_dump_path = argv[i + 1];
			i += 1;
		} else if (s == "--jtagreplay") {
			if (argc - i < 2)
				exit_help("Option --jtagreplay requires an argument\n");
			args.replay_jtag = true;
			args.jtag_replay_path = argv[i + 1];
			i += 1;
		} else if (s == "--dump") {
			if (argc - i < 3)
				exit_help("Option --dump requires 2 arguments\n");
			uint32_t first = std::stoul(argv[i + 1], 0, 0);
			uint32_t last = std::stoul(argv[i + 2], 0, 0);
			if (first < MEM_BASE || last > MEM_BASE + MEM_SIZE || first > last) {
				std::cerr << "Invalid memory range\n";
				exit(-1);
			}
			args.dump_ranges.push_back(std::pair<uint32_t, uint32_t>(
				first, last
			));
			i += 2;
		} else if (s == "--cycles") {
			if (argc - i < 2)
				exit_help("Option --cycles requires an argument\n");
			args.max_cycles = std::stol(argv[i + 1], 0, 0);
			i += 1;
		} else if (s == "--port") {
			if (argc - i < 2)
				exit_help("Option --port requires an argument\n");
			args.port = std::stol(argv[i + 1], 0, 0);
			i += 1;
		} else if (s == "--vpi-port") {
			if (argc - i < 2)
				exit_help("Option --vpi-port requires an argument\n");
			args.vpi_port = std::stol(argv[i + 1], 0, 0);
			i += 1;
		} else if (s == "--gdb-port") {
			if (argc - i < 2)
				exit_help("Option --gdb-port requires an argument\n");
			args.gdb_port = std::stol(argv[i + 1], 0, 0);
			i += 1;
		} else if (s == "--uart0-port") {
			if (argc - i < 2)
				exit_help("Option --uart0-port requires an argument\n");
			args.uart0_port = std::stol(argv[i + 1], 0, 0);
			i += 1;
		} else if (s == "--uart1-port") {
			if (argc - i < 2)
				exit_help("Option --uart1-port requires an argument\n");
			args.uart1_port = std::stol(argv[i + 1], 0, 0);
			i += 1;
		} else if (s == "--vpi-poll") {
			if (argc - i < 2)
				exit_help("Option --vpi-poll requires an argument\n");
			args.vpi_poll_cycles = (uint32_t)std::stoul(argv[i + 1], 0, 0);
			i += 1;
		} else if (s == "--vpi-clk-per-tck") {
			if (argc - i < 2)
				exit_help("Option --vpi-clk-per-tck requires an argument\n");
			args.vpi_clk_per_tck = (uint32_t)std::stoul(argv[i + 1], 0, 0);
			i += 1;
		} else if (s == "--cpuret") {
			args.propagate_return_code = true;
#ifdef CXXRTL_DEBUG_AGENT
		} else if (s == "--debug") {
			args.run_agent = true;
#endif
		} else {
			std::cerr << "Unrecognised argument " << s << "\n";
			exit_help("");
		}
	}
	if (!(args.load_bin || args.port != 0 || args.vpi_port != 0 || args.gdb_port != 0 || args.replay_jtag))
		exit_help("At least one of --bin, --port, --vpi-port, --gdb-port or --jtagreplay must be specified.\n");
	if ((args.port != 0) + (args.vpi_port != 0) + (args.gdb_port != 0) > 1)
		exit_help("Can't combine --port/--vpi-port/--gdb-port\n");
	if (args.dump_jtag && args.port == 0)
		exit_help("--jtagdump is only supported with --port (remote bitbang)\n");
	if (args.replay_jtag && (args.port != 0 || args.vpi_port != 0 || args.gdb_port != 0))
		exit_help("Can't specify --jtagreplay together with --port/--vpi-port/--gdb-port\n");
}
