/* main.c */
/* Copyright 1995 by Steve Kirkendall */

char id_main[] = "$Id: main.c,v 2.42 1998/10/12 19:02:47 steve Exp $";

#include "elvis.h"

#if USE_PROTOTYPES
static void usage(char *hint);
static void guiusage(void);
static int parseflags(int argc, char **argv);
static int choosegui(int argc, char **argv);
static void doexrc(void);
static void buildargs(int argc, char **argv);
static void startfirst(void);
static void init(int argc, char **argv);
void mainfirstcmd(WINDOW win);
void term(void);
int main(int argc, char **argv);
#endif


/* This array contains pointers to all known GUIs.  Ideally, they should be
 * sorted so that fancy/rarely-available GUIs appear near the front of the
 * list, and basic/universal GUIs appear near the end.
 */
static GUI *allguis[] =
{
#ifdef GUI_WIN32
	&guiwin32
#endif

#ifdef GUI_X11
	&guix11,
#endif

#ifdef GUI_PM
	&guipm,
#endif

#ifdef GUI_TERMCAP
	&guitermcap,
#endif

#ifdef GUI_VIO
	&guivio,
#endif

#ifdef GUI_CURSES
	&guicurses,
#endif

#ifdef GUI_BIOS
	&guibios,
#endif

#ifdef GUI_OPEN
	&guiopen,
	&guiscript,
	&guiquit
#endif
};

/* These flags are set according to command-line flags */
static char	*initialcommand;
static char	*initialtag;
static BOOLEAN	initialall;
GUI	*chosengui;

/* Give a usage message, and then exit */
static void usage(hint)
	char	*hint;	/* an error message */
{
	int	i;
	char	guinames[80];

	msg(MSG_INFO, "Usage: elvis [flags] [files]...");
	msg(MSG_INFO, "Flags: -V          Verbose -- give more status information");
	msg(MSG_INFO, "       -a          Create a separate window for each file");
	msg(MSG_INFO, "       -r          Restart a session after a crash");
	msg(MSG_INFO, "       -R          Mark new buffers as \"readonly\"");
	msg(MSG_INFO, "       -e          Start in ex mode instead of vi mode");
	msg(MSG_INFO, "       -i          Start in input mode instead of vi mode");
	msg(MSG_INFO, "       -S          Set the \"safer\" option, for security");
	msg(MSG_INFO, "       -w lines    Set scroll amount to \"lines\"");
	msg(MSG_INFO, "       -f session  Use \"session\" as the session file");
	msg(MSG_INFO, "       -G gui      Use the \"gui\" user interface \\(see below\\)");
	msg(MSG_INFO, "       -c command  Execute \"command\" after loading first file");
	msg(MSG_INFO, "       -s          Read a script from stdin and execute it");
	msg(MSG_INFO, "       -t tag      Perform a tag search");
	msg(MSG_INFO, "       -o logfile  Send messages to logfile instead of stderr");
	msg(MSG_INFO, "       -b blksize  Use blocks of size \"blksize\"");
	msg(MSG_INFO, "       +command    Archaic form of \"-c command\" flag");
	msg(MSG_INFO, "       -           Archaic form of \"-s\" flag");
	guinames[0] = '\0';
	for (i = 0; i < QTY(allguis); i++)
	{
		/* space between names */
		if (i > 0)
			strcat(guinames, " ");

		/* concatenate the gui names into a list */
		switch ((*allguis[i]->test)())
		{
		  case 0:
			sprintf(guinames + strlen(guinames), "(%s)", allguis[i]->name);
			break;

		  case 2:
			sprintf(guinames + strlen(guinames), "%s?", allguis[i]->name);
			break;

		  default:
			strcat(guinames, allguis[i]->name);
		}
	}
	msg(MSG_INFO, "[s]User interfaces: $1", guinames);
	msg(MSG_INFO, "For more information about user interfaces, give the command 'elvis -G?'");
	msg(MSG_INFO, "[s]Report bugs to $1", "kirkenda@cs.pdx.edu");
	if (hint)
	{
		msg(MSG_INFO, hint);
	}
	if (chosengui)
		(*chosengui->term)();
	exit(0);
}

