/***************************************************************************
 * This program is Copyright (C) 1986, 1987, 1988 by Jonathan Payne.  JOVE *
 * is provided to you without charge, and with no warranty.  You may give  *
 * away copies of JOVE, including sources, provided that this notice is    *
 * included in all the files.                                              *
 ***************************************************************************/

/* Contains commands that deal with creating, selecting, killing and
   listing buffers, and buffer modes, and find-file, etc. */

#include "jove.h"

#ifdef MAC
#	include "mac.h"
#else
#	include <sys/stat.h>
#endif

#ifdef MAC
#	undef private
#	define private
#endif

#ifdef	LINT_ARGS
private Buffer
	* buf_alloc(void),
	* mak_buf(void);

private char * line_cnt(Buffer *, char *);

private void	
	BufNSelect(int),
	defb_wind(Buffer *),
	kill_buf(Buffer *),
	mkbuflist(char **);
#else
private Buffer
	* buf_alloc(),
	* mak_buf();

private char * line_cnt();

private void
	BufNSelect(),
	defb_wind(),
	kill_buf(),
	mkbuflist();
#endif	/* LINT_ARGS */

#ifdef MAC
#	undef private
#	define private static
#endif

char	*Mainbuf = "Main",
	*NoName = "Sans un nom!";

Buffer	*world = 0,		/* First in the list */
	*curbuf = 0,
	*lastbuf = 0;	/* Last buffer we were in so we have a default
			   buffer during a select buffer. */

/* Toggle BIT in the current buffer's minor mode flags.  If argument is
   supplied, a positive one always turns on the mode and zero argument
   always turns it off. */

void
TogMinor(bit)
{
	if (is_an_arg()) {
		if (arg_value() == 0)
			curbuf->b_minor &= ~bit;
		else
			curbuf->b_minor |= bit;
	} else
		curbuf->b_minor ^= bit;
	UpdModLine = YES;
}

/* Creates a new buffer, links it at the end of the buffer chain, and
   returns it. */

static Buffer *
buf_alloc()
{
	register Buffer	*b,
			*lastbp;

	lastbp = 0;
	for (b = world; b != 0; lastbp = b, b = b->b_next)
		;

	b = (Buffer *) emalloc(sizeof (Buffer));
	if (lastbp)
		lastbp->b_next = b;
	else
		world = b;
	b->b_first = 0;
	b->b_next = 0;
#ifdef MAC
	b->Type = BUFFER;	/* kludge, but simplifies menu handlers */
	b->Name = 0;
#endif
	return b;
}

/* Makes a buffer and initializes it.  Obsolete.  Used to take two
   arguments, a buffer name and a file name. */

static Buffer *
mak_buf()
{
	register Buffer	*newb;
	register int	i;

	newb = buf_alloc();
	newb->b_fname = 0;
	newb->b_name = NoName;
	set_ino(newb);
	newb->b_marks = 0;
	newb->b_themark = 0;		/* Index into markring */
	/* No marks yet */
	for (i = 0; i < NMARKS; i++)
		newb->b_markring[i] = 0;
	newb->b_modified = 0;
	newb->b_type = B_FILE;  /* File until proven SCRATCH */
	newb->b_ntbf = 0;
	newb->b_minor = 0;
	newb->b_major = TEXT;
	newb->b_first = 0;
	newb->b_keybinds = 0;
#ifdef IPROCS
	newb->b_process = 0;
#endif
	initlist(newb);
#ifdef MAC
	Bufchange = 1;
#endif
	return newb;
}

void
ReNamBuf()
{
	register char	*new = 0,
			*prompt = ProcFmt,
			*second = "%s already exists; new name? ";

	for (;;) {
		new = ask((char *) 0, prompt, new);
		if (!buf_exists(new))
			break;
		prompt = second;
	}
	setbname(curbuf, new);
}

void
FindFile()
{
	register char	*name;
	char	fnamebuf[FILESIZE];

	name = ask_file((char *) 0, curbuf->b_fname, fnamebuf);
	SetABuf(curbuf);
	SetBuf(do_find(curwind, name, 0));
}

private void
mkbuflist(bnamp)
register char	**bnamp;
{
	register Buffer	*b;

	for (b = world; b != 0; b = b->b_next)
		if (b->b_name != 0)
			*bnamp++ = b->b_name;
	*bnamp = 0;
}

