/*
*      Basic cursor motion commands.
* The routines in this file are the basic
* command functions for moving the cursor around on
* the screen, setting mark, and swapping dot with
* mark. Only moves between lines, which might make the
* current buffer framing bad, are hard.
*/

#include    "def.h"

bool move_ptr ();
bool forwchar ();
bool wind_on_dot ();
bool backline ();

extern char MSG_mark_set[];
extern char MSG_no_mark[];
extern char MSG_go_b_n[];
extern char MSG_bad_num[];
#if RUNCHK
extern char ERR_bas_1[];
#endif
extern char MSG_lX[];
extern char MSG_lO[];
extern char MSG_lD[];


extern bool rplc_mode;

/*  pvr
* Move cursor backwards. Do the
* right thing if the count is less than
* 0. Error if you try to move back from
* the beginning of the buffer.
*/
bool
backchar (f, n, k)
    register int f, n, k;
{
    if (n < 0)
	return (forwchar (f, -n, KRANDOM));

    while (n--)
    {
	if (curwp->w_unit_offset == 0)
	{
	    if (!move_ptr (curwp, -(long) R_B_PER_U (curwp),
			   TRUE, TRUE, TRUE))
		return (FALSE);

	    /* step to previous unit */
	    curwp->w_unit_offset = R_CHR_PER_U (curwp) - 1;

	    /* if before first line in window then move window */
	    wind_on_dot (curwp);
	}
	else
	    curwp->w_unit_offset--;
    }
    curwp->w_flag |= WFMODE;	/* update mode line */
    return (TRUE);
}

/*  pvr
* Move cursor forwards. Do the
* right thing if the count is less than
* 0. Error if you try to move forward
* from the end of the buffer.
*/
bool
forwchar (f, n, k)
    register int f, n, k;
{
    if (n < 0)
	return (backchar (f, -n, KRANDOM));

    curwp->w_flag |= WFMODE;	/* update mode line */
    while (n--)
    {
	if (curwp->w_unit_offset >= (R_CHR_PER_U (curwp) - 1))
	{
	    /* move to the mext unit */
	    curwp->w_unit_offset = 0;

	    if (!move_ptr (curwp, (long) R_B_PER_U (curwp),
			   TRUE, TRUE, TRUE))
	    {
		/* I am at the the end of the buffer */
		return (FALSE);
	    }

	    /* if after the last line in window then move window */
	    wind_on_dot (curwp);
	}
	else if			/* if at last byte of buffer then do not step  */
	    (DOT_POS (curwp) < BUF_SIZE (curwp))
	    curwp->w_unit_offset++;	/* step within unit */
    }
    return (TRUE);
}

/*  pvr
*   This function moves the specified pointer by the ammount specified
*   in 'len'.   Move the dot pointer is 'dot' is true, else move
*   the window pointer.  Do the fix up if 'fix' is TRUE.
*   This is a relative move if 'rel' is TRUE, else it is an
*   absolute move.
*/

bool
move_ptr (wp, len, dot, fix, rel)
    WINDOW *wp;
    long len;
    bool dot, fix, rel;
{
    A32 cur_pos, dest_pos, fix_val, last_pos;
    long rel_pos;
    A32 last_fixed_pos, align;
    LINE **line;
    int *l_off;
    char shift;
    bool no_limit;

    no_limit = TRUE;
    if (dot)
    {				/* move dot position */
	l_off = (int *) &wp->w_doto;
	line = &wp->w_dotp;
	align = R_SIZE (wp);	/* bytes -1 in a unit */
    }
    else
    {				/* move window position */
	l_off = (int *) &wp->w_loff;
	line = &wp->w_linep;
	align = R_ALIGN (wp) - 1;	/* interval of bytes to align window */
    }

    /* get the current position in the buffer */
    cur_pos = (*line)->l_file_offset + *l_off;

    if (rel)
    {
	rel_pos = len;
	dest_pos = len + cur_pos;	/* destination position */
    }
    else
    {
	rel_pos = len - cur_pos;/* relative move amount */
	dest_pos = len;		/* destination position */
    }
    if (fix)
    {
	shift = wp->w_disp_shift;

	/* limit at begining */
	if ((long) dest_pos < (long) shift)
	{
	    rel_pos = shift - cur_pos;
	    no_limit = FALSE;
	}
	else
	{
	    /* calculate fixed up destination position */
	    fix_val = dest_pos &= ~align;
	    fix_val += shift;

	    /* calculate the last position in the buffer */
	    last_pos = BUF_SIZE (wp);
	    if (last_pos < (last_fixed_pos = (last_pos & ~align) + shift))
		last_pos = last_fixed_pos - align - 1;

	    /* if we are going to limit at the end of the buffer */
	    if (last_pos < fix_val)
	    {
		fix_val = last_pos;
		no_limit = FALSE;
	    }
	    rel_pos = fix_val - cur_pos;
	}
    }
    while (TRUE)
    {
	if (rel_pos < 0)	/* move  backward through buffer */
	{
	    /* current line? */
	    if (*l_off + rel_pos >= 0)
	    {
		*l_off += (short) rel_pos;
		return (no_limit);
	    }
	    /* are we at the first line */
	    if ((*line)->l_bp->l_size != 0)
	    {			/* no, so step back */
		rel_pos += *l_off;
		(*line) = (*line)->l_bp;	/* move back one line */
		*l_off = (*line)->l_used;
	    }
	    else
	    {			/* yes, limit at the begining */
		*l_off = 0;
		return (FALSE);
	    }
	}
	else
	    /* move forward through buffer */
	{
	    /* is in current line? */
	    if (((A32) (*l_off) + rel_pos) < ((A32) ((*line)->l_used)))
	    {
		*l_off += (short) rel_pos;
		return (no_limit);
	    }
	    if ((*line)->l_fp->l_size != 0)
	    {
		rel_pos -= (*line)->l_used - *l_off;
		*l_off = 0;
		(*line) = (*line)->l_fp;	/* move forward one line */
	    }
	    else
	    {
		*l_off = (*line)->l_used;	/* at last line so limit it */
		return (FALSE);
	    }
	}
    }
}

