/* -------------------------------------------------------------------------- **
 * Program: AuEncode - UUencoding for the Amiga.
 *
 *  Written by Christopher R. Hertel; January, 1993
 *  crh@bubble.mooses.affinity.mn.org
 * -------------------------------------------------------------------------- **
 *  Copyright (C) 1993  Christopher R. Hertel
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 * -------------------------------------------------------------------------- **
 *
 * $Log:	AuEncode.c,v $
 * Revision 1.2  93/09/15  20:43:07  CRH
 * Updated the comments a bit.  Nothing significant.
 * 
 * Revision 1.1  93/09/15  20:29:43  CRH
 * Rewritten to match format of UUENCODE.  This meant that several
 * large chunks were removed.  (They were, in fact, unnecessary fluf).
 * CRH.
 *
 * Revision 1.0  93/03/04  00:37:38  CRH
 * Initial revision
 *
 * -------------------------------------------------------------------------- **
 * My understanding of uuencoding/decoding is based on the knowledge that I
 * gained by reading the uuencode source provided with AmigaUUCP V1.16.
 * Many thanks to the authors of that software!  The following are comments
 * from their code, which are included as my way of giving credit where
 * credit is due.
 *
 * /begin/
 * [uuencode.c:]
 * Written by Mark Horton
 * Modified by ajr (Alan J Rosenthatl,flaps@utcsri.UUCP) to use checksums
 * Modified by fnf (Fred Fish,well!fnf) to use Keith Pyle's suggestion for
 * compatibility
 * Modified by bcn (Bryce Nesbitt,ucbvax!hoser!bryce) to enable CTRL-C for
 * Amiga Lattice C.  Added a transparent file size trailer for later check.
 * Changed fopen from "r" to "rb" for Messy-dos machines (thanks to Andrew
 * Wylie)
 *
 * [uudecode.c:]
 * Modified by bcn (Bryce Nesbitt,ucbvax!cogsci!bryce) to fix a misleading
 * error message on the Amiga port, to fix a bug that prevented decoding
 * certain files, to work even if trailing spaces have been removed from a
 * file, to check the filesize (if present), to add some error checking, to
 * loop for multiple decodes from a single file, and to handle common
 * BITNET mangling.  Kludged around a missing string function in Aztec
 * C. Changed "r" to "rb" and "w" to "wb" for Messy-dos machines
 * (Thanks to Andrew Wylie).
 * /end/
 *
 * Also, many thanks to Michael B. Smith, current keeper of AmigaUUCP.
 * -------------------------------------------------------------------------- **
 * This program uses the AuCode module, which contains most of the uuencoding
 * functionality.  90% of *this* file is dedicated to command line parsing,
 * file I/O, and error checking.  This file was written to compile under SAS
 * Amiga C 5.10 or later.  I use the following command line:
 *
 * 1> lc -L AuEncode.c AuCode.c
 *
 * The primary purpose of this program is to demonstrate the utility of the
 * AuCode module.
 *
 * CRH
 * -------------------------------------------------------------------------- **
 */

#include <stdio.h>  /* Standard I/O */
#include <string.h> /* Standard string functions */
#include <stdlib.h> /* Standard C functions.  */
#include <time.h>   /* C time functions.  */
#include "AuCode.h" /* Amiga-Unix uuencoding module. */

/* -------------------------------------------------------------------------- **
 * Defines...
 *
 *  DEFMODE     - Default file mode.  This value is used on Unix systems.
 *                It represents Unix file protections.  The default value
 *                will work in most cases.
 *  InBUFRSIZE  - Default input buffer size.  As with buffer sizes provided
 *                via the command line, this buffer size will be adjusted
 *                to the greatest whole multiple of au_cdMAXPhrase that is
 *                less than or equal to the original value.
 *  FALSE       - Zero, the standard C value for not true.
 *  TRUE        - If it's not false, it must be true.
 *  DEF_DefName - The "default" default output file name.  This is the
 *                target filename associated with the encoded file.  If no
 *                name is specified, the value assigned to this constant
 *                will be used.
 */
#define DEFMODE 0644
#define InBUFRSIZE 1024
#define FALSE 0
#define TRUE (~0)
#define DEF_DefName "AuEncode.out"

