/*************************************************************************
**                                                                      **
**  ShowIFF - a comfortable IFF file viewer for CLI and WorkBench       **
**                                                                      **
**  All known formats (including Overscan, HAM, HalfBrite, SHAM) are    **
**  supported. If a picture is larger than the screen, you may use the  **
**  mouse to scroll around (If you have enough CHIP memory...)          **
**                                                                      **
**  This program and iff.library by:     Chris Weber                    **
**                                       Bruggerweg 2                   **
**                                       CH-8037 Zürich                 **
**                                       SWITZERLAND                    **
**                                       (BIX: chw)                     **
**                                                                      **
**  Any suggestions or bug reports are welcome!                         **
**                                                                      **
**************************************************************************
**                                                                      **
**  This program is in the Public Domain. Use it at your own risk.      **
**                                                                      **
**  Requirements:  - arp.library V39+                                   **
**                 - iff.library V18+                                   **
**                                                                      **
**  To compile:    - Lattice C compiler V5.04 or above (use 32bit ints) **
**                 - The file 'arpstartup.o' (My ARP startup module)    **
**                 - The file 'arpglue.lib' for Printf etc.             **
**                                                                      **
**************************************************************************
**                                                                      **
**  WorkBench usage:                                                    **
**  ---------------                                                     **
**                                                                      **
**  Select all pictures you wish to see while pressing the SHIFT key,   **
**  then double-click on the ShowIFF icon.                              **
**  You can also set the picture's default tool to 'ShowIFF' using the  **
**  WorkBench 'Info' feature.                                           **
**  If you wish to display all the pictures in a drawer, select the     **
**  drawer and then the ShowIFF icon. This works even with disk icons!  **
**  If a drawer or disk contains another drawer, the pictures in that   **
**  drawer will also be displayed.                                      **
**                                                                      **
**                                                                      **
**  CLI usage:                                                          **
**  ---------                                                           **
**                                                                      **
**  ShowIFF [files] [DELAY delay in seconds] [LOOP] [ALL] [NOBREAK]     **
**  [NOOVERSCAN]                                                        **
**                                                                      **
**  Example:  ShowIFF Lo-Res/Pic* Hi-Res/foo RAM:*.pic DELAY 5 ALL      **
**                                                                      **
**  [files] can be any number of file names, directories or disk names. **
**     All of the ARP wildcards can be used. (?,*,#?,...)               **
**                                                                      **
**  [DELAY] specifies the delay between two pictures in seconds. If not **
**     specified, this is set to infinity. Note that you can always go  **
**     to the next picture by pressing the left mouse button.           **
**                                                                      **
**  [LOOP] if specified, repeatedly displays all the pictures, which is **
**     useful for creating slide-shows or other demos.                  **
**                                                                      **
**  [ALL] if specified, pictures in sub-directories will also be        **
**     displayed.                                                       **
**                                                                      **
**  [NOBREAK] disabled the breaking features described below            **
**                                                                      **
**  [NOOVERSCAN] display all pictures in normal mode, and never in      **
**     overscan. This is useful if you want to see every pixel...       **
**                                                                      **
**                                                                      **
**  Keys and mouse-buttons:                                             **
**  ----------------------                                              **
**                                                                      **
**  While a picture is displayed, you can                               **
**                                                                      **
**  - press the left mouse button, which will display the next picture  **
**                                                                      **
**  - press the right mouse button, which will abort the program        **
**                                                                      **
**  - press Ctrl-C (if the ShowIFF window or the CLI window is active)  **
**       which will abort the ShowIFF program                           **
**                                                                      **
**  - press Ctrl-E (if the ShowIFF window or the CLI window is active)  **
**       which will quit the current directory and return to the        **
**       parent-directory (only in conjunction with the 'ALL' option)   **
**                                                                      **
**  - move the mouse, the picture will scroll if it is larger than the  **
**       screen. Note that HAM pictures may have distortions on the     **
**       left side. This is a hardware limitation of the HAM mode.      **
**                                                                      **
**************************************************************************
**                                                                      **
**    Modification History:                                             **
**    --------------------                                              **
**                                                                      **
**    15-Oct-87  CHW  V1.0   Created this file!                         **
**    30-Jun-88  CHW  V1.4   Directory scan fixed for FFS, cleaned up   **
**    16-Nov-88  CHW  V1.5   Overscan implemented                       **
**    28-Nov-88  CHW  V1.6   Minimal screen size is now 64x64 pixels    **
**    02-Jan-89  CHW  V1.7   Double-buffering implemented               **
**    25-Sep-89  CHW  V2.01  Changed to Lattice C and ARP library       **
**    27-Sep-89  CHW  V2.02  Screen scrolling implemented               **
**    22-Nov-89  CHW  V2.03  Rejects Non-ILBM files correctly           **
**    21-Feb-90  CHW  V2.04  'NoMemForGfx' bug fixed, overscan fixed    **
**    28-Feb-90  CHW  V2.10  SHAM support code added                    **
**    14-Mar-90  CHW  V2.11  Memory fragmentation workaround, cleanup   **
**    08-Apr-90  CHW  V2.12  Pics with more than 6 planes don't guru    **
**                                                                      **
*************************************************************************/

