#include <exec/types.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
/* if you define NO_PRAGMAS before including proto/powerpacker.h */
/* you must link with ppSCglue.o or ppLCglue.o                   */
#include <proto/powerpacker.h>
#include <libraries/ppbase.h>
#include "ppcompress.h"


struct PPBase	*PPBase	= NULL;	/* Home of PP */
APTR		ci	= NULL;	/* PP's CrunchInfo array */
char	*myname;		/* program name, for operation diagnostic */
BPTR	fp;	/* global filehandle */
char	*fn;			/* global filename ptr for user messages */
UBYTE	*data	= NULL;		/* PP's data buffer */
ULONG	datalen;		/* length of PP's data buffer */
BOOL	enforce		= 1;	/* whether to enforce '.pp' extensions */
char	ppsuff[]	= ".pp";	/* filename extension (not?) to enforce */
char	userbreak[]	= "User break";	/* what to say when user kills me */
struct FileInfoBlock	*fib;
int	filesdone = 0;

/* prototypes */
void	main(int, char **);
void	process(char *, ULONG, ULONG, int);
char	*tagname(char *, char *);
char	*untagname(char *, char *);
BOOL	uncrunch(char *, int);
BOOL	crunch(char *, ULONG, ULONG, int);
void	Usage(void);
BOOL __stdargs __saveds	chkbrk(ULONG, ULONG, ULONG, APTR);
void	clean(int, unsigned char);

extern char *GetMyStdIn(char *);
extern BOOL PutMyStdOut(UBYTE *, int);

char version[]="$VER: PPCompress/PPUnCompress/PPCat 0.95 (14.11.92)";


void main(int ac, char **av) {
char	option;
ULONG	eff=CRUN_VERYGOOD, speedup=SPEEDUP_BUFFMEDIUM;
char	*optarg;
int	i;
char	tmpbuf[L_tmpnam+1];

	if ( ac == 0 ) clean(20, QUIT);

	fn=myname=av[0];
	shift;

	if ( !(PPBase = (struct PPBase *)OpenLibrary ("powerpacker.library", 35L)) ) {
		fail("You need powerpacker.library V35+");
		clean(20, QUIT);
	}

	while ( ac ) {
		if (USER_HIT_CTRL_C) {
			info(userbreak);
			clean(10, QUIT);
		}
		if ( av[0][0] ==  '-' ) {
			option	= av[0][1];
			if ( strlen(av[0]) == 2 ) {
				shift;
				optarg=av[0];
			}
			else optarg=&av[0][2];
			switch ( option ) {
			case 'h':
				Usage();
				clean(NULL, QUIT);
				break;
			case 'p':
				i = atoi(optarg);
				if ( (i != 0) && (i != 1) ) {
					fail("-p must signal 0 or 1");
					clean(10, QUIT);
				}
				else enforce = i;
				break;
			case 'e':
				switch ( optarg[0] ) {
				case 'f':
					eff=CRUN_FAST;
					break;
				case 'm':
					eff=CRUN_MEDIOCRE;
					break;
				case 'g':
					eff=CRUN_GOOD;
					break;
				case 'v':
					eff=CRUN_VERYGOOD;
					break;
				case 'b':
					eff=CRUN_BEST;
					break;
				default:
					fail("Unknown efficiency");
					clean(10, QUIT);
					break;
				}
				break;
			case 'b':
				switch ( optarg[0] ) {
				case 's':
					speedup=SPEEDUP_BUFFSMALL;
					break;
				case 'm':
					speedup=SPEEDUP_BUFFMEDIUM;
					break;
				case 'l':
					speedup=SPEEDUP_BUFFLARGE;
					break;
				default:
					fail("Unknown speedup");
					clean(10, QUIT);
					break;
				}
				break;
			}
			shift;
		}
		else {
			process(av[0], eff, speedup, FILE);
			shift;
			filesdone++;
		}
	}
	if ( !filesdone ) process(GetMyStdIn(tmpbuf), eff, speedup, PIPE);
	DeleteFile(tmpbuf);
	clean(0, QUIT);
}

void process(char *file, ULONG eff, ULONG speedup, int mode) {

	fn = file;

	if ( !strcmp(myname, "ppcompress") ) crunch(file, eff, speedup, mode);
	else if ( !strcmp(myname, "ppuncompress") ) uncrunch(file, mode);
	else if ( !strcmp(myname, "ppcat") ) uncrunch(file, PIPE);
	else {
		fail("Must be ppcompress, ppuncompress, or ppcat");
		clean(10, QUIT);
	}

	return;
}

char *tagname(char *in, char *out) {

	if ( !enforce ) strncpy(out, in, 30);	/* if no ext enforcing, don't tag */
	else {
		strncpy(out, in, 27);
		strcat (out, ppsuff);
	}
	return (out);
}

char *untagname(char *in, char *out) {
char	*p;
BPTR	tmpfp;

	strncpy(out, in, 30);
	if ( p=strrchr(out, '.') ) {
		if ( !strcmp(p, ppsuff) ) *p='\0';
		if ( !(tmpfp = Open(in, MODE_OLDFILE)) ) {
			p = &in[(strlen(in)>27?27:strlen(in))];
			strcpy (p, ppsuff);
		}
		else Close(tmpfp);
	}
	return(in);
}

