#include <stdio.h>
#include "abe.h"

/* ABE 'Ascii-Binary Encoding' Encoder
   This is the full and only encoder for ABE files.  It handles
   multiple-block files with redundant information, and outputs all
   sorts of other headers to bulletproof the data.  Refer to the man pages
   for details on the options and file format.

   This program was written with Unix(TM-Bell Labs) in mind, but it
   can be easily ported to other systems, notably MS-DOS using the
   Microsoft C compiler conventions for text/binary files. 

   This program was written by Brad Templeton, and is a companion to
   the full ABE decoder and the simple ABE decoder.  While the simple
   ABE decoder was released to the public domain, this encoder is not.

   It is Copyright 1989 by Brad Templeton.  I hereby grant a licence
   for unlimited use of binaries generated by compiling this source
   code.  The source code may be distributed and modified for non-commercial
   purposes, but all copyright messages must be retained.  Changes which
   alter the file format of ABE files are not permitted without
   the explicit consent of Brad Templeton.  I mean it.

   No fee is required for the use of the program.
   See the MAN page for more details.

   Changes which move this program to other machines and operating
   systems are encouraged.  Please send any port to a new machine to
   me at abe@looking.UUCP.

 */

/* the set of printable characters used in line numberas and in ABE2 files */

char safe_prints[] =
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

struct frq freqs[256];		/* frequencies of characters in the file */
unsigned char *inbuf = 0;	/* allocated look-ahead buffer for one pass */
int inbsize;			/* size of said one-pass mode buffer */
int eofflag = FALSE;		/* got EOF yet? */
int bufdex;			/* index into one-pass look ahead buffer */
int whatset[256+1+3];		/* what set a given char is in */
char whatprint[256+1];		/* the printable in that set to use */

int inmode = ONEPASS;		/* method of processing input file */
int line_num = 1;		/* line number of first line to output */
int file_number = 0;		/* current output block  (all files) */
int blocknum = 0;			/* block number in current file */
long block_bytecount;		/* number of bytes in this block */
long bl_outcount;		/* number of printed chars output this block */
long block_checksum;		/* checksum of all info in block */
long total_checksum = 0;	/* checksum of data bytes in file */
long seek_addr = 0;		/* current position in input file */
tcrc crc = 0;			/* running 32 bit crc */
tcrc bcrc = 0;			/* running 32 bit block crc */

int num_sets = 0;			/* number of characters sets */
int num_prints = 86;			/* number of printables per set */

int encoding_style = ABE1;	/* style for encoding */
int saved_mode = 0664;		/* file permissions, saved for uuencode */
FILE *instream;			/* input stream */

bool give_numbers = TRUE;	/* give line numbers in file */
bool temp_nonums;		/* temporarily give no numbers */
bool did_size = FALSE;		/* have we given the file size */

tcrc crctab [TABSIZE];		/* big mother CRC-32 lookup table */

	/* read in some, or all of the input (depending on the number of
	   passes) and build a frequency table for the bytes */


freqbuild(fname)
char *fname;		/* file name or null string */
{
	FILE *inf;	/* temporary input file */
	int i;		/* general counter */
	int c;		/* byte read from file */
	char *malloc();
	extern int samp_size;		/* size of one pass pre-read sample */


	/* clear freq table */
	for( i = 0; i < 256; i++ ) {
		freqs[i].freq = 0;
		freqs[i].bytenum = i;
		whatprint[i] = 0;
		}

	if( fname && fname[0] ) {
		inf = fopen( fname, READMODE );
		if( !inf )
			error( "Could not open %s", fname );
		}
	 else {
	 	if( inmode == TWOPASS )
			error( "Two pass scan not available on standard input");
		inf = stdin;
#ifdef msdos
		setmode( fileno(inf), O_BINARY );
#endif
		}

	if( inmode == TWOPASS ) {
		/* scan the file for frequency table */
		while( (c = getc(inf)) != EOF )
			freqs[c].freq++;
		fclose(inf);
		}
	 else {
		/* build a buffer to save pre-read chars */
		inbuf = (unsigned char *)malloc( samp_size );
		if( !inbuf )
			error( "Could not allocate enough memory for pre-pass");
		inbsize = fread( inbuf, 1, samp_size, inf );
		eofflag = inbsize < samp_size;
		/* set up frequency table */
		for( i = 0; i <inbsize; i++ )
			freqs[ inbuf[i] ].freq++;
		instream = inf;
		}

}


	/* build the character set maps for this file, based on the
	   frequency tables calculated.  There are 86 printable characters
	   used.  Three sets of such characters are used to encode bytes.

	   The 86 most common bytes go into set 0.  The next 86 go to
	   set 1 and the remaining 84 go to set 2.

	   Some characters (space, NL, CR, NUL) always get a favourite
	   printable character.

	   All remaining printable characters are represented by themselves
	   (in their set) if possible.

	   In ABE2, we have 4 sets of 64 instead of 3 of 86.

	   */


