/*
 *                              M A I N . C
 *
 *  A file maintenance utility to decompress files; compatible with the
 *  public domain COMPRESS v4.0 utility by Spencer W. Thomas, et al.
 *
 *  Algorithm from "A Technique for High Performance Data Compression",
 *  Terry A. Welch, IEEE Computer Vol 17, No 6 (June 1984), pp 8-19.
 *
 *  The impetus for this program comes from a desire to decompress files
 *  in an MS-DOS environment with 16-bit codes using fewer machine
 *  resources than the public domain COMPRESS utility.  Memory usage
 *  for MS-DOS is less than 250 kbytes for 16-bit codes.
 *
 *  The code uses many ANSI-C style contructs with no apologies.
 *-----------------------------------------------------------------------
 *  Authors: Lyle V. Rains
 *      based on the source of COMPRESS v4.0 by Spencer Thomas, et al.
 *-----------------------------------------------------------------------
 *  Revision History:
 */

#define MAIN
#include "main.h"

char *myname                  = MYNAME;
static char *stdinname        = STDIN;
static char *stdoutname       = STDOUT;

/* Magic 2-byte file header */
static char header[]          = {0x1F, 0x9D};

static char xbuf[XBUFSIZE];                /* Big buffers for setvbuf()*/
static char zbuf[ZBUFSIZE];


static void usage(all)
  int all;
{
  U("  Usage:  %s [%ch(elp)] [%ccko] [%cf] [files ...]\n", myname, SW, SW, SW);

  if (!all) return;

#ifdef __DATE__
  U("\n  Version %s, %s\n", VERSION, __DATE__);
#else
  U("\n  Version %s\n", VERSION);
#endif
  U("  Decompresses files in a method compatible with COMPRESS V4.0\n");
  U("  INITIAL PUBLIC RELEASE.  Please send feedback and bug reports to:\n");
  U("      Lyle Rains, Compuserve User ID 71250,324\n");
  U("\n  Switches:\n");
  U("     %ch        this help message.\n", SW);
  U("     %cc        concatenate output to stdout.\n", SW);
#if KILL == YES
  U("     %ck        keep input file (default: delete).\n", SW);
#else
  U("     %ck        kill input file (default: keep).\n", SW);
#endif
  U("     %co        overwrite pre-existing output file.\n", SW);
  U("     %cf        filter (%s stdin to stdout; ignore additional args).\n", SW, myname);
#if DEBUGOUT
  U("     %cd        print debug info to stderr.\n", SW);
#endif
  U("  File arguments:\n");
  U("     infile    %s infile (e.g., FOO.C%s) to default outfile (FOO.C)\n", myname, ZSUFFIX);
  U("     outfile   %s to outfile (e.g., FOO.C) from default infile (FOO.C%s)\n", myname, ZSUFFIX);
  U("     in%cout    %s infile to outfile (overrides %cc and defaults).\n", TO, myname, SW);

  return;
}


static int do_expand();


