/* Auto: make
 */

#include <setjmp.h>

#include "Snap.h"

#define SAFE( cond, jmp_buf)  { if ( !( cond)) longjmp( jmp_buf,-1); }

IMPORT BYTE TranspBuf[ ];

#define ID( a,b,c,d) ( ( a << 24L) | ( b << 16L) | ( c << 8L) | ( d))

struct ckHdr
{
	LONG ChunkName;
	LONG ChunkSize;
};

struct SnapBitMapHeader
{
	UWORD w, h;
	WORD x, y;
	UBYTE nPlanes;
	UBYTE masking;
	UBYTE compression;
	UBYTE pad1;
	UWORD transparentColor;
	UBYTE xAspect, yAspect;
	WORD pageWidth, pageHeight;
};

struct ckHdr FORM =
{
	ID( 'F', 'O', 'R', 'M'),
	0L
};
LONG TYPE = ID( 'I', 'L', 'B', 'M');

struct ckHdr BMHD =
{
	ID( 'B', 'M', 'H', 'D'),
	sizeof ( struct SnapBitMapHeader)
};
struct SnapBitMapHeader BMHdr =
{
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

struct ckHdr CAMG =
{
	ID( 'C', 'A', 'M', 'G'),
	4L
};
ULONG ViewMode = NULL;

struct ckHdr CMAP =
{
	ID( 'C', 'M', 'A', 'P'),
	0L
};

struct ckHdr BODY =
{
	ID( 'B', 'O', 'D', 'Y'),
	0L
};

UBYTE *buf;
WORD bufcnt;
ULONG totalsize;

WORD bump( struct CBFHandle *CBFH, WORD cnt, UBYTE * dataptr, WORD size)
{
	REGISTER LONG tsize = size;

	if ( tsize)
	{
		totalsize += tsize + 1;		/* Don't forget the count-byte */
	}
	if ( bufcnt + tsize + 1 >= 4096 || tsize == 0)
	{
		if ( CBFWrite( CBFH, ( char *)buf, ( LONG) bufcnt) == -1L)
		{
			return 0;
		}
		bufcnt = 0;
	}
	buf[ bufcnt++] = cnt;
	CopyMem( ( char *)dataptr, ( char *)&buf[ bufcnt], tsize);
	bufcnt += tsize;
	return 1;
}

ULONG WriteBody( BM, CBFH)
	struct BitMap *BM;
	struct CBFHandle *CBFH;
{
	WORD scanline, plane;
	REGISTER WORD bpr = BM->BytesPerRow;
	REGISTER WORD i, j;
	LONG offset = 0L;
	REGISTER UBYTE data;
	REGISTER UBYTE *bd;
	jmp_buf failure;

	totalsize = 0L;

	if ( !( buf = AllocMem( 4096L, MEMF_PUBLIC)))
	{
		return NULL;
	}

	if ( setjmp( failure))
	{
		FreeMem( buf, 4096L);
		return NULL;
	}

	bufcnt = 0;
	for ( scanline = 0; scanline < BM->Rows; ++scanline)
	{
		for ( plane = 0; plane < BM->Depth; ++plane)
		{
			bd = BM->Planes[ plane] + offset;
			i = 1;
			j = bpr - 1;
			data = bd[ 0];
			while ( j)
			{
				if ( bd[ i] == data)
				{	/* Equal bytes? */
					--i;	/* First equal byte */
					if ( i > 0)
					{	/* Old "random" data to save */
						SAFE( bump( CBFH, ( WORD) ( i - 1), bd, i), failure);
					}
					bd = &bd[ i];	/* Start of equal bytes */
					i = 2;	/* 0 & 1 is equal       */
					--j;
					while ( i < 128 && j && bd[ i] == data)
					{
						++i;
						--j;
					}
					SAFE( bump( CBFH, ( WORD) - ( i - 1), &bd[ i - 1], ( WORD) 1), failure);
					goto new_block;
				}
				else
				{	/* Not equal. Check block range */
					if ( i == 128)
					{	/* Block limit                  */
						SAFE( bump( CBFH, ( WORD) ( i - 1), bd, i), failure);
					      new_block:
						bd = &bd[ i];	/* Start new block              */
						i = 0;
						if ( j == 0)
						{
							break;
						}
					}
				}
				/* Different byte or a new start */
				data = bd[ i];	/* New possible equal       */
			      next_byte:
				++i;
				--j;
			}
			if ( i != 0)
			{	/* Data to save */
				SAFE( bump( CBFH, ( WORD) ( i - 1), bd, i), failure);
			}
		}
		offset += BM->BytesPerRow;
	}
	/* Flush any bytes left if the buffer */
	SAFE( bump( CBFH, ( WORD) 0, NULL, ( WORD) 0), failure);
	FreeMem( buf, 4096L);
	return totalsize;
}

WORD SaveGS( GS, CBFH)
	struct GfxSnap *GS;
	struct CBFHandle *CBFH;
{
	ULONG BODYPos;
	UBYTE *oldtitle;
	jmp_buf failure;

	oldtitle = GS->window->Title;
	SetWindowTitles( GS->window, "Saving...", ( char *)-1);

	BMHdr.w = GS->BitMap->BytesPerRow * 8;
	BMHdr.h = GS->height;
	BMHdr.x = BMHdr.y = 0;
	BMHdr.nPlanes = GS->depth;
	BMHdr.masking = 0;
	BMHdr.compression = 1;
	BMHdr.transparentColor = dectoint( TranspBuf);
	BMHdr.xAspect = BMHdr.xAspect = 1;
	BMHdr.pageWidth = GS->pagew;
	BMHdr.pageHeight = GS->pageh;
	ViewMode = GS->viewmode;

	CMAP.ChunkSize = ( LONG) 3 *( GS->viewmode & HAM ? 16 : 1L << GS->depth);

	CBFInit( CBFH);

	if ( setjmp( failure))
	{
		CBFEndWrite( CBFH);
		return 0;
	}

	SAFE( CBFWrite( CBFH, ( char *)&FORM,
		      ( LONG) ( sizeof ( FORM) + sizeof ( TYPE) +
			      sizeof ( BMHD) + sizeof ( BMHdr) +
			      sizeof ( CAMG) + sizeof ( ViewMode) +
			      sizeof ( CMAP))) != -1L, failure);
	SAFE( CBFWrite( CBFH, ( char *)GS->rgb, CMAP.ChunkSize) != -1L, failure);
	BODYPos = CBFSeek( CBFH, 0L, OFFSET_CURRENT);
	SAFE( CBFWrite( CBFH, ( char *)&BODY, ( LONG) sizeof ( BODY)) != -1L, failure);
	SAFE( BODY.ChunkSize = WriteBody( GS->BitMap, CBFH), failure);
	FORM.ChunkSize = BODYPos - sizeof ( FORM) + sizeof ( BODY) + BODY.ChunkSize;
	if ( FORM.ChunkSize & 1)
	{			/* Odd size */
		SAFE( CBFWrite( CBFH, "X", 1L) != -1L, failure);
		++FORM.ChunkSize;
	}
	CBFSeek( CBFH, 0L, OFFSET_BEGINNING);
	CBFWrite( CBFH, ( char *)&FORM, ( LONG) sizeof ( FORM));
	CBFSeek( CBFH, BODYPos, OFFSET_BEGINNING);
	CBFWrite( CBFH, ( char *)&BODY, ( LONG) sizeof ( BODY));
	CBFEndWrite( CBFH);
	SetWindowTitles( GS->window, ( char *)oldtitle, ( char *)-1);
	return 1;
}