char whoused[MAX_SETS][MAX_PRINTS];	/* what characters are taken in each set */


int specials1[] = { ' ', 9 /* tab */, 13 /*cr*/, 10 /*nl*/, 0, 255, -1 };
int specials2[] = { ' ', 10, 0, -1 };
/* what the specials will map to */
char specprint1[] = { '.', ':', '\\', '/', '0', '*' };
char specprint2[] = { '.', '/', '0' };

int *specials;
char *specprint;

setbuild()
{
	int i;
	int set;		/* which set */
	int ws;			/* what special character */
	int lastfree[MAX_SETS];	/* last free byte in a given set */
	int bycomp();


	if( num_sets == 0 )
		return;
	qsort( freqs, 256, sizeof(struct frq), bycomp );

	/* now assign a char set and printable character to every byte */

	/* clear out the who-is-allocated table */
	for( set = 0; set < num_sets; set++ ) {
		for( i = 0; i < num_prints; i++ )
			whoused[set][i] = FALSE;
		/* give each character a set */
		for( i = 0; i < num_prints && set*num_prints+i < 256; i++ ) 
			whatset[ freqs[set*num_prints+i].bytenum ]  = set;
		}
	whatset[256] = -1;		/* illegal set */

	/* if space, tab and return are in the first set, grab some special
	   chars for each */
	for( ws = 0; specials[ws] >= 0; ws++ )
		if( whatset[ specials[ws] ] == 0 ) {
			whatprint[ specials[ws] ] = specprint[ws];
			/* mark that char used */
			whoused[0][code_of(specprint[ws])] = TRUE;
			}
	/* now take the printables for themselves, wherever they are found */
	
	for( i = 0; i < 256; i++ ) {
		int c;
		int ccode;
		c = freqs[i].bytenum;
		/* if it is available, take it */
		ccode = code_of(c);
		if( isourprint(c) && !whoused[whatset[c]][ccode] ) {

			whoused[whatset[c]][ccode] = TRUE;
			whatprint[c] = c;
			}
		}
	/* now allocate the rest of the bytes to the first available in
	   their set */


	/* first clear the mark of the first possibly available char */
	for( set = 0; set < num_sets; set++ )
		lastfree[set] = 0;

	for( i = 0; i <256; i++ ) {
		int c;
		int cset;
		int inset;

		c = freqs[i].bytenum;
		cset = whatset[c];

		if( whatprint[c] == 0 ) {
			/* scan for an available char in this set */
			for( inset = lastfree[cset]; inset < num_prints; inset++ ){
				if( !whoused[cset][inset] ) {
					/* mark this char used */
					whoused[cset][inset] = TRUE;
					/* and use it */
					whatprint[c] = printof(inset);
					/* have other searches start here */
					lastfree[cset] = inset+1;
					break;
					}
				}
			/* it should not be possible for the loop to terminate
			   normally */
			}
		}

	/* the character sets have been allocated */

}

	/* byte comparison function for qsort */
int
bycomp( a, b )
struct frq *a, *b;
{
	/* we want descending order */
	return a->freq > b->freq ? -1 : ( a->freq == b->freq ? 0 : 1 );
}


	/* initialize the small look ahead buffer, and general input, for
	   the file encoding pass */

static int lbuf[4];		/* look ahead buffer */
int lbufdex;			/* index into buffer */

