/*
 * The routines in this file implement commands that work word at a time.
 * There are all sorts of word mode commands. If I do any sentence and/or
 * paragraph mode commands, they are likely to be put in this file.
 */

#include        <stdio.h>
#include        "ed.h"

#define WPNULL -1 /* null word pointer */

/* Paragraph fill.  Retain indenting of first line of paragraph.  Remove
 * leading spaces from other lines.  Remove extra space between words.
 * Fill each line to fillcol, breaking at word boundaries.
 * Delete trailing space from all lines.  
 */
parafill( f, n)
{
    register int c; /* current char */
    register short spo; /* space offset */
    register short count;
    short wo; /* word offset */
    WINDOW *wp;
    LINE *flp, /* first line of paragraph */
	 *clp; /* current line of paragraph */

    /* save old context */
	/* we do this by creating a fake window which retains the context.
	 * this will be updated by the editing commands.
	 */
        if ((wp = (WINDOW *) malloc(sizeof(WINDOW))) == NULL) {
                mlwrite("Cannot allocate WINDOW block");
                return (FALSE);
        }
        wp->w_bufp  = curbp;
        wp->w_dotp  = curwp->w_dotp;
        wp->w_doto  = curwp->w_doto;
        wp->w_markp = curwp->w_markp;
        wp->w_marko = curwp->w_marko;
        wp->w_flag  = 0;
        wp->w_force = 0;
	wp->w_wndp = wheadp;
	wheadp = wp;

    /* get first line of paragraph */

	for( flp = curwp->w_dotp; flp != curbp->b_linep; flp = lback( flp)) {
	    if( llength( flp) == 0) /* blank line, found it */
		break;
	}
	flp = lforw( flp);
	if( llength( flp) == 0) /* nothing to do */
	    return( TRUE);

    /* initialize */
	count = 0;
	curwp->w_dotp = clp = flp;

    for(;;) { /* go through all lines */
	spo = count; /* set space offset at current spot */

	/* get to end of spaces */
	for( ;; count++) {
	    c = wgetc( &clp, count);
	    if( (c != ' ') || (c == EOF)) break;
	}
	wo = count;

	/* get to end of word */
	for( ;; count++) {
	    c = wgetc( &clp, count);
	    if( (c == ' ') || (c == EOF)) break;
	}

	if(((clp != flp) || spo) && (spo != wo)) {
	/* adjust spaces.  if at beginning of line, delete all spaces */
	    curwp->w_doto = spo;
	    if( spo) { /* past begin of line */
		count += adjsp( &clp, spo, &wo);
	    } else { /* at begin of line */
		ldelete( wo - spo, FALSE);
		wo = count = 0;
	    }	    
	}

	if( count > fillcol) { /* if past fill column, break. */
	    if(((clp != flp) || spo) && (spo != wo)) {
	    /* collapse the spaces */
		curwp->w_doto = spo;
		ldelete( wo - spo, FALSE);
		wo = spo;
	    }
	    if( wo > 0) { /* break at beginning of word */
		curwp->w_doto = wo;
	    } else { /* break at end of word */
		curwp->w_doto = count;
	    }
	    newline( FALSE, 1);
	    count = 0;
	    clp = curwp->w_dotp; /* updated by newline */
	}
	if( c == EOF) break;
    }
    /* restore context  and delete fake window */
	curwp->w_dotp = wp->w_dotp;
	curwp->w_doto = wp->w_doto;
	curwp->w_markp = wp->w_markp;
	curwp->w_marko = wp->w_marko;
	wheadp = wp->w_wndp;
	free( (char *) wp);

    return( TRUE);
}

/* adjust the spacing between words.  check for colons and periods.
 * assumes spo > 0 and doto is set to spo.
 */
