/*
 *   Copyright (C) 1988-2006 Lucent Technologies Inc. and others.
 *   All Rights Reserved.
 */

/*	cscope - interactive C symbol or text cross-reference
 *
 *	command functions
 */

#include "global.h"
#include <curses.h>	/* KEY_.* */
#if defined(__BORLANDC__) || defined(USING_PD_CURSES)
/* fixes clash in PD CURSES */
#undef ungetch
#include <io.h>
#endif
#include <fcntl.h>	/* O_RDONLY */

BOOL	caseless;		/* ignore letter case when searching */
BOOL	*change;		/* change this line */
BOOL	changing;		/* changing text */
char	newpat[PATLEN + 1];	/* new pattern */
char	pattern[PATLEN + 1];	/* symbol or text pattern */

BOOL	askegrep;		/* ask for subdirectories for "Find this egrep pattern" */
char	subdirpat[PATLEN + 1];	/* pattern for list of subdirectories or file */
BOOL	filterfiles;		/* Filter filenames */

static	char	appendprompt[] = "Append to file: ";
static	char	filterprompt[] = "Filter results through command: ";
static	char	pipeprompt[] = "Pipe to shell command: ";
static	char	readprompt[] = "Read from file: ";
static	char	selectionprompt[] = "Selection: ";
static	char	toprompt[] = "To: ";

static	char	dirprompt[] = "File pattern (return for all): ";

#if defined( __STDC__ ) || defined( __BORLANDC__ )
void addcmd(int f, char *s);
HISTORY *prevcmd(void);
HISTORY *nextcmd(void);
void resetcmd(void);
HISTORY *currentcmd(void);
BOOL	command(int commandc);
void	clearprompt(void);
BOOL	readrefs(char *filename);
BOOL	changestring(void);
void	mark(int i);
void	scrollbar(MOUSEEVENT *p);
int	execute(char *, ...);
#else
BOOL	changestring();
void	clearprompt();
void	editall();
void	editref();
void	help();
void	mark();
void	scrollbar();
#endif

/* execute the command */

