/* cut.c */

/* Author:
 *	Steve Kirkendall
 *	16820 SW Tallac Way
 *	Beaverton, OR 97006
 *	kirkenda@jove.cs.pdx.edu, or ...uunet!tektronix!psueea!jove!kirkenda
 */


/* This file contains function which manipulate the cut buffers. */

#include "config.h"
#include "vi.h"
#if	TURBOC
#include <process.h>		/* needed for getpid */
#endif
#if	TOS
#include <osbind.h>
#define	rename(a,b)	Frename(0,a,b)
#endif

# define NANNONS	9	/* number of annonymous buffers */

static struct cutbuf
{
	short	*phys;	/* pointer to an array of #s of BLKs containing text */
	int	nblks;	/* number of blocks in phys[] array */
	int	start;	/* offset into first block of start of cut */
	int	end;	/* offset into last block of end of cut */
	int	fd;	/* fd of tmp file, or -1 to use tmpfd */
	char	lnmode;	/* boolean: line-mode cut? (as opposed to char-mode) */
}
	named[27],	/* cut buffers "a through "z and ". */
	annon[NANNONS];	/* annonymous cut buffers */

static char	cbname;	/* name chosen for next cut/paste operation */


#ifndef NO_RECYCLE
/* This function builds a list of all blocks needed in the current tmp file
 * for the contents of cut buffers.
 * !!! WARNING: if you have more than ~450000 bytes of text in all of the
 * cut buffers, then this will fail disastrously, because buffer overflow
 * is *not* allowed for.
 */
int cutneeds(need)
	BLK		*need;	/* this is where we deposit the list */
{
	struct cutbuf	*cb;	/* used to count through cut buffers */
	int		i;	/* used to count through blocks of a cut buffer */
	int		n;	/* total number of blocks in list */

	n = 0;

	/* first the named buffers... */
	for (cb = named; cb < &named[27]; cb++)
	{
		if (cb->fd > 0)
			continue;

		for (i = cb->nblks; i-- > 0; )
		{
			need->n[n++] = cb->phys[i];
		}
	}

	/* then the anonymous buffers */
	for (cb = annon; cb < &annon[NANNONS]; cb++)
	{
		if (cb->fd > 0)
			continue;

		for (i = cb->nblks; i-- > 0; )
		{
			need->n[n++] = cb->phys[i];
		}
	}

	return n;
}
#endif

/* This function is called when we are about to abort a tmp file.  If any
 * cut buffers still need the file, then a copy of the file should be
 * created for use by the cut buffers.
 *
 * To minimize the number of extra files lying around, only named cut buffers
 * are preserved in a file switch; the annonymous buffers just go away.
 */
cutswitch(tmpname)
	char	*tmpname; /* name of the tmp file */
{
	char	cutname[50];	/* used to build a new name for the tmp file */
	int	fd;		/* a new fd for the current tmp file */
	int	i, j;

	/* discard all annonymous cut buffers */
	for (i = 0; i < NANNONS; i++)
	{
		cutfree(&annon[i]);
	}

	/* find the first named buffer that uses this tmp file */
	for (i = 0; i < 27; i++)
	{
		if (named[i].nblks > 0 && named[i].fd < 0)
		{
			break;
		}
	}

	/* if none of them use this tmp file, then we're done */
	if (i == 27)
	{
		return;
	}

	/* else we'll need this file and an fd a little longer */
		/* !!! we could use some error checking here */
#if MSDOS || TOS
	strcpy(cutname, o_directory);
	if ((j = strlen(cutname)) && !strchr(":/\\", cutname[j-1]))
		cutname[j++]=SLASH;
	close(tmpfd);
	fd = open(tmpname, O_RDONLY|O_BINARY);
	close(fd);
	sprintf(cutname+j, CUTNAME+3, getpid(), fd);
	rename(tmpname, cutname);
	fd = open(cutname, O_RDONLY|O_BINARY);
	tmpfd = -1; /* we'll try to close this in tmp.c, but who cares? */
#else
	fd = dup(tmpfd);
	sprintf(cutname, CUTNAME, o_directory, getpid(), fd);
	link(tmpname, cutname) || unlink(tmpname);
#endif

	/* have all cut buffers use the new fd instead */
	for (; i < 27; i++)
	{
		if (named[i].nblks > 0 && named[i].fd < 0)
		{
			named[i].fd = fd;
		}
	}
}