/* -------------------------------------------------------------------------- **
 * Macros...
 *  AdjIBufrSize - The input parameter is the requested (or default) input
 *                buffer size.  Our goal is a buffer size that is a whole
 *                multiple of au_cdMAXPhrase (see AuCode.h), so we integer
 *                divide then multiply.  The result is a value that is less
 *                than or equal to the original (4096, in this case), and a
 *                whole multiple of au_cdMAXPhrase.
 *  AdjOBufrSize - The input parameter is the adjusted Input Buffer Size.
 *                Roughly, we calculate the number of Phrases (see
 *                AuCode.h) per input buffer, multiply that by 3 (the
 *                number of overhead characters generated per Phrase), and
 *                then add that number to the number of regular encoded
 *                bytes that will be generated.  Then add two for good
 *                luck.
 *          Max - This is your basic max() macro.  I implement it here in
 *                order to avoid the inclusion of otherwise unnecessary
 *                header files.
 */
#define AdjIBufrSize(B) (((B)/au_cdMAXPhrase)*au_cdMAXPhrase)
#define AdjOBufrSize(B) ((((B)/au_cdMAXPhrase)*3)+((((B)+2)/3)*4)+2)
#define Max(A,B) (((A)>(B))?(A):(B))

/* -------------------------------------------------------------------------- **
 * Typedefs...
 *
 *        boolean - Your basic True/False options.
 */

typedef unsigned char boolean;

/* -------------------------------------------------------------------------- **
 * Globals...
 *  vers    - Program version string.
 *  HelpTxt - An array of help strings.  Should be replaced by a localization
 *            file.
 */

const char *vers =
    "\0$VER: AuEncode v1.2.";

const char *HelpTxt[] =
  {
  "This program encodes files in uuencode format.  The encoded format consists",
  "of printable characters, which may be transmitted via electronic mail.\n",
  "Usage:\tAuEncode [<infile] [>outfile] [filename] [options]\n",
  "where  [<infile] = redirected input.  AuEncode reads from standard",
  "                   input (stdin).",
  "      [>outfile] = redirected output.  AuEncode writes encoded output to",
  "                   standard output (stdout).",
  "      [filename] = specify the encoded filename.  Uuencoded files",
  "                   contain a default target filename, which is generally",
  "                   the same as the name of the original input file.  The",
  "                   uuDEcoding program will use this name as the default",
  "                   output filename.  If you do not specify a filename, a",
  "                   default filename will be used.",
  "       [options] = command line switches, as follows:\n",
  "        -? or -h     = display this message.",
  "        -b<bufrsize> = set the size of the input buffer.  The output",
  "                       buffer size will be adjusted accordingly.  Note",
  "                       that your specified buffer size will be adjusted",
  "                       for greatest efficiency.",
  "        -m<mode>     = file mode.  Unix systems (and possibly some",
  "                       others) use this to indicate read/write/etc.",
  "                       protections that are to be applied to newly",
  "                       created files.  This program provides a suitable",
  "                       default.  Enter mode as a C style octal numeric",
  "                       value (i.e., with a leading zero).",
  "                       Example: -m0644",
  "        -s           = status display.  The encoder will display the",
  "                       converted byte count (via standard error) as",
  "                       encoding progresses.\n",
  NULL
  }; /* HelpTxt[] */

struct {
  char    *defFname;    /* Default file name, stored with encoded file.       */
  boolean  foundName;   /* TRUE if defFname was read from command line.       */
  boolean  statusFlg;   /* Flag: if TRUE, display status info.                */
  long     bSize;       /* Input buffer size.                                 */
  int      mode;        /* Unix file mode to be stored with encoded file.     */
  } Args
         = { DEF_DefName,    /* Initialize to default filename.         */
             FALSE,          /* Filename not yet read from cmd line.    */
             FALSE,          /* No status (do not display status).      */
             InBUFRSIZE,     /* Initialize input buffer size to default.*/
             DEFMODE };      /* File mode (set to default value).       */

/* -------------------------------------------------------------------------- **
 * Functions...
 */

