/*
 * agc_cli.c
 *
 *  Created on: Nov 30, 2008
 *      Author: MZ211D
 */
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "agc_cli.h"
#include "agc_engine.h"
#include "agc_symtab.h"

/* Some legacy vars for now */
int FullNameMode = 0;
int QuietMode = 0;

static char CduLog[] = "yaAGC.cdulog";

static Options_t Options;

/**
The command line options try to stay compatible with the early versions of
yaAGC for some time to enable a transition period but will not list them
in the usage for the command help to discourage its usage. Hence the option
--core is somewhat confusing because in the old builds it represents the
executable core-ropes image file but normally gdb uses this for the
core file. */
static void CliShowUsage(void)
{
	printf ("Usage:\n"
"\tyaAGC [options] exec-ropes-file [core-resume-file]\n\n"
"Options:\n\n"
"--exec=EXECFILE   Use EXECFILE as the exec-ropes-file\n"
"--fullname        Output information used by emacs-GDB interface.\n"
"--symbols=SYMFILE Read symbols from SYMFILE generated by yaYUL.\n"
"--quiet           Do not print version number on startup.\n"
"--cd=DIR          Change current directory to DIR.\n"
"--command=FILE    Execute AGC commands from FILE.\n"
"--directory=DIR   Search for source files in DIR.\n"
"--port=N          Change the server port number (default=19697).\n"
"--nodebug         Disables debugging and run just the simulation\n"
"--interlace=N     Read the socket interface every N CPU instructions.\n"
"--dump-time=N     Create core image every N seconds (default = 10).\n"
"--cdu-log         Used only for debugging. Creates the file yaAGC.cdulog\n"
"                  containing data related to the bandwidth-limiting of\n"
"                  CDU inputs PCDU and MCDU.\n"
"--debug-dsky      Rather than run the core program, go into DSKY-debug\n"
"                  mode. In this mode send pre-determined codes to\n"
"                  the DSKY upon receiving DSKY keypresses.\n"
"--debug-deda      This mode runs the core program as usual, but also\n"
"                  responds to messages from yaDEDA and generates fake\n"
"                  messages to yaDEDA for testing purposes.\n"
"--deda-quiet      Used with --debug-deda to eliminate outputs from yaAGC\n"
"                  to the DEDA.  In other words, lets yaAGC parse the\n"
"                  messages being received from the DEDA, but never to\n"
"                  send any.  That lets \"yaAGC --debug-deda --deda-quiet\"\n"
"                  to be used alongside yaAGS without conflict.\n"
"--cfg=file        The name of a configuration file.  Presently, the\n"
"                  configuration files is used only for --debug-dsky\n"
"                  mode.  It would typically be the same configuration\n"
"                  file as used for yaDSKY.\n\n"
"Note that the exec-ropes file should contain exactly 36 banks\n"
"(36x1024=36864 words, or 73728 bytes). Other sizes may be accepted,\n"
"but it is unclear what (if any) utility such core-rope images\n"
"would have. (In particular, if the core-rope\n"
"is supposed to be for actual Luminary or Colossus software, then\n"
"the checksums of the missing memory banks would be incorrect, and\n"
"so the built-in self-test would fail.)\n\n");
}

extern FILE *rfopen (const char *Filename, const char *mode);


/**
This function parses the specified configuration file.
It loads its contents not a lot of checking is done here.
It returns 0 on "success" and 1 on known error.
*/
int CliParseCfg (char *Filename)
{
	char s[129] = { 0 };
	int KeyCode, Channel, Value, Result = 1;
	char Logic;
	FILE *fin;

	fin = rfopen (Filename, "r");
	if (fin)
	{
		Result = 0;

		while (NULL != fgets (s, sizeof (s) - 1, fin))
		{
			char *ss;

			/* Find newline or form feed and replace with string termination */
			for (ss = s; *ss; ss++) if (*ss == '\n' || *ss == '\r') *ss = 0;

			/* Parse string */
			if (4 == sscanf(s,"DEBUG %d %o %c %x",&KeyCode,&Channel,&Logic,&Value))
			{
				/* Ensure valid values are porvided */
				if (Channel < 0 || Channel > 255) continue;
				if (Logic != '=' && Logic != '&' &&
				    Logic != '|' && Logic != '^') continue;
				if (Value != (Value & 0x7FFF)) continue;
				if (KeyCode < 0 || KeyCode > 31) continue;
				if (NumDebugRules >= MAX_DEBUG_RULES) break;

				/* Set the Debug Rules */
				DebugRules[NumDebugRules].KeyCode = KeyCode;
				DebugRules[NumDebugRules].Channel = Channel;
				DebugRules[NumDebugRules].Logic = Logic;
				DebugRules[NumDebugRules].Value = Value;
				NumDebugRules++;
			}
			else if (!strcmp (s, "LMSIM")) CmOrLm = 0;
			else if (!strcmp (s, "CMSIM")) CmOrLm = 1;
		}
		fclose (fin);
	}
	return (Result);
}

