static char rcsid[] = "$Id: line.c,v 1.1 1992/09/05 01:13:32 mike Exp $";

/* $Log: line.c,v $
 * Revision 1.1  1992/09/05  01:13:32  mike
 * Initial revision
 *
 */

/*
 * LINE.C
 *
 * The functions in this file are a general set of line management utilities.
 * They also touch the buffer and window structures, to make sure that the
 *   necessary updating gets done.
 */

/* Copyright 1990, 1991, 1992 Craig Durland
 *   Distributed under the terms of the GNU General Public License.
 *   Distributed "as is", without warranties of any kind, but comments,
 *     suggestions and bug reports are welcome.
 */

#include "me2.h"

#define NBLOCK  16	/* Line block chunk size */
    /* High Water mark:  when allocating, always leave at least this much
     *	 free space
     */
#define HW 5

extern Mark *znext_mark();
extern Window *window_on_buffer();

static int ljoin();
static void fixmarks(), hole(), insert_c();

	/* slide right part of line left, ie remove n chars @dot */
#define SLIDE(line,dot,n) \
     blkmov(line->l_text +dot, line->l_text +dot +n, llength(line) -dot -n)

/*
 * This routine allocates a block of memory large enough to hold a Line
 *   containing used characters.  The block is rounded up a bit if pad is
 *   TRUE.
 * Return a pointer to the new Line, or NULL if there isn't any memory
 *   left.
 * Print a message in the message line if not enough memory.
 */
Line *lalloc(used,pad) int used, pad;
{
  register Line   *lp;
  register int size = used;

  if (pad) { size = (used +HW +NBLOCK)/NBLOCK; size *= NBLOCK; }
	/* Remember that one char is in Line struct already! */
  if ((lp = (Line *)malloc(sizeof(Line) -1 +size)) == NULL)
	{ mlwrite("Cannot allocate %d bytes",size); return NULL; }
  lp->l_size = size; lp->l_used = used;
  return lp;
}

   /* Link new_line before lp2.  lp2 is already linked in.
    * Before:
    *   lp0 <-> lp2 <-> lp3
    * After:
    *   lp0 <-> new_line <-> lp2 <-> lp3
    */
static void llink_before(new_line,lp2) register Line *new_line, *lp2;
{
  lp2->l_prev->l_next = new_line;	/* lp0 -> new_line */

  new_line->l_next = lp2;		/* new_line -> lp2 */
  new_line->l_prev = lp2->l_prev;	/* lp0 <- new_line */

  lp2->l_prev = new_line;		/* new_line <- lp2 */
}

#if 0
   /* Link new_line over old_line.
    * Before:
    *   lp1 <-> old_line <-> lp2
    * After:
    *   lp1 <-> new_line <-> lp2
    */
static void llink_over(new_line, old_line) register Line *new_line, *old_line;
{
  old_line->l_prev->l_next = new_line;		/* lp1 -> new_line */

  new_line->l_next = old_line->l_next;		/* new_line -> lp2 */
  new_line->l_prev = old_line->l_prev;		/* lp1 <- new_line */

  old_line->l_next->l_prev = new_line;		/* new_line <- lp2 */
}
#endif

/*
 * This routine gets called when more than one line in current buffer is
 *   changed.  It updates all of the required flags in the buffer and window
 *   system.  The flag used is passed as an argument (it is almost always
 *   WFHARD).
 * Set MODE if the mode line needs to be updated.
 */
void lchange(flag) register int flag;
{
  if (!(curbp->b_flags & BFCHG))		/* First change, so */
  {
    flag |= WFMODE;				/* update mode lines */
    curbp->b_flags |= BFCHG;
  }
  fixWB(curbp,flag);	/* update all windows showing the current buffer */
}

/*
 * This routine gets called when a line in the current buffer is edited.  If
 *   windows displaying the current buffer have had more than one line
 *   edited, we need to do a HARD update.
 * lp is the line in the current buffer that has been edited.
 * Notes:
 *   If lp get freed, make sure that the window flags are set to HARD so it
 *     will be updated OK.  If not, we won't be able to find the changed
 *     line.
 *   You would think that I could blow off seting the window flags if the
 *     window was WFHARD.  But there is a problem in that different windows
 *     showing the same buffer may or may not be WFHARD - so you have to
 *     look at each window anyway so you might as well do all the work.
 */
static void elchange(lp) Line *lp;
{
  int flag = WFEDIT;
  register Window *wp;

  if (wp = window_on_buffer(curbp))
  {
    if ((wp->w_flag & WFEDIT) && lp != wp->elp) { lchange(WFHARD); return; }
  }
  else return;	/* no window displaying curbp => no updating needed */

  if (!(curbp->b_flags & BFCHG))		/* First change, so */
  {
    flag |= WFMODE;				/* update mode lines */
    curbp->b_flags |= BFCHG;
  }

  for ( ; wp; wp = wp->nextw)
    if (wp->wbuffer == curbp) { wp->w_flag |= flag; wp->elp = lp; }
}

