/* $Revision Header * Header built automatically - do not edit! *************
 *
 *	(C) Copyright 1985-1991 by Jim Kent & Olaf `Olsen' Barthel
 *
 *	Name .....: SaveILBM.c
 *	Created ..: Monday 21-Jan-91 20:12
 *	Revision .: 0
 *
 *	Date            Author          Comment
 *	=========       ========        ====================
 *	21-Jan-91       Olsen           Modified this file!
 *
 * $Revision Header ********************************************************/

#include "TermGlobal.h"

	/* This is basically Jim Kent's IFF-ILBM writer. Changes I made:
	 *
	 * - Put all support files into a single file.
	 * - Added iffparse.library function calls.
	 * - Added the CAMG chunk and implemented 32 bit viewmode IDs.
	 * - Wrote a small routine to save the data.
	 * - Added the ANNO version chunk.
	 * - Changed the way the original source code was indented and
	 *   the names of some variables.
	 */

	/* A bitmap header. */

struct BitMapHeader
{
	UWORD w, h;			/* raster width & height in pixels */
	UWORD x, y;			/* position for this image */
	UBYTE nPlanes;			/* # source bitplanes */
	UBYTE masking;			/* masking technique */
	UBYTE compression;		/* compression algoithm */
	UBYTE pad1;			/* UNUSED.  For consistency, put 0 here.*/
	UWORD transparentColor;		/* transparent "color number" */
	UBYTE xAspect, yAspect;		/* aspect ratio, a rational number x/y */
	WORD pageWidth, pageHeight;	/* source "page" size in pixels */
};

	/* Compression status. */

#define DUMP	0
#define RUN	1

	/* Run length encoding constants. */

#define MINRUN	3
#define MAXRUN	128
#define MAXDAT	128

	/* Size of the IFF write cache. */

#define IFF_SIZE	4096

	/* Cache and cache index. */

STATIC UBYTE	IFFBuffer[IFF_SIZE];
STATIC WORD	IFFSize;

	/* FlushIFF(struct IFFHandle *IFFHandle):
	 *
	 *	Flush the contents of the IFF write cache.
	 */

STATIC BYTE
FlushIFF(struct IFFHandle *IFFHandle)
{
	if(IFFSize)
	{
		if(WriteChunkBytes(IFFHandle,IFFBuffer,IFFSize) != IFFSize)
		{
			IFFSize = 0;
			return(FALSE);
		}

		IFFSize = 0;
	}

	return(TRUE);
}

	/* PutIFF(struct IFFHandle *IFFHandle,UBYTE c):
	 *
	 *	Send a single byte to the IFF write cache.
	 */

STATIC BYTE
PutIFF(struct IFFHandle *IFFHandle,UBYTE c)
{
	if(IFFSize == IFF_SIZE)
		if(!FlushIFF(IFFHandle))
			return(FALSE);

	IFFBuffer[IFFSize++] = c;

	return(TRUE);
}

	/* WriteIFF(struct IFFHandle *IFFHandle,UBYTE *Data,WORD Size):
	 *
	 *	Write a buffer to the IFF cache.
	 */

STATIC BYTE
WriteIFF(struct IFFHandle *IFFHandle,UBYTE *Data,LONG Size)
{
	LONG Len;

	while(Size)
	{
		if(IFFSize == IFF_SIZE)
			if(!FlushIFF(IFFHandle))
				return(FALSE);

		if((Len = Size) > IFF_SIZE - IFFSize)
			Len = IFF_SIZE - IFFSize;

		CopyMem(&Data[0],&IFFBuffer[IFFSize],Len);

		IFFSize	+= Len;
		Size	-= Len;

		Data = &Data[Len];
	}

	return(TRUE);
}