static void guiusage P_((void))
{
	int	i;
	BOOLEAN	found;
	char	*msgfmt;

	msg(MSG_INFO, "user interfaces:");
	for (i = 0, found = False; i < QTY(allguis); i++)
	{
		switch ((*allguis[i]->test)())
		{
		  case 0:
			msgfmt = "[ss]   -G ($1<<12) $2 \\(UNAVAILABLE\\)";
			break;

		  case 2:
			msgfmt = "[ss]   -G ($1<<12) $2 \\(MAYBE\\)";
			break;

		  default:
			if (found)
				msgfmt = "[ss]   -G ($1<<12) $2";
			else
				msgfmt = "[ss]   -G ($1<<12) $2 \\(DEFAULT\\)";
			found = True;
		}
		msg(MSG_INFO, msgfmt, allguis[i]->name, allguis[i]->desc);
		if (allguis[i]->usage)
			(*allguis[i]->usage)();
	}
	if (chosengui)
		(*chosengui->term)();
	exit(0);
}

/* parse command-line flags.  Leave global variables set to reflect the
 * flags discovered.  Upon return, any arguments handled here should be
 * deleted from argv[]; the return value is the new argc value.
 */
static int parseflags(argc, argv)
	int	argc;	/* number of command-line arguments */
	char	**argv;	/* values of command-line arguments */
{
	int	i, j, del;
	long	size = 0;

	/* copy argv[0] into an option so we can access it in "elvis.ini" */
	o_program = toCHAR(argv[0]);

	/* for each argument... */
	for (i = 1; i < argc; i++)
	{
		/* for now, assume we'll be deleting 0 arguements */
		del = 0;

		/* recognize any normal flags */
		if (argv[i][0] == '-')
		{
			/* the "-" flag has already been processed */
			if (!argv[i][1])
				del = 1;

			for (j = 1; j != 0 && argv[i][j]; j++)
			{
				switch (argv[i][j])
				{
				  case 'a':
					initialall = True;
					del = 1;
					break;

				  case 'r':
					o_recovering = True;
					del = 1;
					break;

				  case 'R':
					o_defaultreadonly = True;
					del = 1;
					break;

				  case 'V':
				  case 's':
				  	/* already handled in chosengui() */
				  	del = 1;
				  	break;

				  case 'f':
					if (argv[i][j + 1])
					{
						o_session = toCHAR(&argv[i][j + 1]);
						del = 1;
					}
					else if (i + 1 < argc)
					{
						o_session = toCHAR(argv[i + 1]);
						del = 2;
						i++;
					}
					else
					{
						usage("-s requires the name of a session file");
					}
					j = -1; /* so we stop processing this arg */
					break;

				  case 'o':
					if (argv[i][j + 1])
					{
						msglog(&argv[i][j + 1]);
						del = 1;
					}
					else if (i + 1 < argc)
					{
						msglog(argv[i + 1]);
						del = 2;
						i++;
					}
					else
					{
						usage("-o requires the name of a log file");
					}
					j = -1; /* so we stop processing this arg */
					break;

				  case 'G':
					usage("only one \"-G gui\" is allowed");
					break;

				  case 'c':
					if (argv[i][j + 1])
					{
						initialcommand = &argv[i][j + 1];
						del = 1;
					}
					else if (i + 1 < argc)
					{
						initialcommand = argv[i + 1];
						del = 2;
						i++;
					}
					else
					{
						usage("-c requires an initial command");
					}
					j = -1; /* so we stop processing this arg */
					break;

				  case 't':
					if (argv[i][j + 1])
					{
						initialtag = &argv[i][j + 1];
						del = 1;
					}
					else if (i + 1 < argc)
					{
						initialtag = argv[i + 1];
						del = 2;
						i++;
					}
					else
					{
						usage("-t requires a tag name");
					}
					j = -1; /* so we stop processing this arg */
					break;

				  case 'b':
					if (argv[i][j + 1])
					{
						size = atol(&argv[i][j + 1]);
						del = 1;
					}
					else if (i + 1 < argc)
					{
						size = atol(argv[i + 1]);
						del = 2;
						i++;
					}
					else
					{
						usage("-b requires a block size for the session file");
					}
					if (size != 512 && size != 1024
						&& size != 2048 && size != 4096
						&& size != 8192)
					{
						usage("bad -b blksize: should be 512, 1024, 2048, 4096, or 8192");
					}
					j = -1; /* so we stop processing this arg */
					o_blksize = size;
					break;

				  case 'e':
				  case 'i':
					o_initialstate = argv[i][j];
					del = 1;
					break;

				  case 'S':
					o_safer = True;
					del = 1;
					break;

				  case 'w':
					if (argv[i][j + 1])
					{
						size = atol(&argv[i][j + 1]);
						del = 1;
					}
					else if (i + 1 < argc)
					{
						size = atol(argv[i + 1]);
						del = 2;
						i++;
					}
					else
					{
						usage("-w requires a window size");
					}
					j = -1; /* so we stop processing this arg */
					break;

				  default:
					if (del)
					{
						usage(NULL);
					}
					j = -1;
				}
			}
		}
		else if (argv[i][0] == '+')
		{
			if (argv[i][1])
			{
				initialcommand = &argv[i][1];
			}
			else
			{
				initialcommand = "$";
			}
			del = 1;
		}

		/* delete arguments, if we're supposed to */
		if (del > 0)
		{
			for (j = i + 1 - del; j < argc - del; j++)
			{
				argv[j] = argv[j + del];
			}
			i -= del;
			argc -= del;
		}
	}

	return argc;
}