BOOL
command(commandc)
int	commandc;
{
	char	filename[PATHLEN + 1];	/* file path name */
#if !defined(__BORLANDC__) && !defined(FOR_WIN32)
	MOUSEEVENT *p;			/* mouse data */
#endif
	register int	c, i;
	register FILE	*file;
	HISTORY *curritem, *item;	/* command history */
	char	*s;

	switch (commandc) {

	case ctrl('C'):	/* toggle caseless mode */
		if (caseless == NO) {
			caseless = YES;
			putmsg2("Caseless mode is now ON");
		}
		else {
			caseless = NO;
			putmsg2("Caseless mode is now OFF");
		}
		egrepcaseless(caseless);	/* turn on/off -i flag */
		return(NO);

	case ctrl('R'):	/* rebuild the cross reference */
		if (isuptodate == YES) {
			putmsg("The -d option prevents rebuilding the symbol database");
			return(NO);
		}
#if !defined(__BORLANDC__) && !defined(FOR_WIN32)
		exitcurses();
#endif
		freefilelist();		/* remake the source file list */
		makefilelist();
		rebuild();
		if (errorsfound == YES) {
			errorsfound = NO;
			askforreturn();
		}		
#if !defined(__BORLANDC__) && !defined(FOR_WIN32)
		entercurses();
#endif
		putmsg("");		/* clear any previous message */
		totallines = 0;
		topline = nextline = 1;
		break;

#if UNIXPC
	case ESC:	/* possible unixpc mouse selection */
#endif
#if !defined(__BORLANDC__) && !defined(FOR_WIN32)
	case ctrl('X'):	/* mouse selection */
		if ((p = getmouseevent()) == NULL) {
			return(NO);	/* unknown control sequence */
		}
		/* if the button number is a scrollbar tag */
		if (p->button == '0') {
			scrollbar(p);
			break;
		}
		/* ignore a sweep */
		if (p->x2 >= 0) {
			return(NO);
		}
		/* if this is a line selection */
		if (p->y1 < FLDLINE) {

			/* find the selected line */
			/* note: the selection is forced into range */
			for (i = disprefs - 1; i > 0; --i) {
				if (p->y1 >= displine[i]) {
					break;
				}
			}
			/* display it in the file with the editor */
			editref(i);
		}
		else {	/* this is an input field selection */
			field = mouseselection(p, FLDLINE, FIELDS);
			setfield();
			resetcmd();
			return(NO);
		}
		break;
#endif

	case '\t':	/* go to next input field */
	case '\n':
	case '\r':
	case ctrl('N'):
#if TERMINFO
	case KEY_DOWN:
	case KEY_ENTER:
	case KEY_RIGHT:
#endif
		field = (field + 1) % FIELDS;
		setfield();
		resetcmd();
		return(NO);

	case ctrl('P'):	/* go to previous input field */
#if TERMINFO
	case KEY_UP:
	case KEY_LEFT:
#endif
		field = (field + (FIELDS - 1)) % FIELDS;
		setfield();
		resetcmd();
		return(NO);
#if TERMINFO
	case KEY_HOME:	/* go to first input field */
		field = 0;
		setfield();
		resetcmd();
		return(NO);

	case KEY_LL:	/* go to last input field */
		field = FIELDS - 1;
		setfield();
		resetcmd();
		return(NO);
#endif
	case ' ':	/* display next page */
	case '+':
	case ctrl('V'):
#if TERMINFO
	case KEY_NPAGE:
#endif
		/* don't redisplay if there are no lines */
		if (totallines == 0) {
			return(NO);
		}
		/* note: seekline() is not used to move to the next 
		 * page because display() leaves the file pointer at
		 * the next page to optimize paging forward
		 */
		break;

	case '-':	/* display previous page */
#if TERMINFO
	case KEY_PPAGE:
#endif
		/* don't redisplay if there are no lines */
		if (totallines == 0) {
			return(NO);
		}
		i = topline;		/* save the current top line */
		nextline = topline;	/* go back to this page */

		/* if on first page but not at beginning, go to beginning */
		if (nextline > 1 && nextline <= mdisprefs) {
			nextline = 1;
		}
		else {	/* go back the maximum displayable lines */
			nextline -= mdisprefs;
			
			/* if this was the first page, go to the last page */
			if (nextline < 1) {
				nextline = totallines - mdisprefs + 1;
				if (nextline < 1) {
					nextline = 1;
				}
				i = totallines + 1;	/* old top is past last line */
			}
		}
		/* move down til the bottom line is just before the previous top line */
		c = nextline;
		for (;;) {
			seekline(nextline);
			display();
			if (i - bottomline <= 0) {
				break;
			}
			nextline = ++c;
		}
		return(NO);	/* display already up to date */

	case '>':	/* write or append the lines to a file */
		if (totallines == 0) {
			putmsg("There are no lines to write to a file");
		}
		else {	/* get the file name */
			(void) move(PRLINE, 0);
			(void) addstr("Write to file: ");
#if defined(__BORLANDC__) || defined(FOR_WIN32)
			s = "wt";	/* This might be processed by a DOS program */
#else
			s = "w";
#endif
			if ((c = mygetch()) == '>') {
				(void) move(PRLINE, 0);
				(void) addstr(appendprompt);
				c = '\0';
#if defined(__BORLANDC__) || defined(FOR_WIN32)
				s = "at";
#else
				s = "a";
#endif
			}
			if (c != '\r' && c != '\n' && c != KEY_ENTER && c != KEY_BREAK &&
			    getline(newpat, COLS - sizeof(appendprompt), c,
			    NO) > 0) {
				shellpath(filename, sizeof(filename), newpat);
				if ((file = fopen(filename, s)) == NULL) {
					cannotopen(filename);
				}
				else {
					seekline(1);
					while ((c = getc(refsfound)) != EOF) {
						(void) putc(c, file);
					}
					seekline(topline);
					(void) fclose(file);
				}
			}
			clearprompt();
		}
		return(NO);	/* return to the previous field */

	case '<':	/* read lines from a file */
		(void) move(PRLINE, 0);
		(void) addstr(readprompt);
		if (getline(newpat, COLS - sizeof(readprompt), '\0',
		    NO) > 0) {
			clearprompt();
			shellpath(filename, sizeof(filename), newpat);
			if (readrefs(filename) == NO) {
				putmsg2("Ignoring an empty file");
				return(NO);
			}
			return(YES);
		}
		clearprompt();
		return(NO);

	case '^':	/* pipe the lines through a shell command */
	case '|':	/* pipe the lines to a shell command */
		if (totallines == 0) {
			putmsg("There are no lines to pipe to a shell command");
			return(NO);
		}
		/* get the shell command */
		(void) move(PRLINE, 0);
		if(commandc == '^') {
			(void) addstr(filterprompt);
			if (getline(newpat, COLS-sizeof(filterprompt),'\0',NO) == 0) {
				clearprompt();
				return(NO);
			}
		}
		else {
			(void) addstr(pipeprompt);
			if (getline(newpat, COLS-sizeof(pipeprompt),'\0', NO) == 0) {
				clearprompt();
				return(NO);
			}
		}
		/* if the ^ command, redirect output to a temp file */
		if (commandc == '^') {
			(void) strcat(strcat(newpat, " > "), temp2);
		}
#if defined(__BORLANDC__) || defined(FOR_WIN32)
		/************************************************************
		 * We don't really have multiple running processes in DOS so
		 * we must run this slightly differently.
		 ************************************************************/
		(void) strcat(strcat(newpat, " < "), temp1);
		system(newpat);
		if (commandc == '^') {
			if (readrefs(temp2) == NO) {
				putmsg("Ignoring empty output of ^ command");
			}
		}
		askforreturn();
#else
		exitcurses();
		if ((file = mypopen(newpat, "w")) == NULL) {
			(void) fprintf(stderr, "%s: cannot open pipe to shell command: %s\n", argv0, newpat);
		}
		else {
			seekline(1);
			while ((c = getc(refsfound)) != EOF) {
				(void) putc(c, file);
			}
			seekline(topline);
			(void) mypclose(file);
		}
		if (commandc == '^') {
			if (readrefs(temp2) == NO) {
				putmsg("Ignoring empty output of ^ command");
			}
		}
		askforreturn();
		entercurses();
#endif
		break;

	case ctrl('L'):	/* redraw screen */
#if TERMINFO
	case KEY_CLEAR:
#endif
		(void) clearok(curscr, TRUE);
		(void) wrefresh(curscr);
		drawscrollbar(topline, bottomline, totallines);
		return(NO);

	case '!':	/* shell escape */
		(void) execute(shell, shell, (char *) 0);
		seekline(topline);
		break;

	case '?':	/* help */
		(void) clear();
		help();
		(void) clear();
		seekline(topline);
		break;

	case ctrl('E'):	/* edit all lines */
		editall();
		break;

	case ctrl('A'):	/* repeat last pattern */
	case ctrl('Y'):	/* (old command) */
		if (*pattern != '\0') {
			(void) addstr(pattern);
			goto repeat;
		}
		break;

	case ctrl('B'):		/* cmd history back */
	case ctrl('F'):		/* cmd history fwd */
		curritem = currentcmd();
		item = (commandc == ctrl('F')) ? nextcmd() : prevcmd();
		clearmsg2();
		if (curritem == item) {	/* inform user that we're at history end */
			putmsg2("End of input field and search pattern history");
		}
		if (item) {
			field = item->field;
			setfield();
			atfield();
			(void) addstr(item->text);
			(void) strcpy(pattern, item->text);
			switch (c = mygetch()) {
			case '\r':
			case '\n':
			case KEY_ENTER:
				goto repeat;
			default:
				(void) ungetch(c);
				atfield();
				(void) clrtoeol();	/* clear current field */
				break;
			}
		}
		return(NO);

	case '\\':	/* next character is not a command */
		(void) addch('\\');	/* display the quote character */

		/* get a character from the terminal */
		if ((commandc = mygetch()) == EOF) {
			return(NO);	/* quit */
		}
		(void) addstr("\b \b");	/* erase the quote character */
		goto ispat;

	case '.':
		atfield();	/* move back to the input field */
		/* FALLTHROUGH */
	default:
		/* edit a selected line */
		if (isdigit(commandc) && commandc != '0' && !mouse) {
			if (returnrequired == NO) {
				editref(commandc - '1');
			}
			else {
				(void) move(PRLINE, 0);
				(void) addstr(selectionprompt);
				if (getline(newpat, COLS - sizeof(
				    selectionprompt), commandc, NO) > 0 &&
				    (i = atoi(newpat)) > 0) {
					editref(i - 1);
				}
				clearprompt();
			}
		}
		/* if this is the start of a pattern */
		else if (isprint(commandc)) {
	ispat:		if (getline(newpat, COLS - fldcolumn - 1, commandc,
			    caseless) > 0) {
					(void) strcpy(pattern, newpat);
					resetcmd();	/* reset command history */
	repeat:
				addcmd(field, pattern);	/* add to command history */
				if ((field == STRING) && (askegrep == YES) &&
				    (extendedmenu == NO)) {
					(void) move(PRLINE, 0);
					(void) addstr(dirprompt);
					(void) getline(subdirpat, COLS - sizeof(dirprompt), '\0', caseless);
				}
				if ((field == CHANGE) && (extendedmenu == NO)) {
					
					/* prompt for the new text */
					(void) move(PRLINE, 0);
					(void) addstr(toprompt);
					(void) getline(newpat, COLS - sizeof(toprompt), '\0', NO);
				}
				/* search for the pattern */
				if (search() == YES) {
					switch (field) {
					case DEFINITION:
						if (extendedmenu == YES) 
						break;
					case FILENAME:
						if (totallines > 1) {
							break;
						}
						topline = 1;
						editref(0);
						break;
					case CHANGE:
						if (extendedmenu == NO)
						return(changestring());
					}
				}
				/* try to edit the file anyway */
				else if (field == FILENAME && 
				    access(newpat, READ) == 0) {
					edit(newpat, "1");
				}
			}
			else {	/* no pattern--the input was erased */
				return(NO);
			}
		}
		else {	/* control character */
			return(NO);
		}
	}
	return(YES);
}

