/*
** Justify Z extension
**
** History:
**	12-Sep-1988 mz	Made WhenLoaded match declaration
**	01-Sep-1988	Corrected hang when flush-justifying a line with no
**			spaces.
**	14-Aug-1988	Corrected right-justification on non-column-1 based
**			lines. Corrected justification over multiple
**			paragraphs.
**	30-Mar-1988	Extracted from "myext".
**
*/
#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#include "zext.h"

#ifndef TRUE
    #define TRUE	-1
    #define FALSE	0
#endif

void        pascal near DumpLine (char far *, PFILE, COL, COL, LINE, char far *, int);
int     pascal near NextLine (char far *, PFILE, LINE, LINE far *, int far *);
flagType    pascal near isterm (char);
void        pascal near _stat (char *);

flagType just2space = TRUE;
int justwidth   = 79;

/*************************************************************************
**
** justify
** justify paragraph(s)
**
** NOARG:	Justify between columns 0 and 78, from the current line to
**		blank line.
** NULLARG:	Justify between current column and 78, from the current line to
**		blank line.
** LINEARG:	Justify between current column and 78, the specified lines.
** "STREAMARG": Justify between specified columns from current line to blank.
**		(handled by boxarg)
** BOXARG:	justify between specified columns the specified rows
** TEXTARG:	Justify between columns 0 and 78, from the current line to
**		blank line, prepending each resulting line with the textarg.
*/
flagType pascal EXTERNAL
justify (
        CMDDATA  argData,
        ARG far  *pArg,
        flagType fMeta
        )
{
    int cbLine;             /* length of line just read	*/
    char    inbuf[BUFLEN];                  /* input buffer                 */
    PFILE   pFile;              /* file handle			*/
    char far *pText;            /* pointer to prepending text	*/
    COL x1;             /* justify to left column	*/
    COL x2;             /* justify to right columne	*/
    LINE    y1;             /* start line			*/
    LINE    y2;             /* end line			*/
    LINE    yOut;               /* output line			*/

    //
    //	Unreferenced parameters
    //
    (void)argData;

    switch (pArg->argType) {

        case NOARG:                 /* justify paragraph	*/
            x1 = 0;                 /* between cols 0...	*/
            x2 = justwidth;             /*	...and 79	*/
            y1 = pArg->arg.noarg.y;         /* current line...	*/
            y2 = -1;                /*	...to blank line*/
            pText = 0;              /* there is no text	*/
            break;

        case NULLARG:               /* justify indented	*/
            x1 = pArg->arg.nullarg.x;       /* between cur col...	*/
            x2 = justwidth;             /*	...and 79	*/
            y1 = pArg->arg.nullarg.y;       /* current line...	*/
            y2 = -1;                /*	...to blank line*/
            pText = 0;              /* there is no text	*/
            break;

        case LINEARG:               /* justify line range	*/
            x1 = 0;                 /* between cols 0...	*/
            x2 = justwidth;             /*	...and 79	*/
            y1 = pArg->arg.linearg.yStart;      /* and range of lines	*/
            y2 = pArg->arg.linearg.yEnd;
            pText = 0;              /* there is no text	*/
            break;

        case BOXARG:                /* justify box		*/
            x1 = pArg->arg.boxarg.xLeft;        /* from left corner...	*/
            x2 = pArg->arg.boxarg.xRight;       /*	    ...to right */
            y1 = pArg->arg.boxarg.yTop;     /* from top...		*/
            y2 = pArg->arg.boxarg.yBottom;      /*	   ...to bottom */
            pText = 0;              /* there is no text	*/
            break;

        case TEXTARG:               /* justify & prepend	*/
            x1 = 0;                 /* between 0... 	*/
            x2 = justwidth;             /*	  ...and 79	*/
            y1 = pArg->arg.textarg.y;       /* current line...	*/
            y2 = -1;                /*     ...to blank line */
            pText = pArg->arg.textarg.pText;    /* there IS text	*/
            break;
    }
    pFile = FileNameToHandle ("", "");

    if (y1 == y2)                   /* if same line, then	*/
        y2 = -1;                    /* just to blank line	*/
    if (x1 == x2)                   /* if same column	*/
        x2 = justwidth;             /* then just to default */
    if (x2 < x1) {                  /* if bas-ackwards	*/
        x1 = 0;                 /* revert to default	*/
        x2 = justwidth;
    }

    /*
    ** while we can get data within the specified limits, format each new line
    ** and output back to the file.
    */
    inbuf[0] = 0;
    yOut = y1;
    while (NextLine(inbuf,pFile,y1,&y2,&cbLine)) {
        /*
        ** if the line was blank, NextLine returned TRUE becase we're formating a
        ** range of text. This means we've reached the end of one paragraph. We dump
        ** the text collected so far (if any), and then a blank line.
        */
        if (cbLine == 0) {
            if (inbuf[0]) {
                DumpLine(inbuf,pFile,x1,x2,yOut++,pText,0);
                y1++;
                if (y2 != (LINE)-1)
                    y2++;
            }
            DumpLine("",pFile,x1,x2,yOut++,pText,0);/* dump blank line	*/
            y1++;
            if (y2 != (LINE)-1)
                y2++;
        } else
            /*
            ** inbuf contains the data collected so far for output. Output one newly
            ** formated line at a time until the contents of inbuf are narrower than
            ** our output columns.
            */
            while ((COL)strlen(inbuf) > (x2-x1)) {  /* while data to output */
                DumpLine(inbuf,pFile,x1,x2,yOut++,pText,fMeta);
                y1++;               /* line moves with insert*/
                if (y2 != (LINE)-1)
                    y2++;
            }
    }
    /*
    ** Dump any partial last line. Then if we were formatting to a blank line,
    ** dump out one of those too.;
    */
    if (inbuf[0])
        DumpLine (inbuf,pFile,x1,x2,yOut++,pText,0); /* dump last line	 */
    if (y2 == -1)
        DumpLine (NULL,pFile,x1,x2,yOut++,pText,0);  /* dump blank line	 */

    return TRUE;

    /* end justify */

}