/* Choose a GUI.  If one was specified via "-G gui", then the o_gui option
 * will be set already, and we just need to verify it.  Otherwise, we need
 * to test each GUI until we find one which tests as being available or
 * maybe available.  Call the GUI's init() function, and leave the global
 * "gui" pointer pointing to the chosen GUI.
 *
 * Like parseflags(), this function should delete any arguments from argv that
 * it uses, and return the new argc value.
 */
static int choosegui(argc, argv)
	int	argc;	/* number of command-line arguments */
	char	**argv;	/* values of command-line arguments */
{
	int	i, j, val;

	/* Initialize "j" just to silence a compiler warning */
	j = 0;

	/* search for "-G gui" in the command line.  Also watch for "-V" */
	for (i = 1; i < argc; i++)
	{
		/* skip non-flag arguments */
		if (argv[i][0] != '-')
			continue;

		/* is it -G?  -V?  - or -s? */
		if (argv[i][1] == 'G')
		{
			/* remember the gui name */
			if (argv[i][2])
				o_gui = toCHAR(&argv[i][2]), j = i + 1;
			else if (i + 1 < argc)
				o_gui = toCHAR(argv[i + 1]), j = i + 2;
			else
				usage("-G requires a user interface name");

			/* delete the "-G" and its argument */
			while (j < argc)
			{
				argv[i++] = argv[j++];
			}
			argv[i] = NULL;
			argc = i;
		}
		else if (argv[i][1] == 'V')
		{
			for (j = 1; argv[i][j] == 'V'; j++)
				o_verbose++;
		}
		else if (argv[i][1] == '\0' || argv[i][1] == 's')
		{
			/* use the "script" interface */
			o_gui = toCHAR("script");

			/* don't read any configuration files */
			optpreset(o_elvispath, NULL, OPT_HIDE);
		}
		else if (!strcmp(argv[i], "-version")
		 || !strcmp(argv[i], "--version")
		 || !strcmp(argv[i], "-v"))
		{
			msg(MSG_INFO, "[s]elvis $1", VERSION);
#ifdef COPY1
			msg(MSG_INFO, "[s]$1", COPY1);
#endif
#ifdef COPY2
			msg(MSG_INFO, "[s]$1", COPY2);
#endif
#ifdef COPY3
			msg(MSG_INFO, "[s]$1", COPY3);
#endif
#ifdef COPY4
			msg(MSG_INFO, "[s]$1", COPY4);
#endif
#ifdef PORTEDBY
			msg(MSG_INFO, "[s]$1", PORTEDBY);
#endif
			exit(0);
		}
		else if (!strcmp(argv[i], "-help")	/* old GNU */
		      || !strcmp(argv[i], "--help")	/* new GNU */
		      || !strcmp(argv[i], "/?")		/* DOS */
		      || !strcmp(argv[i], "-?"))	/* common */
		{
			usage(NULL);
		}

	}

	/* find the specified GUI, or the first available if none specified */
	if (o_gui)
	{
		/* find the named GUI */
		for (i = 0; i < QTY(allguis) && strcmp(allguis[i]->name, tochar8(o_gui)); i++)
		{
		}
		if (i >= QTY(allguis))
		{
			if (CHARcmp(o_gui, toCHAR("?")))
			{
				msg(MSG_ERROR, "[s]invalid gui $1", o_gui);
			}
			guiusage();
		}
	}
	else
	{
		/* Find the first GUI that is definitely available.  While
		 * searching, also remember the first one that *MIGHT* be
		 * available, just in case we don't find any that are definitely
		 * available.
		 */
		for (i = 0, j = -1; i < QTY(allguis) && (val = (*allguis[i]->test)()) != 1; i++)
		{
			if (val == 2 && j == -1)
			{
				j = i;
			}
		}

		/* If we didn't find a definite one, use the maybe one */
		if (i >= QTY(allguis))
		{
			if (j >= 0)
			{
				i = j;
			}
			else
			{
				msg(MSG_ERROR, "no gui available");
				exit(0);
			}
		}
	}

	/* Call the GUI's init() function */
	argc = (*allguis[i]->init)(argc, argv);
	if (argc <= 0)
	{
		exit(0);
	}

	/* Remember the chosen GUI, but don't set the official "gui" pointer
	 * yet because we want any initialization error message to go to
	 * stderr instead of a window.  EXCEPTION: If the gui doesn't support
	 * full screen mode, then we can set "gui" now.
	 */
	chosengui = allguis[i];
	return argc;
}

