/* rxshow.c */

#if 1
	char *_procname = "playproc";
	long _BackGroundIO = 0;
	#define puts(S) /**/
#else
	puts(s)
	char *s;
	{
	Write(Output(),s,strlen(s));
	Write(Output(),"\n",1);
	}
#endif

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <graphics/gfxbase.h>
#include <graphics/view.h>
#include <stdio.h>
#include <fcntl.h>

/*  Masking techniques  */
#define	mskNone			0
#define	mskHasMask		1
#define	mskHasTransparentColor	2
#define	mskLasso		3

/*  Compression techniques  */
#define	cmpNone			0
#define	cmpByteRun1		1

/*  Bitmap header (BMHD) structure  */
struct BitMapHeader {
	UWORD	w, h;		/*  Width, height in pixels */
	WORD	x, y;		/*  x, y position for this bitmap  */
	UBYTE	nplanes;	/*  # of planes  */
	UBYTE	Masking;
	UBYTE	Compression;
	UBYTE	pad1;
	UWORD	TransparentColor;
	UWORD	XAspect, YAspect;
	WORD	PageWidth, PageHeight;
};

/*  Makes my life easier.  */
union typekludge {
	char type_str[4];
	long type_long;
};

struct ChunkHeader {
	union typekludge chunktype;
	long chunksize;
};
#define	TYPE		chunktype.type_long
#define	STRTYPE		chunktype.type_str

#define skipchunk(FD,SIZE) fseek(FD, SIZE, 1)

/*  Useful macro from EA (the only useful thing they ever made)  */
#define MAKE_ID(a, b, c, d)\
	( (a<<24) + (b<<16) + (c<<8) + d )

/*  IFF types we may encounter  */
#define	FORM	MAKE_ID('F', 'O', 'R', 'M')
#define	ILBM	MAKE_ID('I', 'L', 'B', 'M')
#define	BMHD	MAKE_ID('B', 'M', 'H', 'D')
#define	CMAP	MAKE_ID('C', 'M', 'A', 'P')
#define	BODY	MAKE_ID('B', 'O', 'D', 'Y')

#define	GRAB	MAKE_ID('G', 'R', 'A', 'B')
#define	DEST	MAKE_ID('D', 'E', 'S', 'T')
#define	SPRT	MAKE_ID('S', 'P', 'R', 'T')
#define	CAMG	MAKE_ID('C', 'A', 'M', 'G')
#define	CRNG	MAKE_ID('C', 'R', 'N', 'G')
#define	CCRT	MAKE_ID('C', 'C', 'R', 'T')
#define	DPPV	MAKE_ID('D', 'P', 'P', 'V')


/*  Other useful things.  */
#define	CHUNKHEADERSIZE		sizeof(struct ChunkHeader)
#define	SUBTYPESIZE		sizeof(long)


/*  What functions return  */
extern void	*OpenLibrary(), *AllocMem(), *AllocRaster(), *GetColorMap();

struct ViewPort	*readform();
struct ViewPort		*vp;
struct View		v, *oldview;
struct GfxBase		*GfxBase;

struct MsgPort *rexxport ;

static struct rexxmsg {
	struct Message cm_Node;
	LONG	RFU1;
	LONG	RFU2;
	LONG	rm_Action;
	LONG	rm_Result1;
	LONG	rm_Result2;
	char	*cm_Args[16];
	LONG	RFU7;
	LONG	RFU8;
	LONG	RFU9;
	LONG	RFU10;
	LONG	RFU11;
	LONG	RFU12;
};

main(argc, argv)
char **argv;
{
register unsigned short i;
struct rexxmsg *msg;
char buf[256];

GfxBase=(struct GfxBase *)OpenLibrary("graphics.library",0);

rexxport=(struct MsgPort *)CreatePort("rexx_show",0);

for (i=0; i<argc; i++) execute(argv[i]);

for (;;) {
	WaitPort(rexxport);
	msg=(struct rexxmsg *)GetMsg(rexxport);
	strcpy(buf, msg->cm_Args[0]);
	msg->rm_Result2=0;
	ReplyMsg((struct Message *)msg);
	execute(buf);
	}
}

execute(s)
char *s;
{
int n;
static int verbose=0;

if (verbose)
	puts(s);
switch(tolower(*s++)) {
	case 'v':
		verbose=!verbose;
		break;
	case 'l':
		loadit(s);
		break;
	case 'd':
		if (n=atoi(s)) Delay(n);
		break;
	case 'w':
		break;
	case 'q':
		clall(0);
	}
}

loadit(filename)
char *filename;
{
struct ChunkHeader ch;
FILE *fd;

if (!(fd = fopen(filename, "r"))) die("File open failed.");
if (!getchunkheader (fd, &ch)) die("Can't discover file type.");
if (ch.TYPE != FORM) die("Not an IFF file.");
if (!(vp = readform(fd, ch.chunksize))) die("readform() failed.");
fclose(fd);

/*
 * And now we make the views.
 */
oldview = GfxBase -> ActiView;
InitView(&v);
v.DxOffset = oldview -> DxOffset;  /* Track	 */
v.DyOffset = oldview -> DyOffset;  /* Preferences */
v.ViewPort = vp;

if (vp -> Modes & LACE) v.Modes |= LACE;
centerpict(vp);

MakeVPort(&v, vp);
MrgCop(&v);
LoadView(&v);
}