/*
 * Insert n copies of a character at the current location of dot.  In the
 *   easy case all that happens is the text is stored in the line.  In the
 *   hard case, the line has to be reallocated.
 * Dot:  The current dot is left after the inserted characters.
 * Dots:  Don't move - they stay before the inserted characters.  This means
 *   you move them if they were after the current dot.
 * Marks:  Same as dots.  If a mark was set at dot and text inserted, we want
 *   the dot to be after the new next and the mark to be before.  This is
 *   convenient.  Trust me.
 * Return TRUE if everything went as expected, FALSE if can't alloc.
 */
linsert(n,c,overstrike) int n, overstrike; unsigned char c;
{
  Line *old_line, *new_line;
  int doto, n1, i;

  save_for_undo(BU_INSERT,(int32)n);

  old_line = the_dot->line;				/* Current line */
	/* At the end of buffer: special */
  if (old_line == BUFFER_LAST_LINE(curbp))
  {
    if ((new_line = lalloc(n,TRUE)) == NULL) return FALSE;
	/* link in new_line before header_line (old_line) */
    llink_before(new_line,old_line);
    insert_c(old_line,new_line, 0, n,c);
    return TRUE;
  }

  doto = the_dot->offset; n1 = n;			/* Save for later */
  if (overstrike)
  {
    i = doto +n;
    if (i <= llength(old_line)) goto stuff;
    if (i <= old_line->l_size) { old_line->l_used = i; goto stuff; }
    n -= llength(old_line) -doto;
  }
			/* Hard: reallocate */
  if (old_line->l_used+n > old_line->l_size)
  {
    if ((new_line = lalloc(old_line->l_used +n,TRUE)) == NULL) return FALSE;
    blkmov(new_line->l_text,old_line->l_text,doto);
	/* leave hole for new text */
    blkmov(new_line->l_text +doto +n, old_line->l_text +doto,
	   old_line->l_used -doto);

		/* link in new_line to replace old_line */
    old_line->l_prev->l_next = new_line;
    new_line->l_next = old_line->l_next;
    old_line->l_next->l_prev = new_line;
    new_line->l_prev = old_line->l_prev;
    free((char *)old_line);

    insert_c(old_line,new_line, doto, n1, c);
    return TRUE;
  }

			/* Easy: in place */
  hole(old_line,doto,n); old_line->l_used += n;

stuff:
  insert_c(old_line,old_line, doto, n,c);
  return TRUE;
}

   /* insert_c:  a helper routine for linsert() - inserts n characters at
    *   doto in new_line and then updates all the marks pointing to
    *   old_line.
    */
static void insert_c(old_line,new_line,doto,n,c)
  register Line *old_line, *new_line;
  int doto, n;
  unsigned char c;
{
  elchange(new_line);


  {
    register unsigned char *ptr;
    register int i;

    ptr = &new_line->l_text[doto]; i = n; while (i--) *ptr++ = c;
  }

		/* Update windows */
  {
    register Window *wp;

    for (wp = first_window; wp; wp = wp->nextw)
    {
      if (wp->top_line == old_line) wp->top_line = new_line;
      if (wp->dot.line == old_line)
      {
	wp->dot.line = new_line;
	if (wp->dot.offset > doto) wp->dot.offset += n;
      }
    }
  }
		/* Update marks */
  {
    register Mark *mark;

    zsetup_marks();

		/* Update the current dot */
    mark = znext_mark();
    mark->line = new_line; mark->offset += n;

    while (mark = znext_mark())
      if (mark->line == old_line)
      {
	mark->line = new_line;
	if (mark->offset > doto) mark->offset += n;
      }
  }
}

/*
 * Insert a newline into the buffer at the current dot.  The funny
 *   ass-backwards way it does things is not a botch; it just makes the last
 *   line in the file not a special case.
 * Dot/Marks should update same as linsert().
 * Dot:   is left after the newline, ie at the start of the next line.
 * Dots:  Same as marks.
 * Marks:  Stay put if they were before or at dot.  This makes the case
 *   where you set mark at dot and insert a block of text work "right" - the
 *   mark is at the start of the block and the dot is at the end.  Its the
 *   same behavior as inserting a character.
 * Return TRUE if everything works and FALSE on error (memory allocation
 *   failure).
 */
