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

#include "GIFMachine.h"
#include <iff/ILBM.h>

extern struct GIFdescriptor gdesc;
EXTERNBITPLANE;

extern char *AbortMsg;

extern UWORD *SHAMmem;
BYTE *PlaneBuf;

BOOL Laced;

static BPTR ofh;

UBYTE *Planes[6];

static UBYTE CompBuf[256];
static ULONG PlanePos;

BOOL WriteIFF(char *tofile)
{
	register int index;
	register ULONG ChunkSize;
	register UWORD current;

	ColorRegister ColourBuf;

	ULONG FormSize, BodySize, BodySizePos;

	Printf("...Writing IFF file.");

	if (!(ofh = ArpOpen(tofile, MODE_NEWFILE))) {
		Printf("\n......Error %ld trying to create %s.", IoErr(), tofile);
		return TRUE;
	}

	gdesc.gd_Width &= ~1L;	/* make sure width is even */

	if (PutLong(FORM) || PutLong(0L) || PutLong(ID_ILBM))
		return TRUE;

#define WRITTENBY "Written by GIFMachine on "

	ChunkSize = strlen(WRITTENBY) + LEN_DATSTRING;
	if (IS_ODD(ChunkSize))
		ChunkSize++;

	if (PutLong(MakeID('A','N','N','O')) || PutLong(ChunkSize))
		return TRUE;

	if (Write(ofh, WRITTENBY, strlen(WRITTENBY)) != strlen(WRITTENBY)) {
		Printf("\n......Error %ld during write.\n", IoErr());
		return TRUE;
	}

	{
		struct DateTime dat;
		char Date[LEN_DATSTRING];

		DateStamp((LONG *)&dat.dat_Stamp);
		dat.dat_Format = FORMAT_DOS;
		dat.dat_Flags = 0;
		dat.dat_StrDay = NULL;
		dat.dat_StrDate = Date;
		dat.dat_StrTime = NULL;

		memset(Date, 0, LEN_DATSTRING);
		StamptoStr(&dat);

		if (Write(ofh, Date, LEN_DATSTRING) != LEN_DATSTRING) {
			Printf("\n......Error %ld during write.\n", IoErr());
			return TRUE;
		}
	}

	if (IS_ODD(Seek(ofh, 0, OFFSET_CURRENT)))
		PutChar(0);

	if (PutLong(ID_BMHD) || PutLong(sizeof(BitMapHeader)))
		return TRUE;

	if (PutWord(gdesc.gd_Width) || PutWord(gdesc.gd_Height) ||
	    PutLong(0L) /* x,y pos both zero */ || PutChar(6) ||
	    PutChar(mskNone) || PutChar(cmpByteRun1) || PutChar(0) ||
	    PutWord(0) /* background is transparent colour */ ||
	    PutChar((UBYTE)(Laced ? x320x400Aspect : x320x200Aspect)) ||
	    PutChar((UBYTE)(Laced ? y320x400Aspect : y320x200Aspect)) ||
	    PutWord(320) || PutWord((UWORD)(Laced ? 400 : 200)))
		return TRUE;

	if (PutLong(ID_CAMG) || PutLong(4) ||
	    PutLong(Laced ? 0x804 : 0x800))
		return TRUE;

	if (PutLong(ID_CMAP) || PutLong(16 * 3))
		return TRUE;

	for (index = 0; index < 16; index++) {
		current = SHAMmem[index];

		ColourBuf.red   = (current >> 4) & 0xF0;
		ColourBuf.green = current & 0xF0;
		ColourBuf.blue  = (current & 15) << 4;

		if (Write(ofh, (char *)&ColourBuf, sizeofColorRegister) !=
			sizeofColorRegister) {
			Printf("\n......Error %ld during write.\n", IoErr());
				return TRUE;
		}
	}

	ChunkSize = (Laced ? gdesc.gd_Height / 2 : gdesc.gd_Height) * 16 * sizeof(UWORD);

	if (PutLong(MakeID('S','H','A','M')) || PutLong(ChunkSize + 2) || PutWord(0))
		return TRUE;

	if (Write(ofh, (char *)SHAMmem, ChunkSize) != ChunkSize) {
		Printf("\n......Error %ld during write.\n", IoErr());
		return TRUE;
	}

	if (PutLong(ID_BODY))
		return TRUE;

	BodySizePos = Seek(ofh, 0, OFFSET_CURRENT);
	if (PutLong(0L))
		return TRUE;

	/* now we actually write the body chunk out */
	{
		register int plane;
		register int col;
		register UWORD x;
		register UWORD y;
		UBYTE PlaneMask, ColMask;
		UWORD Cols;

		Printf("\n......Line ");

		Cols = (gdesc.gd_Width + 7) / 8;
		if (IS_ODD(Cols))
			Cols++;

		for (y = 0; y < gdesc.gd_Height; y++) {
			Printf("%5ld", y);

			if (CheckAbort(NULL)) {
				Printf("\n%s\n", AbortMsg);
				XCEXIT(ABORTEXITVAL);
			}

			col = 0;
			ColMask = 1L << 7;

			for (x = 0; x < gdesc.gd_Width; x++) {
				current = GetValue(x, y);

				PlaneMask = 1;
				for (plane = 0; plane < 6; plane++) {
					if (current & PlaneMask)
						Planes[plane][col] |= ColMask;

					PlaneMask <<= 1;
				}

				if (ColMask == 1) {
					ColMask = 1L << 7;
					col++;
				} else
					ColMask >>= 1;
			}

			/* now we need to compress the scan line */

			{
				register BOOL state;
				register char c;
				register char lastc;
				register UWORD nbuf;
				register UWORD rstart;

				for (plane = 0; plane < 6; plane++) {

					CompBuf[0] = lastc = c = Planes[plane][0];

					state = FALSE;
					PlanePos = rstart = 0;
					nbuf = col = 1;

					while (col < Cols) {
						CompBuf[nbuf++] = c = Planes[plane][col++];

						switch (state) {
							case FALSE:
							if (nbuf > 128) {
								OutDump(nbuf - 1);
								CompBuf[0] = c;
								nbuf = 1;
								rstart = 0;
								break;
							}

							if (c == lastc) {
								if (nbuf - rstart >= 3) {
									if (rstart > 0)
										OutDump(rstart);
									state = TRUE;
								} else if (rstart == 0)
									state = TRUE;
							} else
								rstart = nbuf - 1;

							break;

							case TRUE:
							if ((c != lastc) || (nbuf - rstart > 128)) {
								OutRun(nbuf - 1 - rstart, lastc);
								CompBuf[0] = c;
								nbuf = 1;
								rstart = 0;
								state = FALSE;
							}

							break;
						}

						lastc = c;
					}

					switch (state) {
						case FALSE:
						OutDump(nbuf);
						break;

						case TRUE:
						OutRun(nbuf - rstart, lastc);
						break;
					}

					/* now write the compressed plane out */
					if (Write(ofh, (char *)PlaneBuf, PlanePos) != PlanePos) {
						Printf("\n......Error %ld during write.\n", IoErr());
						return TRUE;
					}

					memset((char *)Planes[plane], 0, Cols);
				}		
			}

			Printf("\x9B" "5D");
		}

	}

	/* finished writing the body so pad it if needed and then fix a
	   a couple chunk sizes and we are done! */

	if (IS_ODD(Seek(ofh, 0, OFFSET_CURRENT)))
		if (PutChar(0))
			return TRUE;

	BodySize = Seek(ofh, 0, OFFSET_CURRENT) - BodySizePos - 4;
	FormSize = Seek(ofh, 0, OFFSET_CURRENT) - 8;

	Seek(ofh, 4, OFFSET_BEGINNING);
	if (PutLong(FormSize))
		return TRUE;

	Seek(ofh, BodySizePos, OFFSET_BEGINNING);
	PutLong(BodySize);

	Puts("\x9B" "5D\x9BKWritten.");

	return TRUE;
}

void OutDump(int nn)
{
	register int index;

	PlaneBuf[PlanePos++] = nn - 1;

	for (index = 0; index < nn; index++)
		PlaneBuf[PlanePos++] = CompBuf[index];
}

void OutRun(int nn, int cc)
{
	PlaneBuf[PlanePos++] = -(nn - 1);
	PlaneBuf[PlanePos++] = cc;
}

BOOL PutLong(ULONG l)
{
	if (PutWord((UWORD)(l >> 16)))
		return TRUE;
	return PutWord((UWORD)l);
}

BOOL PutWord(UWORD w)
{
	if (PutChar((UBYTE)(w >> 8)))
		return TRUE;
	return PutChar((UBYTE)w);
}

BOOL PutChar(UBYTE b)
{
	char buf[1];

	buf[0] = b;
	if (Write(ofh, buf, 1) != 1) {
		Printf("\n......Error %ld during write.\n", IoErr());
		return TRUE;
	}
}