STATIC LONG
PackRow(struct IFFHandle *FileHandle,UBYTE *Source,LONG Size)
{
	UBYTE	c,LastByte	= 0;
	BYTE	CompMode	= DUMP;
	WORD	InBuf		= 1;		/* number of chars in buffer */
	WORD	RunStart	= 0;		/* buffer index current run starts */
	LONG	PutSize		= 0;

	UBYTE	LineBuf[MAXDAT * 3 / 2];	/* I think MAXDAT+1 would suffice */

	LineBuf[0] = LastByte = *Source++;	/* so have valid LastByte */
	Size--;					/* since one byte eaten. */

	for( ; Size ; --Size)
	{
		LineBuf[InBuf++] = c = *Source++;

		switch(CompMode)
		{
			case DUMP:	/* If the buffer is full, write the length byte, then the data */

					if(InBuf > MAXDAT)
					{
						if(!PutIFF(FileHandle,InBuf-2))
							return(FALSE);

						if(!WriteIFF(FileHandle,LineBuf,InBuf-1))
							return(0);

						PutSize += InBuf;

						LineBuf[0]	= c;
						InBuf		= 1;
						RunStart	= 0;

						break;
					}

					if(c == LastByte)
					{
						if(InBuf - RunStart >= MINRUN)
						{
							if(RunStart > 0)
							{
								if(!PutIFF(FileHandle,RunStart-1))
									return(FALSE);

								if(!WriteIFF(FileHandle,LineBuf,RunStart))
									return(0);

								PutSize += RunStart+1;
							}

							CompMode = RUN;
						}
						else
						{
							if(!RunStart)
								CompMode = RUN;
						}

							/* no dump in progress, so can't lose by making
							   these 2 a run. */
					}
					else
						RunStart = InBuf - 1;/* first of run */

					break;

			case RUN:	if((c != LastByte) ||(InBuf - RunStart > MAXRUN))
					{
						/* output run */

						if(!PutIFF(FileHandle,-(InBuf - RunStart - 2)))
							return(FALSE);

						if(!PutIFF(FileHandle,LastByte))
							return(FALSE);

						PutSize += 2;

						LineBuf[0]	= c;
						InBuf		= 1;
						RunStart	= 0;
						CompMode	= DUMP;
					}

					break;
		}

		LastByte = c;
	}

	switch(CompMode)
	{
		case DUMP:	if(!PutIFF(FileHandle,InBuf-1))
					return(FALSE);

				if(!WriteIFF(FileHandle,LineBuf,InBuf))
					return(FALSE);

				PutSize += InBuf+1;
				break;

		case RUN:	if(!PutIFF(FileHandle,-(InBuf - RunStart - 1)))
					return(FALSE);

				if(!PutIFF(FileHandle,LastByte))
					return(FALSE);

				PutSize += 2;
				break;
	}

	return(PutSize);
}

STATIC LONG
PackBitMap(struct IFFHandle *FileHandle,struct BitMap *BitMap,LONG Height)
{
	LONG i,j,RowLength,CompressedLength = 0,PlaneOffset = 0;

	for(i = 0 ; i < Height ; i++)
	{
		for(j = 0 ; j < BitMap -> Depth ; j++)
		{
			if(!(RowLength = PackRow(FileHandle,BitMap -> Planes[j] + PlaneOffset,BitMap -> BytesPerRow)))
				return(0);

			CompressedLength += RowLength;
		}

		PlaneOffset += BitMap -> BytesPerRow;
	}

	return(CompressedLength);
}