initbuf(fname)
char *fname;
{
	int i;
	extern int inmode;

	if( inmode == TWOPASS ) {
		instream = fopen( fname, READMODE );
		inbuf = 0;
		}
	 else {
		/* instream already set up */
		bufdex = 0;
		}
	seek_addr = 0;
	eofflag = FALSE;
	for( i = 0; i < LB_LEN; i++ ) 
		lbuf[i] = ourget();
	lbufdex = 0;
}

	/* get the next byte from the input, filling the look ahead
	   buffer as needed */
#define MAGIC 010201

nextchar()
{
	short c;

	c = lbuf[lbufdex];
	lbuf[lbufdex] = ourget();
	lbufdex = (lbufdex+1) % LB_LEN;
	if( c != OUR_EOF ) {
		++seek_addr;	/* where it will be after reading this char */
		++block_bytecount;	/* total bytes in this block */
		/* compute CRC with this byte present */
		/* whole file crc */
		crc = UPDC32( c, crc );
		/* block crc */
		bcrc = UPDC32( c, bcrc );
		}
	return (int)c;
}

	/* macro to look ahead in the input look ahead-buffer */

#define lachar(x)	lbuf[(lbufdex+x)%LB_LEN]


	/* the raw routine to get a character from the input */

ourget()
{
	int c;

	if( inbuf ) {
		if( bufdex < inbsize ) 
			return (unsigned char)inbuf[bufdex++];
		 else {
			free( inbuf );
			inbuf = 0;
			}
		}
	if( eofflag )
		return OUR_EOF;
	c = getc(instream);
	if( c == EOF ) {
		eofflag = TRUE;
		return OUR_EOF;
		}
	 else
		return c;
}

int redundant = FALSE;		/* add extra headers to each block */
long blocksize = 0L;		/* how long blocks are to be */
char *file_prefix = NULL;	/* prefix for block file names */
char *m_univname = NULL;	/* option universal file name */
char *univname = NULL;
int decoder = FALSE;		/* do not add mini-decoder to output */
char *decode_name = DECODER;	/* name of simple decoder source */
int samp_size = INB_LEN;	/* size of one pass scan buffer */

FILE *out_desc;			/* output descriptor */
char *fname_array[MAX_FNAME];	/* array of files to process */
int fn_count = 0;		/* number of files */

	/* handle options and control encoding */

main(argc,argv)
int argc;
char **argv;
{
	int i;
	int argnum;
	char *strchr();
	long atol();


	inmode = TWOPASS;
		

	for( argnum = 1; argnum < argc; argnum++ ) {
		char *argline;
		char *argstr;		/* argument string */
		int argval;
		int isplus;		/* boolean tells +arg vs -arg */
		argline = argv[argnum];

		if (argstr = strchr(argline, '=')) {
			argstr++;
			argval = atoi(argstr);
			switch( argline[0] ) {
				case 'b':
					blocksize = atol( argstr );
					break;
				case 'p':
					/* provide a prefix for filenames for
					   the multiple parts */
					file_prefix = argstr;
					/* if no blocksize, pick a default */
					if( blocksize == 0 )
						blocksize = DEF_BLOCKSIZE;
					break;
				case 'u':
					/* specify a universal name */
					m_univname = argstr;
					if( strlen(m_univname) > MAX_UNAME )
						m_univname[MAX_UNAME] = 0;
					break;
				case 'd':
					/* add decoder source from specified
						file */
					decoder = TRUE;
					decode_name = argstr;
					break;
				case 's':
					/* sample size, one pass */
					samp_size = argval;
					inmode = ONEPASS;
					break;
				case 'l':
					/* set starting line number */
					line_num = atol( argstr );
					break;
				default:
					do_options();
					error( "Bad Option %s\n", argline );
				}
			}
		else if( (isplus = argline[0] == '+') || argline[0] == '-' ) {
			switch( argline[1] ) {
				case 'r': /* extra info in each block */
					redundant = isplus;
					break;
				case 'd': /* add decoder source */
					decoder = isplus;
					break;
				case 'e': /* ebcdic */
					encoding_style = isplus ? ABE2 : ABE1;
					break;
				case 'u':
					if( isplus )
						encoding_style = UUENCODE;
					break;
				case 'n':
					give_numbers = isplus;
					break;
				default:
					do_options();
					error( "Bad Option %s\n", argline );
				}
			}
		else {
			/* code for untagged option */
			if( fn_count >= MAX_FNAME )
				error( "Too many file names\n" );
			fname_array[fn_count++] = argline;
			}
		}
	
	mkcrctab();			/* init crc table */

	init_encoding(encoding_style);

	if( !fn_count )
		inmode = ONEPASS;
	init_output(fn_count);

	if( m_univname && fn_count > 1 )
		error( "You may not specify a universal name with multiple files\n" );

	if( fn_count ) {
		for( i = 0; i < fn_count; i++ )
			do_file( fname_array[i] );
		}
	 else
		do_file( NULL );
#ifdef unix
	if( file_prefix && file_prefix[0] == '|' && out_desc )
		pclose( out_desc );
#endif

	return 0;
}
	/* encode a specified source file */