#include <proto/exec.h>
#include <exec/memory.h>
#include <arp/arpbase.h>
#include <proto/graphics.h>
#include <proto/intuition.h>
#include <graphics/gfxbase.h>
#include <graphics/gfxmacros.h>
#include <hardware/custom.h>
#include <libraries/iff.h>		/* Not an official CBM file (yet?:-) */
#include <workbench/startup.h>
#include <string.h>

#define MIN(a,b) ((a)<(b)?(a):(b))
#define MAX(a,b) ((a)>(b)?(a):(b))
#define reg register			/* My favorite alias */

struct MyAnchor
{
	struct AnchorPath APath;
	char   FullPath[255];		/* cheap way to extend ap_Buf[] */
};

struct Picture
{
	struct Screen *Screen;		/* The picture's screen */
	struct Window *Window;		/* The invisible window for mouse handling */
	struct BitMap BitMap;		/* CustomBitMap, may be larger than screen */
	WORD MaxX0,MaxY0;			/* Scrolling limits */
};

char StdWindowName[] = "CON:0/12/640/82/ShowIFF V2.12  08-Apr-90 by Christian A. Weber";
char CLI_Template[]  = "Patterns/...,D=DELAY/k,L=LOOP/s,ALL/s,NB=NOBREAK/s,NO=NOOVERSCAN/s";
char CLI_Help[]      = "ShowIFF V2.12 08-Apr-90 by Christian A. Weber\n"
						"Usage: ShowIFF [files or patterns] [DELAY delay] [LOOP] [ALL]\n"
						"\t\t[NOBREAK] [NOOVERSCAN]";

#define ARG_PATTERN    0				/* Argument numbers for GADS() */
#define ARG_DELAY      1
#define ARG_LOOP       2
#define ARG_ALL        3
#define ARG_NOBREAK    4
#define ARG_NOOVERSCAN 5
#define ARG_INVALID    6
#define NUMARGS        (ARG_INVALID+1)

extern void exit(LONG);					/* My startup code's exit function */
extern struct Library *ArpBase;			/* Also used for DOS functions */
extern struct Library *IntuitionBase;	/* Opened by ArpStartup */
extern struct GfxBase *GfxBase;			/* Opened by ArpStartup */
struct Library *IFFBase;				/* That's the big one! */
extern struct Custom __far custom;		/* For SSHAM copper list */

BOOL WorkBench;
LONG WBDelay=3145728;			/* keep window open for 3 seconds*/

struct Picture pic1,pic2;		/* 2 pictures for double-buffering */
IFFFILE ifffile;
WORD *emptysprite;

LONG delay      = 0x100000;		/* Default parameters for WB */
BOOL loop       = FALSE;
BOOL all        = TRUE;
BOOL nobreak    = FALSE;
BOOL overscan   = TRUE;