/*  pvr
*   Move the window so that the dot is within it's
*   area.   Return TRUE if window was moved.
*/

bool
wind_on_dot (wp)

    WINDOW *wp;
{
    long diff, incr;
    A32 d_offs, w_start, bytes, align;

    /* number of bytes in a row */
    bytes = R_BYTES (wp);
    /* number of bytes to align on */
    align = R_ALIGN (wp);
    /* offset of window from start of the buffer */
    w_start = WIND_POS (wp);
    /* offset of dot from start of the buffer */
    d_offs = DOT_POS (wp);
    /* calculate the amount to move that is 1/3 of the window */
    incr = bytes * wp->w_ntrows / 3;
    /* if dot is before first line in window */
    if ((diff = (d_offs - w_start)) < 0)	/* diff used later */
    {
	move_ptr (wp, diff - incr, FALSE, TRUE, TRUE);
	wp->w_flag |= WFHARD;
	return (TRUE);
    }
    /* if dot is after the last line in window */
    if (0 < (diff -= (wp->w_ntrows * bytes - 1)))
    {
	if (align != 1)
	    diff = (diff & ~(align - 1)) + align;
	move_ptr (wp, diff + incr, FALSE, TRUE, TRUE);
	wp->w_flag |= WFHARD;
	return (TRUE);
    }
    /* is window aligned? */
    if (w_start != ((w_start & ~(align - 1)) + wp->w_disp_shift))
    {				/* if no then move into alignment */
	move_ptr (wp, 0L, FALSE, TRUE, TRUE);
	wp->w_flag |= WFHARD;
	return (TRUE);
    }
    return (FALSE);
}

/*  pvr
* Go to the beginning of the
* buffer. Setting WFHARD is conservative,
* but almost always the case.
*/
bool
gotobob ()
{
    move_ptr (curwp, 0L, TRUE, TRUE, FALSE);	/* move dot */
    move_ptr (curwp, 0L, FALSE, TRUE, FALSE);	/* move window */
    curwp->w_unit_offset = 0;
    curwp->w_flag |= WFHARD;
    return (TRUE);
}

/*  pvr
* Go to the end of the buffer.
* Setting WFHARD is conservative, but
* almost always the case.
* Dot is one byte past the end of the buffer.
*/
bool
gotoeob ()
{
    move_ptr (curwp, BUF_SIZE (curwp), TRUE, TRUE, FALSE);	/* move dot */
    curwp->w_unit_offset = 0;
    wind_on_dot (curwp);
    return (TRUE);
}

/*  pvr
* Move forward by full lines.
* If the number of lines to move is less
* than zero, call the backward line function to
* actually do it. The last command controls how
* the goal column is set.
*/
bool
forwline (f, n, k)
    int f, n, k;
{
    if (n < 0)
	return (backline (f, -n, KRANDOM));

    if (rplc_mode)
    {
	next_pat ();
    }
    else
    {
	/* move dot */
	if (!move_ptr (curwp, (long) R_BYTES (curwp) * n,
		       TRUE, TRUE, TRUE))
	    curwp->w_unit_offset = 0;
	wind_on_dot (curwp);
	curwp->w_flag |= WFMODE;/* update mode line */
    }
    return (TRUE);
}

/*  pvr
* This function is like "forwline", but
* goes backwards. The scheme is exactly the same.
* Check for arguments that are less than zero and
* call your alternate. Figure out the new line and
* call "movedot" to perform the motion.
*/
bool
backline (f, n, k)
    int f, n, k;
{
    if (n < 0)
	return (forwline (f, -n, KRANDOM));

    if (rplc_mode)
    {
	next_pat ();
    }
    else
    {
	if (!move_ptr (curwp, -((long) (R_BYTES (curwp) * n)),
		       TRUE, TRUE, TRUE))
	    curwp->w_unit_offset = 0;

	/* is dot before the top of window? */
	wind_on_dot (curwp);
	curwp->w_flag |= WFMODE;/* update mode line */
    }
    return (TRUE);
}