/*** NextLine - Get next line from file
*
*  Get next line from file, remove leading and trailing spaces, and append
*  it to the input buffer. Each line is deleted from the file as it is
*  read in. This means that the target terminator, (*py2), is decremented
*  by one for each line read in.
*
* Input:
*  pBuf 	= pointer to input buffer
*  pFile	= file pointer
*  y1		= line # to read
*  py2		= pointer to line # to stop at (updated)
*  pcbLine	= pointer to place to put the count of bytes read
*
* Output:
*  Returns TRUE on a line being read & more reformatting should occurr.
*
*************************************************************************/
int pascal near NextLine (
                         char far *pBuf,                 /* input buffer 	*/
                         PFILE   pFile,                  /* file pointer 	*/
                         LINE    y1,                 /* line # to read	*/
                         LINE far *py2,                  /* line # to stop at	*/
                         int far *pcbLine                /* loc to place bytes read*/
                         ) {
    flagType fRet       = TRUE;
    char far *pT;                   /* working pointer	*/
    char    workbuf[BUFLEN];                        /* working buffer       */


    *pcbLine = 0;
    workbuf[0] = 0;
    /*
    ** If asked for line that is not in file, we're done.
    */
    if (y1 >= FileLength(pFile))
        return FALSE;
    /*
    ** if current line past range, (and range is not "-1"), then we're done.
    */
    if ((*py2 != (LINE)-1) && (y1 > *py2))
        return FALSE;
    /*
    ** Get the next line in the file & remove it.
    */
    *pcbLine = GetLine(y1, workbuf, pFile);
    DelLine(pFile, y1, y1);
    if (*py2 == 0)
        fRet = FALSE;
    else if (*py2 != (LINE)-1)
        (*py2)--;
    /*
    ** If the line is blank, and the range is "-1", we're done.
    */
    if (!*pcbLine && (*py2 == -1))
        return FALSE;

    /*
    ** strip leading spaces in newly input line
    */
    pT = workbuf;                   /* point into line	*/
    while (*pT == ' ')
        pT++;                   /* skip leading spaces	*/
    /*
    ** If existing buffer is non-empty, append a space & set pointer to end
    */
    if (strlen(pBuf)) {             /* if non-null string	*/
        pBuf += strlen(pBuf);           /* point to null	*/
        *pBuf++ = ' ';              /* append space 	*/
        if (isterm(*(pBuf-2)))          /* if sentence term...	*/
            *pBuf++ = ' ';              /* append another	*/
    }
    /*
    ** append new line, but compress multiple spaces into one
    */
    while (*pT) {                   /* copy line over	*/
        if (isterm(*pT))                /* if sentence term...	*/
            if (*(pT+1) == ' ') {           /*   ...space		*/
                *pBuf++ = *pT++;            /* copy period		*/
                *pBuf++ = *pT;          /* copy space		*/
            }
        if ((*pBuf++ = *pT++) == ' '    )       /* copy a char		*/
            while (*pT == ' ') pT++;        /* skip multiple spaces */
    }
    if (*(pBuf-1) == ' ')               /* if a trailing space	*/
        pBuf--;                 /* remove it		*/
    *pBuf = 0;

    return fRet;
    /* end NextLine */}

