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

#include        "list.h"

#define NBLOCK  16                      /* Line block chunk size        */
#define NBLOCK1 256                     /* Line block chunk size for large */
					/* allocations.		       */
#ifndef KBLOCK
#define KBLOCK  256                     /* Kill buffer block size.      */
#endif
# define	LBLK(x)		((int) (NBLOCK - 1 + (x)) & ~(NBLOCK-1))
# define	LBLK1(x)	((int) (NBLOCK1 - 1 + (x)) & ~(NBLOCK1-1))

static	LINE	*hd_line = NULL;
int	global_dot;	/* Current offset (in bytes) from start of current line */
void	ldelete_dot PROTO((RSIZE, int));

void
free_line(lp)
register LINE *lp;
{
	vm_free((void *) lp, (void **) &hd_line);
}
# define	alloc_line() ((LINE *) vm_alloc(sizeof (LINE), (void **) &hd_line))
# define	free_line(lp) vm_free((void *) lp, (void **) &hd_line)
LINE    *
lalloc(used) 
register RSIZE used; 
{
	register LINE   *lp;
	register u_int32    size;
	static char	*msg = "Can't get %d bytes";

	size = (u_int32) LBLK(used);

	if ((lp = alloc_line()) == NULL) {
		ewprintf(msg, sizeof(LINE));
		return NULL;
		}
	if (size == 0)
		lp->l_text = NULL;
	else if ((lp->l_text =  (u_char *) chk_alloc(size)) == NULL) {
		free_line(lp);
		ewprintf(msg, size);
		return NULL;
		}
	lp->l_size = size;
	lp->l_lineno = 0;
	/*NOSTRICT*/
	lp->l_used = (u_int32) used;
	return lp;
}
/**********************************************************************/
/*   Delete  a  line  from  a  buffer.  The  line is the actual line  */
/*   number to delete.						      */
/**********************************************************************/
void
lfree(buf, line) 
BUFFER	*buf;
int line;
{	LINE	*lp;
	BUFFER	*saved_bp = curbp;

	curbp = buf;
	lp = linep(line);
	curbp = saved_bp;
	lfree_line(buf, lp, line);
}
/**********************************************************************/
/*   Delete a line from a buffer, given a pointer to the line.	      */
/**********************************************************************/
void
lfree_line(buf, lp, line)
BUFFER	*buf;
register LINE	*lp;
register int	line;
{
	register WINDOW *wp;
	LINE	*next_line;

	next_line = lp->l_fp;

	for (wp = wheadp; wp; wp = wp->w_wndp) {
		if (wp->w_bufp != buf)
			continue;
		if (wp->w_top_line > line)
			wp->w_top_line--;
		if (wp->w_line > line) {
			wp->w_line--;
			wp->w_col = 1;
			}
		}
	if (buf->b_line > line) {
		buf->b_line--;
		buf->b_col = 1;
		}
	buf->b_numlines--;
	lp->l_bp->l_fp = next_line;
	next_line->l_bp = lp->l_bp;
	if (lp->l_text && (lp->l_flags & L_FILE) == 0)
		chk_free((void *) lp->l_text);
	free_line(lp);
	flush_cache(buf);
}

