#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <libraries/asl.h>
#include <libraries/iff.h>	/* The great IFF library	*/
#include <exec/exec.h>
#include <graphics/displayinfo.h>
#include <intuition/intuitionbase.h>
#if defined __SASC
#include <proto/intuition.h>
#include <proto/asl.h>
#include <proto/dos.h>
#include <proto/graphics.h>
#include <proto/exec.h>
#else
#include <clib/intuition_protos.h>
#include <clib/asl_protos.h>
#include <clib/dos_protos.h>
#include <clib/graphics_protos.h>
#include <clib/exec_protos.h>
#endif

#define VERSION "1.1"

/***********************************************\
*												*
*	ILBM basic definitions						*
*												*
\***********************************************/

/* ILBM compression techniques */
#define cmpNone      0
#define cmpByteRun1  1

/* ILBM masking techniques */
#define	mskNone					0
#define	mskHasMask				1
#define	mskHasTransparentColor	2
#define	mskLasso				3

/***********************************************\
*												*
*	Needed for iff.library						*
*												*
\***********************************************/

/* A macro */
#define MAXPACKEDSIZE(s)	((s)*2)

/* how many bytes to buffer in BODY chunk */
#define BODYBUFSIZE			10000


/***********************************************\
*												*
*	Constants and variables						*
*												*
\***********************************************/

static char *Ver = "$VER:ScrDump " VERSION " " __AMIGADATE__ " ©1995-6 Leopold-Soft";

typedef enum SAVEMODE {
	SM_WIN,
	SM_SCR
};

typedef enum ERRORCODE {
	EC_NONE,
	EC_NO_ACTIVE_WIN,
	EC_OLD_OR_NO_IFF_LIBRARY,
	EC_OLD_OS,
	EC_CANT_SAVE
};

#define SF_NOWINBORDERS	(1<<0)
#define SF_SAFE			(1<<1)

static char *defaultFileName = "RAM:ScrDumpFile";

/***********************************************\
*												*
*	SaveScrWin saves a window or screen to the	*
*	given file.									*
*												*
\***********************************************/

