/*  :ts=8 bk=0
 *
 * looki.c:	Yet Another ILBM Viewer, except this one uses the new
 *		iffparse.library.
 *
 * Written for Manx 3.4b.  You should install the iffparse.lib link library
 * where 'ln' can find it.  This program is constructed by saying:
 * 1> cc looki.c
 * 1> ln looki.o -lm -liffparse -lc
 *
 * Note:  This program isn't as fully featured as I'd like (I started late).
 * In particular, I'd have liked it to have color cycling capability (toggled
 * on and off by hitting TAB), and the ability to turn the title bar on and
 * off with F10.
 *
 * Another thing to note is that, because many pictures were/are written
 * incorrectly, this program determines whether HIRES and/or LACE should be
 * set based on the CAMG chunk, which is incorrect (this information should
 * be derived from the XAspect/YAspect fields).  However, the program will
 * note an inconsistency and flame about it.
 *
 * Even with this capability, there are still some images that it won't
 * display "correctly".  In particular, I have encountered some 640 x 200
 * images saved with no CAMG chunk and with an X/Y aspect ratio of 10/11.
 * Thus, these images will be displayed in hi-res *interlace*, because that's
 * what the BMHD specifies: nearly-square pixels.  I refuse to install a
 * special case hack to compensate for this (because I'm a self-righteously
 * indignant kinda guy.  So there :-) ).  I contend I am doing The Right
 * Thing.  If the files were written wrong, then they should look wrong.
 * 
 * This code is provided AS IS.  No warranties, either expressed or implied,
 * of any kind are made.  Damages or misfortune arising from the use or
 * misuse of this code are the sole responsibility of the individual using
 * or misusing it.
 *
 * Leo L. Schwab				8906.02
 */
#include <exec/types.h>
#include <intuition/intuition.h>
#include <iff/iffparse.h>
#include <stdio.h>
#include <math.h>
#include "myilbm.h"

#define	MAX(a,b)	((a) > (b) ? (a) : (b))
#define	MIN(a,b)	((a) < (b) ? (a) : (b))
#define	BPR(w)		((w) + 15 >> 4 << 1)

/*  Note:  These are arbitrary numbers I pulled out of thin air.  */
#define	MAXLORES	512
#define	MAXNONLACE	320


extern void	*OpenLibrary(), *AllocMem(), *OpenScreen(), *OpenWindow();

extern FILE	*fopen();

LONG		handlefile(), displayfile(), displayimage(), loadbody(),
		loadbitmap(), loadline(), createscreen();

struct Screen	*scr, sensor;
struct Window	*win;
struct IFF_File	*iff;
void		*IntuitionBase, *GfxBase, *IFFParseBase;


main (ac, av)
int	ac;
char	**av;
{
	static char	usage[] =
"Usage: %s file [file ... ]	; For viewing files.\n\
   or  %s -c			; For viewing the clipboard.\n\
Click mouse button when finished looking.\n";

	/*
	 * Check for arguments.  WorkBench startup not currently supported.
	 */
	if (ac < 2)
		if (ac == 1) {
			printf (usage, *av, *av);
			exit (20);
		}

	openstuff ();

	ac--;
	while (ac--)
		handlefile (iff, *++av);

	closestuff ();
}

LONG
handlefile (iff, filename)
struct IFF_File	*iff;
register char	*filename;
{
	LONG	error;
	char	cboard;

	cboard = (*filename == '-'  &&  filename[1] == 'c');

	if (cboard) {
		/*
		 * Set up IFF_File for Clipboard I/O.
		 */
		if (!(iff -> iff_Stream =
				(ULONG) OpenClipboard (PRIMARY_CLIP))) {
			puts ("Clipboard open failed.");
			return (FALSE);
		}
		InitIFFasClip (iff);
		BeginCB_Read (iff);
	} else {
		/*
		 * Set up IFF_File for buffered stdio I/O.
		 */
		if (!(iff -> iff_Stream = (ULONG) fopen (filename, "r"))) {
			printf ("%s: File open failed.\n", filename);
			return (FALSE);
		}
		initiffasstdio (iff, IFFM_READ);
	}

	printf ("%s: ", cboard ? "[Clipboard]" : filename);
	fflush (stdout);
	OpenIFF (iff);

	error = printIFFerr (displayfile (iff));

	CloseIFF (iff);
	fputs ("\r\233K", stdout);
	if (cboard) {
		EndCB_Read (iff);
		CloseClipboard (iff -> iff_Stream);
	} else
		fclose (iff -> iff_Stream);

	return (error);
}