/* This function frees a cut buffer */
static cutfree(buf)
	struct cutbuf	*buf;
{
	char	cutname[50];
	int	i;

	/* return immediately if the buffer is already empty */
	if (buf->nblks <= 0)
	{
		return;
	}

	/* else free up stuff */
	buf->nblks = 0;
	free(buf->phys);

	/* see if anybody else needs this tmp file */
	if (buf->fd >= 0)
	{
		for (i = 0; i < 27; i++)
		{
#if	0
			if (named[i].nblks > 0 && named[i].fd >= 0)
#else
			if (named[i].nblks > 0 && named[i].fd == buf->fd)
#endif
			{
				break;
			}
		}
	}

	/* if nobody else needs it, then discard the tmp file */
	if (buf->fd >= 0 && i == 27)
	{
		close(buf->fd);
#if	MSDOS || TOS
		strcpy(cutname, o_directory);
		if ((i = strlen(cutname)) && !strchr(":/\\", cutname[i-1]))
			cutname[i++]=SLASH;
		sprintf(cutname+i, CUTNAME+3, getpid(), buf->fd);
#else
		sprintf(cutname, CUTNAME, o_directory, getpid(), buf->fd);
#endif
		unlink(cutname);
	}
}

/* This function should be called just before termination of vi */
cutend()
{
	int	i;

	/* free all named cut buffers, since they might be forcing an older
	 * tmp file to be retained.
	 */
	for (i = 0; i < 27; i++)
	{
		cutfree(&named[i]);
	}
}


/* This function is used to select the cut buffer to be used next */
cutname(name)
	int	name;	/* a single character */
{
	cbname = name;
}




/* This function copies a selected segment of text to a cut buffer */
cut(from, to)
	MARK	from;		/* start of text to cut */
	MARK	to;		/* end of text to cut */
{
	int		first;	/* logical number of first block in cut */
	int		last;	/* logical number of last block used in cut */
	long		line;	/* a line number */
	register struct cutbuf *cb;
	register long	l;
	register int	i;
	register char	*scan;
	char		*blkc;

	/* decide which cut buffer to use */
	if (!cbname)
	{
		/* free up the last annonymous cut buffer */
		cutfree(&annon[NANNONS - 1]);

		/* shift the annonymous cut buffers */
		for (i = NANNONS - 1; i > 0; i--)
		{
			annon[i] = annon[i - 1];
		}

		/* use the first annonymous cut buffer */
		cb = annon;
		cb->nblks = 0;
	}
	else if (cbname >= 'a' && cbname <= 'z')
	{
		cb = &named[cbname - 'a'];
		cutfree(cb);
	}
	else if (cbname == '.')
	{
		cb = &named[26];
		cutfree(cb);
	}
	else
	{
		msg("Invalid cut buffer name: \"%c", cbname);
		cbname = '\0';
		return;
	}
	cbname = '\0';
	cb->fd = -1;

	/* detect whether we're doing a line mode cut */
	cb->lnmode = (markidx(from) == 0 && markidx(to) == 0);

	/* ---------- */

	/* Reporting... */	
	if (markidx(from) == 0 && markidx(to) == 0)
	{
		rptlines = markline(to) - markline(from);
		rptlabel = "yanked";
	}

	/* ---------- */
blksync();
	/* find the first block in the cut */
	line = markline(from);
	for (first = 1; line > lnum[first]; first++)
	{
	}

	/* fetch text of the block containing that line */
	blkc = scan = blkget(first)->c;

	/* find the mark in the block */
	for (l = lnum[first - 1]; ++l < line; )
	{
		while (*scan++ != '\n')
		{
		}
	}
	scan += markidx(from);

	/* remember the offset of the start */
	cb->start = scan - blkc;

	/* ---------- */

	/* find the last block in the cut */
	line = markline(to);
	for (last = first; line > lnum[last]; last++)
	{
	}

	/* fetch text of the block containing that line */
	if (last != first)
	{
		blkc = scan = blkget(last)->c;
	}
	else
	{
		scan = blkc;
	}

	/* find the mark in the block */
	for (l = lnum[last - 1]; ++l < line; )
	{
		while (*scan++ != '\n')
		{
		}
	}
	if (markline(to) <= nlines)
	{
		scan += markidx(to);
	}

	/* remember the offset of the end */
	cb->end = scan - blkc;

	/* ------- */

	/* remember the physical block numbers of all included blocks */
	cb->nblks = last - first;
	if (cb->end > 0)
	{
		cb->nblks++;
	}
	cb->phys = (short *)malloc((unsigned)(cb->nblks * sizeof(short)));
	for (i = 0; i < cb->nblks; i++)
	{
		cb->phys[i] = hdr.n[first++];
	}
}