do_file( fname )
char *fname;
{
	char unbuf[MAX_UNAME+1];		/* keep universal name */

	if( !m_univname ) {
		formuname( fname, unbuf );
		univname = unbuf;
		}
	 else
		univname = m_univname;

	start_of_file( fname );

	freqbuild( fname );
	setbuild();

	total_checksum = 0;
	crc = 0xffffffffL;


	if( !redundant )
		printmap();

	initbuf( fname );

	if( blocksize )
		print_blockhead(fname);

	file_preface();


	while( outline() )
		if( blocksize && bl_outcount >= blocksize ) {
			close_block(TRUE);
			init_newblock();
			print_blockhead(fname);
			}
	if( blocksize )
		close_block(FALSE);
	write_trailer();
	if( fname )
		fclose( instream );
}
	/* output the byte-character map we have generated */

printmap()
{
	int line, block, i;
	int ccount;
	char linebuf[MAX_LLEN];
	int thechar;
	int setnum;

	/* no map if no character sets */
	if( num_sets == 0 )
		return;

	thechar = 0;
	for( line = 0; line < 8; line++ ) {
		ccount = 0;
		linebuf[ccount++] = CODE_HEAD;
		linebuf[ccount++] = CODE_HEAD;
		linebuf[ccount++] = printof(line);
		switch( encoding_style ) {
		 case ABE1:
			for( block = 0; block < 8; block++ ) {
				setnum = 0;
				for( i = 0; i < 4; i++ ) {
					setnum = setnum * 3 + whatset[thechar];
					linebuf[ccount++] =whatprint[thechar++];
					}
				/* output the set numbers for here */
				linebuf[ccount++] = printof(setnum);
				}
			break;
		 case ABE2:
			for( block = 0; block < 16; block++ ) {
				setnum = 0;
				for( i = 0; i < 2; i++ ) {
					setnum = setnum * 4 + whatset[thechar];
					linebuf[ccount++] =whatprint[thechar++];
					}
				/* output the set numbers for here */
				linebuf[ccount++] = printof(setnum);
				}
			break;
		 }

		linebuf[ccount] = 0;
		(void)lineput( linebuf );
		}
}

	/* encode a line of data and output it to the encoding file */

outline()
{
	switch( encoding_style ) {
		case ABE1:
			return outline1();
		case ABE2:
			return outline2();
		case UUENCODE:
			return out_uuencode();
		}
}

outline1()
{
	int c;
	char linebuf[MAX_LLEN];
	int csum;
	int ccount;
	
	ccount = 0;
	do {
		c = nextchar();
		if( c == OUR_EOF )
			break;
		if( whatset[c] == 0 ) {
			linebuf[ccount++] = whatprint[c];
			}
		 else {
			if( whatset[lachar(0)] > 0 ) {
				linebuf[ccount++] = SETXX + (whatset[c]-1) * 2 +whatset[lachar(0)]-1;
				linebuf[ccount++] = whatprint[c];
				linebuf[ccount++] = whatprint[nextchar()];
				}
			 else {
				if( whatset[c] == 1 && whatset[lachar(1)] > 0){
					linebuf[ccount++] = SET10X +
							whatset[lachar(1)]-1;
					linebuf[ccount++] = whatprint[c];
					linebuf[ccount++]=whatprint[nextchar()];
					linebuf[ccount++]=whatprint[nextchar()];
					}
				 else {
					linebuf[ccount++] =NEWSET1+whatset[c]-1;
					linebuf[ccount++] = whatprint[c];
					}
				}
			}
		
		} while ( ccount < MPERLINE );
	linebuf[ccount] = 0;

	sumput( linebuf );

	return c != OUR_EOF;
}

