/*------------------------------------------------------------+
 | reformat.c -- 5/16/1992 public domain                      |
 |             by Alex Matulich, Unicorn Research Corporation |
 | Reformat a text file with different line lenghts.  Original|
 | hard carriage returns are deleted and new ones added in,   |
 | depending on specified line width.  Original hard returns  |
 | will be kept in the file if:                               |
 |       (1)   They are followed by whitespace,               |
 |       (2)   They are preceded by a newlines, or            |
 |       (3)   They are on a shorter line than the specified  |
 |             width, and re-wrapping is not specified.       |
 | For this program to work, the EOL code in the text must be |
 | with the OS being run.  Use StripCR or AddCR as necessary. |
 +------------------------------------------------------------*/
                                                
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#define OUTBUFSIZ 24000

unsigned long outbufsiz = OUTBUFSIZ;
unsigned short replace, bufptr = 0, lineptr = 0;
signed char *buf = NULL;
FILE *infile, *outfile;
long inpos = 0L, outpos = 0L;       /* file position trackers */

int writebuf(void);


void main(int argc, char *argv[])
{
short j, err = 0, linelength = 72, tabwidth = 8, reformshort = 0, cntinu = 0,
   plast, ptr = 1, pnext;  /* pointers to characters in lookahead buffer */
signed char lk[3];         /* lookahead buffer */

if (argc < 2 || argc > 5) err = 3;
if (!err) {
   short i = 0;
   while (!err && ++i < argc)        /* process any commandline switches */
      if (*argv[i] == '-') {
         if (argv[i][1] == 'l' || argv[i][1] == 'L') {      /* line length */
            linelength = atoi(&argv[i][2]);
            if (linelength < 32) err = 3;
            if (linelength >= OUTBUFSIZ) outbufsiz = linelength;
            }
         else if (argv[i][1] == 't' || argv[i][1] == 'T') { /* tab width */
            if ((tabwidth = atoi(&argv[i][2])) > 30) err = 3;
            }
         else if (argv[i][1] == 'r' || argv[i][1] == 'R') reformshort = 1;
         else
            err = 3;
         if (!err) {
            --argc;
            for (j = i--; j < argc; j++) argv[j] = argv[j+1];
            }
         }
   if (argc != 3 && argc != 2) err = 3;
   }
   
if (err) {
   fputs("REFORMAT by Unicorn Research Corporation\nReformat a text file with new line lengths.\n\nUsage:  Reformat [-r] [-lN] [-tM] infile [outfile]\n\n", stdout);
   fputs("-r = re-wrap lines shorter than N (default is to leave them alone)\n N = the new line length (minimum 32, default 72)\n M = the tab width (maximum 30, assumed 8)\n\n", stdout);
   fputs("If outfile is omitted, then infile will be replaced.\nThe end-of-line code in the text file must be compatible with your\noperating system -- use StripCR / AddCR as necessary.\n", stdout);
   return;
   }
if ((infile = fopen( argv[1], (replace = (argc == 2)) ? "r+" : "r" )) == NULL) {
   fputs("Could not open input file.\n", stdout);
   return;
   }
if (replace)
   outfile = infile;
else if ((outfile = fopen( argv[2], "w" )) == NULL) {
   fputs("Could not open output file.\n", stdout);
   fclose(infile);
   return;
   }
if ((buf = (signed char *)malloc(outbufsiz)) == NULL) {
   fputs("Not enough memory!\n", stdout);
   goto cleanup;
   }

for (inpos = 0; inpos < 3; inpos++)  /* read first three characters */
   if ((lk[inpos] = fgetc(infile)) == EOF)
      { err = 2;  goto clean2; }
buf[bufptr++] = lk[0];               /* record first char in output buffer */
if (!isspace(lk[0]))
   ++lineptr;
else if (lk[0] == '\t') lineptr += tabwidth;

/* the cntinu flag is confusing.  If 1, that means the current OUTPUT line
 * has wrapped while reading the current INPUT line.
 */
do {  /* process the file */
   plast = (ptr + 2) % 3;    /* index of next character */
   pnext = (ptr + 1) % 3;    /* index of previous character */
   if (cntinu && !lineptr && isspace(lk[ptr]) && lk[ptr] != '\n') {
      ptr = pnext;
      continue;
      }
   if (lk[ptr] != '\n'                              /* if not newline */
       || (lk[ptr] == '\n' &&                       /* or if newline and */
       (isspace(lk[pnext]) || lk[plast] == '\n'))){ /* whitespace, then */
      if (lk[ptr] == '\t')                          /* first ajust for */
         lineptr = ((lineptr+tabwidth)/tabwidth)*tabwidth;
      else if (lk[ptr] == '\n') lineptr=cntinu=0;   /* new character or */
      else ++lineptr;
      if (lineptr >= linelength) {                  /* a possible tab, */
         if (isspace(lk[ptr])) {                    /* check if line */
            if (lk[ptr] != '\n') {
               lk[ptr] = '\n';                      /* length exceeded */
               cntinu = 1;
               lineptr = 0;
               }
            else cntinu = 0; 
            }
         else {                                     /* w/nonwhite space */
            for (j = bufptr-1; j > 0; j--)          /* (in which case */
               if (isspace(buf[j])) break;          /* go back & insert */
            if (j) buf[j] = '\n';                   /* a newline), */
            cntinu = 1;
            lineptr = bufptr - 1 - j;               /* and after reset */
            }                                       /* of lineptr we */
         }                                          /* will put the char */
      buf[bufptr++] = lk[ptr];                      /* into the buffer. */
      if (replace) inpos = ftell(infile);
      if (bufptr == outbufsiz) if (err = writebuf()) break;
      }
   else {  /* otherwise substitute a space for the newline */
      if ((!reformshort && !cntinu) || lineptr+1 >= linelength) {
         buf[bufptr++] = '\n';
         lineptr = 0;
         }
      else {
         buf[bufptr++] = ' ';
         ++lineptr;
         }
      cntinu = 0;
      if (replace) inpos = ftell(infile);
      if (bufptr == outbufsiz) if (err = writebuf()) break;
      }
   ptr = pnext;
   } while ((lk[plast] = fgetc(infile)) != EOF);

buf[bufptr++] = lk[pnext];     /* grab the last character */
while (!err && bufptr) {
   inpos = outpos + bufptr;    /* reset to allow remaining writes */
   err = writebuf();
   }

clean2:
if (err == 1)
   fputs("Error writing output file.\n", stdout);
else if (err == 2)
   fputs("Error reading input file.\n", stdout);
else if (replace) {
   fseek(outfile, outpos, 0);
   fputc('\x1A', outfile);
   fputs(argv[1], stdout);
   fputs(" is now reformatted.\nDelete any text after the ctrl-Z.\n", stdout);
   }
else {
   fputs("Reformatted file ", stdout);
   fputs(argv[2], stdout);
   fputs(" successfully created.\n", stdout);
   }

cleanup:
free(buf);
fclose(infile);
if (!replace) fclose(outfile);
}