lnewline()
{
  register Line *dotp, *lp;
  register int doto;

  save_for_undo(BU_INSERT,(int32)1);

  lchange(WFHARD);	/* more than one line is going to change */

  dotp = the_dot->line; doto = the_dot->offset;
  if ((lp = lalloc(doto,FALSE)) == NULL)	/* New first half line */
	return FALSE;
  if (0 != doto)		/* only shuffle text around if have to */
  {
    blkmov(lp->l_text, dotp->l_text, doto);
    SLIDE(dotp,0,doto);
  }
  dotp->l_used -= doto;
  llink_before(lp,dotp);	/* link in lp before dotp */

  {				/* Update windows */
    register Window *wp;

    for (wp = first_window; wp; wp = wp->nextw)
    {
      if (wp->top_line == dotp) wp->top_line = lp;
      if (wp->dot.line == dotp)
      {
	if (wp->dot.offset <= doto) wp->dot.line = lp;
	else wp->dot.offset -= doto;
      }
    }
  }
  {				/* Update marks */
    register Mark *mark;

    zsetup_marks();

    mark = znext_mark();
    mark->offset = 0;			/* Update the current dot */

    while (mark = znext_mark())
      if (mark->line == dotp)
      {
	if (mark->offset <= doto) mark->line = lp;
	else mark->offset -= doto;
      }
  }

  return TRUE;
}

/* Adjust dots and marks when part of a line is deleted.
 * 3 cases (Dot at 1, 2 or 3):
 *                      <---n---->
 *   lp -> -------1----|------2---|----3-----
 *                    offset     post
 * n characters deleted starting at offset.
 * If case 1: Don't need to do anything.
 *         2: Move dot back to offset.
 *         3: Move dot left n characters.
 */
static void futz1(lp,offset,post,n) register Line *lp;
{
  register Mark *mark;
  register Window *wp;

  for (wp = first_window; wp; wp = wp->nextw)
    if (wp->dot.line == lp && offset < wp->dot.offset)
      if (post <= wp->dot.offset) wp->dot.offset -= n;		/* 3 */
      else wp->dot.offset = offset;				/* 2 */

  for (zsetup_marks(); mark = znext_mark(); )
    if (mark->line == lp && offset < mark->offset)
      if (post <= mark->offset) mark->offset -= n;		/* 3 */
      else mark->offset = offset;				/* 2 */
}

/* Adjust dots and marks in the case where so much text has been deleted
 *   after (lp,offset) that it has caused line lx to be deleted.  If a dot
 *   was on lx, it needs to be moved back to (lp,offset).
 */
static void futz2(lp,lx,offset) register Line *lp, *lx;
{
  register Mark *mark;
  register Window *wp;

  for (wp = first_window; wp; wp = wp->nextw)
  {
    if (wp->top_line == lp)   wp->top_line = lx;
    if (wp->dot.line == lp) { wp->dot.line = lx; wp->dot.offset = offset; }
  }
  for (zsetup_marks(); mark = znext_mark(); )
	if (mark->line == lp) { mark->line = lx; mark->offset = offset; }
}

/*
 * This function deletes n characters, starting at dot.  It understands how
 *   do deal with end of lines, etc.
 * Input
 *   n : number of characters to delete.  Note that this is a 32 bit int,
 *     make sure you pass it correctly.
 *   save_text:  TRUE if the text should be put in the cut buffer.
 * Returns:
 *   TRUE  if all of the characters were deleted.
 *   FALSE if there were problems (malloc failed).
 * Note that deleting characters at the end of the buffer is considered OK
 *   even though nothing is actually deleted.
 * Dot:  Doesn't move - stays at the start of the deletion.
 * Dots:  Several cases:  If before or at dot, act like dot.  If caught in
 *   the middle of a big deletion - move back to the start of the deletion.
 *   If after the deleted area, just stay put (move left the size of the
 *   deletion).
 * Marks:  Same as dots.
 */
ldelete(n,save_text) int32 n; int save_text;
{
  register Line *dotp, *lp, *next_line;
  register int doto, chunk, wflag = 0, s = FALSE, i,t,z;

  save_for_undo(BU_DELETE,n);
/* ??? how about a copy to bag for cut buffer and not screw with it
 * below? */

  dotp = lp = the_dot->line; doto = i = the_dot->offset;
  t = TRUE;
  while (TRUE)
  {
    if (lp == BUFFER_LAST_LINE(curbp))		/* Hit end of buffer */
	{ s = TRUE; break; }
    next_line = lforw(lp);
    chunk = z = llength(lp) -i; if (z > n) z = n;
    if (save_text && z && !bag_append(cut_buffer,&lp->l_text[i],z)) break;
    if (n <= chunk || t)	/* --xxx--, xxx, --xxx, xxx--- */
    {
      if (z)
      {
	wflag = WFEDIT;
	SLIDE(lp,i,z); lp->l_used -= z; n -= z;
	futz1(lp,i,i+z,z);
      }
      if (n <= 0) { s = TRUE; break; }
      i = 0; t = FALSE;
    }
    else	/* delete line */
    {
      futz2(lp,dotp,doto); n -= chunk;
      free((char *)lp);
    }
    lp = next_line;
    n--; if (save_text && !bag_append(cut_buffer,"\n",1)) break;
  }

  t = TRUE;
  if (dotp != lp) { t = ljoin(dotp,lp); lchange(WFHARD); }
  else if (wflag) elchange(lp);

  return (s && t);
}

