/**************************************************************
 *
 *	CRISP - Custom Reduced Instruction Set Programmers Editor
 *
 *	(C) Paul Fox, 1989
 *
 *    Please See COPYLEFT notice.
 *
 **************************************************************/

# define	MAIN

#include        "list.h"
# include	<signal.h>
# include	"clk.h"

extern	char	*strtok();

# define	MAX_M	12	/* Number of -m switches		*/
# if !defined(ROOT)
# define ROOT "/usr/local/crisp"
# endif
extern	char	*bpath;
extern	char	*bhelp;
extern	int	tab_char;
extern	int	echo_flags;
int	startup_flag = FALSE;	/* TRUE whilst reading BFILE.		*/
int	scode_flag;		/* Keyboard generates scan codes.	*/
int	plus_line_number = 0;

int	display_enabled = FALSE;/* Set to TRUE when we do a (set_term_characters). */
char	*m_strings[MAX_M];	/* Array of pointer to -m strings.	*/
int	m_cnt = 0;
int	compat_flag = FALSE;	/* If TRUE normal stdout I/O otherwise */
				/* optimised.				*/
int	ega43_flag = FALSE;	/* TRUE if in 43 line mode.		*/
int	m_flag = FALSE;		/* Set to TRUE whilst processing -m strings*/
				/* to avoid messages being printed.	*/
int	b_level = 0;		/* Value of BLEVEL.			*/
struct mac_stack mac_stack[MAX_NESTING];/* Macro name stack.		*/
int	ms_cnt;
int	msg_level = 0;
int	current_umask;		/* Current umask() value for creating 	*/
				/* files.				*/
int	use_vmin = FALSE;	/* Set to TRUE if we can use VMIN 	*/
				/* on a system V box. (Only TRUE  	*/
				/* if mouse in use for fast response).	*/
int	wait_flag = TRUE;		/* Set to FALSE if read_char should*/
					/* return -1 on no key pressed.	*/
					/* No macros currently use the  */
					/* fact that read_char can return -1*/
int	rdonly_mode = FALSE;		/* TRUE if -R specified.	*/
BUFFER  *curbp;                         /* Current buffer               */
BUFFER	*scrap_bp;			/* Pointer to scrap buffer.	*/
WINDOW  *curwp;                         /* Current window               */
BUFFER  *bheadp = NULL;                 /* BUFFER listhead              */
WINDOW  *wheadp = (WINDOW *)NULL;       /* WINDOW listhead              */
int	p_level = 0;
char	*prog_name;
extern	long interval;
int	ref_flag = FALSE;		/* If TRUE, dump refs on exit.	*/
int	pflag = FALSE;			/* TRUE if profiling on.	*/
int	sflag = FALSE;			/* TRUE if stats reporting on exit.*/
int	private_flags = 0;
int	dump_core = FALSE;
int	do_backups = TRUE;
int	ctrl_c = FALSE;			/* TRUE when SIGINT occured.	*/
int	scrolling_regions_enabled = TRUE;
					/* TRUE to enable CS termcap entry.	*/
extern int display_ctrl;
/**********************************************************************/
/*   Prototypes.						      */
/**********************************************************************/
void	int_signal();
void	stop_sig();
void	mem_error();
void	chop_argv PROTO((int *, char **));
void	init_path PROTO((char **));
int	main PROTO((int, char **));
void	setup_env PROTO((void));
void	cr_exit();
void	process PROTO((void));
void	edinit PROTO((void));
int	do_exit PROTO((void));
static int	doswitches PROTO((int, char **));
void	usage PROTO((void));
int	bus_error();
void	enable_display();

void
int_signal()
{
	ctrl_c = TRUE;
	signal(SIGINT, int_signal);
}

