/* SQ.C CHANGE HISTORY:
* 1.7  Add checkurk() memory check, changed credits 8-14-83 -CAF
*      Check for proper linking and sufficient memory to execute
*      Made inbuff and outbuff global for speed gain.
* 1.6  Add wild card expansion (wildexp) 6-12-82 -CAF
* 1.5  Fix BUG that caused a rare few squeezed files
*      to be incorrect and fail the USQ crc check.
*      The problem was that some 17 bit codes were
*      generated but are not supported by other code.
*      THIS IS A MAJOR CHANGE affecting TR2.C and SQ.H and
*      requires recompilation of all files which are part
*      of SQ. Two basic changes were made: tree depth is now
*      used as a tie breaker when weights are equal. This
*      makes the tree shallower. Although that may always be
*      sufficient, an error trap was added to cause rescaling
*      of the counts if any code > 16 bits long is generated.
*      Add debugging displays option '-'.
* 1.4  Break up long introductory lines.
*      Send introduction only to console.
*      Send errors only to console.
* 1.3  Close files properly in case of error exit.
*
* This program compresses a file without losing information.
* The usq.com program is required to unsqueeze the file
* before it can be used.
*
* Typical compression rates are:
*      .COM    6%      (Don't bother)
*      .ASM    33%     (using full ASCII set)
*      .DIC    46%     (using only uppercase and a few others)
* Squeezing a really big file takes a few minutes.
*
* Useage:
*      SQ item ...
* where ... represents more (optional) items and
* "item" is either:
*      drive:          to change the output drive
*      file            input file
*      drive:file      input file
*      -               toggle debugging display mode
*
* If no such items are given on the command line you will be
* prompted for commands (one at a time). An empty command
* terminates the program.
*
* SQ uses the dio package, so input and output can be redirected
* by special items on the command line such as:
*      <file           reads console input from file
*      >file           sends console output to file
*      +file           sends console output to console and file
* Also console output of another program using dio can be piped
* to the input of this one or vice-versa. Example:
*      A>fls parameters |sq
* where fls might be a program that expands patterns like *.com
* to a list of ambiguous file names for sq to squeeze.
*
* The squeezed file name is formed by changing the second
* letter of the file type to Q. If there is no file type,
* the squeezed file type is QQQ. If the name exists it is
* overwritten!
*
* Examples:
*      A>SQ GRUMP              makes GRUMP.QQQ on A:
*      A>SQ D:CRAP.XYZ         makes CRAP.XQZ on A:
*      A>SQ B: D:CRAP.COM      makes CRAP.CQM on B:
*      B>SQ X.A C: Y.B         makes X.AQ on B: and Y.BQ on C:
*
* The transformations compress strings of identical bytes and
* then encode each resulting byte value and EOF as bit strings
* having lengths in inverse proportion to their frequency of
* occurrance in the intermediate input stream. The latter uses
* the Huffman algorithm. Decoding information is included in
* the squeezed file, so squeezing short files or files with
* uniformly distributed byte values will actually increase size.
*/

#define VERSION "1.7   08-14-83"

#include <a:bdscio.h>
#include <a:dio.h>
#include "sqcom.h"
#include "sq.h"
#define STDERR 4        /* console only (error) stream */

/* Sneak in a few "local externs": N.B. sq.crl must be linked first! */
char outfile[16];       /* output file spec. */
unsigned Sentinel;      /* be sure this doesn't get munged ! */
#define SENTINEL        055555