STATIC BYTE
WriteILBM(UBYTE *FileName,UBYTE *Colours,struct BitMap *BitMap,LONG OffsetX,BYTE OffsetY,LONG Width,LONG Height,LONG Compressed,LONG PageWidth,LONG PageHeight,ULONG ViewModes)
{
	struct IFFHandle	*FileHandle;
	struct BitMapHeader	 BitMapHeader;
	LONG			 RowOffset;
	LONG			 i,j;
	WORD			 NumColours = 32;
	BYTE			 Success = TRUE;

	if(BitMap)
	{
		if((NumColours = 1 << BitMap -> Depth) > 32)
			NumColours = 32;

		if(ViewModes & HAM)
			NumColours = 16;
	}

	if(FileHandle = AllocIFF())
	{
		if(FileHandle -> iff_Stream = Open(FileName,MODE_NEWFILE))
		{
			InitIFFasDOS(FileHandle);

			if(!OpenIFF(FileHandle,IFFF_WRITE))
			{
				/* say its a FORM ILBM */

				if(!PushChunk(FileHandle,'ILBM','FORM',IFFSIZE_UNKNOWN))
				{
					/* initialize the BitMapHeader to normal values */

					BitMapHeader . masking		= 0;
					BitMapHeader . pad1		= 0;
					BitMapHeader . transparentColor	= 0;

					if(Compressed)
						BitMapHeader . compression = 1;
					else
						BitMapHeader . compression = 0;

					BitMapHeader . pageWidth	= PageWidth;
					BitMapHeader . pageHeight	= PageHeight;
					BitMapHeader . yAspect		= 11;

					/* mask out all unwanted bits. */

					ViewModes &= ~(SPRITES | VP_HIDE | GENLOCK_AUDIO | GENLOCK_VIDEO);

					if((ViewModes & HIRES) && (ViewModes & LACE))
						BitMapHeader . xAspect = 10;

					if(!(ViewModes & HIRES) && (ViewModes & LACE))
						BitMapHeader . xAspect = 20;

					if((ViewModes & HIRES) && !(ViewModes & LACE))
						BitMapHeader . xAspect = 5;

					if(!(ViewModes & HIRES) && !(ViewModes & LACE))
						BitMapHeader . xAspect = 10;

					/* if it's not just a color map give the dimensions of rasters */

					if(BitMap)
					{
						BitMapHeader . w	= Width;
						BitMapHeader . h	= Height;
						BitMapHeader . nPlanes	= BitMap -> Depth;
						BitMapHeader . x	= OffsetX;
						BitMapHeader . y	= OffsetY;
					}

					if(!PushChunk(FileHandle,0,'BMHD',IFFSIZE_UNKNOWN))
					{
						if(WriteChunkBytes(FileHandle,&BitMapHeader,sizeof(BitMapHeader)) == sizeof(BitMapHeader))
						{
							if(!PopChunk(FileHandle))
							{
								if(!PushChunk(FileHandle,0,'ANNO',IFFSIZE_UNKNOWN))
								{
									extern UBYTE VersTag[];

									if(WriteChunkBytes(FileHandle,&VersTag[1],strlen(&VersTag[1])) != strlen(&VersTag[1]))
										Success = FALSE;

									if(PopChunk(FileHandle))
										Success = FALSE;
								}

								if(Success && !PushChunk(FileHandle,0,'CMAP',IFFSIZE_UNKNOWN))
								{
									if(WriteChunkBytes(FileHandle,Colours,3 * NumColours) == 3 * NumColours)
									{
										if(!PopChunk(FileHandle))
										{
											if(!PushChunk(FileHandle,0,'CAMG',IFFSIZE_UNKNOWN))
											{
												if(WriteChunkBytes(FileHandle,&ViewModes,sizeof(ULONG)) == sizeof(ULONG))
												{
													if(!PopChunk(FileHandle))
													{
														if(!PushChunk(FileHandle,0,'BODY',IFFSIZE_UNKNOWN))
														{
															if(Compressed)
															{
																if(!PackBitMap(FileHandle,BitMap,Height))
																	Success = FALSE;
																else
																{
																	if(!FlushIFF(FileHandle))
																		Success = FALSE;
																}
															}
															else
															{
																i = Height;
																RowOffset = 0;

																while(--i >= 0 && Success)
																{
																	for(j = 0 ; j < BitMap -> Depth ; j++)
																	{
																		if(WriteChunkBytes(FileHandle,BitMap -> Planes[j] + RowOffset,Height) != Height)
																		{
																			Success = FALSE;
																			break;
																		}

																		RowOffset += BitMap -> BytesPerRow;
																	}
																}
															}

															if(PopChunk(FileHandle))
																Success = FALSE;
														}
														else
															Success = FALSE;
													}
													else
														Success = FALSE;
												}
												else
													Success = FALSE;
											}
											else
												Success = FALSE;
										}
										else
											Success = FALSE;
									}
									else
										Success = FALSE;
								}
								else
									Success = FALSE;
							}
							else
								Success = FALSE;
						}
						else
							Success = FALSE;
					}
					else
						Success = FALSE;

					if(PopChunk(FileHandle))
						Success = FALSE;
				}
				else
					Success = FALSE;
			}
			else
				Success = FALSE;

			Close(FileHandle -> iff_Stream);
		}
		else
			Success = FALSE;

		FreeIFF(FileHandle);
	}
	else
		Success = FALSE;

	return(Success);
}

	/* SaveRPort():
	 *
	 *	Save the contents of a given RastPort to an IFF-ILBM
	 *	file, use iffparse.library to get the job done.
	 */