/**********************************************************************/
/*   Function called to handle JOB control stop signal.		      */
/**********************************************************************/
void
stop_sig()
{
# if defined(SIGTSTP)
# if defined(SIGTTOU)
	/***********************************************/
	/*   Ignore   ISGTTOU   whilst  we  move  the  */
	/*   cursor.				       */
	/***********************************************/
	signal(SIGTTOU, SIG_DFL);
# endif

	get_ready_to_stop(TRUE);

# if defined(SIGTTOU)
	signal(SIGTTOU, SIG_DFL);
# endif
	signal(SIGTSTP, SIG_DFL);
# if defined(HAVE_SIGSETMASK)
	sigsetmask(0);
# endif
	/***********************************************/
	/*   The  following  line puts us to sleep by  */
	/*   sending  a  SIGTSTP  when  the signal is  */
	/*   set to its default.		       */
	/***********************************************/
	kill(getpid(), SIGTSTP);

	/***********************************************/
	/*   Get  here  after  user resumes execution  */
	/*   (e.g. via 'fg').			       */
	/***********************************************/
	signal(SIGTSTP, stop_sig);
	resume_after_stopping(TRUE);
	update();
# endif
}
void
mem_error()
{	static int mem_error_level = 0;

	if (mem_error_level++ == 0) {
		signal(SIGSEGV, SIG_DFL);
# if defined(SIGBUS)
		signal(SIGBUS, SIG_DFL);
# endif
		str_exec("_fatal_error");
		}
	ewprintf("");
	ttmove(nrow-1, 0);
	ttflush();
	printf("\r\nSEGMENTATION VIOLATION DETECTED.\n\r\n");
	dump_core = TRUE;
	cr_exit();
}
void
init_path(name)
char **name;
{	register char *cp = *name;
	char	buf[BUFSIZ];
	register char *dp = buf;

	while (*cp)
		if (*cp == 'R' && strncmp(cp+1, "OOT", 3) == 0) {
			strcpy(dp, ROOT);
			dp += strlen(dp);
			cp += 4;
			}
		else
			*dp++ = *cp++;
	*dp = NULL;
	*name = strdup(buf);
}
/**********************************************************************/
/*   Main entry point into CRISP.				      */
/**********************************************************************/
int
main(_argc, _argv) 
int	_argc;
char 	**_argv; 
{
	int	i;
	int	arg_index;
	void	edinit();
	void	init_register();

# if	defined(HAVE_SIGINTERRUPT)
	siginterrupt(SIGALRM, 1);
# endif

	/***********************************************/
	/*   Get    current   umask   value   without  */
	/*   changing it.			       */
	/***********************************************/
	current_umask = umask(0);
	umask(current_umask);
	/***********************************************/
	/*   The   following   function  is  used  to  */
	/*   check  the  FIRST  argument  only of the  */
	/*   command  line  for  some special strings  */
	/*   to  detect  whether  we are running on a  */
	/*   windowing system.			       */
	/***********************************************/
	chop_argv(&_argc, _argv);

	init_playback();
	init_bookmarks();
	init_cmap();
	init_path(&bpath);
	init_path(&bhelp);
	
# if defined(SIGBUS)
	signal(SIGBUS, bus_error);
# endif
	signal(SIGSEGV, bus_error);
# if defined(SIGIOT)
	signal(SIGIOT, bus_error);
# endif
	signal(SIGINT, int_signal);

# if defined(SIGPIPE)
	/***********************************************/
	/*   If  you  run  crisp  and  pipe  it  into  */
	/*   'sleep'  (a  silly  thing  to  do)  make  */
	/*   sure  that  when  the pipe is broken, we  */
	/*   restore    the   tty   mode,   otherwise  */
	/*   xterm's on the sun disappear !	       */
	/***********************************************/
	signal(SIGPIPE, cr_exit);
# endif

	prog_name = _argv[0];

	setup_env();


	/***********************************************/
	/*   Initialise  display  first -- the window  */
	/*   system  may  want to grab arguments from  */
	/*   the command line before we parse it.      */
	/***********************************************/
	vtinit(&_argc, _argv);

	arg_index = doswitches(_argc, _argv);

	/***********************************************/
	/*   Finish  off  by  realising  our X-window  */
	/*   if necessary.			       */
	/***********************************************/
	if (scrfn.scr_init)
		(*scrfn.scr_init)();

	ttopen(TRUE);
	vtinit1();

	init_builtin();
	init_macros();
	key_init();
	u_init();				/* Undo buffer.		*/
	edinit();                               /* Buffers, windows.    */
	k_init();				/* Kill buffer.		*/
	init_register();			/* Initialise register  */
						/* macro array.		*/

# if defined(SIGTSTP)
	signal(SIGTSTP, stop_sig);
# endif
	sym_init();
	
	/***********************************************/
	/*   Call  startupfile  to  get  the  crisp()  */
	/*   macro  loaded  and  execute  it. If this  */
	/*   fails,  then  lets  abort  crisp so that  */
	/*   user  can  try and figure out whats gone  */
	/*   wrong.				       */
	/***********************************************/
	if (startupfile() < 0) {
		u_close();
		vttidy(TRUE);
		printf("\n*** CRISP has failed to locate the 'crisp.cm' startup macro.\n");
		printf("*** This is due to either your environment variable BPATH\n");
		printf("*** not being setup properly or you have not compiled the macros.\n");
		_exit(1);
		}
	p_level = 0;
	for (i = 0; i < m_cnt; i++) {
		m_flag = TRUE;
		ld_macro(m_strings[i]);
		}
# if defined(SIGBUS)
	signal(SIGBUS, mem_error);
# endif
	signal(SIGSEGV, mem_error);
	signal(SIGTERM, cr_exit);
# if defined(SIGHUP)
	signal(SIGHUP, cr_exit);
# endif
# if defined(SIGIOT)
	signal(SIGIOT, SIG_DFL);
# endif

	/***********************************************/
	/*   Execute  startup  macro and command line  */
	/*   macros before reading in files.	       */
	/***********************************************/
	p_level = 0;
	str_exec("startup");
	for (i = 0; i < m_cnt; i++) {
		m_flag = TRUE;
		str_exec(m_strings[i]);
		}
	m_flag = FALSE;

	if (arg_index < _argc) {
		BUFFER	*firstbp = NULL;

		while (arg_index < _argc) {
			atomic_fileedit(_argv[arg_index++], EDIT_NORMAL);
			if (!firstbp)
				firstbp = curbp;
			}
		showbuffer(curbp = firstbp, curwp);
		/***********************************************/
		/*   Hook  to  allow  restore  state macro to  */
		/*   get called.			       */
		/***********************************************/
		str_exec("_startup_complete 1");
		}
	else {
		/***********************************************/
		/*   Make  sure  we  don't print the New file  */
		/*   message if file doesn't exist.	       */
		/***********************************************/
		startup_flag = TRUE;
		atomic_fileedit(ggetenv("BFILE"), EDIT_NORMAL);
		startup_flag = FALSE;
		/***********************************************/
		/*   Hook  to  allow  restore  state macro to  */
		/*   get called.			       */
		/***********************************************/
		str_exec("_startup_complete 0");
		}
	if (plus_line_number) {
		set_hooked();
		gotoline(plus_line_number);
		}

	while (p_level >= 0) {
		p_level = 0;
		process();
		check_exit();
		}
	cr_exit();
	return 0;
}
/**********************************************************************/
/*   Function  called  to  check  the  first argument of the command  */
/*   line,  or  the  actual  name of the binary to determine whether  */
/*   we  are  running  natively under a window system. We remove the  */
/*   argument from the command line if we can match it.		      */
/**********************************************************************/
void
chop_argv(argcp, argv)
int	*argcp;
char	**argv;
{
	char	*cp = strrchr(argv[0], '/');
	register int i;
	
	/***********************************************/
	/*   Go for the filename first.		       */
	/***********************************************/
	if (cp == NULL)
		cp = argv[0];
	else
		cp++;
	/***********************************************/
	/*   We'll   use  'xcr'  for  X-windows,  and  */
	/*   reserve   'wcr'   for   other  windowing  */
	/*   environments.			       */
	/***********************************************/
	if (strcmp(cp, "xcr") == 0 || strcmp(cp, "vcr") == 0 
	    || strcmp(cp, "wcr") == 0)
		display_ctrl |= DC_WINDOW;
		
	/***********************************************/
	/*   Now check first argument.		       */
	/***********************************************/
	if (*argcp > 1 && strcmp(argv[1], "-x11") == 0) {
		display_ctrl |= DC_WINDOW;
		for (i = 1; i < *argcp - 1; i++)
			argv[i] = argv[i+1];
		(*argcp)--;
		}
}
void
setup_env()
{	char	*cp;
	char buf[128];

	if (ggetenv("BPATH") == NULL) {
		sprintf(buf, "BPATH=%s", bpath);
		if (cp = ggetenv("HOME")) {
			strcat(buf, ";");
			strcat(buf, cp);
			}
		gputenv(buf);
		}
	else
		bpath = ggetenv("BPATH");

	if (ggetenv("TERM") == NULL) {
		extern char *default_term;
		gputenv(default_term);
		}
	if (ggetenv("BHELP") == NULL) {
		sprintf(buf, "BHELP=%s", bhelp);
		gputenv(buf);
		}
	if (ggetenv("BPACKAGES") == NULL) {
		extern char *bpackages;
		gputenv(bpackages);
		}
	if (ggetenv("BFLAGS") == NULL) {
		extern char *bflags;
		gputenv(bflags);
		}
	if (ggetenv("BFILE") == NULL) {
		extern char *bfile;
		gputenv(bfile);
		}
	if (cp = ggetenv("BFLAGS")) {
		char	**cpp = &cp;
		doswitches(-1, cpp);
		}
	if (cp = ggetenv("BLEVEL"))
		b_level = atoi(cp) + 1;
	else
		b_level = 1;
	sprintf(buf, "BLEVEL=%d", b_level);
	gputenv(buf);
}
void
check_exit()
{
	if (anycb() == FALSE)
		cr_exit();
}
void
cr_exit()
{	extern int	imode;
	void	_cleanup();

	if (!dump_core)
		trigger(REG_EXIT);
	if (ref_flag) {
		extern int dflag;
		dflag = 1;
		trace_refs();
		}
	if (!imode)
		ins_mode();
	u_close();
	vttidy(TRUE);
	
	if (sflag) {
		extern long st_charout;
		printf("[Chars out: %ld]\n", st_charout);
# if 0
		printf("Symbol Table:\n");
		printf("Globals:  %s\n", spstats(gsym_tbl));
		printf("Locals:\n");
		for (i = j = 0; i < MAX_NESTING && j < 3; i++) {
			if (lsym_tbl[i]->lookups + lsym_tbl[i]->enqs + 
				lsym_tbl[i]->splays == 0)
				j++;
			printf("  level %02d: %s\n", i, spstats(lsym_tbl[i]));
			}
# endif
		}

	if (dump_core)
		abort();
	if (pflag)
		exit(EXIT_SUCCESS);

	_cleanup();
	_exit(EXIT_SUCCESS);
}
void
process()
{	KEY	c;
	int	last_p_level;

	p_level++;
	while (1) {
		update();
		last_p_level = p_level;
		c = getkey((long) 0);
		(void) exec_key(c);
		if (p_level < last_p_level)
			return;
		}
}