void
lchange(flag) 
register int flag; 
{	register WINDOW *wp;

	curbp->b_flag |= BFCHG;

	for (wp = wheadp; wp; wp = wp->w_wndp)
		if (wp->w_bufp == curbp)
			wwin_modify(wp, wp == curwp ? flag :
				flag == WFDELL ? WFHARD : flag);
}
int
l_insert(ch)
int	ch;
{	char	buf[2];

	if (ch == '\n')
		lnewline();
	else {
		buf[0] = (char) ch;
		buf[1] = NULL;
		if (rdonly())
			return FALSE;
		lchange(WFEDIT);
		llinsert(buf, (u_int32) 1, FALSE);
		lchange(WFEDIT);
		}
	return TRUE;
}
int
linsert(str, n)
char	*str;
int	n;
{	register char *cp;
	register u_int32	len;
	int	nline = 0;

	if (rdonly())
		return -1;

	while (n > 0) {
		int	nl = FALSE;
		char	*cpend = str + n;
		for (cp = str; cp < cpend; cp++) {
			if (*cp == '\n') {
				nl = TRUE;
				break;
				}
			}
		len = cp - str;
		n -= len + 1;
/*		if (len && strip_cr_flag && str[len-1] == '\r')
			len--;*/
		if (llinsert(str, len, nl) == FALSE)
			return -1;
		str = cp + 1;
		nline += nl;
		}
	return nline;
}
int
llinsert(cp, len, nl) 
char	*cp;
u_int32	len;
int	nl;
{	extern LINE *global_lp;
	register LINE   *lp1;
	register LINE   *lp2;
	register u_int32    tdoto;
	int	line;

	lchange(WFEDIT);
	line = *cur_line;
	lpad_lines();
	if (len && *cur_line == curbp->b_numlines) {
		if ((lp2=lalloc(len)) == NULL) {
false_exit:
			vm_unlock(*cur_line);
			return FALSE;
			}
		u_delete((RSIZE) 1);
		lp1 = vm_lock_line(line);
		curbp->b_numlines++;
		curbp->b_cline++;
		lp2->l_fp = lp1;
		lp2->l_bp = lp1->l_bp;
		lp1->l_bp->l_fp = lp2;
		lp1->l_bp = lp2;

		lp1 = lp2;
		lp1->l_used = 0;
		vm_unlock(*cur_line + 1);
		}

	tdoto = current_offset(*cur_col, TRUE);
	lp1 = global_lp;
	if (len) {
		u_delete((RSIZE) len);
		if (lrealloc(lp1, tdoto, len) == FALSE)
			goto false_exit;
		memcpy(&lp1->l_text[tdoto], cp, len);
		vm_unlock(line);
		}

	if (nl) {
		lnewline1(lp1, tdoto+len);
		}
	else {
		global_dot = tdoto + len;
		*cur_col = current_col(global_dot);
		}
	lchange(WFEDIT);
	return TRUE;
}
/**********************************************************************/
/*   Function   to   create   new  zero-length  lines  when  we  are  */
/*   inserting on a line thats past the end of the buffer.	      */
/**********************************************************************/
void
lpad_lines()
{	LINE	*lp2, *lp1;
	int	line = *cur_line;
	int	col;

	if (line <= curbp->b_numlines)
		return;
	/***********************************************/
	/*   Write   out  the  undo  information  for  */
	/*   these appended lines.		       */
	/***********************************************/
	u_dot();
	*cur_line = curbp->b_numlines;
	col = *cur_col;
	*cur_col = 1;
	u_delete((RSIZE) (line - curbp->b_numlines));
	*cur_col = col;
	*cur_line = line;

	lp1 = vm_lock_line(curbp->b_numlines);
	while (curbp->b_numlines < line) {
		lp2 = lalloc(0);
		lp2->l_used = 0;
		curbp->b_numlines++;
		lp2->l_bp = lp1;
		lp2->l_fp = lp1->l_fp;
		lp1->l_fp->l_bp = lp2;
		lp1->l_fp = lp2;

		lp1 = lp2;
		}
	curbp->b_linep = lp1;
	lchange(WFHARD);
	flush_cache(curbp);
}
int
lrealloc(lp, dot, len)
register	LINE	*lp;
int	dot;
u_int32	len;
{
	register char	*cp1;
	register char	*cp2;
	register char	*cp3;

	if (lp->l_flags & L_FILE)
		lnormal(lp, len);
	if (lp->l_used+len > lp->l_size) {
		int newlen = lp->l_used + len;
		int size = (lp->l_used > NBLOCK1 ? LBLK1(newlen) : LBLK(newlen));
		if (lp->l_text)
			lp->l_text = (u_char *) chk_realloc((void *) lp->l_text, size);
		else
			lp->l_text = (u_char *) chk_alloc(size);
		lp->l_size = size;
		} 
	cp1 = (char *) &lp->l_text[lp->l_used];
	cp2 = cp1 + len;
	cp3 = (char *) &lp->l_text[dot];
	while (cp1 != cp3)
		*--cp2 = *--cp1;
	lp->l_used += len;
	return TRUE;
}
void
lnormal(lp, len)
register LINE *lp;
u_int32	len;
{	register char *cp1;
	
	if ((lp->l_flags & L_FILE) == 0)
		return;
	lp->l_size = LBLK(lp->l_used) + len;
	cp1 = chk_alloc(lp->l_size);
	memcpy(cp1, lp->l_text, lp->l_used);
	lp->l_text = (u_char *) cp1;
	lp->l_flags &= ~L_FILE;
}
void
lnewline()
{

	if (rdonly())
		return;

	lchange(WFINSL);
	lpad_lines();
	lnewline1(vm_lock_line(*cur_line), 
		(u_int32) current_offset(*cur_col, FALSE));
}
int
lnewline1(lp1, tdoto)
register LINE *lp1;
register u_int32 tdoto;
{	register LINE   *lp2;

	lchange(WFINSL);
	u_delete((RSIZE) 1);
	if ((lp2=lalloc((RSIZE) (lp1->l_used - tdoto))) == NULL) {
		vm_unlock(*cur_line);
		return FALSE;
		}
	if (*cur_line == curbp->b_numlines)
		curbp->b_linep = lp2;
	curbp->b_numlines++;

	if (tdoto < lp1->l_used) {
		memcpy(lp2->l_text, lp1->l_text + tdoto, lp1->l_used - tdoto);
		lp2->l_used = lp1->l_used - tdoto;
		lp1->l_used = tdoto;
		}

	lp2->l_bp = lp1;
	lp2->l_fp = lp1->l_fp;
	lp1->l_fp->l_bp = lp2;
	lp1->l_fp = lp2;

	vm_unlock(*cur_line);
	vm_unlock(*cur_line + 1);
	(*cur_line)++;
	*cur_col = 1;
	global_dot = 0;
	lchange(WFINSL);
	ladjust();
	return TRUE;
}
void
ladjust()
{	register WINDOW *wp;

	for (wp = wheadp; wp; wp = wp->w_wndp) {
		if (wp->w_bufp != curbp || wp == curwp)
			continue;
		if (wp->w_line >= *cur_line)
			wp->w_line++;
		if (wp->w_top_line >= *cur_line)
			wp->w_top_line++;
		if (wp->w_mined >= *cur_line)
			wp->w_mined++;
		if (wp->w_maxed >= *cur_line)
			wp->w_maxed++;
		}

	/***********************************************/
	/*   If  current  buffer  has  a marked area,  */
	/*   then   update  the  the  end  of  region  */
	/*   pointer if it ends after the cursor.      */
	/***********************************************/
	if (curbp->b_anchor) {
		extern int end_line;
		get_marked_areas(curwp);
		if (end_line > *cur_line)
			curbp->b_anchor->a_line++; 
		}
}