/*  pvr
* Scroll forward by a specified number
* of lines, or by a full page if no argument.
* (KRW) Added cursor (dot) weighting to force cursor
*       to same position on new page.
*/
bool
forwpage (f, n, k)
    int f, n, k;
{
    long mov_lines;

    if (rplc_mode)
	next_pat ();
    else
    {
	if (curwp->w_ntrows <= 2)
	    mov_lines = 2;
	else
	    mov_lines = curwp->w_ntrows - 2;

	/* check if last line is already displayed */
	if (WIND_POS (curwp) + (R_BYTES (curwp) * curwp->w_ntrows) <
	    curwp->w_bufp->b_linep->l_bp->l_file_offset +
	    curwp->w_bufp->b_linep->l_bp->l_used)
	{
	    move_ptr (curwp, (long) (R_BYTES (curwp) * mov_lines),
		      FALSE, TRUE, TRUE);
	}
	/* move dot by same amount */
	if (!move_ptr (curwp, (long) (R_BYTES (curwp) * mov_lines),
		       TRUE, TRUE, TRUE))
	    curwp->w_unit_offset = 0;

	curwp->w_flag |= WFHARD;
    }
    return (TRUE);
}

/*  pvr
* This command is like "forwpage",
* but it goes backwards.
*/
bool
backpage (f, n, k)
    int f, n, k;
{
    long mov_lines;

    if (rplc_mode)
	next_pat ();
    else
    {
	if (curwp->w_ntrows <= 2)
	    mov_lines = 2;
	else
	    mov_lines = curwp->w_ntrows - 2;

	/* move window */
	move_ptr (curwp, -(long) (R_BYTES (curwp) * mov_lines),
		  FALSE, TRUE, TRUE);
	/* move dot by same amount */
	if (!move_ptr (curwp, -(long) (R_BYTES (curwp) * mov_lines),
		       TRUE, TRUE, TRUE))
	    curwp->w_unit_offset = 0;

	curwp->w_flag |= WFHARD;
    }
    return (TRUE);
}

/*
* Set the mark in the current window
* to the value of dot. A message is written to
* the echo line unless we are running in a keyboard
* macro, when it would be silly.
*/
bool
setmark ()
{

    if (curbp == blistp)	/* jam - hack to do goto/kill */
	pickone ();
    else
    {
	curwp->w_markp = curwp->w_dotp;
	curwp->w_marko = curwp->w_doto;
	if (kbdmop == NULL)
	{
	    writ_echo (MSG_mark_set);
	}
    }
    return (TRUE);
}

/*  pvr
* Swap the values of "dot" and "mark" in
* the current window. This is pretty easy, because
* all of the hard work gets done by the standard routine
* that moves the mark about. The only possible
* error is "no mark".
*/
bool
swapmark ()
{
    register short odoto;
    register LINE *odotp;

    if (curwp->w_markp == NULL)
    {
	writ_echo (MSG_no_mark);
	return (FALSE);
    }

    odotp = curwp->w_dotp;
    curwp->w_dotp = curwp->w_markp;
    curwp->w_markp = odotp;
    odoto = curwp->w_doto;
    curwp->w_doto = curwp->w_marko;
    curwp->w_marko = odoto;
    wind_on_dot (curwp);
    curwp->w_flag |= WFMODE;	/* update mode line */
    return (TRUE);
}

/*  pvr
* Go to a specific byte position in buffer.
* If an argument is present, then
* it is the byte number, else prompt for a byte number
* to use.
*/
bool
gotoline (f, n, k)
    int f, n, k;
{
    A32 index;
    register int s;
    char buf[32];

    if (f == FALSE)
    {

	if ((s = ereply (MSG_go_b_n, buf, sizeof (buf), 0) != TRUE))
	    return (s);
	switch (R_TYPE (curwp))
	{
	case TEXT:
	case ASCII:
	case EBCDIC:
	case BINARY:
	case HEX:
	    sscanf (buf, MSG_lX, &index);
	    break;
	case OCTAL:
	    sscanf (buf, MSG_lO, &index);
	    break;
	case DECIMAL:
#if	FLOAT_DISP
	case FLOAT:
#endif
	    sscanf (buf, MSG_lD, &index);
	    break;
#if RUNCHK
	default:
	    writ_echo (ERR_bas_1);
	    break;
#endif
	}
    }

    if (n <= 0)
    {
	writ_echo (MSG_bad_num);
	return (FALSE);
    }

    move_ptr (curwp, index, TRUE, TRUE, FALSE);
    curwp->w_unit_offset = 0;

    curwp->w_flag |= WFMODE;	/* update mode line */

    wind_on_dot (curwp);
    return (TRUE);
}