LONG
displayfile (iff, filename)
register struct IFF_File *iff;
char			 *filename;
{
	/*
	 * Properties that will be collected automatically.
	 */
	static LONG			ilbmprops[] = {
		ID_ILBM, ID_BMHD,
		ID_ILBM, ID_CMAP,
		ID_ILBM, ID_CAMG
	};
	register struct ContextNode	*cn;
	register LONG			error;
	char				foundbody = 0, hunt = 0;

	/*
	 * Declare property, collection and stop chunks.
	 */
	if (error = PropChunks (iff, ilbmprops, 3L))
		return (error);
	if (error = StopChunk (iff, ID_ILBM, ID_BODY))
		return (error);
	if (error = CollectionChunk (iff, ID_ILBM, ID_CRNG))
		return (error);

	/*
	 * We want to stop at the end of an ILBM context.
	 */
	if (error = StopOnExit (iff, ID_ILBM, ID_FORM))
		return (error);

	/*
	 * Take first parse step to enter main chunk.
	 */
	if (error = ParseIFF (iff, IFFPARSE_STEP))
		return (error);

	/*
	 * Test the chunk info to see if this is a simple ILBM.
	 */
	if (!(cn = CurrentChunk (iff))) {
		/*
		 * This really should never happen.  If it does, it means
		 * our parser is broken.
		 */
		puts ("Weird parsing error; no top chunk!");
		return (1);
	}
	if (cn -> cn_ID != ID_FORM  ||  cn -> cn_Type != ID_ILBM) {
		printf
		 ("This is a(n) %.4s.%.4s.  Looking for embedded ILBMs...",
		  &cn -> cn_Type, &cn -> cn_ID);
		fflush (stdout);
		hunt = TRUE;
	}

	/*
	 * Start scanning the file looking for interesting things.
	 * The parser will do all the yucky IFF work for us.
	 */
	while (!error) {
		/*
		 * ParseIFF() will return on BODY chunk or end of
		 * context for an ILBM FORM.
		 */
		error = ParseIFF (iff, IFFPARSE_SCAN);

		/*
		 * If we've left the ILBM FORM context, then we now have
		 * collected all possible information.  We are now ready
		 * to display the image.
		 */
		if (error == IFFERR_EOC) {
			error = displayimage (iff, filename);
			deletescreen ();
			if (error)
				break;
			continue;
		}

		/*
		 * Other values for error are real errors.
		 */
		if (error)
			break;

		/*
		 * NULL error means that we've stopped on an ILBM.BODY.
		 */
		if (error = loadbody (iff))
			return (error);
		foundbody = TRUE;
	}

	if (error == IFFERR_EOF)
		if (!foundbody) {
			if (hunt)
				puts ("None found.");
			else
				puts ("No imagery to display.");
			return (1);
		} else {
			if (hunt)
				putchar ('\n');
			return (0);
		}
	else
		return (error);
}

LONG
displayimage (iff, filename)
struct IFF_File	*iff;
char		*filename;
{
	if (scr) {
		/*  Well, we must have found *something*...  */
		register int	sw, sh;

		sw = sensor.Width;
		if (scr -> ViewPort.Modes & HIRES)
			sw <<= 1;
		sh = sensor.Height;
		if (scr -> ViewPort.Modes & LACE)
			sh <<= 1;

		if (scr -> Width > sw  &&  scr -> Height > sh) {
			/*  Overscan;  center screen's viewport.  */
			scr -> ViewPort.DxOffset += sw - scr->Width >> 1;
			scr -> ViewPort.DyOffset += sh - scr->Height >> 1;
			MakeScreen (scr);
			RethinkDisplay ();
		}
		SetWindowTitles (win, -1L, filename);
		ScreenToFront (scr);
		ActivateWindow (win);

		WaitPort (win -> UserPort);
	}
	return (0);
}