static readcutblk(cb, blkno)
	struct cutbuf	*cb;
	int		blkno;
{
	int		fd;	/* either tmpfd or cb->fd */

	/* decide which fd to use */
	if (cb->fd >= 0)
	{
		fd = cb->fd;
	}
	else
	{
		fd = tmpfd;
	}

	/* get the block */
	lseek(fd, (long)cb->phys[blkno] * (long)BLKSIZE, 0);
	if (read(fd, tmpblk.c, BLKSIZE) != BLKSIZE)
	{
		msg("Error reading back from tmp file for pasting!");
	}
}


/* This function inserts text from a cut buffer, and returns the MARK where
 * insertion ended.  Return MARK_UNSET on errors.
 */
MARK paste(at, after, retend)
	MARK	at;	/* where to insert the text */
	int	after;	/* boolean: insert after mark? (rather than before) */
	int	retend;	/* boolean: return end marker (rather than start) */
{
	register struct cutbuf	*cb;
	register int		i;

	/* decide which cut buffer to use */
	if (cbname >= 'a' && cbname <= 'z')
	{
		cb = &named[cbname - 'a'];
	}
	else if (cbname >= '1' && cbname <= '9')
	{
		cb = &annon[cbname - '1'];
	}
	else if (cbname == '.')
	{
		cb = &named[26];
	}
	else if (!cbname)
	{
		cb = annon;
	}
	else
	{
		msg("Invalid cut buffer name: \"%c", cbname);
		return MARK_UNSET;
	}

	/* make sure it isn't empty */
	if (cb->nblks == 0)
	{
		if (cbname)
			msg("Cut buffer \"%c is empty", cbname);
		else
			msg("Cut buffer is empty");
		cbname = '\0';
		return MARK_UNSET;
	}
	cbname = '\0';

	/* adjust the insertion MARK for "after" and line-mode cuts */
	if (cb->lnmode)
	{
		at &= ~(BLKSIZE - 1);
		if (after)
		{
			at += BLKSIZE;
		}
	}
	else if (after)
	{
		/* careful! if markidx(at) == 0 we might be pasting into an
		 * empty line -- so we can't blindly increment "at".
		 */
		pfetch(markline(at));
		if ( (markidx(at) != 0) || (plen != 0) )
		{
			if (IsHiBitOnMark(at)) {
				at += 2 ;
			} else {
				at ++ ;
			}
		}
	}

	/* put a copy of the "at" mark in the mark[] array, so it stays in
	 * sync with changes made via add().
	 */
	mark[27] = at;

	/* simple one-block paste? */
	if (cb->nblks == 1)
	{
		/* get the block */
		readcutblk(cb, 0);

		/* isolate the text we need within it */
		if (cb->end)
		{
			tmpblk.c[cb->end] = '\0';
		}

		/* insert it */
		ChangeText
		{
			add(at, &tmpblk.c[cb->start]);
		}
	}
	else
	{
		/* multi-block paste */

		ChangeText
		{
			i = cb->nblks - 1;

			/* add text from the last block first */
			if (cb->end > 0)
			{
				readcutblk(cb, i);
				tmpblk.c[cb->end] = '\0';
				add(at, tmpblk.c);
				i--;
			}

			/* add intervening blocks */
			while (i > 0)
			{
				readcutblk(cb, i);
				add(at, tmpblk.c);
				i--;
			}

			/* add text from the first cut block */
			readcutblk(cb, 0);
			add(at, &tmpblk.c[cb->start]);
		}
	}

	/* Reporting... */
	rptlines = markline(mark[27]) - markline(at);
	rptlabel = "pasted";

	/* correct the redraw range */
	redrawafter = preredraw = markline(at);
	postredraw = markline(mark[27]);

	/* return the mark at the beginning of inserted text */
	if (retend)
	{
		return mark[27] - 1L;
	}
	return at;
}