static void HelpMsg(void)
  /* ------------------------------------------------------------------------ **
   * This function sends the help message text to stdout.  I use stdout for
   * two reasons:
   *                1)  stdout is safe because the user *asked* for help.
   *                2)  The number of displayable characters varies depending
   *                    on font, window size, etc.  Via stdout, the help info
   *                    can be redirected to a file and viewed with a utility
   *                    such as <more>.
   *
   *  Notes:  For the Amiga, this code could be replaced with calls to
   *          locale.library under OS version 2.1 or greater.
   */
  {
  int i;

  printf( "[%s]\n\n", &vers[2] );
  for( i = 0; HelpTxt[i]; i++ )
    printf( "%s\n", HelpTxt[i] );
  } /* HelpMsg */

static boolean ParseCmdLine( int argc, char *argv[] )
  /* ------------------------------------------------------------------------ **
   * This function parses and responds to command line input.
   *
   *  Input:  argc  - Count of arguments in argv.
   *          argv  - An array of pointers to character strings.
   *                  Basically, the previous two are the same standard C
   *                  input parameters that are fed into main().
   *        ArgsPtr - A pointer to a CLI_Args structure.  This record will
   *                  store the results of the parse.
   *
   *  Output: A boolean.  TRUE if we parsed successfully and can continue
   *          processing, else FALSE.
   *
   *  Notes:  If there was *no* command line input, then we were run from the
   *          workbench.  We don't know how to do that yet, so we error out.
   *          If only one argument, we display a brief message to stderr, and
   *          just keep on going (in case the user used redirection).
   *          If the first argument is a '?', or if a -h or -? switch is read
   *          from the command line, we will dump the error text and exit.
   */
  {
  int i;

  if( 0 == argc )   /* No arguments = Amiga Workbench */
    return( FALSE );

  if( 1 == argc )   /* One argument may be redirected I/O.  */
    {               /* Output to stderr still safe.         */
    fprintf( stderr, "[%s]\t Type '%s ?' for help.\n", &vers[2], argv[0] );
    fprintf( stderr, "Copyright (C) 1993 Christopher R. Hertel\n" );
    fprintf( stderr, "Please see the Gnu General Public License, " );
    fprintf( stderr, "version 2,\nfor terms and conditions.\n" );
    fprintf( stderr, "Ctrl-C <return> to cancel.\n" );
    return( TRUE ); /* Assume redirected I/O. */
    }

  if( '?' == argv[1][0] )   /* If first character of arg #1 is a '?'... */
    {                       /* ...then send help (and exit).            */
    HelpMsg();
    return( FALSE );
    }

  for( i = 1; i < argc; i++ )       /* For each command line parameter */
    {
    char *Source = argv[i];

    if( '-' == *Source )
      switch( Source[1] )
        {
        case '?':   /* Dump help & return.    */
        case 'H':
        case 'h':
          HelpMsg();
          return( FALSE );
        case 'B':   /* Read new buffer size.  */
        case 'b':
          sscanf( &(Source[2]), "%ld", &(Args.bSize) );
          break;
        case 'M':   /* Read new mode setting. */
        case 'm':
          sscanf( &(Source[2]), "%o", &(Args.mode) );
          break;
        case 'S':   /* Turn on status flag.   */
        case 's':
          Args.statusFlg = TRUE;
          break;
        default:    /* error */
          fprintf( stderr, "? Invalid option: %s\n", Source );
        } /* switch */
    else
      /* Assume that it's a filename. */
      {
      if( Args.foundName )
        fprintf( stderr, "? Extra filename? [%s]\n", Source );
      else
        {
        Args.defFname  = Source;
        Args.foundName = TRUE;
        } /* else */
      } /* else */
    } /* for */

  return( TRUE );
  } /* ParseCmdLine */