/*************************************************************************/

void ZeroMem(reg UBYTE *adr,reg LONG len)
{
	while(--len>=0) *adr++=0;
}

/*************************************************************************/
/* Free all planes of a bitmap */

void FreeBitMap(reg struct BitMap *bm)
{
	reg LONG planesize=bm->BytesPerRow*(bm->Rows+8),i;

	for(i=0; i<bm->Depth; ++i)
		if(bm->Planes[i])
		{
			FreeMem(bm->Planes[i],planesize);
			bm->Planes[i]=0;
		}
}

/*************************************************************************/
/* Initialize a BitMap structure and allocate CHIP memory for the planes */

BOOL AllocBitMap(reg struct BitMap *bm,LONG d,LONG w,LONG h)
{
	reg LONG planesize,i;

	InitBitMap(bm,d,w,h);
	planesize=bm->BytesPerRow*(bm->Rows+8);

	for(i=0; i<d; ++i)
		if(!(bm->Planes[i]=AllocMem(planesize,MEMF_CHIP|MEMF_CLEAR)))
		{
			FreeBitMap(bm);
			return FALSE;
		}
	return TRUE;
}

/*************************************************************************/
/* Adjust the X/Y position of a screen for correct overscan display */

void SetOverscan(struct Screen *screen)
{
	reg WORD rows,x=screen->Width,y=screen->Height;
	reg struct ViewPort *vp=&(screen->ViewPort);

	rows = GfxBase->NormalDisplayRows; if(rows>300) rows>>=1;
	x -= 320;  if(vp->Modes & HIRES) x -= 320;
	y -= rows; if(vp->Modes & LACE)  y -= rows;
	x >>=1; if(x<0) x=0; y >>=1; if(y<0) y=0; if(y>40) y=40;

	if(vp->Modes & HAM)	/* Correct overscan HAM color distortions */
	{
		if(GfxBase->ActiView->DxOffset-x < 96)
			x=GfxBase->ActiView->DxOffset-96;
	}
	vp->DxOffset = -x; vp->DyOffset = -y;
	MakeScreen(screen); RethinkDisplay();
}

/*************************************************************************/
/* Make SHAM copper list if this is an SHAM picture */

void MakeSHAM(struct Picture *pic)
{
	reg LONG i,j,step=(pic->Screen->ViewPort.Modes&LACE)?2:1;
	reg WORD copx=GfxBase->ActiView->DxOffset+pic->Screen->Width;
	reg struct UCopList *ucop=AllocMem(sizeof(struct UCopList),MEMF_CLEAR);
	reg struct SHamChunk
	{
		struct Chunk	Chunk;
		UWORD			Version;
		UWORD			Colors[1024][16];
	} *shamck;

	if(ucop && (shamck=FindChunk(ifffile,ID_SHAM)))
	{
		if(shamck->Version == 0)
		{
			Printf("SHAM ");
			for(i=1; i<MIN(pic->Screen->Height,shamck->Chunk.ckSize>>5); ++i)
			{
				CWAIT(ucop,(i-1)*step,(UWORD)((copx>>1)%(UWORD)228))
				for(j=1; j<16; ++j) CMOVE(ucop,custom.color[j],shamck->Colors[i][j])
			}
			CEND(ucop)
			pic->Screen->ViewPort.UCopIns = ucop; RemakeDisplay();
		}
		else Printf("Unsupported SHAM mode: %ld",(LONG)shamck->Version);
	}
}

/*************************************************************************/
/* Free a picture (that is a window, screen and a custom BitMap) */

void ClosePicture(reg struct Picture *pic)
{
	if(pic->Window)
	{
		ScreenToBack(pic->Screen);
		ClearPointer(pic->Window);
		CloseWindow(pic->Window);
		pic->Window=0;
	}

	if(pic->Screen)
	{
		CloseScreen(pic->Screen);
		pic->Screen=0;
	}

	FreeBitMap(&pic->BitMap);
	RemakeDisplay();	/* remake copperlist to increase MEMF_LARGEST */
}