main(argc, argv)
  int argc;
  char *argv[];
{
  char **nextarg;
  int argsleft;
  char *arg, *out;
  int kill, overwrite, cat;
  char name_tmp[NAMEBUFSIZ];
  FILE *infile, *outfile;
  char userinput[2];

  if ((argsleft = argc) <= 1) {
#   if FILTER == YES
      do_expand(stdin, stdout, stdinname, stdoutname); exit (NORMAL);
#   else
      /* No arguments -- give short usage message and exit with error */
      usage(QUICK); exit (ERROR);
#   endif
  }

  kill = KILL;
  cat = NO;
  overwrite = NO;

  /* Process command line arguments */
  nextarg = argv;
  while (--argsleft > 0) {
    if (*(arg = *++nextarg) == SW) {
      /* Process command switches */
      while (*++arg) {
        switch (*arg) {
        case 'h':
        case 'H':
        case '?':
          usage(ALL); exit (NORMAL);
        case 'f':
        case 'F':
          do_expand(stdin, stdout, stdinname, stdoutname); exit (NORMAL);
        case 'c':
        case 'C':
          cat = !cat; break;
        case 'o':
        case 'O':
          overwrite = !overwrite; break;
        case 'k':
        case 'K':
          kill = !kill; break;
        default:
          eprintf("%s: bad switch '-%c'.\n", myname, *arg);
          usage(QUICK);
          exit (ERROR);
#       if DEBUGOUT
          case 'd':
          case 'D':
            debug = !debug; break;
#       endif
        }
      }
    }
    else if (*arg == TO) {
      /* Inappropriate place to name output file.  Find file name. */
      if (!*++arg) arg = --argsleft ? *++nextarg : "<noname>";
      errexit(("%s: no input file for output '=%s'.\n", myname, arg), ERROR);
    }
    else {
      /* Get input/output filespecs and open them for processing.
       * Note:  filename processing is necessarily system dependent.
       * I am providing suitable routines for MS-DOS, but you must
       * supply your own routines as necessary for other systems.
       *
       * First check to see if there is an output file spec. If the
       * output prefix character isn't in the current argument, check
       * the beginning of the next arg, in case the user added whitespace.
       */
      if (  (out = strchr(arg, TO)) != 0
         || (  (argsleft > 1)
            && (out = (**(nextarg + 1) == TO) ? (--argsleft, *++nextarg) : NULL) != 0
            )
         )
      {
        /* We found the prefix character.  Now find the output
         * filespec, again allowing for the possible inclusion of
         * whitespace after the prefix character.
         */
        *out = '\0';
        if ( !*++out && (out = --argsleft ? *++nextarg : NULL) == 0 ) {
          errexit(("%s: missing output filespec for '%s='.\n", myname, arg),ERROR);
        }
      }
      else if (cat) out = stdoutname;
      else {
        /* Create a default output filespec, trying the following:
         *  1. If the input filespec file extension ends with ZSUFFIX,
         *     then default output spec is the same as the input, with
         *     ZSUFFIX removed.
         *  2. If the input doesn't end in ZSUFFIX, assume that the
         *     user is really giving the desired output spec, and try to
         *     create a default input spec by appending ZSUFFIX to
         *     the filespec and seeing if any such input file exists.
         *  3. Give up (we really tried!), print an error and continue.
         */
        out = strcpy(name_tmp, arg);
        if (!to_xname(out)) {
          out = arg;
          arg = name_tmp;
          to_zname(arg);
        }
      }
      if (!overwrite && out != stdoutname && (outfile = fopen(out, "r")) != 0) {
        eprintf("%s: output '%s' exists.  Overwrite? ", myname, out);
        if (fgets(userinput, 2, stdin) && tolower(*userinput) == 'y')
          fclose(outfile);
        else
          exit (ERROR);
      }
      if ((outfile = (out == stdoutname) ? (FILE *)stdout : fopen(out, "w")) == 0) {
        errexit(("%s: can't open output '%s'.\n", myname, out), ERROR);
      }
      if ((infile = fopen(arg, "r")) == 0) {
        errexit(("%s: can't open input '%s'.\n", myname, arg), ERROR);
      }
      eprintf("expanding '%s' to '%s'.\n", arg, out);
      DBG(("\t overwrite = %d, cat = %d, kill = %d\n", overwrite, cat, kill));
      do_expand(infile, outfile, arg, out);
      if (kill) unlink(arg);
    }
  }
}

static int do_expand(in, out, inname, outname)
  FILE *in;
  FILE *out;
  char *inname;
  char *outname;
{
  int maxbits;
  int clearmode;

  setvbuf(in, zbuf, _IOFBF, ZBUFSIZE);
  setvbuf(out, xbuf, _IOFBF, XBUFSIZE);
  setbinary(in);                        /* Some systems require this    */
  setbinary(out);
  /* Check for the magic number header */
  if ((char)getc(in) != header[0] || (char)getc(in) != header[1]) {
    errexit(("%s: '%s' not in compressed format.\n", myname, inname), ERROR);
  }
  maxbits = getc(in);
  clearmode = maxbits & BLOCK_MASK;
  if ((maxbits &= BIT_MASK) > Maxbits) {
    errexit(("%s: '%s' uses %d-bit compression (%s allows %d-bits maximum).\n",
            myname, inname, maxbits, myname, Maxbits), ERROR);
  }
  switch (expand(in, out, maxbits, clearmode)) {
  case OK:
    fclose(in);
    if (out != stdout) fclose(out);
    else fflush(out);
    return (OK);
  case NOMEM:
    errexit(("%s: not enough memory to expand '%s'.\n", myname, inname), ERROR);
  case TOKTOOBIG:
    errexit(("%s: token too long in '%s'.\n", myname, inname), ERROR);
  case READERR:
    errexit(("%s: read error on input '%s'.\n", myname, inname), ERROR);
  case WRITEERR:
    errexit(("%s: write error on output '%s'.\n", myname, outname), ERROR);
  case CODEBAD:
    errexit(("%s: illegal code found in '%s'.\n", myname, inname), ERROR);
  case TABLEBAD:
    errexit(("%s: internal error -- tables corrupted.\n", myname), ERROR);
  default:
    errexit(("%s: internal error -- illegal return value.\n", myname), ERROR);
  }
}  
