/* Copyright 1990 by Christopher A. Wichura.
   See file GIFMachine.doc for full description of rights.
*/

#include "GIFMachine.h"
#include <dos/rdargs.h>
#include <dos/dosasl.h>
#include <dos/stdio.h>
#include <workbench/startup.h>

struct GIFdescriptor gdesc;

struct RGB **BitPlane;
struct RGB GlobalColourTable[256];

extern UWORD *SHAMmem;

extern BYTE *PlaneBuf;

extern BOOL Laced;

extern UBYTE *Planes[24];

ULONG ImageNumber;

extern struct MinList CommentList;

/* indicates which memory list to allocate from */
extern UWORD CurrentMem;

/* the current GIF file handle */
BPTR GIFfh = NULL;

/* here we have some defines relating to our GADS call */
#define ESC "\x9B"
#define GIFMACH ESC "33;42mGIFMachine" ESC "32;40m"

#define ARG_TEMPLATE "GIFfiles/M/A,TO/K,ALL/S,NOBORDER/K/N,XCOMP/S,DITHER/S,XFLIP/s,YFLIP/s,DEEP/S,BUFSIZE/K/N"
#define ARG_FILES  0
#define ARG_TO     1
#define ARG_ALL    2
#define ARG_NOBORD 3
#define ARG_XCOMP  4
#define ARG_DITHER 5
#define ARG_FLIPX  6
#define ARG_FLIPY  7
#define ARG_DEEP   8
#define ARG_BUFSIZ 9
#define ARG_sizeof 10

/* we will make the argument array global so that other modules can get at
   the ARG_TO, ARG_ALL and ARG_XCOMP fields easily */
struct RDArgs *ArgsPtr;
char *ArgArray[ARG_sizeof];
BOOL ArgToIsDir;

/* size of the read buffered i/o space */
static ULONG BufSize = 2048;	/* default size is 2k */

int NoBorderLineThresh = 0;

/* some mem pointers used when we do dithering */
BYTE *CurrentLineErr[3];
BYTE *LastLineErr[3];

/* this flag says if we scaled the image */
BOOL DidXComp;

/* we print this when the user hits the break key */
char *AbortMsg = "*** User Interruption!\n";

/* storage for our library bases */
struct Library *MathIeeeDoubBasBase = NULL;
struct Library *IFFParseBase = NULL;

/* storage for our anchor when looking for pattern matches */
struct Anchor {
		struct AnchorPath APath;
		char              Path[256];
	} *anchor = NULL;

/* here we have our main routine */
int __regargs main(char *cmdptr, int cmdlen, struct WBStartup *WBMsg)
{
	register char **FilesPtr;
	struct RDArgs MyArgs;
	extern UBYTE __far arg_help[];
	extern UBYTE __far VersionID[];

	InitMemory();

	if (WBMsg) {
		WarnMustUseCLI();
		MyExit(5);
	}

	if (!(MathIeeeDoubBasBase = OpenLibrary("mathieeedoubbas.library", 0))) {
		MyPrintf("Unable to access %s!\n", "mathieeedoubbas.library");
		MyExit(5);
	}

	if (!(IFFParseBase = OpenLibrary("iffparse.library", 0))) {
		MyPrintf("Unable to access %s!\n", "iffparse.library");
		MyExit(5);
	}

	memset ((char *)&MyArgs, 0, sizeof(struct RDArgs));

	if (!(MyArgs.RDA_ExtHelp = (UBYTE *)MyAlloc(strlen(arg_help) + (2 * strlen(GIFMACH)) + strlen(VersionID) + 1))) {
		PutStr("Out of memory!\n");
		MyExit(5);
	}

	MySPrintf((char *)MyArgs.RDA_ExtHelp, arg_help, GIFMACH, VersionID, GIFMACH);

	if (!(ArgsPtr = ReadArgs(ARG_TEMPLATE, (LONG *)&ArgArray, &MyArgs))) {
		PrintFault(IoErr(), NULL);
		MyExit(5);
	}

	MyFree((char *)MyArgs.RDA_ExtHelp);

	if (ArgArray[ARG_TO])
		ArgToIsDir = IsDir(ArgArray[ARG_TO]);

	if (ArgArray[ARG_NOBORD]) {
		NoBorderLineThresh = *((LONG *)ArgArray[ARG_NOBORD]);
		if (NoBorderLineThresh < 0 || NoBorderLineThresh > 100) {
			PutStr("Invalid NOBORDER line threshhold specified.\n");
			MyExit(3);
		}
	}

	if (ArgArray[ARG_BUFSIZ])
		BufSize = *((LONG *)ArgArray[ARG_BUFSIZ]) * 1024;

	if (!(FilesPtr = (char **)ArgArray[ARG_FILES])) {
		PutStr("No GIF files selected.\n");
		MyExit(3);
	}

	InitDiff();	/* one time init for the RGBdiff function */

	while (*FilesPtr)
		DoPattern(*FilesPtr++);

	MyExit(0);
}