static long Encode( long inBsize, boolean Stats )
  /* ------------------------------------------------------------------------ **
   * The actual encoding of the source file is performed here.
   *
   *  Input:  inBsize - Size of the input buffer.  This value will be modifed
   *                    so that it is a multiple of the Phrase size.  The
   *                    output buffer size will be calculated based on the
   *                    resultant size of the input buffer.  The calculations
   *                    result in optimized input/output buffer sizes.
   *          Stats   - Boolean.  If TRUE, statistics will be displayed via
   *                    stderr.
   *
   *  Output: The total number of converted (input) bytes.
   */
  {
  char *p;            /* Position within sBufr pointer.                       */
  int   readcnt,      /* Number of bytes received (input) per fread() call.   */
        codecnt;      /* Used to count encoded bytes per call to au_cdEncode()*/
  long  total = 0;    /* Running total number of input bytes.                 */
  long  outcount = 0; /* Running total number of generated (output) bytes.    */
  long  t;            /* Time value, used when displaying stats.              */
  long  outBsize;     /* Calculated size of the output buffer.                */
  char *sBufr;        /* Pointer to the source (input) buffer.                */
  char *tBufr;        /* Pointer to the target (output) buffer.               */

  inBsize  = AdjIBufrSize(inBsize);       /* Calc correct input buffer size.*/
  inBsize  = Max( inBsize, au_cdMAXPhrase );  /* Make sure size > 0.        */
  outBsize = AdjOBufrSize(inBsize);       /* Calc correct output buffer size*/
  sBufr = (char *)malloc( inBsize );      /* Allocate input buffer.         */
  tBufr = (char *)malloc( outBsize );     /* Allocate output buffer.        */
  if( !sBufr || !tBufr )                  /* If either allocation failed... */
    {
    fprintf( stderr, "? Buffer allocation failure.\n" );
    if( sBufr ) free( sBufr );
    if( tBufr ) free( tBufr );
    return( 0L );
    }

  if( Stats )   /* If Stats requested, display buffer sizes and get time.   */
    {
    fprintf( stderr,
             "Input buffer: [%ld] bytes.  Output buffer: [%ld] bytes.\n",
             inBsize, outBsize  );
    t = time( NULL );
    }

  while( readcnt = fread( sBufr, 1, inBsize, stdin ) )    /* Read a chunk.    */
    {
    codecnt = 0;                /* We've encoded 0 bytes of the input buffer. */
    p = sBufr;                  /* Point to start of input buffer.            */
    while( readcnt = (readcnt-codecnt) )  /* Encode as much as possible.      */
      {                                   /* If any are left over we'll loop. */
      codecnt = au_cdEncode( p, readcnt, tBufr, outBsize );
      p = &p[codecnt];                    /* Point to first non-encoded byte  */
                                          /* within sBufr.                    */
      total += codecnt;                   /* Add encoded byte count to total. */
      fputs( tBufr, stdout );             /* Write 'em. */

      if( Stats )                         /* Running statistics. */
        {
        outcount += strlen( tBufr );
        fprintf( stderr, "Read: [%ld], Written: [%ld].\r", total, outcount );
        }
      }
    }

  if( Stats )                             /* Finished: concluding statistics. */
    {
    t = time(NULL)-t;
    fprintf( stderr, "Total: %ld bytes read & encoded, ", total );
    fprintf( stderr, "%ld bytes written ", outcount );
    if( t )
      fprintf( stderr, "in %ld second%s.\n", t, (1==t)?(""):("s") );
    else
      fprintf( stderr, "in less than one second.\n" );
    }
  return( total );
  } /* Encode */

int main( int argc, char **argv )
  /* ------------------------------------------------------------------------ **
   * Mainline.
   *
   * The following steps are performed:
   *    1)  Parse the command line.
   *    2)  Write the encoding header.
   *    3)  Encode the file (stdin to stdout).
   *    4)  Write the encoding trailer.
   *
   * ------------------------------------------------------------------------ **
   */
  {
  long ByteCount;                   /* Total bytes read from input. */

  if( ParseCmdLine( argc, argv ) )  /* Parse the command line.      */
    {
    /* Write code header to output. */
    printf( "\nbegin %o %s\n", Args.mode, Args.defFname );

    /* Encode the input file to the output file.  */
    ByteCount = Encode( Args.bSize, Args.statusFlg );

    /* Write code trailer to output.  */
    printf( "``\nend\nsize %ld\n", ByteCount );
    }
  return( 0 );
  } /* main */

/* ========================================================================== */