/* execute the initialization buffer (which may create some windows) */
static void doexrc()
{
	BUFFER	buf;	/* the buffer itself */
	MARKBUF	top;	/* top of the buffer */
	MARKBUF	bottom;	/* bottom of the buffer */

	/* If a buffer named "Elvis initialization" exists */
	buf = bufpath(o_elvispath, INIT_FILE, toCHAR(INIT_BUF));
	if (buf)
	{
		/* Temporarily make all display-mode options available */
		dispinit(True);

		/* Execute its contents. */
		(void)experform((WINDOW)0, marktmp(top, buf, 0),
			marktmp(bottom, buf, o_bufchars(buf)));

		/* save the current values as the default values */
		optsetdflt();

		/* After this, only the current display-mode's options should
		 * be available.
		 */
		dispinit(False);
	}
}

/* make the "elvis args" buffer contain file names from command line */
static void buildargs(argc, argv)
	int	argc;	/* number of command-line arguments */
	char	**argv;	/* values of command-line arguments */
{
	int	i;

	/* skip "--", if given */
	argc--;
	argv++;
	if (argc > 0 && !strcmp(argv[0], "--"))
	{
		argc--;
		argv++;
	}

	/* Copy each argument to the args list.  For some operating systems,
	 * we'll be expanding wildcards in each argument as we do this.
	 */
	for (argnext = i = 0; i < argc; i++)
	{
		(void)exaddfilearg(&arglist, &argnext, argv[i], OSEXPANDARGS);
	}

	/* if nothing read was added, then still allocate something */
	if (!arglist)
		arglist = (char **)safealloc(1, sizeof(char *));

	/* reset the "next" pointer */
	argnext = 0;
}

/* start first file */
static void startfirst()
{
	BUFFER	buf;

	/* If "Elvis args" is empty */
	if (!arglist[0])
	{
		/* Create an anonymous buffer and a window for it */
		buf = bufalloc(NULL, 0, False);
		assert(buf);
		(void)(*gui->creategw)(tochar8(o_bufname(buf)), "");
	}
	else
	{
		/* Load buffers and make windows for either just the first
		 * argument, or as many of the arguments as possible.
		 */
		assert(argnext == 0);
		do
		{
			/* Create a buffer & window the first file */
			buf = bufload((CHAR *)0, arglist[argnext++], True);
			assert(buf);
		} while ((*gui->creategw)(tochar8(o_bufname(buf)), "")
					&& initialall && arglist[argnext]);
	}
}

/* perform "-c cmd" or "-t tag" */
void mainfirstcmd(win)
	WINDOW	win;	/* the first window */
{
	CHAR	tagcmd[100];

	/* If "-c cmd" was given */
	if (initialcommand)
	{
		/* Execute the command */
		exstring(win, toCHAR(initialcommand));
	}

	/* If "-t tag" was given */
	if (initialtag)
	{
		/* Compose a ":tag" command */
		CHARcpy(tagcmd, toCHAR("tag "));
		CHARcat(tagcmd, toCHAR(initialtag));

		/* Execute the command */
		exstring(win, tagcmd);
	}
}