void MyExit(ULONG result)
{
	if (GIFfh)
		Close(GIFfh);

	if (IFFParseBase)
		CloseLibrary(IFFParseBase);

	if (MathIeeeDoubBasBase)
		CloseLibrary(MathIeeeDoubBasBase);

	if (anchor)
		MatchEnd(&anchor->APath);

	if (ArgsPtr)
		FreeArgs(ArgsPtr);

	FreeAll(1);
	FreeAll(0);

	XCEXIT(result);
}


/* this will walk through a pattern doing conversions */
void DoPattern(char *pat)
{
	register int error;

	if (!(anchor = (struct Anchor *)MyAlloc(sizeof(struct Anchor)))) {
		PutStr("Out of memory!\n");
		MyExit(10);
	}

	anchor->APath.ap_Strlen = sizeof(anchor->Path);
	anchor->APath.ap_Flags = APF_DOWILD;
	anchor->APath.ap_BreakBits = SIGBREAKF_CTRL_C;

	error = MatchFirst(pat, &anchor->APath);

	while (!error) {
		if (anchor->APath.ap_Info.fib_DirEntryType > 0) {
			if (ArgArray[ARG_ALL]) {
				if (!(anchor->APath.ap_Flags & APF_DIDDIR))
					anchor->APath.ap_Flags |= APF_DODIR;
				anchor->APath.ap_Flags &= ~APF_DIDDIR;
			}
		} else
			Convert(anchor->APath.ap_Buf);

		error = MatchNext(&anchor->APath);
	}

	MatchEnd(&anchor->APath);
	MyFree((char *)anchor);
	anchor = NULL;

	switch(error) {
		case ERROR_BREAK:
			PutStr(AbortMsg);
			MyExit(ABORTEXITVAL);
			break;

		case ERROR_OBJECT_NOT_FOUND:
			PutStr("File not found.\n");
			break;

		case ERROR_BUFFER_OVERFLOW:
			PutStr("Path too long!\n");
			break;

		case ERROR_NO_MORE_ENTRIES:	/* normal termination */
			break;

		default:
			MyPrintf("I/O Error #%ld!\n", error);
			break;
	}
}