/*************************************************************************/
/* Allocate a picture (Allocate custom BitMap, open screen and window) */

BOOL OpenPicture(reg struct Picture *pic,reg struct BitMapHeader *bmhd)
{
	reg WORD xov=64,yov=40;		/* Maximal overscan values for LoRes */
	struct NewScreen ns;
	struct NewWindow nw;
	UWORD colortable[512];

	ClosePicture(pic);

	ZeroMem((UBYTE *)&ns,sizeof(ns));
	ZeroMem((UBYTE *)&nw,sizeof(nw));

	ns.Width        = 320;							/* Minimal width */
	ns.Height       = GfxBase->NormalDisplayRows;	/* Minimal height */
	ns.Depth        = MAX(bmhd->nPlanes,1);
	ns.ViewModes    = (UWORD)GetViewModes(ifffile);
	ns.Type         = CUSTOMSCREEN|CUSTOMBITMAP|SCREENQUIET|SCREENBEHIND;
	ns.CustomBitMap = &pic->BitMap;

	if(ns.ViewModes & HIRES) { ns.Width  <<= 1; xov <<= 1; }
	if(ns.ViewModes & LACE)  { ns.Height <<= 1; yov <<= 1; }

	if(overscan)
	{
		if((bmhd->w > ns.Width) && (bmhd->w <= (ns.Width+xov)))
			ns.Width = bmhd->w;

		if((bmhd->h > ns.Height) && (bmhd->h <= (ns.Height+yov)))
			ns.Height = bmhd->h;
	}

	if(AllocBitMap(&pic->BitMap,(LONG)bmhd->nPlanes,
				MAX(bmhd->w,ns.Width),MAX(bmhd->h,ns.Height)))
	{
		if(nw.Screen=pic->Screen=OpenScreen(&ns))
		{
			if(ns.Depth>6)
			{
				pic->Screen->BitMap.Depth=ns.ViewModes & HIRES ? 4:5;
				RemakeDisplay();
			}
			nw.Width      = ns.Width;
			nw.Height     = ns.Height;
			nw.IDCMPFlags = MOUSEBUTTONS|MOUSEMOVE|DELTAMOVE;
			nw.Flags      = BACKDROP|BORDERLESS|ACTIVATE|SIMPLE_REFRESH
								|NOCAREREFRESH|REPORTMOUSE|RMBTRAP;
			nw.Type       = CUSTOMSCREEN;

			if(pic->Window=OpenWindow(&nw))
			{
				reg long count;

				pic->MaxX0=bmhd->w-ns.Width;
				pic->MaxY0=bmhd->h-ns.Height;

				SetPointer(pic->Window,emptysprite,1L,16L,0L,0L);
				if(!(count=GetColorTab(ifffile,colortable)))
				{
					/* Provide default colors for pictures without a CMAP */
					count=2; colortable[0]=0xeca; colortable[1]=0x000;
				}
				if(count>32) count = 32; /* Fix old DigiView pictures */
				LoadRGB4(&(pic->Screen->ViewPort),colortable,count);
				SetOverscan(pic->Screen);
				return TRUE;
			}
		}
	}
	ClosePicture(pic);
	return FALSE;
}

/*************************************************************************/
/* Close all resources and exit to CLI/WorkBench */

void Fail(reg char *reason)
{
	Printf("%s\n",reason);

	ClosePicture(&pic1); ClosePicture(&pic2);

	if(ifffile)		{ CloseIFF(ifffile); ifffile=0; }
	if(emptysprite) { FreeMem(emptysprite,20); emptysprite=0; }
	if(IFFBase)		{ CloseLibrary(IFFBase); IFFBase=0; }

	if(WorkBench) WaitForChar(Input(),WBDelay);	/* Let them see our text */
	exit(0L);	/* Back to the startup code (important for WorkBench) */
}

/*************************************************************************/
/* This gets called if the user hits ^C or sends us a BREAK signal */

__stdargs void _abort(void)
{
	Fail("\n***BREAK - ShowIFF terminated.");
}