adjsp( lp, spo, wo)
register LINE **lp;
register short spo, *wo;
{
    short result, spaces, n, c;

    /* decide on number of spaces */
	switch( lgetc( *lp, spo -1)) {
	    case ':':
		spaces = 2;
		break;

	    case '.':
		spaces = 1; /* just a guess */
		n = spo -2;
		if( n == 0) /* period at beginning of line */
		    break;
		else {
		    c = lgetc( *lp, n);
		    if((('a' <= c) && (c <= 'z'))
		    || (('A' <= c) && (c <= 'Z'))) { /* letter */
			n -= 1;
			if( n == 0) /* single letter followed by . */
			    break;
			else {
			    c = lgetc( *lp, n);
			    if((c == ' ')
			    || (c == '.'))/* single letter followed by . */
				break;
			}
		    } else /* not a letter */
			break;
		}
		if( *wo < llength( *lp)) {
		    c = lgetc( *lp, *wo);
		    if((c < 'A') || ('Z' < c)) /* not a cap letter */
			break;
		}
		spaces = 2;
		break;

	    default:
		spaces = 1;
		break;
	}

    /* adjust them */
	if( (*wo - spo) == spaces) /* perfect */
	    return( 0);
	else if( (*wo - spo) > spaces) { /* delete extras */
	    result = *wo - spo - spaces;
	    ldelete( result, FALSE);
	    result = - result;
	} else { /* pad with spaces */
	    result = spaces - (*wo - spo);
	    linsert( result, ' ');
	}
    *lp = curwp->w_dotp;
    *wo = spo + spaces;
    return( result);
}

/* Get next char, word-oriented style.  If at end of line, append next
 * line by replacing newline with a space.  Return EOF
 * when there is nothing left.
 */
wgetc( clp, n)
LINE **clp;
int n;
{
    LINE *newlp;

    if( n > llength( *clp)) {
	mlwrite( "Bad para fill");
	return( EOF);
    }
    if( n == llength( *clp)) {
	newlp = lforw( *clp);
	if( llength( newlp) == 0) return( EOF);
	curwp->w_doto = n;
	ldelete( 1, FALSE);
	linsert( 1, ' ');
	*clp = curwp->w_dotp;
    }
    return( lgetc( *clp, n));
}

/* Break line on spaces. Back-over whatever precedes the point on the current
 * line and stop on the first space or the beginning of the line. If we
 * reach the beginning of the line, do nothing.
 * Otherwise, break the line at the space, eat previous spaces, and jump
 * back to the end of the word.
 * Returns TRUE on success, FALSE on errors.
 */
wrapword()
{
        register int wo, spo, oldo;
	LINE *oldp;

        oldp = curwp->w_dotp;
	oldo = curwp->w_doto;
	for( spo = oldo; spo > 0; --spo) {
	    if( lgetc( oldp, spo) == ' ') break;
	}
	if( spo) wo = spo + 1; /* wo points at start of word */
	else /* can't do it */
	    return( FALSE);
	for( ; spo > 0; --spo) {
	    if( lgetc( oldp, spo) != ' ') break;
	}
	if( spo) spo = spo + 1; /* spo points at start of spaces */
	else /* can't do it */
	    return( FALSE);

	/* delete spaces and insert new line */
	    curwp->w_doto = spo;
	    forwdel( FALSE, wo - spo);
	    newline( FALSE, 1);

	if( oldo > spo) /* adjust point */
	    curwp->w_doto = oldo - wo;

        return( TRUE);
}
                                
/*
 * Move the cursor backward by "n" words. All of the details of motion are
 * performed by the "backchar" and "forwchar" routines. Error if you try to
 * move beyond the buffers.
 */
backword(f, n)
{
        if (n < 0)
                return (forwword(f, -n));
        if (backchar(FALSE, 1) == FALSE)
                return (FALSE);
        while (n--) {
                while (inword() == FALSE) {
                        if (backchar(FALSE, 1) == FALSE)
                                return (FALSE);
                }
                while (inword() != FALSE) {
                        if (backchar(FALSE, 1) == FALSE)
                                return (FALSE);
                }
        }
        return (forwchar(FALSE, 1));
}

/*
 * Move the cursor forward by the specified number of words. All of the motion
 * is done by "forwchar". Error if you try and move beyond the buffer's end.
 */
forwword(f, n)
{
        if (n < 0)
                return (backword(f, -n));
        while (n--) {
                while (inword() == FALSE) {
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                }
                while (inword() != FALSE) {
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                }
        }
        return (TRUE);
}

/*
 * Move the cursor forward by the specified number of words. As you move,
 * convert any characters to upper case. Error if you try and move beyond the
 * end of the buffer. Bound to "M-U".
 */