/**********************************************************************/
/*   CHUNK_SIZE  is  the  size to read the file in so we can read it  */
/*   in  in  pieces.  On  16-bit  machines we cannot read the entire  */
/*   file  in  in one go otherwise we would be restricted to reading  */
/*   in  files  no  larger  than 64K. To make life easier, on 32-bit  */
/*   machines  we  can  read  in  the  file  in one chunk. On 16 bit  */
/*   machines  we  are restricted to reading in files with each line  */
/*   no longer than CHUNK_SIZE long.				      */
/*   								      */
/*   On  32-bit  machines  we read the file in chunks so that we can  */
/*   get  the  percentage  message  printed  so  we  can  watch  the  */
/*   progress on long files.					      */
/**********************************************************************/
# if	defined(WORD_SIZE_16)
# define	CHUNK_SIZE	(28 * 1024)
# else
# define	CHUNK_SIZE	(28 * 1024)
/*# define	CHUNK_SIZE	(1 * 1024 * 1024 * 1024)*/
# endif

/**********************************************************************/
/*   Look  at  TEST_SIZE  bytes at beginning of file to determine if  */
/*   its binary or not.						      */
/**********************************************************************/
# define	TEST_SIZE	128

/**********************************************************************/
/*   If  more  than  this  percentage  are binary, then assume whole  */
/*   file is binary						      */
/**********************************************************************/
# define	TEST_PERCENT	10