main(argc, argv)
int argc;
char *argv[];
{
       int i,c;
       int getchar();          /* Directed io version */
       char inparg[16];        /* parameter from input */

       checkurk();             /* check for armageddon */
       Sentinel = SENTINEL;    /* unlikely value */
       wildexp(&argc, &argv);  /* do the shell's work */
       dioinit(&argc, argv);   /* obey directed to args */

       debug = FALSE;

       /* Initialize output drive to default drive */
       outdrv[0]  = '\0';
       /* But prepare for a specific drive */
       outdrv[1] = ':';
       outdrv[2] = '\0';       /* string terminator */

       /* Process the parameters in order */
       for(i = 1; i < argc; ++i)
               obey(argv[i]);

       if(argc < 2) {
               if(! _diflag) {
                       fprintf(STDERR,"File squeezer %s\n", VERSION);
                       fprintf(STDERR,"Conceived by Richard Greenlaw Modified by Chuck Forsberg et al.\n");
                       fprintf(STDERR,"Accepts redirection and pipes.\n");
                       fprintf(STDERR, "Parameters (from command line or singly from stdin)\nconsist of output drives and input file names.\n");
               }
               do {
                       fprintf(STDERR, "\n*");
                       for(i = 0; i < 16; ++i) {
                               if((c = getchar()) == EOF)
                                       c = '\n';       /* fake empty (exit) command */
                               if((inparg[i] = c) == '\n') {
                                       inparg[i] = '\0';
                                       break;
                               }
                       }
                       if(inparg[0] != '\0')
                               obey(inparg);
               } while(inparg[0] != '\0');
       }
       dioflush();     /* clean up any directed io */
       if (Sentinel != SENTINEL)
               fprintf(STDERR,"out of memory: translation suspect\007\n");
}

obey(p)
char *p;
{
       char *q;

       if(*p == '-') {
               /* toggle debug option */
               debug = !debug;
               return;
       }
       if(*(p + 1) == ':') {
               /* Got a drive */
               if(isalpha(*p)) {
                       if(*(p+2) == '\0') {
                               /* Change output drive */
                               printf("\nOutput drive =%s",p);
                               outdrv[0] = *p;
                               return;
                       }
               } else {
                       fprintf(STDERR, "\nERROR - Ignoring %s", p);
                       return;
               }
       }

       /* Check for ambiguous (wild-card) name */
       for(q = p; *q != '\0'; ++q)
               if(*q == '*' || *q == '?') {
                       fprintf(STDERR, "\nAmbiguous name %s ignored", p);
                       return;
       }
       /* First build output file name */
       outfile[0] = '\0';              /* empty */
       strcat(outfile, outdrv);        /* drive */
       strcat(outfile, (*(p + 1) == ':') ? p + 2 : p); /* input name */

       /* Find and change output file type */
       for(q = outfile; *q != '\0'; ++q)
               if(*q == '.')
                       if(*(q + 1) == '\0')
                               *q = '\0';      /* kill trailing dot */
                       else
                               switch(*(q+2)) {
                               case 'q':
                               case 'Q':
                                       fprintf(STDERR, "\n%s ignored ( already squeezed?)", p);
                                       return;
                               case '\0':
                                       *(q+3) = '\0';
                                       /* fall thru */
                               default:
                                       *(q + 2) = 'Q';
                                       goto named;
                               }
       /* No file type */
       strcat(outfile, ".QQQ");
named:
       squeeze(p, outfile);
}

squeeze(infile, outfile)
char *infile, *outfile;
{
       int i, c;

       printf("\n%s -> %s: ", infile, outfile);

       if(fopen(infile, &inbuff) == ERROR) {
               fprintf(STDERR, "Can't open %s\n", infile);
               return;
       }
       if(fcreat(outfile, &outbuff) == ERROR) {
               fprintf(STDERR, "Can't create %s\n", outfile);
               fclose(&inbuff);
               return;
       }

       /* First pass - get properties of file */
       crc = 0;        /* initialize checksum */
       init_ncr();
       printf("scanning, ");
       init_huff();
       fclose(&inbuff);

       /* Write output file header with decoding info */
       wrt_head(infile);

       /* Second pass - encode the file */
       printf("squeezing, ");
       if(fopen(infile, &inbuff) == ERROR) {
               fprintf(STDERR, "Can't open %s\n", infile);
               goto closeout;
       }
       init_ncr();     /* For second pass */

       /* Translate the input file into the output file */
       while((c = gethuff()) != EOF)
               if(putc(c, &outbuff) == ERROR) {
                       fprintf(STDERR, "ERROR - write error in %s\n", outfile);
                       goto closeall;
               }
       printf(" done.");
closeall:
       fclose(&inbuff);
closeout:
       fflush(&outbuff);
       fclose(&outbuff);
}
/*
* Check for proper linking and sufficient memory to execute
*/
checkurk()
{
       char *endext(), *topofmem(), *codend(), *externs();

       if (codend() > externs()        /* check for bad -e value! */
        || (topofmem()-1000) < endext() ) {
               printf("checkurk(): bad memory layout\n");
               exit();
       }
}