/* output line, add to checksum */
sumput( line )
char *line;
{
	total_checksum = (lineput( line ) + total_checksum) % CSUM_MOD;
}

/* characters for the X0X mapping in ABE2 */
static char x0xmaps[] = A2X0XSET;

outline2()
{
	int c;
	char linebuf[MAX_LLEN];
	int csum;
	int ccount;
	
	ccount = 0;
	do {
		int cset;

		c = nextchar();
		if( c == OUR_EOF )
			break;
		cset = whatset[c];
		if( cset == 0 ) {
			linebuf[ccount++] = whatprint[c];
			}
		 else {
			if( whatset[lachar(0)] > 0 ) {
				linebuf[ccount++] = A2SETXX + (cset-1) * 3 +whatset[lachar(0)]-1;
				linebuf[ccount++] = whatprint[c];
				linebuf[ccount++] = whatprint[nextchar()];
				}
			 else {
				/* 8 mappings only, 303 not allowed */
				int setmap;
				if( whatset[lachar(0)] == 0 &&
						whatset[lachar(1)] > 0 &&
						(setmap =
						(cset-1)*3+whatset[lachar(1)]-1)
						< 8 ) {
					linebuf[ccount++] =x0xmaps[setmap];
					linebuf[ccount++] = whatprint[c];
					linebuf[ccount++]=whatprint[nextchar()];
					linebuf[ccount++]=whatprint[nextchar()];
					}
				 else {
					linebuf[ccount++] = A2NEWSET1+cset-1;
					linebuf[ccount++] = whatprint[c];
					}
				}
			}
		
		} while ( ccount < MPERLINE );
	linebuf[ccount] = 0;

	sumput( linebuf );

	return c != OUR_EOF;
}

/* VARARGS */
error(a,b,c,d,e,f)
int a,b,c,d,e,f;
{
	fprintf( stderr, a, b, c, d, e, f );
	fprintf( stderr, "\n" );
	exit(1);
}

	/* output a general line, with line number, checksum and NL */
int
lineput( line )
char *line;
{
	int i;
	int sum;

	sum = 0;

	for ( i = 0; line[i]; i++ )
		sum += line[i];
	block_checksum += sum;
	bl_outcount += i;
	/* calc and place checksum for line */
	if( temp_nonums ) {
		if( strncmp( line, "From ", 5 ) == 0 )
			fprintf( stderr,"Warning: line %s begins with 'From'\n",
				line );
		bl_outcount++;
		fprintf( out_desc, "%s\n", line );
		}
	 else {
		fprintf( out_desc, "%c%c%c%c%s\n",
			safe_prints[ SFBYTE + line_num / (NUM_SAFE*NUM_SAFE)],
			safe_prints[ (line_num / NUM_SAFE) % NUM_SAFE ],
			safe_prints[ line_num % NUM_SAFE ],
			safe_prints[ sum % NUM_SAFE ],
			line );
		line_num++;
		bl_outcount += 5;
		}
	return sum;
}

/* names for the defined styles of encoding.  (index as encoding_style ) */

char *stylenames[] = {"ABE1", "ABE2", "UUENCODE"};

	/* set up the first output descriptor */

init_output(count)
int count;
{
	char fcbuf[20];

	if( file_prefix ) {
		file_number = 0;
		init_newblock();
		}
	else
		out_desc = stdout;	
	/* print the main header */
	fprintf( out_desc, ";ABE ASCII-Binary-Encoding (by Brad Templeton)\n" );
	fprintf( out_desc, ";Use 'sort' and/or 'dabe' to decode\n" );
	if( decoder ) {
		FILE *decf;
		char decname[MAX_FNAME];
		int c;

		sprintf( decname, decode_name, stylenames[encoding_style] );
		decf = fopen( decname, "r" );
		if( !decf )
			error( "Could not open decoder %s", decname );
		fprintf( out_desc, "--------Tiny DABE (%s) ------\n", decname );
		while( (c = getc(decf)) != EOF )
			putc( c, out_desc );
		fclose( decf );
		fprintf( out_desc, "--------Cut here to extract tiny decoder------\n" );
		}
	temp_nonums = FALSE;
	sprintf( fcbuf, "%d", count ? count : 1 );
	subheading( "filecount", fcbuf );
}

	/* output the main start-of-file header */