BOOL uncrunch(char *in_fn, int mode) {
int err;
char in_fn2[31];
char out_fn[31];

	strncpy(in_fn2, in_fn, 30);
	in_fn = untagname(in_fn2, out_fn);
	err = ppLoadData(in_fn, DECR_NONE, MEMF_PUBLIC, &data, &datalen, NULL);
	if ( err ) {
		fail(ppErrorMessage(err));
		return(FALSE);
	}
	if ( mode == FILE ) {
		fn=out_fn;
		if ( !(fp=(BPTR)Open(out_fn, MODE_NEWFILE)) ) {
			fail("Could not create file");
			clean(20, FREEMEM);
			return(FALSE);
		}
		if ( Write(fp, data, datalen) != datalen ) {
			fail("Could not write whole file");
			clean(20, CLOSEFP | FREEMEM);
			return(FALSE);
		}
		clean(0, CLOSEFP | FREEMEM);
		if ( strcmp(in_fn, out_fn) ) {
			if ( !(DeleteFile(in_fn)) ) {
				fn=in_fn;
				fail("Could not delete file");
			}
		}
	}
	else {
	BOOL stat;
		stat=PutMyStdOut(data, datalen);
		clean(0, FREEMEM);
		if ( !stat ) {
			Write(Output(), "\n", 1);
			return(FALSE);
		}
	}

	return(TRUE);
}

BOOL crunch(char *in_fn, ULONG eff, ULONG speedup, int mode) {
ULONG	crunchlen;
char	out_fn[31];
BPTR	lock;

	if ( !(ci = ppAllocCrunchInfo(eff, speedup, chkbrk, NULL)) ) {
		fail("Can't allocate crunch space");
		clean(20, QUIT);
	}

	if ( (fp=(BPTR)Open(in_fn, MODE_OLDFILE)) == NULL ) {
		fail("Could not open file");
		clean(0, FREECI);
		return(FALSE);
	}
	if ( !(fib = AllocMem(sizeof(struct FileInfoBlock), MEMF_PUBLIC)) ) {
		fail("Could not get data size");
		clean(20, FREECI);
	}
	if ( !(lock = Lock(in_fn, ACCESS_READ)) ) {
		fail("Could not lock file");
		clean(0, CLOSEFP | FREECI | FREEFIB);
		return(FALSE);
	}
	if ( !(Examine(lock, fib)) ) {
		fail("Could not stat file");
		UnLock(lock);
		clean(0, CLOSEFP | FREECI | FREEFIB);
		return(FALSE);
	}
	UnLock(lock);
	if ( fib->fib_DirEntryType > 0 ) {
		clean(0, CLOSEFP | FREECI | FREEFIB);
		return(FALSE);		/* ignore any dirs in list */
	}
	datalen=fib->fib_Size;
	FreeMem(fib, sizeof(struct FileInfoBlock));
	if ( !(data=AllocMem(datalen, MEMF_PUBLIC)) ) {
		fail("Could not allocate a crunch buffer");
		clean(0, CLOSEFP | FREECI);
		return(FALSE);
	}
	if ( Read(fp, data, datalen) != datalen ) {
		fail("Could not read whole file");
		clean(0, CLOSEFP | FREEMEM | FREECI);
		return(FALSE);
	}

	switch ( crunchlen=ppCrunchBuffer(ci, data, datalen) ) {
	case	PP_CRUNCHABORTED:
		info(userbreak);
		clean(0, CLOSEFP | FREEMEM | FREECI);
		return(FALSE);
	case	PP_BUFFEROVERFLOW:
		fail("Buffer overflow; no compression possible");
		clean(0, CLOSEFP | FREEMEM | FREECI);
		return(FALSE);
	default:
		clean(0, CLOSEFP | FREECI);
	}

	if ( mode == FILE ) {
		tagname(in_fn, out_fn);
		fn=out_fn;
		if ( !(fp=(BPTR)Open(out_fn, MODE_NEWFILE)) ) {
			fail("Could not create file");
			clean(20, FREEMEM);
			return(FALSE);
		}
		if ( !(ppWriteDataHeader(fp, eff, FALSE, NULL)) ) {
			fail("Could not write PP header");
			clean(20, CLOSEFP | FREEMEM);
			return(FALSE);
		}
		if ( Write(fp, data, crunchlen) != crunchlen ) {
			fail("Could not write whole file");
			clean(20, CLOSEFP | FREEMEM);
			return(FALSE);
		}
		clean(0, CLOSEFP | FREEMEM);
		if ( enforce ) {
			if ( !(DeleteFile(in_fn)) ) {
				fn=in_fn;
				fail("Could not delete file");
			}
		}
	}
	else {
	BOOL stat;
		if ( !(ppWriteDataHeader(Output(), eff, NULL, NULL)) ) {
			fail("Could not write PP header");
			clean(20, FREEMEM);
			return(FALSE);
		}
		stat=PutMyStdOut(data, crunchlen);
		clean(0, FREEMEM);
		if ( !stat ) return(FALSE);
	}

	return(TRUE);
}

void Usage(void) {
	fprintf(stderr, "%s: Usage:\n%s [-p 0|1] [-e f|m|g|v|b] [-b s|m|l] file [file [...] ]\n",myname, myname);
	return;
}

/* callback func for ppcruncher; check for user break */
BOOL __stdargs __saveds chkbrk(ULONG lensofar, ULONG crunlen, ULONG totlen, APTR userdata) {
	if (USER_HIT_CTRL_C) return(FALSE);
	else return(TRUE);
}

/* use this to free global allocations or to quit */
void clean(int code, unsigned char flags) {
	/* free all resources */
	if (flags & FREEFIB)	FreeMem(fib, sizeof(struct FileInfoBlock));
	if (flags & FREECI)	ppFreeCrunchInfo(ci);
	if (flags & FREEMEM)	FreeMem (data, datalen);
	if (flags & CLOSEFP)	Close(fp);
	if (flags & QUIT) {
		if (PPBase)	CloseLibrary ((struct Library *)PPBase);
		exit (code);
	}
	else return;
}