/* Write the buffer to the output file, when full or when done.
 * Stop writing when end of buffer is hit, or, if we are in replace mode,
 * when current input pointer is hit. */
int writebuf()
{
unsigned short i = 0, j = 0, k = 0, buflimit;
if (replace) {
   if ((buflimit = inpos - outpos) <= 0) return -100;
   /* For the next buffer fill, we may need to back up a bit and
    * insert a newline.  So, we dump out just half the buffer. */
   if (bufptr >= outbufsiz) buflimit = (buflimit >> 1) + 1;
   if (fseek(outfile, outpos, 0)) return 1;
   }
else
   buflimit = bufptr;

/* this loop is used either in replace or create mode, so we'll avoid
   using ftell() -- we will just assume that all EOLs are 2 characters */

while (i < buflimit) {
   if (fputc(buf[k++], outfile) == EOF) return 1;
   if (buf[i++] == '\n') 
      if (++i >= buflimit) break; /* safety if we have 2-char EOLs */
   }                              /* k is the number of characters written */

if (replace) {
   outpos = ftell(outfile);
   if (fseek(infile, inpos, 0)) return 2;
   }

/* shift remaining contents of buffer to the beginning of it */
i = k;
while (i < bufptr) buf[j++] = buf[i++];
bufptr -= k;
return 0;
}