start_of_file(fname)
char *fname;
{
	char headline[MAX_LLEN];

	temp_nonums = FALSE;
	did_size = FALSE;

	sprintf( headline, "%c%cS%u,%u,%u,%s", MAIN_HEAD, MAIN_HEAD,
			EARLIEST_SIMD, VERNUM, EARLIEST_VERNUM,
			stylenames[encoding_style] );
	(void)lineput( headline );
	/* indicate if this file should have blocks or not */
	subheading( "blocking", blocksize ? "true" : "false" );
	if( !give_numbers ) {
		subheading( "linenumbers", "false" );
		temp_nonums = TRUE;
		}

	if( univname )
		subheading( "uname", univname );
	if( !redundant )
		fileinfo(fname);		/* write general file info */
	blocknum = 0;
}

	/* output main header information on the file */

fileinfo(fname)
char *fname;
{
	subheading( "os", OUROS );
	/* we do not output the real name if an explicit universal name
	   was given */
	if( fname ) {
		if( m_univname == NULL )
			subheading( "fname", fname );
		osfileinfo(fname);
		}
}

#ifdef unix

#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>

	/* operating system specific file information */

osfileinfo(fname)
char *fname;
{
	struct stat ourf;
	struct passwd *opwent, *getpwuid();
	char numbuf[40];

	if( stat( fname, &ourf ) == 0 ) {
		if( opwent = getpwuid( ourf.st_uid ) )
			subheading( "owner", opwent->pw_name );
		sprintf( numbuf,"%ld", (long)ourf.st_mtime );
		subheading( "date", numbuf );
		sprintf( numbuf, "%u", ourf.st_mode );
		saved_mode = ourf.st_mode;
		subheading( "perm", numbuf );
		sprintf( numbuf, "%lu",  (long)ourf.st_size );
		did_size = TRUE;
		subheading( "size", numbuf );
		}
		
}

#else
# ifdef msdos
	/* almost identical unix code due to Microsoft C compat library */
#include <sys/types.h>
#include <sys/stat.h>


osfileinfo(fname)
char *fname;
{
	struct stat ourf;
	unsigned short mode;
	struct passwd *opwent, *getpwuid();
	char numbuf[40];

	if( stat( fname, &ourf ) == 0 ) {
		sprintf( numbuf,"%ld", (long)ourf.st_mtime );
		subheading( "date", numbuf );
		mode = ourf.st_mode & (S_IREAD|S_IWRITE|S_IEXEC);
		/* duplicate the mode bits down from user to general */
		mode |= (mode >> 3) | (mode >> 6);
		saved_mode = mode;
		sprintf( numbuf, "%u", mode );
		subheading( "perm", numbuf );
		sprintf( numbuf, "%lu",  (long)ourf.st_size );
		did_size = TRUE;
		subheading( "size", numbuf );
		}
		
}
# else
/* other OS output lines */
osfileinfo()
{
}
# endif
#endif

		/* output a sub-header line */
subheading(hclass,value)
char *hclass;
char *value;
{
	char headline[MAX_LLEN];
	sprintf( headline, "%c%c%s=%s", SUB_HEAD, SUB_HEAD, hclass, value );
	(void)lineput( headline );
}

	/* begin a new output block (file) */
init_newblock()
{
	char blockname[FNAMELEN];


	blocknum++;

	if( file_number >= MAX_BLOCKS )
		error( "Too many blocks -- limit of 255" );
	if( file_prefix ) {
#ifdef unix
		if( file_prefix[0] == '|' ) {
			sprintf( blockname, file_prefix+1, file_number );
			out_desc = popen( blockname, "w" );
			}
		 else
#endif
			{
			sprintf( blockname, "%s%2.2x", file_prefix,
							file_number );
			out_desc = fopen( blockname, "w" );
			}
		file_number++;
		if( !out_desc )
			error( "Could not open %s for output", blockname );
		}
}

	/* output the header for a block */