char *
ask_buf(def)
Buffer	*def;
{
	char	*bnames[100];
	register char	*bname;
	register int	offset;
	char	prompt[100];

	if (def != 0 && def->b_name != 0)
		sprintf(prompt, ": %f (default %s) ", def->b_name);
	else
		sprintf(prompt, ProcFmt);
	mkbuflist(bnames);
	offset = complete(bnames, prompt, RET_STATE);
	if (offset == EOF)
		complain((char *) 0);
	if (offset == ORIGINAL || offset == AMBIGUOUS)
		bname = Minibuf;
	else if (offset == NULLSTRING) {
		if (def)
			bname = def->b_name;
		else
			complain((char *) 0);
	} else if (offset < 0)
		complain((char *) 0);
	else
		bname = bnames[offset];

	return bname;
}

void
BufSelect()
{
	register char	*bname;

	bname = ask_buf(lastbuf);
	SetABuf(curbuf);
	SetBuf(do_select(curwind, bname));
}

#ifdef MSDOS

private void
BufNSelect(n)
{
	char *bnames[100];
	char *bname;
	int i;

	mkbuflist(bnames);
   	for (i=0; i<n; i++)
	    if (bnames[i] == 0)
	       complain("[No such buffer]");
	bname = bnames[n-1];
	SetABuf(curbuf);
	SetBuf(do_select(curwind, bname));
}

void Buf1Select() { BufNSelect(1); }
void Buf2Select() { BufNSelect(2); }
void Buf3Select() { BufNSelect(3); }
void Buf4Select() { BufNSelect(4); }
void Buf5Select() { BufNSelect(5); }
void Buf6Select() { BufNSelect(6); }
void Buf7Select() { BufNSelect(7); }
void Buf8Select() { BufNSelect(8); }
void Buf9Select() { BufNSelect(9); }
void Buf10Select() { BufNSelect(10); }

#endif /* MSDOS */

private void
defb_wind(b)
register Buffer *b;
{
	register Window	*w = fwind;
	char	*alt;

	if (lastbuf == b || lastbuf == 0) {
		lastbuf = 0;
		alt = (b->b_next != 0) ? b->b_next->b_name : Mainbuf;
	} else
		alt = lastbuf->b_name;

	do {
		if (w->w_bufp == b) {
			if (one_windp() || alt != Mainbuf)
				(void) do_select(w, alt);
			else {
				Window	*save = w->w_next;
				del_wind(w);
				w = save->w_prev;
			}
		}
		w = w->w_next;
	} while (w != fwind || w->w_bufp == b);
}

Buffer *
getNMbuf()
{
	register Buffer	*delbuf;
	register char	*bname;

	bname = ask_buf(curbuf);
	if ((delbuf = buf_exists(bname)) == 0)
		complain("[No such buffer]");
	if (delbuf->b_modified)
		confirm("%s modified, are you sure? ", bname);
	return delbuf;
}

void
BufErase()
{
	register Buffer	*delbuf;

	if (delbuf = getNMbuf()) {
		initlist(delbuf);
		delbuf->b_modified = 0;
	}
}

private void
kill_buf(delbuf)
register Buffer	*delbuf;
{
	register Buffer	*b,
			*lastb = 0;
#ifndef MAC
	extern Buffer	*perr_buf;
#endif

#ifdef IPROCS
	pbuftiedp(delbuf);	/* check for lingering processes */
#endif
	for (b = world; b != 0; lastb = b, b = b->b_next)
		if (b == delbuf)
			break;
	if (lastb)
		lastb->b_next = delbuf->b_next;
	else
		world = delbuf->b_next;

#define okay_free(ptr)	if (ptr) free(ptr)

	lfreelist(delbuf->b_first);
	okay_free(delbuf->b_name);
	okay_free(delbuf->b_fname);
	flush_marks(delbuf);
	free((char *) delbuf);

	if (delbuf == lastbuf)
		SetABuf(curbuf);
#ifndef MAC
	if (perr_buf == delbuf) {
		ErrFree();
		perr_buf = 0;
	}
#endif
	defb_wind(delbuf);
	if (curbuf == delbuf)
		SetBuf(curwind->w_bufp);
#ifdef MAC
	Bufchange = 1;
#endif
}

/* offer to kill some buffers */