/* here we have the routine that gets ready to do the conversion */
void Convert(char *name)
{
	register int index;
	char *basename;
	char *ptr;
	char sig[7];
	int size;
	int error;
	int colours;
	LONG cmdcode;
	
	struct DateStamp StartTime, EndTime;

	CurrentMem++;

	if (!(GIFfh = Open(name, MODE_OLDFILE))) {
		MyPrintf("Error #%ld trying to open %s...\n", IoErr(), name);
		goto LeaveConvert;
	}

	SetVBuf(GIFfh, BUF_FULL, BufSize);

	sig[6] = NULL;

	if (FRead(GIFfh, sig, 1, 6) != 6 || strncmp("GIF", sig, 3)) {
		MyPrintf("%s is not a GIF file...\n", name);
		goto LeaveConvert;
	}

	MyPrintf("Converting %s ", name);

	basename = FilePart(name);
	ptr = basename + strlen(basename) - 4;

	if (!strnicmp(".gif", ptr, 4))
		*ptr = NULL;

	size = strlen(basename) + 6;

	if (ArgArray[ARG_TO]) {
		if (ArgToIsDir)
			size += strlen(ArgArray[ARG_TO]) + 1;
		else
			size = strlen(ArgArray[ARG_TO]) + 1;
	}

	if (!(ptr = MyAlloc(size))) {
		PutStr("... Out of memory!\n");
		goto LeaveConvert;
	}

	if (ArgArray[ARG_TO]) {
		strcpy(ptr, ArgArray[ARG_TO]);

		if (ArgToIsDir) {
			AddPart(ptr, basename, size);
			strcat(ptr, (ArgArray[ARG_DEEP] ? ".deep" : ".sham"));
		}
	} else {
		strcpy(ptr, basename);
		strcat(ptr, (ArgArray[ARG_DEEP] ? ".deep" : ".sham"));
	}

	MyPrintf("to %s...\n", ptr);

	DateStamp(&StartTime);

	if (FRead(GIFfh, (char *)&gdesc, 1, 7) != 7) {
		PutStr("Error reading screen descriptor.\n");
		goto LeaveConvert;
	}

	FlipWord(&gdesc.gd_Width);
	FlipWord(&gdesc.gd_Height);

	MyPrintf("Signature = \"%s\", Width = %ld, Height = %ld\n",
		sig, gdesc.gd_Width, gdesc.gd_Height);

	NewList((struct List *)&CommentList);

	DidXComp = 0;
	colours = 1L << ((gdesc.gd_ColInfo & 7) + 1);

	if (!(gdesc.gd_ColInfo & 1L << 7)) {
		PutStr("No global colour map supplied, using internal.\n");

		for (index = 0; index < colours; index++) {
			GlobalColourTable[index].rgb_Red   =
			GlobalColourTable[index].rgb_Green =
			GlobalColourTable[index].rgb_Blue  = index;
		}
	} else {
		MyPrintf("Global colour map contains %ld entries.\n", colours);

		for (index = 0; index < colours; index++) {
			if (FRead(GIFfh, &GlobalColourTable[index], 1, 3) != 3) {
				MyPrintf("Error reading global colour #%ld.\n",
					index);
				goto LeaveConvert;
			}
		}
	}

	size = ((gdesc.gd_Width + 7) / 8) + 1;
	size += (size + 127) >> 7;

	if (!(BitPlane = (struct RGB **)MyAlloc(gdesc.gd_Height * sizeof(struct RGB *))) ||
	    !(SHAMmem  = (UWORD *)MyAlloc(gdesc.gd_Height * 16 * sizeof(UWORD))) ||
	    !(PlaneBuf = (BYTE *)MyAlloc(size))) {
		PutStr("Out of memory trying to allocate picture.\n");
		goto LeaveConvert;
	}

	size = (gdesc.gd_Width + 1) * sizeof(struct RGB);

	for (index = 0; index < gdesc.gd_Height; index++)
		if (!(BitPlane[index] = (struct RGB *)MyAlloc(size))) {
			PutStr("Out of memory trying to allocate picture.\n");
			goto LeaveConvert;
		}

	size = ((gdesc.gd_Width + 7) / 8) + 1;
	for (index = 0; index < (ArgArray[ARG_DEEP] ? 24 : 6); index++)
		if (!(Planes[index] = (UBYTE *)MyAlloc(size))) {
			PutStr("Out of memory trying to allocate picture.\n");
			goto LeaveConvert;
		}

	if (ArgArray[ARG_DITHER]) {
		size = gdesc.gd_Width * sizeof(BYTE);

		for (index = 0; index < 3; index++)
			if (!(CurrentLineErr[index] = (BYTE *)MyAlloc(size)) ||
			    !(LastLineErr[index] = (BYTE *)MyAlloc(size))) {
				PutStr("Out of memory trying to allocate picture.\n");
				goto LeaveConvert;
			}
	}

	ImageNumber = 1;

	/* at this point we start looking for images, extensions or the gif
	   terminator.  we call the appropriate routine as we find each. */

	for (error = FALSE; error == FALSE;) {
		if ((cmdcode = FGetC(GIFfh)) == -1) {
			PutStr("...I/O error reading GIF file.\n");
			goto LeaveConvert;
		}

		switch(cmdcode) {
			case GIF_IMAGE:
				error = DoImage(GIFfh);
				break;

			case GIF_EXTENSION:
				error = DoExtension(GIFfh);
				break;

			case GIF_TERMINATOR:
				if (ArgArray[ARG_NOBORD])
					StripBorder();

				if (ArgArray[ARG_FLIPX])
					DoXFlip();

				if (ArgArray[ARG_FLIPY])
					DoYFlip();

				if (ArgArray[ARG_XCOMP]) {
					DoXComp();
					DidXComp = 1;
				}

				if (gdesc.gd_Height > 200 && DidXComp)
					Laced = TRUE;
				else
					Laced = FALSE;

				if (!ArgArray[ARG_DEEP]) {
					if (ArgArray[ARG_DITHER])
						DitherTo12();
					else
						ReduceTo12();

					GIFtoSHAM();
				}

				error = WriteIFF(ptr, (BOOL)ArgArray[ARG_DEEP]);
				break;

			default:
				MyPrintf("...Unknown directive #%ld encountered.\n",
					cmdcode);
				error = TRUE;
		}
	}

	DateStamp(&EndTime);

	{
		register ULONG Hours;
		register ULONG Minutes;
		register ULONG Seconds;
		register ULONG Seconds2;
	
		Seconds = (EndTime.ds_Days * 86400) + (EndTime.ds_Minute * 60) + (EndTime.ds_Tick / TICKS_PER_SECOND);
		Seconds2 = (StartTime.ds_Days * 86400) + (StartTime.ds_Minute * 60) + (StartTime.ds_Tick / TICKS_PER_SECOND);

		Seconds -= Seconds2;

		Hours = Seconds / 3600;
		Seconds -= Hours * 3600;

		Minutes = Seconds / 60;
		Seconds -= Minutes * 60;

		MyPrintf("...Conversion time was %ld hour%s, %ld minute%s and %ld second%s.\n",
			Hours, (Hours != 1 ? "s" : ""),
			Minutes, (Minutes != 1 ? "s" : ""),
			Seconds, (Seconds != 1 ? "s" : ""));
	}

LeaveConvert:
	FreeAll(CurrentMem--);

	if (GIFfh) {
		Close(GIFfh);
		GIFfh = NULL;
	}
}

/* this will check to see if we have a directory or not */
BOOL IsDir(char *name)
{
	register BPTR lock;
	register BOOL result = FALSE;

	struct FileInfoBlock __aligned fib;

	if (lock = Lock(name, ACCESS_READ)) {
		if (Examine(lock, &fib)) {
			if (fib.fib_DirEntryType > 0)
				result = TRUE;
		}
		UnLock(lock);
	}

	return result;
}

/* this will convert a word from LSB/MSB to MSB/LSB */
void FlipWord(UWORD *word)
{
	register UBYTE swap1;
	register UBYTE swap2;

	swap1 = *word & 0xFF;
	swap2 = (*word & 0xFF00) >> 8;
	*word = swap1 << 8 | swap2;
}