/**********************************************************************/
/*   Function  called  on  startup  to  initialise  the  buffer  and  */
/*   windowing data structures.					      */
/**********************************************************************/
void
edinit()
{
	register BUFFER *bp;
	register WINDOW *wp;
	extern	u_int16	win_num;
	extern WINDOW *new_window();

	bheadp = NULL;
	scrap_bp = bp = bfind("/SCRAP/anon", TRUE);
	scrap_bp->b_flag |= BFREAD | BF_SYSBUF;

	wp = new_window();

	if (bp==NULL || wp==NULL) panic("edinit");
	curbp  = bp;                            /* Current ones.        */
	bp->b_nwnd  = 1;                        /* Displayed.           */
	bp->b_keyboard = NULL;
	wheadp = wp;
	curwp  = wp;
	wp->w_bufp  = bp;
	wp->w_top_line = wp->w_line = wp->w_col = 1;
	wp->w_popup = FALSE;
	wp->w_corner_hints[TL_CORNER] = CORNER_3 | CORNER_6;
	wp->w_corner_hints[TR_CORNER] = CORNER_9 | CORNER_6;
	wp->w_corner_hints[BL_CORNER] = CORNER_12 | CORNER_3;
	wp->w_corner_hints[BR_CORNER] = CORNER_12 | CORNER_9;
	wp->w_h = nrow-2;
	wp->w_tiled = W_ROOT;
	cur_line = &bp->b_line;
	cur_col = &bp->b_col;

	if (wp->w_tiled) {
		wp->w_x = 1;
		wp->w_w = ncol - 2;
		wp->w_h--;
		}
	else {
		wp->w_w = ncol;
		}
	wp->w_num = win_num++;
	wp->w_flag  = WFHARD;            /* Full.                */
	w_title(wp, "*scratch*", "");
}
int
do_exit()
{
	p_level--;
	return 0;
}
static void
do_plus_switch(str)
char	*str;
{
	int	not = FALSE;
	
	if (str[0] == 'n' && str[1] == 'o') {
		not = TRUE;
		str += 2;
		}
	if (strcmp(str, "scroll") == 0) {
		scrolling_regions_enabled = not;
		return;
		}
	usage();
}
static int
doswitches(_argc, _argv)
int	_argc;
char	**_argv;
{	int	c;
	char *optarg = "";
	extern char *strchr();
	extern int dflag;
	int	errflag = 0;
	int optind;
	char	*av[16];
	char	*cp;
	static char	buf[128];

	if (_argc < 0) {
		strncpy(buf, *_argv, sizeof buf - 1);
		_argc = 1;
		for (cp = strtok(buf, " "); cp; cp = strtok((char *) NULL, " ")) {
			/***********************************************/
			/*   Ignore    -Dxxx    switch    for   BRIEF  */
			/*   compatability.			       */
			/***********************************************/
			if (strncmp(cp, "-D", 2) != 0)
				av[_argc++] = cp;
			}
		_argv = av;
		}
	for (optind = 1; optind < _argc; optind++) {
		cp = _argv[optind];
		if (*cp == '+') {
			cp++;
			if (*cp >= '0' && *cp <= '9')
				plus_line_number = atoi(cp);
			else
				do_plus_switch(cp);
			continue;
			}
		if (*cp++ != '-')
			break;
		if (strcmp(cp, "mouse") == 0) {
			mouse_init("");
			use_vmin = TRUE;
			continue;
			}
		while (c = *cp++) {
			if (strchr("eiMmP", c)) {
				if (*cp == NULL && (optind+1) >= _argc)
					usage();
				if (*cp) {
					optarg = cp;
					cp = "";
					}
				else
					optarg = _argv[++optind];
				}
			switch (c) {
				case 'b': do_backups = FALSE; break;
				case 'C': scode_flag = TRUE; break;
				case 'c': compat_flag = TRUE; break;
				case 'd': dflag = TRUE; break;
				case 'E': display_enabled = TRUE; break;
				case 'P': 
					private_flags = atoi(optarg);
					break;
				case '4': ega43_flag = TRUE; break;
				case 'e':
					echo_flags = atoi(optarg);
					break;
				case 'f': 
					dflag |= DB_FLUSH;
					break;
				case 'i':
					interval = atoi(optarg);
					break;
				case 'M': {
/*					extern long vm_maxlines;
					vm_maxlines = atoi(optarg);*/
					break;
					}
					
				case 'm':
					if (m_cnt >= MAX_M-1)
						break;
					m_strings[m_cnt++] = optarg;
					break;
		
				case 'p':	pflag = TRUE; break;
				case 'r':	ref_flag = TRUE; break;
				case 'R':	rdonly_mode = TRUE; break;
				case 's':	sflag = TRUE; break;
				case 't':	tab_char = FALSE; break;	
				case 'w':	wait_flag = FALSE; break;
				default:
					errflag++;
				}
			}
		}
	if (errflag)
		usage();
	return optind;
}
void
usage()
{
	fprintf(stderr, "Usage: cr [+nn] [-4bcdifpstwCEPR] [-mouse] [-e n]\n");
	fprintf(stderr, "\t[-m macro] [-M n] [+[no]scroll] file ..\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "	+nn	Goto line nn.\n");
	fprintf(stderr, "	-4	43-line mode EGA (Xenix only)\n");
	fprintf(stderr, "	-C	Keyboard generates scan codes.\n");
	fprintf(stderr, "	-c	Compatability mode\n");
	fprintf(stderr, "	-b	Don't create backups.\n");
	fprintf(stderr, "	-E	Enable display on startup.\n");
	fprintf(stderr, "	-d	Create crisp.log error log.\n");
	fprintf(stderr, "	-e nn	Set echo line state flag.\n");
	fprintf(stderr, "	-f	Flush log file as created.\n");
	fprintf(stderr, "	-iN	Set interval timer to N seconds\n");
	fprintf(stderr, "	-M n	Set virtual memory size to n lines\n");
	fprintf(stderr, "	-P nn	Set private mode flag for debugging.\n");
	fprintf(stderr, "	-mouse	Enable mouse support.\n");
	fprintf(stderr, "	-m str	Execute macro 'str'.\n");
	fprintf(stderr, "	-p	Turn profiling on.\n");
	fprintf(stderr, "	-r	Dump macro ref statistics.\n");
	fprintf(stderr, "	-R	Read-only mode -- files have readonly attribute set.\n");
	fprintf(stderr, "	-s	Report statistics on exit.\n");
	fprintf(stderr, "	-t	Use spaces to fill out tabs\n");
	fprintf(stderr, "	-w	Allow read_char to return -1.\n");
	fflush(stderr);
	_exit(EXIT_FAILURE);
}
int
bus_error()
{
	vttidy(FALSE);
	fprintf(stderr, 
"\n\nA program error (segmentation error) occured during startup.\n");
	cr_exit();
	return 0;
}
void
display_windows()
{
	acc_assign_int((long) display_enabled);
	display_enabled = argv[1].l_flags == F_INT ?
		argv[1].l_int : !display_enabled;
}
/*******************************************************************/
/*   Macro to pretend user typed Ctrl-Z.			   */
/*******************************************************************/
void
suspend()
{
# if defined(SIGTSTP)
	kill(getpid(), SIGTSTP);
# endif
}