print_blockhead(fname)
char *fname;
{
	char blockhead[MAX_LLEN];

	sprintf( blockhead, "%c%cstartblock=%d,%lu,%u,%s", SUB_HEAD,
		SUB_HEAD, blocknum, seek_addr, EARLIEST_VERNUM, univname );
	block_checksum = 0;
	block_bytecount = 0;
	bl_outcount = 0;
	bcrc = 0xffffffffL;		/* block crc */
	(void)lineput( blockhead );

	if( !give_numbers && !temp_nonums ) {
		subheading( "linenumbers", "false" );
		temp_nonums = TRUE;
		}

	/* give all the main info again if redundant blocks requested */
	if( redundant ) {
		subheading( "style", stylenames[encoding_style] );
		printmap();
		fileinfo(fname);
		}
}

	/* output the trailer for a block */

close_block(closefile)
int closefile;
{
	char blockhead[MAX_LLEN];

	sprintf( blockhead, "%c%ccloseblock=%d,%lu,%lu,%lu", SUB_HEAD, SUB_HEAD,
			blocknum,  (long)block_checksum % CSUM_MOD, 
			(long)block_bytecount, ~bcrc );
	(void)lineput( blockhead );
	fprintf( out_desc, ";ABE encoding end of part %d\n", blocknum );
	if( file_prefix && closefile ) {
#ifdef unix
		if( file_prefix[0] == '|' )
			pclose( out_desc );
		 else
#endif
			fclose( out_desc );
		out_desc = (FILE *)0;
		}
	temp_nonums = FALSE;
		
}

	/* write the end of file records */

write_trailer()
{
	char numbuf[40];

	if( blocksize ) {
		sprintf( numbuf, "%d", blocknum+1 );	/* they start at 0 */
		subheading( "total-blocks", numbuf );
		}
	if( !did_size ) {
		sprintf( numbuf, "%lu", (long)seek_addr );
		subheading( "size", numbuf );
		}
	subheading( "end_file", univname );
	sprintf( numbuf, "%lu", ~crc );
	subheading( "filecrc32", numbuf );
	sprintf( numbuf, "%c%cE%ld", MAIN_HEAD, MAIN_HEAD,
			(long) total_checksum % CSUM_MOD );
	(void)lineput( numbuf );
	fprintf( out_desc, ";End of ABE encoding\n" );
}

	/* form a universal filename from the true pathname */
	/* this should be OS-dependent code, but for now, DIRCHARS
	   contains the OS-dependent information */

formuname( name, ubuf )
char *name;	/* full pathname */
char *ubuf;	/* buffer of 15 bytes (at least) for universal name */
{
	int len;
	int i;

	if( name == NULL || (len  = strlen(name)) == 0 ||
			strchr( DIRCHARS, name[len-1] ) != NULL ) {
		strcpy( ubuf, "stdin" );
		return;
		}
	for( i = len-1; i >= 0; i-- )
		if( strchr( DIRCHARS, name[i] ) ) {
			break;
			}

	strncpy( ubuf, name+i+1, MAX_UNAME );
	ubuf[MAX_UNAME] = 0;
}

	/* provide the printable character for a given number */
printof(c)
int c;
{
	return (encoding_style == ABE1) ? c + A1FPRINT : safe_prints[c];
}

	/* provide the integer code for a given printable char */

code_of(n)
int n;
{
	if( encoding_style == ABE1 )
		return n - A1FPRINT;
	 else /* ABE2 */
		return n - (n > '9' ? (n > 'Z' ? 'a'-38 : 'A'-12) : '.');
}

	/* is a char one of our printable chars? */
isourprint(c)
int c;
{
	return (encoding_style == ABE1) ? (c >= A1FPRINT && c <= A1LPRINT) :
		(( c >= '.' && c <= '9') || (c >= 'A' && c <= 'Z') ||
			( c >= 'a' && c <= 'z' ));
}

init_encoding( style )
{
	switch( style ) {
		case ABE1:
			num_sets = 3;
			num_prints = 86;
			specials = specials1;
			specprint = specprint1;
			break;
		case ABE2:
			num_sets = 4;
			num_prints = 64;
			specials = specials2;
			specprint = specprint2;
			break;
		case UUENCODE:
			inmode = ONEPASS;
			samp_size = 100;	/* token read ahead */
			num_sets = 0;
			break;
		}
}

	/* any preface on the data */