void
KillSome()
{
	register Buffer	*b,
			*next;
	Buffer	*oldb;
	register char	*y_or_n;

	for (b = world; b != 0; b = next) {
		next = b->b_next;
		if (yes_or_no_p("Kill %s? ", b->b_name) == NO)
			continue;
		if (IsModified(b)) {
			y_or_n = ask("No", "%s modified; should I save it? ", b->b_name);
			if (CharUpcase(*y_or_n) == 'Y') {
				oldb = curbuf;
				SetBuf(b);
				SaveFile();
				SetBuf(oldb);
			}
		}
		kill_buf(b);
	}
}

void
BufKill()
{
	Buffer	*b;

	if ((b = getNMbuf()) == 0)
		return;
	kill_buf(b);
}

private char *
line_cnt(b, buf)
register Buffer	*b;
char	*buf;
{
	register int	nlines = 0;
	register Line	*lp;

	for (lp = b->b_first; lp != 0; lp = lp->l_next, nlines++)
		;
	sprintf(buf, "%d", nlines);
	return buf;
}

private char	*TypeNames[] = {
	0,
	"Scratch",
	"File",
	"Process",
};

void
BufList()
{
	register char	*format = "%-2s %-5s %-11s %-1s %-*s  %-s";
	register Buffer	*b;
	int	bcount = 1,		/* To give each buffer a number */
		buf_width = 11;
	char	nbuf[10];

	for (b = world; b != 0; b = b->b_next)
		buf_width = max(buf_width, strlen(b->b_name));

	TOstart("Buffer list", TRUE);	/* true means auto-newline */

	Typeout("(* means buffer needs saving)");
	Typeout("(+ means file hasn't been read yet)");
	Typeout(NullStr);
	Typeout(format, "NO", "Lines", "Type", NullStr, buf_width, "Name", "File");
	Typeout(format, "--", "-----", "----", NullStr, buf_width, "----", "----");
	for (b = world; b != 0; b = b->b_next) {
		Typeout(format, itoa(bcount++),
				line_cnt(b, nbuf),
				TypeNames[b->b_type],
				IsModified(b) ? "*" :
					 b->b_ntbf ? "+" : NullStr,
				buf_width,
				/* For the * (variable length field) */
				b->b_name,
				filename(b));

		if (TOabort)
			break;
	}
	TOstop();
}

void
bufname(b)
register Buffer	*b;
{
	char	tmp[100],
		*cp;
	int	try = 1;

	if (b->b_fname == 0)
		complain("[No file name]");
	cp = basename(b->b_fname);
	strcpy(tmp, cp);
	while (buf_exists(tmp)) {
		sprintf(tmp, "%s.%d", cp, try);
		try += 1;
	}
	setbname(b, tmp);
}

void
initlist(b)
register Buffer	*b;
{
	lfreelist(b->b_first);
	b->b_first = b->b_dot = b->b_last = 0;
	(void) listput(b, b->b_first);
	
	SavLine(b->b_dot, NullStr);
	b->b_char = 0;
	AllMarkSet(b, b->b_dot, 0);
	if (b == curbuf)
		getDOT();
}

/* Returns pointer to buffer with name NAME, or if NAME is a string of digits
   returns the buffer whose number equals those digits.  Otherwise, returns
   0. */

Buffer *
buf_exists(name)
register char	*name;
{
	register Buffer	*bp;
	int	n;

	if (name == 0)
		return 0;

	for (bp = world; bp != 0; bp = bp->b_next)
		if (strcmp(bp->b_name, name) == 0)
			return bp;

	/* Doesn't match any names.  Try for a buffer number... */

	if (chr_to_int(name, 10, YES, &n) != INT_BAD) {
		for (bp = world; n > 1; bp = bp->b_next) {
			if (bp == 0)
				break;
			n -= 1;
		}
		return bp;
	}

	return 0;
}

/* Returns buffer pointer with a file name NAME, if one exists.  Stat's the
   file and compares inodes, in case NAME is a link, as well as the actual
   characters that make up the file name. */

Buffer *
file_exists(name)
register char	*name;
{
	struct stat	stbuf;
	register struct stat	*s = &stbuf;
	register Buffer	*b = 0;
	char	fnamebuf[FILESIZE];

#ifdef MSDOS
	strlwr(name);
#endif /* MSDOS */
	if (name) {
		PathParse(name, fnamebuf);
		if (stat(fnamebuf, s) == -1)
			s->st_ino = 0;
		for (b = world; b != 0; b = b->b_next) {
			if (
#ifndef MSDOS
			    (b->b_ino != 0 && b->b_ino == s->st_ino &&
			     b->b_dev != 0 && b->b_dev == s->st_dev) ||
#endif /* MSDOS */
			    (strcmp(b->b_fname, fnamebuf) == 0))
				break;
		}
	}
	return b;
}