/**
This is a private function of the Cli module. It sets the
internal Options structure members to their default value.
The Option structure handle will be returned through the
commandline parse function. */
static void CliInitializeOptions(void)
{
	  Options.core = (char*)0;
	  Options.resume = (char*)0;
	  Options.cdu_log = (char*)0;
	  Options.symtab = (char*)0;
	  Options.directory = (char*)0;
	  Options.cd = (char*)0;
	  Options.cfg = (char*)0;
	  Options.fromfile = (char*)0;
	  Options.port  = 19697;
	  Options.dump_time = 10;
	  Options.debug_dsky = 0;
	  Options.debug_deda = 0;
	  Options.deda_quiet = 0;
	  Options.quiet = 0;
	  Options.fullname = 0;
	  Options.debug = 1;
	  Options.resumed = 0;
	  Options.interlace = 50;
	  Options.version = 0;
}
/**
This function takes a character string and checks the string for
known command line options. To support both single and double dash
options this function will normalize the double dash to s single dash just
by skipping the first dash. If the token is recognized the function
will return CLI_E_OK else it will return CLI_E_UNKOWNTOKEN.
\param *token The character string
\return The success of failure indication. */
static int CliProcessArgument(char* token)
{
	int result = CLI_E_OK;
	int j;

	/* Transform -- to just - for compatibility */
	if (!strncmp(token,"--",2)) token++;

	/* Parse the token */
	if (!strcmp (token, "-help") || !strcmp (token, "/?"))	result = 1;
	else if (!strncmp (token, "-nx", 3)) /* Ignore for now */;
	else if (!strncmp (token, "-args", 5)) /* Ignore for now */;
	else if (!strncmp (token, "-core=", 6))
	{
		/* If --core is used assume classic behavior is expected */
		Options.core = strdup(&token[6]);

		/* with classi behavior default is nodebug */
		Options.debug = 0;
	}
	else if (!strncmp (token, "-directory=", 11))Options.directory = strdup(&token[11]);
	else if (!strncmp (token, "-cd=", 4))Options.cd = strdup(&token[4]);
	else if (!strncmp (token, "-exec=", 6))Options.core = strdup(&token[6]);
	else if (!strncmp (token, "-resume=", 8))Options.resume = strdup(&token[8]);
	else if (1 == sscanf (token, "-port=%d", &j)) Options.port = j;
	else if (1 == sscanf (token, "-dump-time=%d", &j)) Options.dump_time = j;
	else if (!strcmp (token, "-debug-dsky")) Options.debug_dsky = 1;
	else if (!strcmp (token, "-debug-deda")) Options.debug_deda = 1;
	else if (!strcmp (token, "-deda-quiet")) Options.deda_quiet = 1;
	else if (!strcmp (token, "-cdu-log")) Options.cdu_log = CduLog;
	else if (!strncmp (token, "-cfg=", 5)) Options.cfg = strdup(&token[5]);
	else if (!strcmp (token, "-fullname")) Options.fullname = 1;
	else if (!strcmp (token, "-quiet"))Options.quiet = 1;
	else if (!strcmp (token, "-nodebug")) Options.debug = 0;
	else if (!strcmp (token, "-debug")) Options.debug = 1;
	else if (!strcmp (token, "-version")) Options.version = 6;
	else if (!strncmp (token, "-command=",9)) Options.fromfile = strdup(&token[9]);
	else if (!strncmp (token, "-interpreter=",13)) /* Ignore for now */;
	else if (!strncmp (token, "-symbols=", 9)) Options.symtab = strdup(&token[9]);
	else if (!strncmp (token, "-symtab=", 8)) Options.symtab = strdup(&token[8]);
	else if (1 == sscanf (token,"-interlace=%d", &j)) Options.interlace = j;
	else if (Options.core == (char*)0) Options.core = strdup(token);
	else if (Options.resume == (char*)0) Options.resume = strdup(token);
	else result = CLI_E_UNKOWNTOKEN;

	return (result);
}

/**
This function takes the command line argument count and argument array
as inputs and returns an Option structure if all parses correctly.
When errors are encountered the parser will return a NULL reference
\param argc The argument count
\param *argv The pointer to the argument array.
\return A handle to an Option structure. */
Options_t* CliParseArguments(int argc, char *argv[])
{
	Options_t* result = (Options_t*)0;
	int i;

	/* Set all the defaults in the option structure */
	CliInitializeOptions();

	/* Parse the command-line tokens */
	for (i = 1; i < argc; i++) if (CliProcessArgument(argv[i])) break;

	/* If there is an issue with the provided command line interface
	 * display the usage message. Otherwise proceed with the automatic
	 * values based on the core-ropes image name.
	 */
	if (argc == 1 || i < argc || (!Options.core && !Options.debug_dsky))
	{
		/* Check if only version info is requested */
		if (Options.version) result = &Options;
		else /* Show the usage message */
			CliShowUsage();
	}
	else
	{
		/* If a new working directory is specified then change to it
		 * immediately */
		if (Options.cd > 0) chdir(Options.cd);

		/* Options are properly Parsed */
		result = &Options;

		/* Must have .bin extension to find the symbol table based on the
		 * core basename with the bin extension.
		 */
		if (strstr(Options.core,".bin"))
		{
			int FullPathLength = strlen(Options.core);

			/* If Debugging without symtab set default symtab */
			if (Options.debug && !Options.symtab)
			{
				Options.symtab = (char*)calloc(1,FullPathLength + 4);
				strcpy(Options.symtab,Options.core);
				strcpy(Options.symtab + strlen(Options.core)-3,"symtab");
			}
		}

		/* If a configuration file is specified load its contents */
		if (Options.cfg)
		{
			if (CliParseCfg (Options.cfg))
			{
			  result = (Options_t*)0;
			  printf("\n*** Unknown configurstion file. ***\n\n");
			}
		}
	}

	return (result);
}