LONG
loadbody (iff)
struct IFF_File *iff;
{
	register struct StoredProperty	*sp;
	register struct BitMapHeader	*bmhd;
	LONG				camg, error;
	UBYTE				*cmap;

	if (!(sp = FindProp (iff, ID_ILBM, ID_BMHD))) {
		puts ("No ILBM.BMHD chunk!");
		return (1);
	}
	bmhd = (struct BitMapHeader *) sp -> sp_Data;

	if (error = createscreen (iff, bmhd))
		return (error);

	setcolors (iff, bmhd);

	return (loadbitmap (iff, bmhd, &scr -> BitMap));
}

LONG
createscreen (iff, bmhd)
struct IFF_File			*iff;
register struct BitMapHeader	*bmhd;
{
	static struct NewScreen		scrdef = {
		0, 0, 0, 0, 0,
		0, 1,
		NULL,			/*  Modes  */
		CUSTOMSCREEN,		/* | SCREENBEHIND,	*/
		NULL,
		NULL,
		NULL, NULL,
	};
	static struct NewWindow		windef = {
		0, 0, 0, 0,
		-1, -1,
		MOUSEBUTTONS,
		BORDERLESS | BACKDROP,
		NULL, NULL, NULL,
		NULL,			/*  Screen filled in later.  */
		NULL,
		0, 0, 0, 0,
		CUSTOMSCREEN
	};
	register struct StoredProperty	*sp;
	double				ratio;
	ULONG				camg;
	UWORD				wide, high, modes;

	wide = MAX (bmhd -> w, bmhd -> PageWidth);
	high = MAX (bmhd -> h, bmhd -> PageHeight);

	if (bmhd -> YAspect)
		ratio = (double) bmhd -> XAspect / (double) bmhd -> YAspect;
	else
		ratio = 0;

	/*
	 * Compute what HIRES/LACE *should* be set to.
	 * (See note in opening comment.)
	 */
	if (fabs (ratio - 1.0) < 0.15)
		/*  Nearly square pixels.  */
		if (wide >= MAXLORES)
			modes = HIRES | LACE;
		else
			modes = 0;
	else if (fabs (ratio - 2.0) < 0.15)
		/*  Short, wide pixels.  */
		modes = LACE;
	else if (fabs (ratio - 0.5) < 0.15)
		/*  Tall, narrow pixels.  */
		modes = HIRES;
	else {
		/*  Weird aspect ratio; we'll have to guess...  */
		modes = 0;
		if (wide >= MAXLORES)
			modes |= HIRES;
		if (high >= MAXNONLACE)
			modes |= LACE;
	}

	/*
	 * Grab CAMG's idea of the viewmodes.
	 */
	if (sp = FindProp (iff, ID_ILBM, ID_CAMG)) {
		camg = * (ULONG *) sp -> sp_Data;
		scrdef.ViewModes =
		 (camg & (HIRES | LACE | DUALPF | HAM | EXTRA_HALFBRITE));
		if (modes ^ (camg & (HIRES | LACE)))
/*- - - - - - - - - - -*/
printf ("XAspect/YAspect (%d/%d) inconsistent with CAMG.\n",
	bmhd -> XAspect, bmhd -> YAspect);
/*- - - - - - - - - - -*/
	} else {
		/*
		 * No CAMG present; use computed modes.
		 */
		scrdef.ViewModes = modes;
		if (bmhd -> nplanes > 5)
			/*
			 * With 6 or more planes and the absence of a CAMG,
			 * we have to make an assumption.  I assume HAM.
			 */
			scrdef.ViewModes |= HAM;
	}

	/*
	 * Open the screen and window.
	 */
	deletescreen ();
	scrdef.Width		= wide;
	scrdef.Height		= high;
	scrdef.Depth		= bmhd -> nplanes;
	if (!(scr = OpenScreen (&scrdef))) {
		puts ("Failed to open display screen.");
		return (1);
	}

	windef.Screen	= scr;
	windef.Width	= wide;
	windef.Height	= high;
	if (!(win = OpenWindow (&windef))) {
		puts ("Failed to open window.");
		return (1);
	}
	ShowTitle (scr, FALSE);
	return (0);
}

