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

#include "GIFMachine.h"
#include <iff/ILBM.h>
#include <libraries/iffparse.h>
#include <graphics/view.h>
#include <proto/iffparse.h>
#include <dos/datetime.h>

extern struct GIFdescriptor gdesc;
EXTERNBITPLANE;

extern struct MinList CommentList;

extern char *AbortMsg;

extern UWORD *SHAMmem;
BYTE *PlaneBuf;

BOOL Laced;

UBYTE *Planes[24];

static UBYTE CompBuf[256];
static ULONG PlanePos;

/* our version number stuff */
extern ULONG __far Version;
extern ULONG __far Revision;

BOOL WriteIFF(char *tofile, BOOL DeepFlag)
{
	register struct IFFHandle *iff;
	register int index;
	register ULONG ChunkSize;
	register UWORD current;
	char WrittenBy[40];

	ColorRegister ColourBuf;
	BitMapHeader  bmh;

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

	if (!(iff = AllocIFF())) {
		PutStr("\n......Error allocating IFF handle.\n");
		return TRUE;
	}

	if (!(iff->iff_Stream = Open(tofile, MODE_NEWFILE))) {
		MyPrintf("\n......Error %ld trying to create %s.", IoErr(), tofile);
		goto EndWriteIFF;
	}

	InitIFFasDOS(iff);

	if (OpenIFF(iff, IFFF_WRITE)) {
		PutStr("\n......Error writing to IFF.\n");
		goto EndWriteIFF;
	}

	if (PushChunk(iff, ID_ILBM, FORM, IFFSIZE_UNKNOWN)) {
		PutStr("\n......Error writing to IFF.\n");
		goto EndWriteIFF;
	}

	if (PushChunk(iff, 0L, MakeID('A','N','N','O'), IFFSIZE_UNKNOWN)) {
		PutStr("\n......Error writing to IFF.\n");
		goto EndWriteIFF;
	}

	MySPrintf(WrittenBy, "Written by GIFMachine v%ld.%ld on ", Version, Revision);

	if (WriteChunkBytes(iff, (APTR)&WrittenBy, strlen(WrittenBy)) != strlen(WrittenBy)) {
		PutStr("\n......Error writing to IFF.\n");
		goto EndWriteIFF;
	}

	{
		struct DateTime dat;
		char Date[LEN_DATSTRING + 1];
		int DateLength;

		DateStamp(&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 + 1);
		DateToStr(&dat);

		DateLength = strlen(Date) + 1;
		if (WriteChunkBytes(iff, (APTR)&Date, DateLength) != DateLength) {
			PutStr("\n......Error writing to IFF.\n");
			goto EndWriteIFF;
		}
	}

	if (PopChunk(iff)) {
		PutStr("\n......Error writing to IFF.\n");
		goto EndWriteIFF;
	}

	{
		struct CommentNode *cn;
		char ANNObuf[1];

#define C_BLOCK_ID "GIF Comment Block: "

		ANNObuf[0] = 0;

		while (cn = (struct CommentNode *)RemHead((struct List *)&CommentList)) {
			if (PushChunk(iff, 0L, MakeID('A','N','N','O'), IFFSIZE_UNKNOWN)) {
				PutStr("\n......Error writing to IFF.\n");
				goto EndWriteIFF;
			}

			if (WriteChunkBytes(iff, (APTR)C_BLOCK_ID, strlen(C_BLOCK_ID)) != strlen(C_BLOCK_ID)) {
				PutStr("\n......Error writing to IFF.\n");
				goto EndWriteIFF;
			}

			if (WriteChunkBytes(iff, (APTR)cn->cn_Comment, cn->cn_CommentLength) != cn->cn_CommentLength) {
				PutStr("\n......Error writing to IFF.\n");
				goto EndWriteIFF;
			}

			if (WriteChunkBytes(iff, (APTR)ANNObuf, 1) != 1) {
				PutStr("\n......Error writing to IFF.\n");
				goto EndWriteIFF;
			}

			if (PopChunk(iff)) {
				PutStr("\n......Error writing to IFF.\n");
				goto EndWriteIFF;
			}
		}
	}

	if (PushChunk(iff, 0L, ID_BMHD, sizeof(BitMapHeader))) {
		PutStr("\n......Error writing to IFF.\n");
		goto EndWriteIFF;
	}

	bmh.w	             = (gdesc.gd_Width & 1) ? gdesc.gd_Width + 1 : gdesc.gd_Width;
	bmh.h	             = (gdesc.gd_Height & 1) ? gdesc.gd_Height + 1 : gdesc.gd_Height;
	bmh.x       = bmh.y  = 0;
	bmh.nPlanes          = (DeepFlag ? 24 : 6);
	bmh.masking          = mskNone;
	bmh.compression      = cmpByteRun1;
	bmh.pad1             = 0;
	bmh.transparentColor = 0;
	bmh.xAspect          = (Laced ? x320x400Aspect : x320x200Aspect);
	bmh.yAspect          = (Laced ? y320x400Aspect : y320x200Aspect);
	bmh.pageWidth        = 320;
	bmh.pageHeight       = (Laced ? 400 : 200);

	if (WriteChunkBytes(iff, (APTR)&bmh, sizeof(BitMapHeader)) != sizeof(BitMapHeader)) {
		PutStr("\n......Error writing to IFF.\n");
		goto EndWriteIFF;
	}

	if (PopChunk(iff)) {
		PutStr("\n......Error writing to IFF.\n");
		goto EndWriteIFF;
	}

	if (PushChunk(iff, 0L, ID_CAMG, 4)) {
		PutStr("\n......Error writing to IFF.\n");
		goto EndWriteIFF;
	}

	{
		LONG CAMGbuf[1];

		CAMGbuf[0] = (DeepFlag ? 0 : HAM) | (Laced ? LACE : 0);

		if (WriteChunkBytes(iff, (APTR)&CAMGbuf, 4) != 4) {
			PutStr("\n......Error writing to IFF.\n");
			goto EndWriteIFF;
		}
	}

	if (PopChunk(iff)) {
		PutStr("\n......Error writing to IFF.\n");
		goto EndWriteIFF;
	}

	if (!DeepFlag) {
		if (PushChunk(iff, 0L, ID_CMAP, 16 * 3)) {
			PutStr("\n......Error writing to IFF.\n");
			goto EndWriteIFF;
		}

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

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

			if (WriteChunkBytes(iff, (APTR)&ColourBuf, sizeofColorRegister) != sizeofColorRegister) {
				PutStr("\n......Error writing to IFF.\n");
				goto EndWriteIFF;
			}
		}

		if (PopChunk(iff)) {
			PutStr("\n......Error writing to IFF.\n");
			goto EndWriteIFF;
		}

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

		if (PushChunk(iff, 0L, MakeID('S','H','A','M'), IFFSIZE_UNKNOWN)) {
			PutStr("\n......Error writing to IFF.\n");
			goto EndWriteIFF;
		}

		{
			UWORD SHAMversion[1];

			SHAMversion[0] = 0;

			if (WriteChunkBytes(iff, (APTR)&SHAMversion, sizeof(UWORD)) != sizeof(UWORD)) {
				PutStr("\n......Error writing to IFF.\n");
				goto EndWriteIFF;
			}
		}

		if (WriteChunkBytes(iff, (APTR)SHAMmem, ChunkSize) != ChunkSize) {
			PutStr("\n......Error writing to IFF.\n");
			goto EndWriteIFF;
		}

		if (gdesc.gd_Height & 1) {
			UWORD BlankSHAM[16];

			memset((char *)BlankSHAM, 0, sizeof(BlankSHAM));

			if (WriteChunkBytes(iff, (APTR)BlankSHAM, sizeof(BlankSHAM)) != sizeof(BlankSHAM)) {
				PutStr("\n......Error writing to IFF.\n");
				goto EndWriteIFF;
			}
		}

		if (PopChunk(iff)) {
			PutStr("\n......Error writing to IFF.\n");
			goto EndWriteIFF;
		}
	} /* end if (!DeepFlag) */

	if (PushChunk(iff, 0L, ID_BODY, IFFSIZE_UNKNOWN)) {
		PutStr("\n......Error writing to IFF.\n");
		goto EndWriteIFF;
	}

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

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

		Cols = (bmh.w + 7) / 8;
		if (IS_ODD(Cols))
			Cols++;

		for (y = 0; y < ((gdesc.gd_Height & 1) ? gdesc.gd_Height + 1 : gdesc.gd_Height) ; y++) {
			MyPrintf("%5ld", y);

			if (SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
				MyPrintf("\n%s", AbortMsg);
				CloseIFF(iff);
				Close(iff->iff_Stream);
				FreeIFF(iff);
				MyExit(ABORTEXITVAL);
			}

			if (y != gdesc.gd_Height)
				for (index = 0; index < (DeepFlag ? 3 : 1); index++) {
					col = 0;
					ColMask = 1L << 7;

					for (x = 0; x < gdesc.gd_Width; x++) {
						if (DeepFlag)
							current = *((UBYTE *)&BitPlane[y][x]+index);
						else
							current = GetValue(x, y);

						PlaneMask = 1;
						for (plane = 0; plane < (DeepFlag ? 8 : 6); plane++) {
							if (current & PlaneMask)
								Planes[plane + index * 8][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 < (DeepFlag ? 24 : 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 (WriteChunkBytes(iff, (APTR)PlaneBuf, PlanePos) != PlanePos) {
						PutStr("\n......Error writing to IFF.\n");
						goto EndWriteIFF;
					}

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

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

	}

	if (PopChunk(iff)) {
		PutStr("\n......Error writing to IFF.\n");
		goto EndWriteIFF;
	}

	if (PopChunk(iff)) {
		PutStr("\n......Error writing to IFF.\n");
		goto EndWriteIFF;
	}

	PutStr("\x9B" "5D\x9BKWritten.\n");

EndWriteIFF:
	CloseIFF(iff);
	if (iff->iff_Stream)
		Close(iff->iff_Stream);
	FreeIFF(iff);

	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;
}