static int SaveScrWin(struct Screen *scr, struct Window *win, char *name, ULONG flags) {
	extern struct IntuitionBase *IntuitionBase;
	extern struct ExecBase *SysBase;
	struct Library *IFFBase;
	IFFL_HANDLE iffH;
	BOOL success = FALSE;
	UBYTE *bodyBuf = NULL;
	struct IFFL_BMHD bmhd;
	UBYTE *oneLine = NULL;
	enum SAVEMODE mode = scr ? SM_SCR : SM_WIN;
	BOOL OS3 = SysBase->LibNode.lib_Version >= 39;
	struct RastPort *srcRPort;
	struct DisplayInfo myDisplayInfo;
	UBYTE *colors = NULL;
	BOOL quickSave = (!OS3 || (mode == SM_SCR &&
						GetBitMapAttr(scr->RastPort.BitMap, BMA_FLAGS)
							& BMF_STANDARD));
	LONG depth, xStart=0, yStart=0, width, height;
	int i;
	ULONG modeID, iffrowbytes;
	struct RastPort stdLineRPort1, stdLineRPort2, scrLineRPort;
	struct BitMap *stdLineBMap1=NULL, *stdLineBMap2=NULL, *scrLineBMap=NULL;
	char vStr[80];
	time_t tt;
	struct tm *t;

	if (!OS3 && mode == SM_WIN)
		return EC_OLD_OS;

	if (!(IFFBase = OpenLibrary(IFFNAME, 21L)))
		return EC_OLD_OR_NO_IFF_LIBRARY;

	if (name == NULL || name[0] == '\0')
		name = defaultFileName;

	if (mode == SM_WIN) {
		scr = win->WScreen;
		srcRPort = win->RPort;
	} else {
		srcRPort = &scr->RastPort;
	}

	modeID = GetVPModeID(&scr->ViewPort);

	if (OS3) {
		depth = GetBitMapAttr(srcRPort->BitMap, BMA_DEPTH);
	} else {
		depth = srcRPort->BitMap->Depth;
	}

	if (mode == SM_WIN) {
		if (flags && SF_NOWINBORDERS) {
			xStart = win->BorderLeft;
			yStart = win->BorderTop;
			width  = win->Width  - xStart - win->BorderRight;
			height = win->Height - yStart - win->BorderBottom;
		} else {
			width = win->Width;
			height = win->Height;
		}
	} else {
		width  = scr->Width;
		height = scr->Height;
	}

	iffrowbytes = ((width + 15L) & ~15L) >> 3;

	GetDisplayInfoData(NULL, (UBYTE *) &myDisplayInfo, sizeof(struct DisplayInfo),
			DTAG_DISP, modeID);

	if (!(colors = malloc((1<<depth) * 3)) ||
		!(bodyBuf = malloc(BODYBUFSIZE)))
		goto error;

	if (!quickSave) {
		if (!(stdLineBMap1 = AllocBitMap(width+16, 1, depth, BMF_MINPLANES, NULL)) ||
			!(stdLineBMap2 = AllocBitMap(width+16, 1, depth, BMF_MINPLANES, NULL)) ||
			!(scrLineBMap  = AllocBitMap(scr->Width, 1, depth, BMF_MINPLANES, srcRPort->BitMap)) ||
			!(oneLine = malloc((iffrowbytes<<3) + 16)))
			goto error;

		InitRastPort(&stdLineRPort1);
		stdLineRPort1.BitMap = stdLineBMap1;
		InitRastPort(&stdLineRPort2);
		stdLineRPort2.BitMap = stdLineBMap2;
		scrLineRPort = *srcRPort;
		scrLineRPort.Layer = NULL;
		scrLineRPort.BitMap = scrLineBMap;
	}

	iffH = IFFL_OpenIFF(name, IFFL_MODE_WRITE);

	if (iffH) {
		/*
			Let's write the BMHD chunk
		*/

		bmhd.w					= width;
		bmhd.h					= height;
		bmhd.x					= xStart;
		bmhd.y					= yStart;
		bmhd.nPlanes			= depth;
		bmhd.masking			= mskNone;
		bmhd.compression		= cmpByteRun1;
		bmhd.pad1				= 0;
		bmhd.transparentColor	= 0;
		bmhd.xAspect			= myDisplayInfo.Resolution.y;
		bmhd.yAspect			= myDisplayInfo.Resolution.x;
		bmhd.pageWidth			= bmhd.w;
		bmhd.pageHeight			= bmhd.h;
		success = IFFL_PushChunk(iffH, ID_ILBM, ID_BMHD);
		if (success) success = IFFL_WriteChunkBytes(iffH, &bmhd, sizeof(bmhd));
		if (success) success = IFFL_PopChunk(iffH);


		/*
			Let's write the CMAP chunk
		*/
		if (success) success = IFFL_PushChunk(iffH, ID_ILBM, ID_CMAP);
		for (i=0; i<(1<<depth); i++) {
			ULONG tmpCol[3];

			if (OS3) {
				GetRGB32(scr->ViewPort.ColorMap, (ULONG)i, 1, tmpCol);
				colors[i*3+0] = tmpCol[0] >> 24;
				colors[i*3+1] = tmpCol[1] >> 24;
				colors[i*3+2] = tmpCol[2] >> 24;
			} else {
				tmpCol[0] = GetRGB4(scr->ViewPort.ColorMap, (ULONG)i);
				colors[i*3+0] = ((tmpCol[0]			) >> 8) * 0x11;
				colors[i*3+1] = ((tmpCol[0] & 0x0F0 ) >> 4) * 0x11;
				colors[i*3+2] = ((tmpCol[0] & 0x00F )	  ) * 0x11;
			}
		}
		if (success) success = IFFL_WriteChunkBytes(iffH, colors, (LONG)(1<<depth)*3);
		if (success) success = IFFL_PopChunk(iffH);


		/*
			Let's write a CAMG chunk
		*/
		if (success) success = IFFL_PushChunk(iffH, ID_ILBM, ID_CAMG);
		if (success) success = IFFL_WriteChunkBytes(iffH, &modeID, sizeof(modeID));
		if (success) success = IFFL_PopChunk(iffH);

		/*
			Let's write an ANNO chunk
		*/
		time(&tt);
		t = localtime(&tt);
		strcpy(vStr, "$VER: Written by ScrDump " VERSION " (");
		i = strlen(vStr);
		t->tm_mon++;
		vStr[i++] = '0' + t->tm_mday/10;
		vStr[i++] = '0' + t->tm_mday%10;
		vStr[i++] = '.';
		vStr[i++] = '0' + t->tm_mon/10;
		vStr[i++] = '0' + t->tm_mon%10;
		vStr[i++] = '.';
		vStr[i++] = '0' + (t->tm_year%100)/10;
		vStr[i++] = '0' + t->tm_year%10;
		strcpy(vStr + i, ") ©1995-6 Leopold-Soft");

/*		sprintf(vStr, "%02d.%02d.%02d) ©1995-6 Leopold-Soft",
			t->tm_mday, t->tm_mon, t->tm_year % 100);
*/
		if (success) success = IFFL_PushChunk(iffH, ID_ILBM, ID_ANNO);
		if (success) success = IFFL_WriteChunkBytes(iffH, vStr, strlen(vStr)+1);
		if (success) success = IFFL_PopChunk(iffH);

		/*
			Let's write a BODY chunk
		*/
		if (success) {
			int row, iplane, bodysize=0;
			int rowOffset = -srcRPort->BitMap->BytesPerRow;

			success = IFFL_PushChunk(iffH, ID_ILBM, ID_BODY);

			for (row=0; row < bmhd.h; ++row) {
				if (!quickSave) {
					int i;
					ReadPixelLine8(		  srcRPort, xStart, row+yStart, width, oneLine, &scrLineRPort);
					for (i=0; i<16; i++)
						oneLine[width+i] = 0;
					WritePixelLine8(&stdLineRPort1,      0,       0, width+16, oneLine, &stdLineRPort2);
				} else {
					rowOffset += srcRPort->BitMap->BytesPerRow;
				}
				for (iplane=0; iplane < depth; ++iplane) {
					int comprsize;

					/* Flush buffer if full */
					if (bodysize > (BODYBUFSIZE-MAXPACKEDSIZE(iffrowbytes))) {
						if (success) {
							success = IFFL_WriteChunkBytes(iffH, bodyBuf, bodysize);
							bodysize = 0;
						}
					}

					/* Compress one row	*/
					if (success) {
						if (!quickSave) {
							success = comprsize = IFFL_CompressBlock(
									stdLineRPort1.BitMap->Planes[iplane],
									bodyBuf+bodysize, iffrowbytes, IFFL_COMPR_BYTERUN1);
						} else {
							success = comprsize = IFFL_CompressBlock(
									srcRPort->BitMap->Planes[iplane] + rowOffset,
									bodyBuf+bodysize, iffrowbytes, IFFL_COMPR_BYTERUN1);
						}
						bodysize += comprsize;
					}
				}
			}

			/* Flush the rest of body */
			if (bodysize && success)
				success = IFFL_WriteChunkBytes(iffH, bodyBuf, bodysize);

			if (success) success = IFFL_PopChunk(iffH);
		}
		IFFL_CloseIFF(iffH);
	}

error:
	if (oneLine) free(oneLine);
	if (scrLineBMap) FreeBitMap(scrLineBMap);
	if (stdLineBMap2) FreeBitMap(stdLineBMap2);
	if (stdLineBMap1) FreeBitMap(stdLineBMap1);
	if (bodyBuf) free(bodyBuf);
	if (colors) free(colors);

	CloseLibrary(IFFBase);

	return success ? EC_NONE : EC_CANT_SAVE;
}