char *
ralloc(obj, size)
register char	*obj;
{
	register char	*new;

	if (obj)
		new = realloc(obj, (unsigned) size);
	if (new == 0 || !obj)
		new = emalloc(size);
	return new;
}

void
setbname(b, name)
register Buffer	*b;
register char	*name;
{
	UpdModLine = YES;	/* Kludge ... but speeds things up considerably */
	if (name) {
		if (b->b_name == NoName)
			b->b_name = 0;
		b->b_name = ralloc(b->b_name, strlen(name) + 1);
		strcpy(b->b_name, name);
	} else
		b->b_name = 0;
#ifdef MAC
	Bufchange = 1;
#endif
}

void
setfname(b, name)
register Buffer	*b;
register char	*name;
{
	char	wholename[FILESIZE],
		oldname[FILESIZE],
		*oldptr = oldname;
	Buffer	*save = curbuf;

	SetBuf(b);
	UpdModLine = YES;	/* Kludge ... but speeds things up considerably */
	if (b->b_fname == 0)
		oldptr = 0;
	else
		strcpy(oldname, b->b_fname);
	if (name) {
#ifdef MSDOS
		strlwr(name);
#endif /* MSDOS */
		PathParse(name, wholename);
		curbuf->b_fname = ralloc(curbuf->b_fname, strlen(wholename) + 1);
		strcpy(curbuf->b_fname, wholename);
	} else
		b->b_fname = 0;
	DoAutoExec(curbuf->b_fname, oldptr);
	curbuf->b_mtime = curbuf->b_dev = curbuf->b_ino = 0;	/* until they're known. */
	SetBuf(save);
#ifdef MAC
	Bufchange = 1;
#endif
}

void
set_ino(b)
register Buffer	*b;
{
	struct stat	stbuf;

	if (b->b_fname == 0 || stat(pr_name(b->b_fname, NO), &stbuf) == -1) {
 		b->b_dev = 0;
		b->b_ino = 0;
		b->b_mtime = 0;
	} else {
 		b->b_dev = stbuf.st_dev;
		b->b_ino = stbuf.st_ino;
		b->b_mtime = stbuf.st_mtime;
	}
}

/* Find the file `fname' into buf and put in in window `w' */

Buffer *
do_find(w, fname, force)
register Window	*w;
register char	*fname;
{
	register Buffer	*b;

	b = file_exists(fname);
	if (b == 0) {
		b = mak_buf();
		setfname(b, fname);
		bufname(b);
		set_ino(b);
		b->b_ntbf = 1;
	}
	if (force) {
		Buffer	*oldb = curbuf;

		SetBuf(b);	/* this'll read the file */
		SetBuf(oldb);
	}
	if (w)
		tiewind(w, b);
	return b;
}

/* set alternate buffer */

void
SetABuf(b)
Buffer	*b;
{
	if (b != 0)
		lastbuf = b;
}


/* check to see if BP is a valid buffer pointer */
int
valid_bp(bp)
register Buffer	*bp;
{
	register Buffer	*b;

	for (b = world; b != 0; b = b->b_next)
		if (b == bp)
			break;
	return b != 0;
}

void
SetBuf(newbuf)
register Buffer	*newbuf;
{
	register Buffer	*oldb = curbuf,
			*b;

	if (newbuf == curbuf || newbuf == 0)
		return;

	if (!valid_bp(newbuf))
		complain("Internal error: (0x%x) is not a valid buffer pointer!", newbuf);
	lsave();
	curbuf = newbuf;
	curline = newbuf->b_dot;
	curchar = newbuf->b_char;
	getDOT();
	/* do the read now ... */
	if (curbuf->b_ntbf)
		read_file(curbuf->b_fname, 0);
#ifdef MAC
	Modechange = 1;
#endif

#ifdef IPROCS
	if (oldb != 0 && ((oldb->b_process == 0) != (curbuf->b_process == 0))) {
		if (curbuf->b_process)
			PushPBs();		/* Push process bindings */
		else if (oldb->b_process)
			PopPBs();
	}
#endif
}

Buffer *
do_select(w, name)
register Window	*w;
register char	*name;
{
	register Buffer	*new;

	if ((new = buf_exists(name)) == 0) {
		new = mak_buf();
		setfname(new, (char *) 0);
		setbname(new, name);
	}
	if (w)
		tiewind(w, new);
	return new;
}