static void init(argc, argv)
	int	argc;	/* number of command-line arguments */
	char	**argv;	/* values of command-line arguments */
{
	BUFFER	buf;
	char	*lc;		/* locale name */
	char	lcfile[100];	/* combination of locale name and file name */

#ifdef OSINIT
	/* initialize the OS */
	osinit(argv[0]);
#endif

	/* initialize options */
	optglobinit();

	/* Choose a GUI, and call its init() function */
	argc = choosegui(argc, argv);

	/* Parse command-line flags */
	argc = parseflags(argc, argv);

	/* set up the default options for windows */
	wininit();

	/* Create or restart the session file */
	bufinit();

	/* Set the "gui" pointer to the chosen gui.  After this point, all
	 * error/warning/info messages will written to the window instead
	 * of stderr.
	 */
	gui = chosengui;
	o_gui = toCHAR(gui->name);

	/* Create the "Elvis ex history" buffer, and tweak its options */
	buf = bufalloc(toCHAR(EX_BUF), (BLKNO)0, True);
	o_inputtab(buf) = 'e';

	/* Create the "Elvis error list" buffer, and tweak its options */
	buf = bufalloc(toCHAR(ERRLIST_BUF), (BLKNO)0, True);
	o_undolevels(buf) = 0;

	/* Execute the initialization buffer (which may create some windows) */
	doexrc();

	/* Load the verbose messages, plus a few others */
	if (((lc = getenv("LC_ALL")) != NULL && *lc)
	 || ((lc = getenv("LC_MESSAGES")) != NULL && *lc)
	 || ((lc = getenv("LANG")) != NULL && *lc))
	{
		/* Try to find "elvis.msg" in a locale-dependent subdirectory.
		 * If you can't find it there, then look for the standard one.
		 */
		strcpy(lcfile, dirpath(lc, MSG_FILE));
		buf = bufpath(o_elvispath, lcfile, toCHAR(MSG_BUF));
		if (!buf || o_bufchars(buf) == 0)
		{
			(void)bufpath(o_elvispath, MSG_FILE, toCHAR(MSG_BUF));
		}
	}
	else
	{
		/* just use the standard versbose messages */
		(void)bufpath(o_elvispath, MSG_FILE, toCHAR(MSG_BUF));
	}
	(void)bufpath(o_elvispath, BEFOREREAD_FILE, toCHAR(BEFOREREAD_BUF));
	(void)bufpath(o_elvispath, AFTERREAD_FILE, toCHAR(AFTERREAD_BUF));
	(void)bufpath(o_elvispath, BEFOREWRITE_FILE, toCHAR(BEFOREWRITE_BUF));
	(void)bufpath(o_elvispath, AFTERWRITE_FILE, toCHAR(AFTERWRITE_BUF));

	/* Store the filename arguments in a list */
	buildargs(argc, argv);

	/* start the first file (i.e., make sure we have at least 1 window) */
	startfirst();
}

/* Terminate elvis.  By the time this function is called, all windows have
 * been closed, and hence any buffers that can be discarded have been.
 */
void term()
{
#ifdef DEBUG_ALLOC
	int	i;
	BUFFER	buf, next;
#endif

	/* Call the GUI's term() function */
	gui->term();
	gui = NULL;

	/* Flush any final messages */
	msgflush();

#ifdef DEBUG_ALLOC
	/* Free the args list so it isn't reported as a memory leak */
	for (i = 0; arglist[i]; i++)
		safefree(arglist[i]);
	safefree(arglist);

	/* Free all the buffers so they aren't reported as memory leaks */
	for (buf = ((BUFFER)0); buf; buf = next)
	{
		next = buflist(buf);
		if (!o_internal(buf))
			buffree(buf);
	}
#endif

	/* Close the session */
	sesclose();

	/* Check for any memory leaks */
	safeterm();
}

int main(argc, argv)
	int	argc;	/* number of command-line arguments */
	char	**argv;	/* values of the command-line arguments */
{
	init(argc, argv);
	(*gui->loop)();
	term();
#if !defined (GUI_WIN32)
	exit((int)o_exitcode);
#endif
	return (int)o_exitcode;
}