upperword(f, n)
{
        register int    c;

        if (n < 0)
                return (FALSE);
        while (n--) {
                while (inword() == FALSE) {
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                }
                while (inword() != FALSE) {
                        c = lgetc(curwp->w_dotp, curwp->w_doto);
                        if (c>='a' && c<='z') {
                                c -= 'a'-'A';
                                lputc(curwp->w_dotp, curwp->w_doto, c);
                                lchange(WFHARD);
                        }
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                }
        }
        return (TRUE);
}

/*
 * Move the cursor forward by the specified number of words. As you move
 * convert characters to lower case. Error if you try and move over the end of
 * the buffer. Bound to "M-L".
 */
lowerword(f, n)
{
        register int    c;

        if (n < 0)
                return (FALSE);
        while (n--) {
                while (inword() == FALSE) {
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                }
                while (inword() != FALSE) {
                        c = lgetc(curwp->w_dotp, curwp->w_doto);
                        if (c>='A' && c<='Z') {
                                c += 'a'-'A';
                                lputc(curwp->w_dotp, curwp->w_doto, c);
                                lchange(WFHARD);
                        }
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                }
        }
        return (TRUE);
}

/*
 * Move the cursor forward by the specified number of words. As you move
 * convert the first character of the word to upper case, and subsequent
 * characters to lower case. Error if you try and move past the end of the
 * buffer. Bound to "M-C".
 */
capword(f, n)
{
        register int    c;

        if (n < 0)
                return (FALSE);
        while (n--) {
                while (inword() == FALSE) {
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                }
                if (inword() != FALSE) {
                        c = lgetc(curwp->w_dotp, curwp->w_doto);
                        if (c>='a' && c<='z') {
                                c -= 'a'-'A';
                                lputc(curwp->w_dotp, curwp->w_doto, c);
                                lchange(WFHARD);
                        }
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                        while (inword() != FALSE) {
                                c = lgetc(curwp->w_dotp, curwp->w_doto);
                                if (c>='A' && c<='Z') {
                                        c += 'a'-'A';
                                        lputc(curwp->w_dotp, curwp->w_doto, c);
                                        lchange(WFHARD);
                                }
                                if (forwchar(FALSE, 1) == FALSE)
                                        return (FALSE);
                        }
                }
        }
        return (TRUE);
}

/*
 * Kill forward by "n" words. Remember the location of dot. Move forward by
 * the right number of words. Put dot back where it was and issue the kill
 * command for the right number of characters. Bound to "M-D".
 */
delfword(f, n)
{
        register int    size;
        register LINE   *dotp;
        register int    doto;

        if (n < 0)
                return (FALSE);
        dotp = curwp->w_dotp;
        doto = curwp->w_doto;
        size = 0;
        while (n--) {
                while (inword() == FALSE) {
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                        ++size;
                }
                while (inword() != FALSE) {
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                        ++size;
                }
        }
        curwp->w_dotp = dotp;
        curwp->w_doto = doto;
        return (ldelete(size, TRUE));
}

/*
 * Kill backwards by "n" words. Move backwards by the desired number of words,
 * counting the characters. When dot is finally moved to its resting place,
 * fire off the kill command. Bound to "M-Rubout" and to "M-Backspace".
 */
delbword(f, n)
{
        register int    size;

        if (n < 0)
                return (FALSE);
        if (backchar(FALSE, 1) == FALSE)
                return (FALSE);
        size = 0;
        while (n--) {
                while (inword() == FALSE) {
                        if (backchar(FALSE, 1) == FALSE)
                                return (FALSE);
                        ++size;
                }
                while (inword() != FALSE) {
                        if (backchar(FALSE, 1) == FALSE)
                                return (FALSE);
                        ++size;
                }
        }
        if (forwchar(FALSE, 1) == FALSE)
                return (FALSE);
        return (ldelete(size, TRUE));
}

/*
 * Return TRUE if the character at dot is a character that is considered to be
 * part of a word. The word character list is hard coded. Should be setable.
 */
inword()
{
        register int    c;

        if (curwp->w_doto == llength(curwp->w_dotp))
                return (FALSE);
        c = lgetc(curwp->w_dotp, curwp->w_doto);
        if (c>='a' && c<='z')
                return (TRUE);
        if (c>='A' && c<='Z')
                return (TRUE);
        if (c>='0' && c<='9')
                return (TRUE);
        if (c=='$' || c=='_')                   /* For identifiers      */
                return (TRUE);
        return (FALSE);
}