/************************************************************************/
/* That's the big one! Load & display a picture (with mouse-scrolling) */

void ShowPicture(reg char *name)
{
	if(!strcmp(name+strlen(name)-5,".info")) return;	/* Skip icons */
	Printf("%s ... ",name);

	if(ifffile) CloseIFF(ifffile);
	if(ifffile=OpenIFF(name))
	{
		if(*(((ULONG *)ifffile)+2) == ID_ILBM)
		{
			reg struct BitMapHeader *bmhd;
			if(bmhd=GetBMHD(ifffile))
			{
				Printf("%ld x %ld x %ld ",bmhd->w,bmhd->h,bmhd->nPlanes);
retry:			if(OpenPicture(&pic1,bmhd))
				{
					if(DecodePic(ifffile,&pic1.BitMap))
					{
						reg LONG i;
						reg WORD xoff=0,yoff=0;

						MakeSHAM(&pic1);
						ScreenToFront(pic1.Screen);
						ClosePicture(&pic2);
						for(i=delay*50L; i>=0; --i)
						{
							reg struct IntuiMessage *msg;

							WaitTOF();
							while(msg=(struct IntuiMessage *)GetMsg(pic1.Window->UserPort))
							{
								if(msg->Class == MOUSEMOVE)
								{
									xoff+=msg->MouseX;
									if(xoff>pic1.MaxX0) xoff=pic1.MaxX0;
									if(xoff<0) xoff=0;
									yoff+=msg->MouseY;
									if(yoff>pic1.MaxY0) yoff=pic1.MaxY0;
									if(yoff<0) yoff=0;
									pic1.Screen->ViewPort.RasInfo->RxOffset=xoff;
									pic1.Screen->ViewPort.RasInfo->RyOffset=yoff;
									/* ScrollVPort(&pic1.Screen->ViewPort); */
									MakeScreen(pic1.Screen); RethinkDisplay();
								}
								else if(!nobreak)
								{
									if(msg->Code == SELECTDOWN) goto showend;
									else if(msg->Code == MENUDOWN) _abort();
								}
								ReplyMsg((struct Message *)msg);
							}
							if(!nobreak) CheckAbort(_abort);
						}
showend:				pic2=pic1; ZeroMem((UBYTE *)&pic1,sizeof(pic1));
						Puts(" - Done");
					}
					else
					{
						ClosePicture(&pic1);
						Printf("- Decode error: %ld\n",IFFError());
					}
				}
				else if(pic2.Window)
				{
					ClosePicture(&pic2); goto retry;
				}
				else Puts("- Can't open screen!");
			}
			else Printf("- Mangled IFF file (%ld)\n",IFFError());
		}
		else Puts("- not an ILBM file!");
	}
	else Puts("- not an IFF file!");
}

/************************************************************************/
/* Expand a pattern and call ShowPicture() for each matching file */