/**********************************************************************/
/*   Size of lines for binary files.				      */
/**********************************************************************/
int	binary_chunk_size = 16;

int
lreadin_file(fd, size, fname, flags)
int	fd;
unsigned long	size;
char *fname;
int	flags;
{
	register char	*bp, *bp1, *bpend;
	unsigned long	filesize = size;
	char	*buf;
	char	**chunk_list = curbp->b_chunk;
	unsigned long	pos = 0;
	LINE	*lp, *clp;
	int	nline = curbp->b_numlines;
	int	current_line = *cur_line;
	RSIZE	nbytes = 0;
	int	long_line = FALSE;
	
	clp = linep(*cur_line);
	u_dot();
	*cur_col = 1;
	/***********************************************/
	/*   Make  sure  we  dont  get  any  progress  */
	/*   messages for at least 1 second.	       */
	/***********************************************/
	second_passed();
	while (size > 0) {
		int len = size > CHUNK_SIZE ? CHUNK_SIZE : size;
		size -= len;
		
		/***********************************************/
		/*   Allocate  each  chunk  of  the file on a  */
		/*   list   which   can  be  freed  when  the  */
		/*   buffer is cleared (in bclear).	       */
		/***********************************************/
		buf = (char *) chk_alloc(sizeof (char *) + len);
		*(char ***) buf = chunk_list;
		chunk_list = (char **) buf;
		
		/***********************************************/
		/*   Skip past the link pointer.	       */
		/***********************************************/
		buf += sizeof (char *);
		len = sys_read(fd, buf, len);
		
		/***********************************************/
		/*   See  if  we  can  guess the type of file  */
		/*   (binary  or  text)  by  looking at first  */
		/*   part  of  file  and  if too many illegal  */
		/*   characters   present,   then  go  for  a  */
		/*   binary mode.			       */
		/***********************************************/
		if (pos == 0 && binary_chunk_size > 0 && flags == EDIT_NORMAL) {
			int	bins = 0;
			int	size = len > TEST_SIZE ? TEST_SIZE : len;
			int	i = size;
			while (i-- > 0) 
				if (buf[i] == 0 || buf[i] & 0x80)
					bins++;
			if (bins > (TEST_PERCENT * size) / 100)
				flags = EDIT_BINARY;
			}
#if 0
		/* JDJ - check for DOS style CR-LF */
		if (flags == EDIT_NORMAL) {
			int i = 0;
			while (buf[i] != '\n' && i < 1000) 
				i++;
			if (buf[i-1] == '\r')
				flags = EDIT_CR;
		}
		/* JDJ - end of hack */
#endif
		pos += len;
		bp = buf;
		bpend = buf + len;
		while (bp < bpend) {
			/***********************************************/
			/*   If  file  is binary then make lines into  */
			/*   the specified chunk size.		       */
			/***********************************************/
			if (flags & EDIT_BINARY) {
				bp1 = bp + binary_chunk_size;
				if (bp1 >= bpend)
					bp1 = bpend;
				}
			else {
				for (bp1 = bp; bp1 < bpend; bp1++)
					if (*bp1 == '\n')
						break;
				if (long_line) {
					flush_cache(curbp);
					*cur_line = current_line + curbp->b_numlines - nline - 1;
					*cur_col = current_col(clp->l_bp->l_used);
					llinsert(bp, (u_int32) (bp1 - bp), FALSE);
					long_line = bp1 >= bpend;
					lp = clp;
					goto continue_loop;
					}
				if (bp1 >= bpend) {
					long diff = bpend - bp;
					if (size) {
						if (bp != buf) {
							size += diff;
							pos -= diff;
							lseek(fd, pos, 0);
							long_line = FALSE;
							break;
							}
						else {
							long_line = TRUE;
							}
						}
					}
				}
			lp = lalloc((RSIZE) 0);
			lp->l_flags |= L_FILE;
			lp->l_text = (u_char *) bp;
			lp->l_size = lp->l_used = bp1 - bp;
			nbytes += lp->l_size + 1;
			
			lp->l_fp = clp;
			lp->l_bp = clp->l_bp;
			
			clp->l_bp = lp;
			lp->l_bp->l_fp = lp;
			
			curbp->b_numlines++;
continue_loop:
			if (flags & EDIT_BINARY) {
				bp = bp1;
				}
			else {
				if (flags & EDIT_CR && bp[lp->l_used-1] == '\r')
					lp->l_used--;
				bp = bp1+1;
				}
			if ((curbp->b_flag & BF_SYSBUF) == 0)
				percentage(pos, filesize, "Reading", fname);
			}
		}
	flush_cache(curbp);
	curbp->b_chunk = chunk_list;
	if (flags & EDIT_BINARY)
		curbp->b_flag |= BFBINARY;
	if (flags & EDIT_CR)
		curbp->b_flag |= BF_CR_MODE;
	nline = curbp->b_numlines - nline;
	u_delete((RSIZE) nbytes);
	*cur_line = current_line + nline;
	return nline;
}
/**********************************************************************/
/*   D*  elete  'n'  characters from the current cursor position. We  */
/*   do  checki  first  to  see  whether  file is read-only and call  */
/*   ldelete_dot()  with  a  character  offset  rather than a column  */
/*   position.							      */
/**********************************************************************/
void
ldelete(n) 
RSIZE n;
{
	if (rdonly() || *cur_line >= curbp->b_numlines)
		return;
	ldelete_dot(n, current_offset(*cur_col, FALSE));
}
/**********************************************************************/
/*   Low  level  primitive to delete characters from a line possibly  */
/*   spanning  lines).  All error checking has been done and we have  */
/*   an index into a character position into the line.		      */
/**********************************************************************/
void
ldelete_dot(n, dot)
RSIZE	n;
int	dot;
{	register LINE	*this_line = vm_lock_line(*cur_line);
	register LINE	*next_line = NULL;
	register LINE	*last_line = curbp->b_linep;

	u_insert(n, dot);
	/***********************************************/
	/*   Handle  case  where we delete less bytes  */
	/*   than there are in the line.	       */
	/***********************************************/
	if (n <= this_line->l_used - dot) {
		memcpy(&this_line->l_text[dot], 
			&this_line->l_text[dot+n], 
			this_line->l_used - dot - n);
		this_line->l_used -= n;
		lchange(WFEDIT);
		return;
		}

	/***********************************************/
	/*   Handle  case  where  we  going to delete  */
	/*   over   multiple  lines.  First  subtract  */
	/*   number of bytes remaining after cursor.   */
	/***********************************************/
	n -= this_line->l_used - dot;
	this_line->l_used = dot;
	/***********************************************/
	/*   Set   flag   for  display  code.  If  we  */
	/*   delete    entire   current   line   then  */
	/*   display  code  can maybe optimise and do  */
	/*   a screen delete line.		       */
	/***********************************************/
	if (dot == 0 && n == this_line->l_used + 1 && curbp->b_anchor == NULL)
		lchange(WFDELL);
	else
		lchange(WFHARD);
	/***********************************************/
	/*   Now   keep   deleting  subsequent  lines  */
	/*   until we've finished the delete.	       */
	/***********************************************/
	while (n > 0 && (next_line = this_line->l_fp) != last_line) {
		if (--n <= next_line->l_used) {
			if (n)
				memcpy(next_line->l_text, 
					&next_line->l_text[n], 
					next_line->l_used - n);
			next_line->l_used -= n;
			lrealloc(this_line, this_line->l_used, next_line->l_used);
			memcpy(&this_line->l_text[dot], next_line->l_text,
				next_line->l_used);
			lfree_line(curbp, next_line, *cur_line + 1);
			break;
			}
		n -= next_line->l_used;
		lfree_line(curbp, next_line, *cur_line + 1);
		}
	if (n && dot == 0 && next_line == last_line)
		lfree(curbp, *cur_line);
}
/**********************************************************************/
/*   Function to replace text in a buffer ==> do a delete + insert.   */
/*   Handle  the  most common case of an insert <= size of deletion.  */
/*   This  function  only  gets  called  on a translate and can be a  */
/*   big  win  on  a  global  translate because we can also keep the  */
/*   undo state pretty small.					      */
/*   								      */
/*   Note  that  we  dont  set  cur_col for the common case, because  */
/*   this is very expensive.					      */
/**********************************************************************/
int
lreplace(str, bytes_to_insert, bytes_to_delete, dot)
char	*str;
int	bytes_to_insert;
RSIZE	bytes_to_delete;
int	dot;
{	
	register LINE *this_line;
	int	diff;

	*cur_col = current_col(dot);
	if (str[0] == NULL) {
		ldelete_dot(bytes_to_delete, dot);
		global_dot = dot;
		return 0;
		}	
	/***********************************************/
	/*   If  new  string is longer, or contains a  */
	/*   newline  then  do  a  delete followed by  */
	/*   an insert.				       */
	/***********************************************/
	diff = bytes_to_delete - bytes_to_insert;
	if (diff < 0 || strchr(str, '\n') != NULL) {
		ldelete_dot(bytes_to_delete, dot);
		return linsert(str, bytes_to_insert);
		}
	this_line = vm_lock_line(*cur_line);

	u_replace((char *) &this_line->l_text[dot], (RSIZE) bytes_to_delete, (RSIZE) bytes_to_insert);
	if (diff)
		memcpy(&this_line->l_text[dot + bytes_to_insert],
			&this_line->l_text[dot + bytes_to_delete],
			llength(this_line) - dot - bytes_to_delete);
	lchange(WFEDIT);
	memcpy(&this_line->l_text[dot], str, bytes_to_insert);
	global_dot = dot + bytes_to_insert;
	this_line->l_used -= diff;
	return 0;
}
int
space_fill(lp, num, start, col)
LINE	*lp;
int	num;
int	start;
int	col;
{	register u_char *cp1;
	register u_char *cp2;
	register int num_to_copy;
	int	num_tabs = 0;
	int	size;
	int	saved_col = *cur_col;
	int	sizeof_space = cur_cmap->cm_length[' '];

	/***********************************************/
	/*   We  can  tab fill if the buffer is using  */
	/*   hard-tabs  and  the  character  map says  */
	/*   that tabs are special.		       */
	/***********************************************/
	if (curbp->b_flag & BFTABS && col && cur_cmap->cm_flags['\t'] == 1) {
		int	tcol = col - 1;
		while (num > 2) {
			int tab_width = next_tab_stop(tcol + 1) - tcol;
			if (tab_width > num)
				break;
			num_tabs++;
			tcol += tab_width;
			num -= tab_width;
			}
		num /= sizeof_space;
		num += num_tabs;
		}
	else
		num /= sizeof_space;
	u_dot();
	*cur_col = col;
	u_delete((RSIZE) num);
	*cur_col = saved_col;
	lnormal(lp, (u_int32) 0);
	if (lp->l_size - lp->l_used < num) {
		if (lp->l_size)
			lp->l_text = (u_char *) chk_realloc((void *) lp->l_text, LBLK(lp->l_used + num));
		else
			lp->l_text = (u_char *) chk_alloc(num);
		}

	num_to_copy = lp->l_used - start;
	cp1 = &lp->l_text[lp->l_used];
	cp2 = cp1 + num;
	while (num_to_copy-- > 0)
		*--cp2 = *--cp1;

	lp->l_used += num;
	cp1 = &lp->l_text[start];
	size = num;
	num -= num_tabs;
	while (num_tabs-- > 0)
		*cp1++ = '\t';
	while (num-- > 0) {
		*cp1++ = ' ';
		}
	return start + size;
}
void
renumber_lines(bp)
register BUFFER	*bp;
{
	register LINE	*lp;
	register u_int16 line_no = 1;
	LINE	*end_lp = bp->b_linep;
	
	for (lp = lforw(bp->b_linep); lp != end_lp; lp = lforw(lp)) {
		lp->l_lineno = line_no++;
		}
}
void
set_binary_size()
{
	acc_assign_int((long) binary_chunk_size);
	binary_chunk_size = (int) argv[1].l_int;
}