deletescreen ()
{
	if (win)	CloseWindow (win), win = NULL;
	if (scr)	CloseScreen (scr), scr = NULL;
}

setcolors (iff, bmhd)
struct IFF_File			*iff;
register struct BitMapHeader	*bmhd;
{
	register struct StoredProperty	*sp;
	register LONG			idx;
	register int			ncolors;
	register UBYTE			*rgb;
	struct ViewPort			*vp = &scr -> ViewPort;
	LONG				r, g, b;
	int				nc, ns;

	if (sp = FindProp (iff, ID_ILBM, ID_CMAP)) {
		/*
		 * Compute the actual number of colors we need to convert.
		 */
		nc = sp -> sp_Size / 3;
		ns = 1 << bmhd -> nplanes;
		ncolors = MIN (ns, nc);

		rgb = sp -> sp_Data;
		idx = 0;
		while (ncolors--) {
			r = *rgb++ >> 4;
			g = *rgb++ >> 4;
			b = *rgb++ >> 4;
			SetRGB4 (vp, idx++, r, g, b);
		}
	}
}

LONG
loadbitmap (iff, bmhd, destmap)
register struct IFF_File	*iff;
register struct BitMapHeader	*bmhd;
struct BitMap			*destmap;
{
	register int		i, n, p;
	register UWORD		*srcline, *destline;
	struct BitMap		mapcopy;
	UWORD			*linebuf;
	LONG			error;
	int			srcw, srch, destw, desth, rows, deep, mod;

	/*
	 * Check compression type.
	 */
	if (bmhd -> Compression != cmpNone &&
	    bmhd -> Compression != cmpByteRun1) {
		printf ("Unknown compression format type %d.\n",
			bmhd -> Compression);
		return (1);
	}

	CopyMem (destmap, &mapcopy, (LONG) sizeof (mapcopy));

	srcw	= BPR (bmhd -> w);
	srch	= bmhd -> h;
	destw	= mapcopy.BytesPerRow;
	desth	= mapcopy.Rows;
	rows	= MIN (srch, desth);
	mod	= destw - srcw;
	if (mod < 0)
		mod = -mod;

	deep	= bmhd -> nplanes;
	if (bmhd -> Masking == mskHasMask)
		deep++;

	/*
	 * Allocate a one-line buffer to load imagery in.  The line is
	 * then copied into the destination bitmap.  This seeming
	 * duplicity makes clipping loaded images easier.
	 */
	if (!(linebuf = AllocMem ((LONG) srcw * deep, 0L))) {
		puts ("Failed to allocate unpacking buffer");
		return (1);
	}

	/*
	 * Load the BODY into the allocated line buffer, then copy into
	 * the destination bitmap.
	 */
	for (i = rows;  i--; ) {
		if (error = loadline (iff,
				      linebuf,
				      srcw,
				      deep,
				      bmhd -> Compression))
			break;

		srcline = linebuf;
		for (p = 0;  p < bmhd -> nplanes;  p++) {
			destline = (UWORD *) mapcopy.Planes[p];
			*destline = 0xffff;
			n = (MIN (srcw, destw)) >> 1;
			while (n--)
				*destline++ = *srcline++;
			if (srcw > destw)
				(UBYTE *) srcline += mod;
			mapcopy.Planes[p] += destw;
		}
	}

	FreeMem (linebuf, (LONG) srcw * deep);
	return (error);
}