void ShowPattern(char *pathname)
{
	reg BPTR file;
	char buf[256];

	if(PreParse(pathname,buf))	/* TRUE if name contains any wildcards */
	{
		reg struct MyAnchor *myanchor;
iswild:
		if(myanchor=ArpAlloc(sizeof(struct MyAnchor)))
		{
			reg BOOL showit=TRUE;
			reg LONG error;

			myanchor->APath.ap_StrLen = 255;	/* Want full path built */
			myanchor->APath.ap_Flags = APF_DOWILD;
			myanchor->APath.ap_BreakBits=SIGBREAKF_CTRL_C;

			error=FindFirst(pathname,&myanchor->APath);
			while(!error)
			{
				/* +1 because of a lattice bug */
				if(SetSignal(0L,SIGBREAKF_CTRL_E+1) & SIGBREAKF_CTRL_E)
				{
					Puts("***DIR BREAK"); showit=FALSE;
					/* myanchor->APath.ap_Flags &= ~APF_DODIR; */
				}

				if(myanchor->APath.ap_Info.fib_DirEntryType>=0)	/* Dir */
				{
					if(all)
					{
						if(!(myanchor->APath.ap_Flags & APF_DIDDIR))
						{
							myanchor->APath.ap_Flags |= APF_DODIR;
							showit=TRUE;
						}
						myanchor->APath.ap_Flags &= ~APF_DIDDIR;
					}
				}
				else if(showit) ShowPicture(myanchor->APath.ap_Buf);
				error=FindNext(&myanchor->APath);
			}

			FreeAnchorChain(&myanchor->APath);

			switch(error)
			{
				case ERROR_BREAK:				_abort(); break;

				case ERROR_OBJECT_NOT_FOUND:	Puts("File not found."); break;

				case ERROR_BUFFER_OVERFLOW:		Puts("Path too long!"); break;

				case ERROR_NO_MORE_ENTRIES:		break;	/* Normal termination */

				default:	Printf("IO error %ld!\n",error); break;
			}
		}
		else Fail("No mem for anchor!");
	}
	else if(file=Open(pathname,MODE_OLDFILE))	/* Just a file ? */
	{
		Close(file); ShowPicture(pathname); return;
	}
	else	/* No wildcards, and not a file: it's a device or a directory */
	{
		strcpy(buf,pathname); TackOn(pathname=buf,"*");
		goto iswild;	/* Not really elegant, but it works */
	}
}

/************************************************************************/
/* Main program: parse the command line or WorkBench-args */

void ARPMain(LONG arglen,reg char *argline)
{
	WorkBench=(arglen==0);
	emptysprite=AllocMem(20L,MEMF_CHIP|MEMF_CLEAR);

	if(!(IFFBase = OpenLibrary(IFFNAME,0L))) Fail("No iff.library!");
	if(IFFBase->lib_Version < IFFVERSION)
		Printf("WARNING: you have an old version of iff.library!\n");

	if(arglen)		/* From CLI */
	{
		char *argv[NUMARGS];		/* Filled in by GADS() */

		if(*argline != '\n')
		{
			if(GADS(argline,(LONG)strlen(argline),CLI_Help,argv,CLI_Template)>0)
			{
				if(argv[ARG_DELAY]) delay=Atol(argv[ARG_DELAY]);
				loop     = (BOOL)argv[ARG_LOOP];
				all      = (BOOL)argv[ARG_ALL];
				nobreak  = (BOOL)argv[ARG_NOBREAK];
				overscan = (BOOL)!argv[ARG_NOOVERSCAN];
				do
				{
					reg char **pattern=(char **)argv[ARG_PATTERN];
					if(*pattern)
					{
						while(*pattern) ShowPattern(*pattern++);
					}
					else ShowPattern("*");
				} while(loop);
			}
			else Fail(CLI_Help);
		}
		else Fail(CLI_Help);
	}
	else	/* Called from Workbench */
	{
		reg struct WBStartup *startup = (struct WBStartup *)argline;
		struct WBArg *arg;
		reg WORD i;

		if(startup->sm_NumArgs > 1)	/* Started with some arguments */
		{
			arg = startup->sm_ArgList;
			for(i=1; i<startup->sm_NumArgs; ++i)
			{
				arg++;
				if(arg->wa_Lock) CurrentDir(arg->wa_Lock);
				else Fail("Can't lock!");
				if(arg->wa_Name && *arg->wa_Name) ShowPattern(arg->wa_Name);
				else ShowPattern("*");
			}
		}
		else
		{
			WBDelay=6291456;	/* 6 seconds delay */
#ifdef GERMAN
			Fail("Bitte klicken Sie alle gewünschten Bild-Dateien oder Schubladen bei ge-\n"
		     "drückter SHIFT-Taste an, klicken Sie danach 2x auf das ShowIFF-Piktogramm.");
#else
			Fail("Please select all drawers and files to view while holding\n"
		     "down the SHIFT key, then double-click the ShowIFF icon.");
#endif
		}
	}
	WBDelay=1048576;			/* One second delay */
#ifdef GERMAN
	Fail("Fertig.");
#else
	Fail("All done.");
#endif

}