static int SaveScreen(struct Screen *scr, char *name, ULONG flags) {
	return SaveScrWin(scr, NULL, name, flags);
}



static int SaveWindow(struct Window *win, char *name, ULONG flags) {
	return SaveScrWin(NULL, win, name, flags);
}



/***********************************************\
*												*
*	The main program							*
*												*
\***********************************************/

int main(int argc, char **argv) {
	int errorcode = EXIT_SUCCESS;
	int i;
	enum SAVEMODE savemode = SM_SCR;
	ULONG flags = 0;
	char *fileName = defaultFileName;

	for (i = 1; i < argc; i++) {
		if (!strcmp(argv[i], "-w")) {
			savemode = SM_WIN;
		} else if (!strcmp(argv[i], "-s")) {
			savemode = SM_SCR;
		} else if (!strcmp(argv[i], "-b")) {
			flags |= SF_NOWINBORDERS;
		} else if (!strcmp(argv[i], "-B")) {
			flags &= ~SF_NOWINBORDERS;
		} else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "?")) {
			Printf("\nScreen Dump v" VERSION " ©1995-6 Leopold-Soft\n"
					"Usage: %s [-w] [-s] [-b] [-B] [-h|?] [filnam]\n"
					"-w\tSave active window (requires OS3)\n"
					"-s\tSave uppermost screen (default)\n"
					"-b\tDon't save window borders\n"
					"-B\tSave window borders (default)\n"
					"-h|?\tShow this help\n"
					"filnam\tSpecify file name to save to (default: %s)\n\n"
					, argv[0], defaultFileName);
			return EXIT_SUCCESS;
		} else {
			fileName = argv[i];
		}
	}

	Delay(500);

	switch(savemode) {
		case SM_SCR:
			errorcode = SaveScreen(IntuitionBase->FirstScreen, fileName, flags);
			DisplayBeep(IntuitionBase->FirstScreen);
			break;
		case SM_WIN:
			if (!IntuitionBase->ActiveWindow) {
				errorcode = EC_NO_ACTIVE_WIN;
			} else {
				errorcode = SaveWindow(IntuitionBase->ActiveWindow, fileName, flags);
				DisplayBeep(IntuitionBase->ActiveWindow->WScreen);
			}
			break;
	}

	switch (errorcode) {
		case EC_NONE:
			Printf("%s: Saved to \"%s\", ok!\n", argv[0], fileName);
			break;
		case EC_NO_ACTIVE_WIN:
			Printf("%s: Error: no active window!\n", argv[0]);
			errorcode = EXIT_FAILURE;
			break;
		case EC_OLD_OR_NO_IFF_LIBRARY:
			Printf("%s: Error: iff.library v21 or newer required!\n", argv[0]);
			errorcode = EXIT_FAILURE;
			break;
		case EC_OLD_OS:
			Printf("%s: Error: Can't save windows under OS2.x!\n", argv[0]);
			errorcode = EXIT_FAILURE;
			break;
		default:
			Printf("%s: Error: can't save to \"%s\"!\n", argv[0], fileName);
			errorcode = EXIT_FAILURE;
			break;
	}
	return errorcode;
}