/*
 * A sleazy hack for overscan images.  If the guy is using 'morerows',
 * then this doesn't quite work.
 */
centerpict(vp)
register struct ViewPort *vp;
{
if (vp -> Modes & HIRES)
	vp -> DxOffset += (640 - vp->DWidth) / 2;
else
	vp -> DxOffset += (320 - vp->DWidth) / 2;

if (vp -> Modes & LACE)
	vp -> DyOffset += (400 - vp->DHeight) / 2;
else
	vp -> DyOffset += (200 - vp->DHeight) / 2;
}

clall(n)
{
if (oldview) {
	LoadView(oldview);
	WaitTOF();
	if (v.LOFCprList) {
		FreeCprList(v.LOFCprList);
		v.LOFCprList = NULL;
		}
	if (v.SHFCprList) {
		FreeCprList(v.SHFCprList);
		v.SHFCprList = NULL;
		}
	oldview = NULL;
	}
if (vp) {
	closeviewport(vp);
	vp = NULL;
	}
if (rexxport) DeletePort(rexxport);
exit(n);
}

die(str)
char *str;
{
puts(str);
clall(20);
}

static struct ViewPort	*viewport;		/*  Working viewport  */

/*
 * Okay, here's how this works.  This routine assumes that the file
 * descriptor points to just past the FORM type and size fields in the file.
 * It returns a pointer to a ViewPort structure with all necessary
 * substructures, suitable for MakeVPort()ing.  It assumes someone else will
 * know how to deallocate all said structures and resources.  Fortunately,
 * there's enough information in ViewPorts to be able to intelligently free
 * anything they may use.
 */
struct ViewPort *readform(fd, formsize)
FILE *fd;			/*  File descriptor  */
long formsize;			/*  Size of this FORM  */
{
struct BitMapHeader	bmhd;
struct ChunkHeader	ch;
long			subtype;
register int		i, n;
char			gotheader = 0, gotcmap = 0, gotcamg = 0;
void			*tmp;
char			errmsg[]="XXXX: Unknown chunk";

if (!getsubtype(fd, &subtype)) return NULL;
formsize -= sizeof (subtype);

if (subtype != ILBM) {
	puts ("FORM not an ILBM, skipping...");
	skipchunk (fd, formsize - sizeof (subtype));
	return NULL;
}

if (!(viewport = AllocMem ((long) sizeof (*viewport), MEMF_CLEAR)))
	ackphft ("ViewPort allocation failed.");
InitVPort (viewport);

while (formsize > 0) {

	if (!getchunkheader (fd, &ch))
		ackphft ("Malformed IFF FORM.");
	formsize -= sizeof (ch);

	switch (ch.TYPE) {
	case BMHD: {
		register struct BitMap *bm;
		register struct RasInfo *ri;

		fread ((char *)&bmhd, (int) ch.chunksize, 1, fd);

		if (!(bm = AllocMem(sizeof(struct BitMap), MEMF_CLEAR)))
			ackphft ("No memory for BitMap");
		InitBitMap (bm,
				 (long) bmhd.nplanes,
				 (long) bmhd.w, (long) bmhd.h);

		if (!(ri = AllocMem(sizeof(struct RasInfo), MEMF_CLEAR)))
			ackphft ("No memory for RasInfo");
		ri -> BitMap = bm;
		ri -> RxOffset = ri -> RyOffset = NULL;
		ri -> Next = NULL;

		viewport -> DWidth = bmhd.w;
		viewport -> DHeight = bmhd.h;
		viewport -> RasInfo = ri;

		gotheader = 1;
		break;
	}

	case CMAP: {
		register UBYTE *ctable;
		register UWORD *cmap;

		if (!(ctable = AllocMem (ch.chunksize, NULL)))
			ackphft ("EA colortable alloc failed.");
		fread (ctable, (int) ch.chunksize, 1, fd);

		if (!(cmap = AllocMem (ch.chunksize * 2 / 3, NULL)))
			ackphft ("Colormap alloc failed.");

		for (i = n = 0; n < ch.chunksize; i++, n+=3)
			cmap[i] = ((ctable[n]   >> 4) << 8) +
				  ((ctable[n+1] >> 4) << 4) +
				  ( ctable[n+2] >> 4);

		if ((1 << bmhd.nplanes) != i)
			puts ("Warning: Colormap not sized to nplanes, hope it's HAM.");
		viewport -> ColorMap = GetColorMap ((long) i);
		LoadRGB4 (viewport, cmap, (long) i);
		FreeMem (cmap, ch.chunksize * 2 / 3);
		FreeMem (ctable, ch.chunksize);

		gotcmap = 1;
		break;
	}

	case CAMG:
		/*  Use subtype as a temporary buffer  */
		fread ((char *)&subtype, (int) ch.chunksize, 1, fd);
		viewport -> Modes = (UWORD) (subtype & 0xffff);

		gotcamg = 1;
		break;

	case BODY:
		if (!gotheader || !gotcmap)
			ackphft ("BODY before BMHD or CMAP.");

		loadbitmap (fd, viewport, &bmhd);
		break;

	case CRNG:
	case GRAB:
	case DEST:
	case SPRT:
	case DPPV:	/*  Anyone know what this one is for?  */
		skipchunk (fd, ch.chunksize);
		break;

	default:
		* ((long *)errmsg) = ch.TYPE;
		puts(errmsg);
		skipchunk (fd, ch.chunksize);
	}

	formsize -= ch.chunksize;
	if (ch.chunksize & 1) {		/*  Odd length chunk  */
		formsize --;
		fseek (fd, 1L, 1);
	}
}

/*  Post-processing in case of lack of CAMG chunk  */
if (!gotcamg) {
	if (bmhd.w > 370)	/*  Arbitrary limit  */
		viewport -> Modes |= HIRES;
	if (bmhd.h > 256)
		viewport -> Modes |= LACE;
	}

tmp = viewport;
viewport = NULL;
return tmp;
}