/*** DumpLine - Dump one line of text to the file
*
*  Dump one line of text to the file. Prepend any required text or spaces,
*  and perform word break/cut at right hand column.
*
* Input:
*  pBuf     = Pointer to the buffer containing data to output. If NULL, pText
*	      will not be prepended to output text.
*  pFile
*  x1
*  x2
*  yOut
*  pText
*  fFlush
*
* Output:
*  Returns .....
*
* Exceptions:
*
* Notes:
*
*************************************************************************/
void pascal near DumpLine (
                          char far *pBuf,                 /* data to output	*/
                          PFILE   pFile,                  /* file to output to	*/
                          COL x1,                 /* left-hand column	*/
                          COL x2,                 /* right-hand column	*/
                          LINE    yOut,                   /* line to output to	*/
                          char far *pText,                /* text to prepend	*/
                          int fFlush                  /* flush both sides	*/
                          ) {
    int i;
    char far *pT;
    char far *pT2;
    char    workbuf[BUFLEN];                        /* working buffer       */
    char    flushbuf[BUFLEN];                       /* working buffer       */
    char    fSpace;                 /* space seen flag	*/

    /*
    ** Start by prepending any text, and then filling out to the left hand column
    ** to justify to.
    */
    workbuf[0] = 0;                 /* start with null	*/
    if (pText && pBuf)
        strcpy(workbuf,pText);          /* if starting with text*/
    i = strlen(workbuf);                /* length of line-so-far*/
    while (i++ < x1)
        strcat(workbuf," ");            /* fill out with spaces */

    /*
    ** Append the data to be output, and then starting at the right column, scan
    ** back for a space to break at. If one is not found before the left hand
    ** column, then break at the right hand column. Copy any line left over back
    ** to the passed in buffer
    */
    if (pBuf) {
        strcat(workbuf,pBuf);           /* get total line	*/
        *pBuf = 0;                  /* empty input buffer	*/
    }
    if ((COL)strlen(workbuf) > x2) {            /* if we need to cut	*/
        pT = &workbuf[x2];              /* point at potential cut*/
        while ((pT > (char far *)&workbuf[0]) && (*pT != ' ')) pT--; /* back up to space*/
        if (pT <= (char far *)&workbuf[x1]) {   /* if none found in range*/
            if (pBuf)
                strcpy(pBuf,&workbuf[x2]);      /* copy remainder of line*/
            workbuf[x2] = 0;            /* and terminate this one*/
        } else {
            while (*++pT == ' ');           /* Skip leading spaces	 */
            if (pBuf)
                strcpy(pBuf,pT);            /* copy remainder of line*/
            *pT = 0;                /* and terminate this one*/
        }
    }
    /*
    ** This code is invoked when the user wants to justify both right and left
    ** sides of his text. We determine how many spaces we need to add, and scan
    ** through and add one space to each run of spaces until we've added enough
    */
    if (fFlush) {                   /* right & left justify?*/
        pT = workbuf + strlen(workbuf) - 1;
        if (pT)
            while (*pT == ' ')
                *pT-- = 0;
        if (strchr(workbuf,' ')) {
            while ((i = x2 - strlen(workbuf)) > 0) {/* count of spaces to add */
                strcpy(flushbuf,workbuf);       /* start with unmodified*/
                pT = workbuf + x1;
                pT2 = flushbuf + x1;        /* skip fixed part	*/
                fSpace = FALSE;         /* assume no spaces	*/
                while (*pT) {           /* while data to copy	*/
                    if ((*pT == ' ') && i) {    /* time to insert a space*/
                        fSpace = TRUE;      /* we've seen a space   */
                        *pT2++ = ' ';
                        i--;
                        while (*pT == ' ')
                            *pT2++ = *pT++;     /* copy run of spaces	*/
                    }
                    if (*pT)
                        *pT2++ = *pT++;     /* copy line		*/
                    else if (!fSpace)
                        break;          /* no embedded spaces	*/
                }
                *pT2 = 0;
                strcpy(workbuf,flushbuf);       /* copy back		*/
                if (!fSpace)
                    break;
            }
        }
    }

    CopyLine ((PFILE) NULL, pFile, yOut, yOut, yOut); /* create new line	*/
    PutLine (yOut, workbuf, pFile);         /* output line		*/

    /* end DumpLine */

}

/*************************************************************************
**
** isterm
** returns true/false based on the character being a sentence terminator:
** one of '.', '?', '!'. Also, always returns false if just2space is off.
*/
flagType pascal near isterm(
                           char c               /* character to test		*/
                           )
{
    return (flagType)(just2space && ((c == '.') || (c == '!') || (c == '?')));
    /* end isterm */}


/*
** WhenLoaded
** Executed when these extensions get loaded. Identify self & assign keys.
*/
void justifyWhenLoaded () {
    PSWI    pwidth;

    if (pwidth = FindSwitch("rmargin"))
        justwidth = *pwidth->act.ival;
}