file_preface()
{
	char pline[MAX_LLEN];

	switch( encoding_style ) {
		case UUENCODE:
			sprintf( pline, "begin %o %s", saved_mode & 0777,
					univname );
			sumput( pline );
			break;
		}
}

/* this uses grave instead of space */

#define UUE(x)  (( ((x)-1) & 0x3f )+'!')

out_uuencode()
{
	char uinbuf[46];
	int len;
	int c;

	/* read in up to 45 bytes */
	for( len = 0; len < 45; len++ ) {
		if( (c = nextchar()) == OUR_EOF )
			break;
		uinbuf[len] = c;
		}
	/* we must never allow a line of length 2, 3, or 4, as that might,
	   just maybe, generate one of our header lines */
	if( len > 1 && len < 5 ) {
		int i;
		/* spew it out as single char lines -- what a waste */
		for( i = 0; i < len; i++ )
			uuline( uinbuf+i, 1 );
		}
	 else
		uuline( uinbuf, len );
	if( len == 0 )
		sumput( "end" );
	return len > 0;
}

uuline(uinbuf, len)
char *uinbuf;		/* binary data */
int len;		/* number of bytes of binary data */
{
	char uubuf[MAX_LLEN];
	char *ubp;
	int i;

	ubp = uubuf;

	*ubp++ = UUE(len);

	for( i = 0; i < len; i += 3 ) {
		*ubp++ = UUE( uinbuf[i] >> 2 );
		*ubp++ = UUE( (uinbuf[i] << 4) & 060 | (uinbuf[i+1] >> 4)&017 );
		*ubp++ = UUE( (uinbuf[i+1] << 2) & 074 | (uinbuf[i+2] >> 6)&03);
		*ubp++ = UUE( uinbuf[i+2] & 077 );
		}
	*ubp = 0;
	/* end of file if len was 0 */
	sumput( uubuf );
}

do_options() {
	fprintf( stderr, "Usage:\n" );
	fprintf( stderr, "\tabe [options] files\n" );
	fprintf( stderr, "or to read from stdin:\n" );
	fprintf( stderr, "\tabe [options]\n" );
	fprintf( stderr, "\nAbe options:\n" );
	fprintf( stderr, "\t+r\tAdd redundant info to each block of multi-block encodings\n" );
	fprintf( stderr, "\t+d\tAdd source to tiny decoder\n" );
	fprintf( stderr, "\t+e\tUse character set safe to translate into EBCDIC\n" );
	fprintf( stderr, "\t+u\tUse UUENCODE style of encoding\n" );
	fprintf( stderr, "\t-n\tDo not place line sequence 'numbers' in output\n" );
	fprintf( stderr, "\tblocksize=int\t\tBlock size for multi-block encoding files\n" );
	fprintf( stderr, "\tprefix=path-prefix\tPrefix for names of multi-block encoding files\n" );
	fprintf( stderr, "\tuname=simple-filename\tUniversal filename (single file encode only)\n" );
	fprintf( stderr, "\tdecoder=pathname\tName of tiny decoder source code file\n" );
	fprintf( stderr, "\tsample=int\t\tSample size for one pass read of stdin\n" );

	fprintf( stderr, "\nIf you use -n, tiny decoders won't work.  On the other hand, -n and +u\n" );
	fprintf( stderr, "together make an encoding most UUDECODE programs can decode.\n" );
}

/* Crc-32 builder, thanks to Rahul Dhesi */
#define CRC_32          0xedb88320L    /* CRC-32 polynomial */

/* calculates CRC of one item */
tcrc
onecrc (item)
int item;
{
   int i;
   tcrc accum = 0;
   item <<= 1;
   for (i = 8;  i > 0;  i--) {
      item >>= 1;
      if ((item ^ accum) & 0x0001)
         accum = (accum >> 1) ^ CRC_32;
      else
         accum >>= 1;
   }
   return (accum);
}

/* generates CRC table, calling onecrc() to make each term */
mkcrctab()
{
   int i;
   for (i = 0;  i < TABSIZE;  i++)
      crctab[i] = onecrc (i);
}