/* clear the prompt line */

void
clearprompt()
{
	(void) move(PRLINE, 0);
	(void) clrtoeol();
}

/* read references from a file */

BOOL
readrefs(filename)
char	*filename;
{
	register FILE	*file;
	register int	c;

#if defined(__BORLANDC__) || defined(FOR_WIN32)
	if ((file = fopen(filename, "rt")) == NULL) {
#else
	if ((file = fopen(filename, "r")) == NULL) {
#endif
		cannotopen(filename);
		return(NO);
	}
	if ((c = getc(file)) == EOF) {	/* if file is empty */
		return(NO);
	}
	totallines = 0;
	nextline = 1;
	if (writerefsfound() == YES) {
		(void) putc(c, refsfound);
		while ((c = getc(file)) != EOF) {
			(void) putc(c, refsfound);
		}
		(void) fclose(file);
#if defined(__BORLANDC__) || defined(FOR_WIN32)
		(void) freopen(temp1, "rt", refsfound);
#else
		(void) freopen(temp1, "r", refsfound);
#endif
		countrefs();
	}
	return(YES);
}

/* change one text string to another */

BOOL
changestring()
{
	char	buf[PATLEN + 1];	/* input buffer */
	char	newfile[PATHLEN + 1];	/* new file name */
	char	oldfile[PATHLEN + 1];	/* old file name */
	char	linenum[NUMLEN + 1];	/* file line number */
	char	msg[MSGLEN + 1];	/* message */
	FILE	*script;		/* shell script file */
	BOOL	anymarked = NO;		/* any line marked */
	MOUSEEVENT *p;			/* mouse data */
	int	c, i;
	char	*s;

	/* open the temporary file */
	if ((script = fopen(temp2, "w")) == NULL) {
		cannotopen(temp2);
		return(NO);
	}
	/* create the line change indicators */
	change = (BOOL *) mycalloc((unsigned) totallines, sizeof(BOOL));
	changing = YES;
	initmenu();

	/* until the quit command is entered */
	for (;;) {
		/* display the current page of lines */
		display();
	same:
		/* get a character from the terminal */
		(void) move(PRLINE, 0);
		(void) addstr("Select lines to change (press the ? key for help): ");
		if ((c = mygetch()) == EOF || c == ctrl('D') || c == ctrl('Z')) {
			break;	/* change lines */
		}
		/* see if the input character is a command */
		switch (c) {
		case ' ':	/* display next page */
		case '+':
		case ctrl('V'):
#if TERMINFO
		case KEY_NPAGE:
#endif
		case '-':	/* display previous page */
#if TERMINFO
		case KEY_PPAGE:
#endif
		case '!':	/* shell escape */
		case '?':	/* help */
			(void) command(c);
			break;

		case ctrl('L'):	/* redraw screen */
#if TERMINFO
		case KEY_CLEAR:
#endif
			(void) command(c);
			goto same;

		case ESC:	/* kept for backwards compatibility */
#if UNIXPC
			if((p = getmouseevent()) == NULL) {
				clearprompt();
				goto nochange;	/* unknown escape sequence */
			}
			break;
#endif
			/* FALLTHROUGH */

		case '\r':	/* don't change lines */
		case '\n':
		case KEY_ENTER:
		case KEY_BREAK:
		case ctrl('G'):
			clearprompt();
			goto nochange;

		case '*':	/* mark/unmark all displayed lines */
			for (i = 0; topline + i < nextline; ++i) {
				mark(i);
			}
			goto same;

		case 'a':	/* mark/unmark all lines */
			for (i = 0; i < totallines; ++i) {
				if (change[i] == NO) {
					change[i] = YES;
				}
				else {
					change[i] = NO;
				}
			}
			/* show that all have been marked */
			seekline(totallines);
			break;
		case ctrl('X'):	/* mouse selection */
			if ((p = getmouseevent()) == NULL) {
				goto same;	/* unknown control sequence */
			}
			/* if the button number is a scrollbar tag */
			if (p->button == '0') {
				scrollbar(p);
				break;
			}
			/* find the selected line */
			/* note: the selection is forced into range */
			for (i = disprefs - 1; i > 0; --i) {
				if (p->y1 >= displine[i]) {
					break;
				}
			}
			mark(i);
			goto same;
		default:
			/* if a line was selected */
			if (isdigit(c) && c != '0' && !mouse) {
				if (returnrequired == NO) {
					mark(c - '1');
				}
				else {
					clearprompt();
					(void) move(PRLINE, 0);
					(void) addstr(selectionprompt);
					if (getline(buf, COLS - sizeof(
					    selectionprompt), c, NO) > 0 &&
					    (i = atoi(buf)) > 0) {
						mark(i - 1);
					}
				}
			}
			goto same;
		}
	}
	/* for each line containing the old text */
	(void) fprintf(script, "ed - <<\\!\nH\n");
	*oldfile = '\0';
	seekline(1);
	for (i = 0; fscanf(refsfound, "%s%*s%s%*[^\n]", newfile, linenum) == 2;
	    ++i) {
		/* see if the line is to be changed */
		if (change[i] == YES) {
			anymarked = YES;
		
			/* if this is a new file */
			if (strcmp(newfile, oldfile) != 0) {
				
				/* make sure it can be changed */
				if (access(newfile, WRITE) != 0) {
					(void) sprintf(msg, "Cannot write to file %s", newfile);
					putmsg(msg);
					anymarked = NO;
					break;
				}
				/* if there was an old file */
				if (*oldfile != '\0') {
					(void) fprintf(script, "w\n");	/* save it */
				}
				/* edit the new file */
				(void) strcpy(oldfile, newfile);
				(void) fprintf(script, "e %s\n", oldfile);
			}
			/* output substitute command */
			(void) fprintf(script, "%ss/", linenum);	/* change */
			for (s = pattern; *s != '\0'; ++s) {	/* old text */
				if (*s == '/') {
					(void) putc('\\', script);
				}
				(void) putc(*s, script);
			}
			(void) putc('/', script);			/* to */
 			for (s = newpat; *s != '\0'; ++s) {	/* new text */
				if (strchr("/\\&", *s) != NULL) {
					(void) putc('\\', script);
				}
				(void) putc(*s, script);
			}
			(void) fprintf(script, "/gp\n");	/* and print */
		}
	}
	(void) fprintf(script, "w\nq\n!\n");	/* write and quit */
	(void) fclose(script);
	clearprompt();

	/* if any line was marked */
	if (anymarked == YES) {
		
		/* edit the files */
		(void) refresh();
		(void) fprintf(stderr, "Changed lines:\r\n");
		(void) execute(shell, shell, temp2, (char *) 0);
		askforreturn();
	}
nochange:
	changing = NO;
	initmenu();
	free((char *) change);
	seekline(topline);
	return(YES);	/* clear any marks on exit without change */
}

/* mark/unmark this displayed line to be changed */

void
mark(i)
int	i;
{
	int	j;
	
	j = i + topline - 1;
	if (j < totallines) {
		(void) move(displine[i], selectlen);
		if (change[j] == NO) {
			change[j] = YES;
			(void) addch('>');
		}
		else {
			change[j] = NO;
			(void) addch(' ');
		}
	}
}

/* scrollbar actions */

void
scrollbar(p)
MOUSEEVENT *p;
{
	/* reposition list if it makes sense */
	if (totallines == 0) {
		return;
	}
	switch (p->percent) {
		
	case 101: /* scroll down one page */
		if (nextline + mdisprefs > totallines) {
			nextline = totallines - mdisprefs + 1;
		}
		break;
		
	case 102: /* scroll up one page */
		nextline = topline - mdisprefs;
		if (nextline < 1) {
			nextline = 1;
		}
		break;

	case 103: /* scroll down one line */
		nextline = topline + 1;
		break;
		
	case 104: /* scroll up one line */
		if (topline > 1) {
			nextline = topline - 1;
		}
		break;
	default:
		nextline = p->percent * totallines / 100;
	}
	seekline(nextline);
}