LONG
SaveRPort(struct RastPort *RPort,struct ViewPort *VPort,LONG LeftEdge,LONG TopEdge,LONG Width,LONG Height,LONG ParentWidth,LONG ParentHeight,LONG Cookie,STRPTR Name)
{
	UBYTE		 ColTable[32 * 3];
	struct BitMap	 DstMap,*SMap = RPort -> BitMap;
	LONG		 IFF_Result,i,r,g,b;

	LONG		 PacketDims[4];
	BYTE		 ClosedWindow = FALSE;

		/* It is impossible to drag the packet window to
		 * a place where it doesn't overlap the main window.
		 * To avoid unpleasant effects we'll close it
		 * and reopen it after saving the main window
		 * contents.
		 */

	if(PacketWindow)
	{
		PacketDims[0] = PacketWindow -> LeftEdge;
		PacketDims[1] = PacketWindow -> TopEdge;
		PacketDims[2] = PacketWindow -> Width;
		PacketDims[3] = PacketWindow -> Height;

		PacketWindow -> Flags |= WFLG_RMBTRAP;

		ClearMenuStrip(PacketWindow);

		CloseWindow(PacketWindow);

		PacketWindow = NULL;

		ClosedWindow = TRUE;
	}

		/* Reset buffer counter. */

	IFFSize = 0;

		/* Perform `cookie cut'? */

	if(LeftEdge || TopEdge || Cookie)
	{
		Cookie = TRUE;

		memset(&DstMap,0,sizeof(struct BitMap));

		InitBitMap(&DstMap,SMap -> Depth,Width,Height);

		for(i = 0 ; i < SMap -> Depth ; i++)
		{
			if(!(DstMap . Planes[i] = (PLANEPTR)AllocVec(DstMap . BytesPerRow * DstMap . Rows,MEMF_CHIP | MEMF_CLEAR)))
			{
				IFF_Result = FALSE;
				goto Stop;
			}
		}

		BltBitMap(SMap,LeftEdge,TopEdge,&DstMap,0,0,Width,Height,0xC0,0xFF,NULL);

		SMap = &DstMap;
	}

		/* Convert the colours. */

	for(i = 0 ; i < 32 ; i++)
	{
		UWORD TmpCol = GetRGB4(VPort -> ColorMap,i);

		r = (TmpCol >> 8) & 0xF;
		g = (TmpCol >> 4) & 0xF;
		b = (TmpCol     ) & 0xF;

		ColTable[i * 3 + 0] = r << 4;
		ColTable[i * 3 + 1] = g << 4;
		ColTable[i * 3 + 2] = b << 4;
	}

		/* Write the IFF-ILBM file. */

	IFF_Result = WriteILBM(Name,ColTable,SMap,LeftEdge,TopEdge,Width,Height,TRUE,ParentWidth,ParentHeight,GetVPModeID(VPort));

		/* Free the display cookie. */

Stop:	if(Cookie)
	{
		for(i = 0 ; i < DstMap . Depth ; i++)
		{
			if(DstMap . Planes[i])
				FreeVec(DstMap . Planes[i]);
		}
	}

		/* Delete truncated file or set the `not executable' bit
		 * if successful.
		 */

	if(!IFF_Result)
		DeleteFile(Name);
	else
		SetProtection(Name,FIBF_EXECUTE);

		/* Be a good boy and reopen the packet window. */

	if(ClosedWindow)
	{
		if(PacketWindow = OpenWindowTags(NULL,
			WA_Left,	PacketDims[0],
			WA_Top,		PacketDims[1],

			WA_Width,	PacketDims[2],
			WA_Height,	PacketDims[3],

			WA_Activate,	TRUE,
			WA_DragBar,	TRUE,
			WA_DepthGadget,	TRUE,
			WA_CloseGadget,	TRUE,
			WA_RMBTrap,	TRUE,
			WA_SizeGadget,	TRUE,
			WA_MinWidth,	80,
			WA_MinHeight,	27,
			WA_MaxWidth,	Screen -> Width,
			WA_MaxHeight,	27,
			WA_CustomScreen,Screen,

			WA_IDCMP,	STRINGIDCMP | IDCMP_CLOSEWINDOW | IDCMP_MENUPICK | IDCMP_ACTIVEWINDOW | IDCMP_RAWKEY,

			WA_Title,	"Packet Window",
		TAG_DONE))
		{
			AddGList(PacketWindow,PacketGadgetList,(UWORD)-1,(UWORD)-1,NULL);
			RefreshGList(PacketGadgetList,PacketWindow,NULL,(UWORD)-1);
			GT_RefreshWindow(PacketWindow,NULL);

			SetMenuStrip(PacketWindow,PacketMenu);

			PacketWindow -> Flags &= ~WFLG_RMBTRAP;
		}
		else
			DeletePacketWindow();
	}

	return(IFF_Result);
}