loadbitmap (fd, vp, header)
FILE *fd;
struct ViewPort *vp;
struct BitMapHeader *header;
{
	register struct BitMap	*bm;
	register int		i, n;
	int			plane_offset = 0;

	bm = vp -> RasInfo -> BitMap;

	if (header->Compression != cmpNone &&
	    header->Compression != cmpByteRun1)
		ackphft ("Unrecognized compression technique.");

	for (i=0; i < bm->Depth; i++)
		if (!(bm -> Planes[i] = AllocRaster ((long) vp -> DWidth,
						     (long) vp -> DHeight)))
			ackphft ("Bitplane allocation failed.");

	for (i=0; i < bm->Rows; i++) {
		for (n=0; n < bm->Depth; n++) {
/*- - - - - - - - - - -*/
if (!header->Compression) {	/* No compression */
	if (!fread (bm -> Planes[n] + plane_offset,
		    bm -> BytesPerRow, 1, fd))
		ackphft ("Failure in BODY read.");

} else {
	int		so_far;
	register UBYTE	*dest = bm -> Planes[n] + plane_offset;
	char		len;

	/*
	 * Note:  All file I/O after this point is assumed to be sucessful.
	 * This is clearly a poor assumption, but it saves on typing.
	 * And besides, putting the checking in is simple :-) :-).
	 */
	so_far = bm -> BytesPerRow;
	while (so_far > 0) {
		if ((len = getc (fd)) >= 0) {	/*  Literal byte copy  */
			so_far -= ++len;
			fread (dest, len, 1, fd);
			dest += len;

		} else if ((UBYTE) len == 128)	/*  NOP  */
			;

		else if (len < 0) {		/*  Replication count  */
			UBYTE	byte;

			len = -len + 1;
			so_far -= len;
			byte = getc (fd);
			while (--len >= 0)
				*dest++ = byte;
		}
	}
	if (so_far)
		ackphft ("Compression quite screwed up.");
}
/*- - - - - - - - - - -*/
		}
		plane_offset += bm -> BytesPerRow;
	}
}


getchunkheader (fd, header)
FILE *fd;		/*  File descriptor (that's what 'fd' stands for)  */
struct ChunkHeader *header;
{
	return (fread ((char *)header, sizeof(*header), 1, fd));
}

getsubtype (fd, type)
FILE *fd;
long *type;
{
	/*  !! NOT PORTABLE !!  */
	return (fread ((char *)type, sizeof(*type), 1, fd));
}

/*
 * This function assumes the existence of the global variable viewport, which
 * is a pointer to a working ViewPort structure.  This is to allow graceful
 * cleanup of allocated resources in case of an exceptional failure.
 */
freepict ()
{
	register struct BitMap *bm;
	register int i;

	if (viewport) {
		if (viewport -> RasInfo) {

/*- - - - - - - - - - -*/
if (bm = viewport -> RasInfo -> BitMap) {
	for (i=0; i < bm->Depth; i++)
		if (bm -> Planes[i])
			FreeRaster (bm -> Planes[i],
					(long) viewport -> DWidth,
					(long) viewport -> DHeight);
	FreeMem (bm, sizeof (*bm));
	}
/*- - - - - - - - - - -*/

			FreeMem (viewport -> RasInfo,
				 (long) sizeof (struct RasInfo));
		}

		if (viewport -> ColorMap)
			FreeColorMap (viewport -> ColorMap);

		FreeVPortCopLists (viewport);
		FreeMem (viewport, (long) sizeof (*viewport));
		viewport = NULL;
	}
}

closeviewport (vp)
struct ViewPort *vp;
{
viewport = vp;
freepict ();
}

/*
 * Premature termination routine.
 */
ackphft (str)
char *str;
{
freepict();
die(str);
}