LONG
loadline (iff, buf, wide, deep, cmptype)
struct IFF_File	*iff;
register UBYTE	*buf;
register int	wide;
int		deep, cmptype;
{
	register int	i;

	for (i = deep;  i--; ) {
		if (cmptype == cmpNone) {	/* No compression */
			if (ReadChunkBytes (iff, buf, (LONG) wide) != wide)
				return (IFFERR_READ);

		} else {
			register int	so_far;
			register UBYTE	*dest = buf;
			BYTE		len;

			so_far = wide;
			while (so_far > 0) {
				if (ReadChunkBytes (iff, &len, 1L) != 1)
					return (IFFERR_READ);

				if (len >= 0) {
					/*  Literal byte copy  */
					so_far -= ++len;
					if (ReadChunkBytes
					    (iff, dest, (LONG) len) != len)
						return (IFFERR_READ);
					dest += len;

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

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

					len = -len + 1;
					so_far -= len;
					if (ReadChunkBytes
					    (iff, &byte, 1L) != 1)
						return (IFFERR_READ);
					while (--len >= 0)
						*dest++ = byte;
				}
			}
			if (so_far) {
				puts ("Image didn't decompress right.");
				return (1);
			}
		}
		buf += wide;
	}
	return (0);
}


/*
 * File I/O functions which the IFF library will call.
 */
static LONG
buf_read (stream, buf, nbytes, unused)
FILE	*stream;
UBYTE	*buf;
LONG	nbytes;
LONG	unused;
{
	return (fread (buf, 1, (int) nbytes, stream) != nbytes);
}

static LONG
buf_write (stream, buf, nbytes, unused)
FILE	*stream;
UBYTE	*buf;
LONG	nbytes;
LONG	unused;
{
	return (fwrite (buf, 1, (int) nbytes, stream) != nbytes);
}

static LONG
buf_seek (stream, unused1, nbytes, unused2)
FILE	*stream;
LONG	nbytes;
LONG	unused1, unused2;
{
	return (fseek (stream, nbytes, 1) == -1);
}

initiffasstdio (iff, accessmode)
register struct IFF_File *iff;
LONG			 accessmode;
{
	extern LONG	GenericStub();

	/*
	 * Initialize the IFF structure to point to the buffered I/O
	 * routines.  Unbuffered I/O is terribly slow.
	 */
	InitIFF (iff,
		 IFFM_FSEEK | IFFM_RSEEK | (accessmode & IFFM_MODE),
		 GenericStub,
		 buf_read,
		 buf_write,
		 buf_seek);
}


/*
 * IFF error printing function.
 */
printIFFerr (error)
LONG	error;
{
	/*
	 * English error messages for possible IFFERR_#? returns from various
	 * IFF routines.  To get the index into this array, take your IFFERR
	 * code, negate it, and subtract one.
	 *  idx = -error - 1;
	 */
	static char	*errormsgs[] = {
		"End of file (not an error).",
		"End of context (not an error).",
		"No lexical scope.",
		"Insufficient memory.",
		"Stream read error.",
		"Stream write error.",
		"Stream seek error.",
		"File is corrupt.",
		"IFF syntax error.",
		"Not an IFF file.",
		"Return to client.  You should never see this."
	};

	if (error < 0)
		printf ("IFF error: %s\n", errormsgs[(-error) - 1]);
	return (error);
}

/*
 * Housekeeping.
 */
openstuff ()
{
	if (!(IntuitionBase = OpenLibrary ("intuition.library", 33L)))
		die ("Intuition won't open.  This is not good.\n");

	if (!(GfxBase = OpenLibrary ("graphics.library", 33L)))
		die ("Graphics won't open.\n");

	if (!(IFFParseBase = OpenLibrary ("iffparse.library", 0L)))
		die ("IFF parser library won't open.\n");

	if (!(iff = AllocIFF ()))
		die ("Failed to create IFF control structure.\n");

	GetScreenData (&sensor, (LONG) sizeof (sensor), WBENCHSCREEN, NULL);
	/*  Convert to lo-res pixels.  */
	if (sensor.ViewPort.Modes & HIRES)
		sensor.Width >>= 1;
	if (sensor.ViewPort.Modes & LACE)
		sensor.Height >>= 1;
}

closestuff ()
{
	deletescreen ();
	if (iff)		FreeIFF (iff);
	if (IFFParseBase)	CloseLibrary (IFFParseBase);
	if (GfxBase)		CloseLibrary (GfxBase);
	if (IntuitionBase)	CloseLibrary (IntuitionBase);
}

die (str)
char *str;
{
	extern long	Output();

	Write (Output (), str, (long) strlen (str));
	closestuff ();
	exit (20);
}