/*
 * Join two lines in the current buffer.
 * If line1 is the magic header line always return TRUE; merging the last
 *   line with the header line can be thought of as always being a
 *   successful operation, even if nothing is done and this makes the cut
 *   buffer work "right".
 * If the next line will fit in the current line then shuffle data around.
 *   else have to allocate a new line that can hold both.
 * Return FALSE on error and TRUE if all looks ok.
 * Dot:   Doesn't move visually.
 * Dots:  Same as dot.
 * Marks: Same as dot.
 */
static int ljoin(lp1,lp2) Line *lp1, *lp2;
{
  int n;
  register Line *lp3;

  n = lp1->l_used;
  if (lp2 == BUFFER_LAST_LINE(curbp))	/* At the buffer end */
  {
    if (n == 0)	/* fold blank line into buffer end */
    {
      futz2(lp1,lp2,0);
      lp1->l_prev->l_next = lp2; lp2->l_prev = lp1->l_prev;
      free((char *)lp1);
      return TRUE;
    }
    n = TRUE;
  xx:
    lp1->l_next = lp2; lp2->l_prev = lp1;
    return n;
  }
		/* if lp2 will fit at end of lp1, pack 'em */
  if (lp2->l_used <= lp1->l_size -n)
  {
    blkmov(&lp1->l_text[n],lp2->l_text,lp2->l_used);
    lp1->l_used += lp2->l_used;
    lp1->l_next = lp2->l_next; lp2->l_next->l_prev = lp1;
    lp3 = lp1; lp1 = NULL;
  }
  else		/* no fit, alloc a line big enough to hold both */
  {
    if ((lp3 = lalloc(n +lp2->l_used, TRUE)) == NULL) { n = FALSE; goto xx; }
    blkmov(lp3->l_text, lp1->l_text, n);
    blkmov(lp3->l_text +n, lp2->l_text, lp2->l_used);
	/* Replace lp1 and lp2 with lp3 */
    lp1->l_prev->l_next = lp3; lp3->l_next = lp2->l_next;
    lp2->l_next->l_prev = lp3; lp3->l_prev = lp1->l_prev;
    free((char *)lp1);
  }
  free((char *)lp2);

  {
    register Window *wp;
    for (wp = first_window; wp; wp = wp->nextw)
    {
      if (wp->top_line == lp1 || wp->top_line == lp2) wp->top_line = lp3;
      if (wp->dot.line == lp2) { wp->dot.line = lp3; wp->dot.offset += n; }
      else if (wp->dot.line == lp1) wp->dot.line = lp3;
    }
  }
  if (lp1) fixmarks(lp1,lp3,0);
  fixmarks(lp2,lp3,n);

  return TRUE;
}

	/* open a hole of size n at dot in a line */
static void hole(line,dot,n) Line *line; int dot, n;
{
  register unsigned char *ptr, *qtr;
  register int x;

  qtr = &line->l_text[llength(line) -1];
  ptr = qtr +n;
  x = llength(line) -dot;
  while (x--) *ptr-- = *qtr--;
}

static void fixmarks(lp,lp1,n) Line *lp, *lp1; int n;
{
  register Mark *mark;

  for (zsetup_marks(); mark = znext_mark(); )
    if (mark->line == lp)
    {
      mark->line = lp1;
      mark->offset += n;
    }
}

/* This routine takes a line of text and a buffer pointer and appends the
 *   text to the end of the buffer.  This also effectively adds a newline to
 *   the end of the text (ie text is appended as a line).
 * WARNINGS:
 *   Don't pass this a \n!  Use "" instead if you want a blank line.
 *   Window flags are not updated - the caller has to take care of that.
 *     You better know what you are doing.
 * Returns:  TRUE is everything went as expected, FALSE if could not
 *   allocate a line.
 */
buffer_append(bp,text) Buffer *bp; char *text;
{
  int ntext;
  register Line *lp;

  ntext = strlen(text);
  if ((lp = lalloc(ntext,FALSE)) == NULL) return FALSE;
  blkmov(lp->l_text,text,ntext);
  llink_before(lp,BUFFER_LAST_LINE(bp));

  return TRUE;
}
