
/* SysPic's script language 

	(c) Copyright 1995-2001 Grzegorz Calkowski

	This file is part of SysPic.

	SysPic  is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2, or (at your option)
	any later version.

	Bison is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Bison; see the file COPYING.  If not, write to
	the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include <proto/diskfont.h>

#include "headers.h"
#include "errors.h"
#include "syspic_protos.h"
#include "errors_protos.h"
#include "script_protos.h"

#include "ilbminfo.h"

#include <clib/alib_stdio_protos.h>
#include <devices/timer.h>
#include <proto/timer.h>

#define MYDEBUG 1
#include "vartools/debug.h"

/*****************************************************************************/

/* static protos */

static void perr(STRPTR str);
static void parse(STRPTR str, STRPTR template);

static void allocbuf(struct codepool *pool);
static void extendbuf(struct codepool *pool);
static void putbyte(struct codepool *pool, UBYTE b);
static void putword(struct codepool *pool, WORD w);
static void putstring(struct codepool *pool, STRPTR str, UBYTE len);
static WORD readword(UBYTE *buf);

static UWORD processtext(STRPTR src, STRPTR dest, UWORD destlen);


static BOOL childwait(ULONG ticks);
static void childsuicide(void);
static __inline void obtain(void);
static __inline void release(void);

static void getsysinfo(void);

/* externs */

extern struct ExecBase *SysBase;
extern struct Library *DiskfontBase, *TimerBase;
extern UBYTE filename[128];
extern struct RDArgs *rdargs2;
extern struct Task *syspictask;
extern struct SignalSemaphore *sema;
extern struct ILBMInfo *ilbm;
extern struct TextAttr SafeFont;

extern ULONG memrev;

/*****************************************************************************/

#define MSG_UNRECOGCMD	"unrecognized command"
#define MSG_BADININIT	"illegal command in init block"
#define MSG_VALUERANGE  "value out of range"
#define MSG_MISSINGPROG "missing MAIN: block"
#define MSG_BADINPROG	"illegal command in code block"
#define MSG_NOFONT		"can't find font"
#define MSG_BADARGS		"bad arguments"

enum TOKEN
{
	TOK_MAIN = 1, TOK_EXIT,
	TOK_SHOW, TOK_WAIT, TOK_CR,
	TOK_FONT, TOK_SAMPLE,
	TOK_TEXT, TOK_RTEXT, 
	TOK_TEXTXY, TOK_RTEXTXY, TOK_GOTOXY,
	TOK_BOX, TOK_PLAY, 
	TOK_SETAPEN, TOK_SETBPEN, TOK_SETFONT,
	TOK_JAM1, TOK_JAM2,
	TOK_TYPERATE, TOK_TYPESPACE, TOK_LINESPACE, TOK_SHADOW,
	TOK_LEFTMARGIN, TOK_RIGHTMARGIN, TOK_CENTER
};

struct keydef
{
	STRPTR str;
	enum TOKEN type;
	STRPTR template;
};

struct keydef keywords[] =
{
	"box",			TOK_BOX,				"X/N/A,Y/N/A,A/N/A,B/N/A",
	"center",		TOK_CENTER,			"S/A",
	"cr",				TOK_CR,  			NULL,
	"EXIT:", 		TOK_EXIT,			NULL,
	"fade",			TOK_SHOW,			NULL,
	"font",			TOK_FONT,			"N/N/A,F/A,S/N/A",
	"gotoxy", 		TOK_GOTOXY,			"X/N/A,Y/N/A",
	"jam1",			TOK_JAM1,			NULL,
	"jam2",			TOK_JAM2,			NULL,
	"leftmargin",	TOK_LEFTMARGIN,	"N/N/A",
	"linespace", 	TOK_LINESPACE,		"N/N/A",
	"MAIN:", 		TOK_MAIN,			NULL,
//	"play",			TOK_PLAY,			
	"rightmargin",	TOK_RIGHTMARGIN,	"N/N/A",
	"rtext",			TOK_RTEXT,			"S/A,CR/S",
	"rtextxy",		TOK_RTEXTXY,		"X/N/A,Y/N/A,S/A,CR/S",
//	"sample",		TOK_SAMPLE,
	"setapen",		TOK_SETAPEN,		"N/N/A",
	"setbpen",		TOK_SETBPEN,		"N/N/A",
	"setfont",		TOK_SETFONT,		"N/N/A",
	"shadow",		TOK_SHADOW,			"N/N,N/N,OFF/S",
	"show",			TOK_SHOW,			NULL,
	"text",			TOK_TEXT,			"S/A,Z,CR/S",
	"textxy",		TOK_TEXTXY,			"X/N/A,Y/N/A,S/A,Z,CR/S",
	"typerate",		TOK_TYPERATE,		"N/N/A",
	"typespace",	TOK_TYPESPACE,		"N/N/A",
	"wait",			TOK_WAIT,			"N/N/A"
};

/*****************************************************************************/

#ifndef AFF_68060
#define AFF_68060       (1L<<7)
#endif


struct SystemInfo
{
	UBYTE KickRelease[4], WBRelease[4];
	WORD	KickVersion, KickRevision,
			WBVersion, WBRevision;
	ULONG	bchip, bfast, btotal,
			kchip, kfast, ktotal;
	UBYTE	mchip[6], mfast[6], mtotal[6];
	UBYTE	cpu[6], fpu[6], chipset[4];

	struct ClockData clockdata;
	UBYTE	 day[3], month[3], year[5];
	UBYTE	 wdayname[4], monthname[4];
};

STRPTR months[] =
{
	NULL, "January", "February", "March", "April", "May", "June",
	"July", "August", "September", "October", "November", "December"
};

STRPTR weekdays[] =
{
	"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
};

/*****************************************************************************/

#define MAXARGS	8
#define MAXFONTS	16

// structure which holds precompiled "programs" 
struct codepool
{
	UBYTE *buf;
	ULONG index, size;
};

// actaullly we need two: 1 for "main" program, 2nd for "exit" program
static struct codepool progpool, exitpool;

static UBYTE	*namecopy,	// ptr to the copy of filename, used with errmsgs
					*scriptbuf;
static UWORD	linecnt;
static LONG		args[MAXARGS];
	
// pointers to the buffers pointed by codepool->buf (copy)
static UBYTE	*progbufptr, *exitbufptr;

static struct TextFont *fontarray[MAXFONTS], *topazfont;

struct SystemInfo *sysinfo;
	
/*****************************************************************************/

static int searchfunc(STRPTR symbol, struct keydef *keydef)
{
	return Stricmp(symbol, keydef->str);
}

//
// BOOL processscript()
//
// Opens the given file ('filename'), and determines if it's SysPic's script
// (must have word 'SYSPIC' at offest 0, case insensitive).
//
// Yes, parses the script.
// 1. conactenates the options into one line and calls parseargs() again
// 2. parses INIT: section (if any)
// 3. parses MAIN: and EXIT: sections - precompiles, results are stored in
//    'codepools'
//
// returns TRUE a program has been precompiled ("main" or "exit")
//

BOOL processscript(void)
{
	BPTR	 fh;
	UBYTE	 header[6];
	LONG	 len, numkeys;
	UBYTE	 *worker, *s, *optbuf, *worker2, linebuf[255],
			 wordbuf[20], namebuf[128], txtbuf[255];
	BOOL	 doinit = FALSE, doprog = FALSE, retval = FALSE;
	enum	 TOKEN ttype;
	struct keydef *key;
	struct codepool *poolptr;
	struct TextAttr attr = {0,0,0,0};
	UWORD	 tmp, i;

	if (fh = Open(filename, MODE_OLDFILE))
	{
		if (Read(fh, header, sizeof(header)) != sizeof(header))
		{
			Close(fh);
			fail(ERR_READERR);
		}

		if (Strnicmp(header, "SysPic", sizeof(header)))
		{
			Close(fh);
			return retval;
		}

		// store the filename
		strcpy(namebuf, filename);
		namecopy = namebuf;

		Seek(fh, 0, OFFSET_END);
		len = Seek(fh, sizeof(header), OFFSET_BEGINING) - sizeof(header);

		/* allocate buffer twice big as it's length -
       * we use the second half for options
		 * 8 bytes just for "error" margin...
       */
		if (!(scriptbuf = AllocVec((len << 1) + 8, memrev)))
		{
			Close(fh);
			fail(ERR_NOMEM);
		}

		if (Read(fh, scriptbuf, len) != len)
		{
			Close(fh);
			fail(ERR_READERR);
		}

		Close(fh);

		//------------------------------------------------

		scriptbuf[len] = 0; /* null-terminate the buffer */
		worker = scriptbuf;
		optbuf = scriptbuf + len + 1;
		*optbuf = 0; worker2 = optbuf;

		while (getline(&worker, linebuf))
		{
			linecnt++;

			/* skip leading blanks and remove trailing ones */
			s = stpblkend(stpblk(linebuf));

			/* comment? */
			if (*s == ';')
				continue;
			else
				stripcomment(s);

			if (!Stricmp(s, "INIT:"))
			{
				doinit = TRUE;
				break;
			}	
			else
				if (!Stricmp(s, "MAIN:") || !Stricmp(s, "EXIT:"))
					byemsg("missing 'init:' section", 20);

			strcat(s, " ");
			strcat(worker2, s);
			worker2 += strlen(s);
		}

		// ReadArgs() needs newline at the end
		strcat(worker2, "\n");
		
		// second (local) parse
		parseargs(optbuf, strlen(optbuf));

		if (doinit)
		{
			// we'll probably need some system info

			if (!(sysinfo = AllocMem(sizeof(struct SystemInfo), memrev)))
				fail(ERR_NOMEM);

			getsysinfo();

			// we'll need (probably) open diskfont.library

			if (!(DiskfontBase = OpenLibrary("diskfont.library", 39)))
				fail(ERR_NODISKFONT);

			// topaz is the default
			if (!(topazfont = OpenFont(&SafeFont))) fail(ERR_FAILED);
			for (i=0; i<MAXFONTS; i++)	fontarray[i] = topazfont;

			numkeys = sizeof(keywords) / sizeof(struct keydef);

			while (getline(&worker, linebuf))
			{		
				++linecnt;

				s = stpblk(linebuf);
				if (*s == ';')	continue;
				if (!getword(&s, wordbuf))	continue;

				key = bsearch(wordbuf, keywords, numkeys, sizeof(struct keydef), searchfunc);

				if (!key)
					perr(MSG_UNRECOGCMD);

				ttype = key->type;					

				strcat(s, "\n");	// needed by ReadArgs()

				switch (ttype)
				{
					case TOK_FONT:
						parse(s, "N/N/A,F/A,S/N/A");

						tmp = *(LONG *)args[0] - 1;
						if (tmp < 0 || tmp >= MAXFONTS)
							perr(MSG_VALUERANGE);

						attr.ta_Name = (STRPTR)args[1];
						attr.ta_YSize = *(LONG *)args[2];

						if (fontarray[tmp])
							CloseFont(fontarray[tmp]);

						if (!(fontarray[tmp] = OpenDiskFont(&attr)))
							perr(MSG_NOFONT);

						break;

					case TOK_SAMPLE:
						break;

					/*************/
					case TOK_MAIN:
						doprog = TRUE;
						break;

					default:
						perr(MSG_BADININIT);
				}

				if (doprog)
					break;
			}

			if (!doprog)
				perr(MSG_MISSINGPROG);

			//------------------------------------------------

			retval = TRUE;

			poolptr = &progpool;
			allocbuf(poolptr);

			while (getline(&worker, linebuf))
			{		
				++linecnt;

				s = stpblk(linebuf);
				if (*s == ';')	continue;
				if (!getword(&s, wordbuf))	continue;

				key = bsearch(wordbuf, keywords, numkeys, sizeof(struct keydef), searchfunc);

				if (!key)
					perr(MSG_UNRECOGCMD);

				ttype = key->type;					

				strcat(s, "\n");	// needed by ReadArgs()


				if (ttype != TOK_EXIT)
					putbyte(poolptr, ttype);

				if (key->template)
					parse(s, key->template);

				switch (ttype)
				{
					case TOK_SETAPEN:
					case TOK_SETBPEN:
					case TOK_SETFONT:
						tmp = *(LONG *)args[0];

						if (ttype == TOK_SETFONT)
						{
							--tmp;
							if (tmp < 0 || tmp >= MAXFONTS)
								perr(MSG_VALUERANGE);
						}
						else
						{						
							if (tmp < 0 || tmp > 255)
								perr(MSG_VALUERANGE);
						}

						putbyte(poolptr, tmp);
						break;

					case TOK_TYPERATE:
					case TOK_TYPESPACE:
					case TOK_LINESPACE:
						tmp = *(LONG *)args[0];
						putbyte(poolptr, (BYTE)tmp);
						break;

					case TOK_TEXT:
						tmp = processtext((STRPTR)args[0], txtbuf, sizeof(txtbuf));
						putbyte(poolptr, tmp);
						putstring(poolptr, txtbuf, tmp);

						if (args[1])
						{
							tmp = processtext((STRPTR)args[1], txtbuf, sizeof(txtbuf));
							putbyte(poolptr, TOK_RTEXT);
							putbyte(poolptr, tmp);
							putstring(poolptr, txtbuf, tmp);
						}

						if (args[2])
							putbyte(poolptr, TOK_CR);

						break;

					case TOK_RTEXT:
						tmp = processtext((STRPTR)args[0], txtbuf, sizeof(txtbuf));
						putbyte(poolptr, tmp);
						putstring(poolptr, txtbuf, tmp);

						if (args[1])
							putbyte(poolptr, TOK_CR);

						break;

					case TOK_GOTOXY:
						putword(poolptr, *(LONG *)args[0]);
						putword(poolptr, *(LONG *)args[1]);
						break;

					case TOK_TEXTXY:
						putword(poolptr, *(LONG *)args[0]);
						putword(poolptr, *(LONG *)args[1]);
						tmp = processtext((STRPTR)args[2], txtbuf, sizeof(txtbuf));
						putbyte(poolptr, tmp);
						putstring(poolptr, txtbuf, tmp);

						if (args[3])
						{
							tmp = processtext((STRPTR)args[3], txtbuf, sizeof(txtbuf));
							putbyte(poolptr, TOK_RTEXT);
							putbyte(poolptr, tmp);
							putstring(poolptr, txtbuf, tmp);
						}

						if (args[4])
							putbyte(poolptr, TOK_CR);

						break;

					case TOK_RTEXTXY:
						putword(poolptr, *(LONG *)args[0]);
						putword(poolptr, *(LONG *)args[1]);
						tmp = processtext((STRPTR)args[2], txtbuf, sizeof(txtbuf));
						putbyte(poolptr, tmp);
						putstring(poolptr, txtbuf, tmp);

						if (args[3])
							putbyte(poolptr, TOK_CR);

						break;

					case TOK_BOX:
						putword(poolptr, *(LONG *)args[0]);
						putword(poolptr, *(LONG *)args[1]);
						putword(poolptr, *(LONG *)args[2]);
						putword(poolptr, *(LONG *)args[3]);
						break;

					case TOK_CENTER:
						if (!Stricmp((STRPTR)args[0], "on"))
							putbyte(poolptr, 1);
						else
							if (!Stricmp((STRPTR)args[0], "off"))
								putbyte(poolptr, 0);
							else
								perr(MSG_BADARGS);
						break;

					/* shadow command */

					case TOK_SHADOW:

						if (args[2])  // OFF?
						{
							// no shadow, both pen and len = 0
							putword(poolptr, 0);
						}
						else
						{
							putbyte(poolptr, args[0] ? *(LONG *)args[0] : 0);
							putbyte(poolptr, args[1] ? *(LONG *)args[1] : 1);
						}
						break;

					/* commands with one numeric argument of word size */

					case TOK_LEFTMARGIN:
					case TOK_RIGHTMARGIN:
					case TOK_WAIT:
						putword(poolptr, *(LONG *)args[0]);
						break;
				
					/* one byte commands */
					case TOK_SHOW:
					case TOK_JAM1:
					case TOK_JAM2:
					case TOK_CR:
						break;

					case TOK_EXIT:
						putbyte(poolptr, 0); // terminate progpool

						poolptr = &exitpool;
						allocbuf(poolptr);
						break;

					default:
						perr(MSG_BADINPROG);
				}
			}
			putbyte(poolptr, 0); // terminate the pool

			progbufptr = progpool.buf;
			exitbufptr = exitpool.buf;

			FreeVec(scriptbuf);
			scriptbuf = 0;

			if (sysinfo)
			{
				FreeMem(sysinfo, sizeof(struct SystemInfo));
				sysinfo = NULL;
			}
		}
	}
	else
		fail(ERR_READERR);

	return retval;
}

/*****************************************************************************/

void scriptcleanup(void)
{
	UWORD i;
	struct TextFont *font;

	if (sysinfo)
		FreeMem(sysinfo, sizeof(struct SystemInfo));

	if (progpool.buf)
		FreeMem(progpool.buf, progpool.size);

	if (exitpool.buf)
		FreeMem(exitpool.buf, exitpool.size);

	if (scriptbuf)
		FreeVec(scriptbuf);

	// closefonts

	if (topazfont)
	{
		for (i=0; i<MAXFONTS; i++)
		{
			font = fontarray[i];
			if (font != topazfont)
				CloseFont(font);
		}
		CloseFont(topazfont);
	}
}

/*****************************************************************************/

/* code buffer handling funcs */

static void allocbuf(struct codepool *pool)
{
	UWORD size = 1024;

	if (pool->buf = AllocMem(size, memrev))
	{
		pool->index = 0;
		pool->size = size;
	}
	else
		fail(ERR_NOMEM);
}

static void extendbuf(struct codepool *pool)
{
	UBYTE *tmp;

	if (tmp = AllocMem(pool->size + 1024, memrev))
	{
		CopyMem(pool->buf, tmp, pool->size);
		FreeMem(pool->buf, pool->size);
		pool->size += 1024;
		pool->buf = tmp;
	}
	else
		fail(ERR_NOMEM);
}

static void putbyte(struct codepool *pool, UBYTE b)
{
	if (pool->index == pool->size)
		extendbuf(pool);

	pool->buf[pool->index++] = b;
}

static void putword(struct codepool *pool, WORD w)
{
	if (pool->index > pool->size - 2)
		extendbuf(pool);
	
	pool->buf[pool->index++] = w >> 8;
	pool->buf[pool->index++] = w & 0xff;
}

static void putstring(struct codepool *pool, STRPTR str, UBYTE len)
{
	if (pool->index > pool->size - len)
		extendbuf(pool);

	strncpy(&pool->buf[pool->index], str, len);
	pool->index += len;
}

//------------------------------------------------

static WORD readword(UBYTE *buf)
{
	WORD tmp;

	tmp = (WORD)(*buf) << 8;
	tmp |= *(buf+1);

	return tmp;
}

/*****************************************************************************/

static void parse(STRPTR str, STRPTR template)
{
	UBYTE	tmp[41];

	if (rdargs2)
	{
		FreeArgs(rdargs2);
		FreeDosObject(DOS_RDARGS, rdargs2);
	}

	if (!(rdargs2 = (struct RDArgs *)AllocDosObject(DOS_RDARGS, NULL)))
				fail(ERR_NOMEM);

	rdargs2->RDA_Source.CS_Buffer = str;
	rdargs2->RDA_Source.CS_Length = strlen(str);
	rdargs2->RDA_Source.CS_CurChr = NULL;
	rdargs2->RDA_Flags = RDAF_NOPROMPT;

	memset(args, 0, sizeof(args));

	if (!ReadArgs(template, args, rdargs2))
	{
		Fault(IoErr(), NULL, tmp, sizeof(tmp)-1);
		perr(tmp);
		bye(RETURN_FAIL);
	}
}

/*****************************************************************************/

static void perr(STRPTR str)
{
	UBYTE tmp[100];

	SPrintF(tmp, "error in script %s at line %lu: %s\n", namecopy, (ULONG)linecnt, str);
	DosWrite(tmp);
	bye(RETURN_FAIL);
}


/*****************************************************************************/

//
//	"childcode"
//
// routine running on separate task, executes script "programs",
// writes/types texts, etc.
//

static BYTE	sigkill = -1, sigexit = -1;
ULONG childkill_sigmask, childexit_sigmask;

static struct MsgPort *timeport;
static struct timerequest *timeio;
static struct timeval *tv;
static UBYTE devopened,  parentsignalled;
static ULONG sigmask, timeport_sigmask;

void __asm __saveds childcode(void)
{
	UBYTE	 b, c, *bufptr,
			 progdone = FALSE, doexit = FALSE, fontprop,
			 typerate = 0, center = FALSE, shadowpen;
	BYTE	 typespace = 0, linespace = 1, shadowlen = 0;
	ULONG	 sig;
	UWORD	 x = 0, y = 0, rx, lmargin = 0, rmargin, len, baseline, x2, y2, tx,
			 scrwidth, apen;
	struct RastPort *rp;
	struct TextFont *font;

	if (!(timeport = CreateMsgPort()))
		childsuicide();

	timeport_sigmask = 1L << timeport->mp_SigBit;

	if (!(timeio = CreateIORequest(timeport, sizeof(struct timerequest))))
		childsuicide();

	if (OpenDevice(TIMERNAME, UNIT_VBLANK, (struct IORequest *)timeio, 0))
		childsuicide();
	devopened = TRUE;

	tv = &timeio->tr_time;

	sigexit = AllocSignal(-1); childexit_sigmask = 1L << sigexit;
	sigkill = AllocSignal(-1); childkill_sigmask = 1L << sigkill;
	sigmask = childexit_sigmask | childkill_sigmask;

	SetSignal(0L, sigmask | timeport_sigmask);

	if (!(bufptr = progbufptr))
	{
		if (!(bufptr = exitbufptr))
			childsuicide(); /* nothing to do? */

		progdone = parentsignalled = TRUE;
		Signal(syspictask, SIGBREAKF_CTRL_C);

		// wait for signal to "exit" part
		childwait(0);
	}

	obtain();
	rp = ilbm->win->RPort;
	SetDrMd(rp, JAM1);
	SetFont(rp, font = topazfont); fontprop = FALSE;

	scrwidth = ilbm->win->Width;

	// init right margin value
	rx = rmargin = scrwidth - 1;
	release();

	for(;;)
	{
		sig = SetSignal(0L, sigmask);

		if (sig & childkill_sigmask)
			childsuicide();

		if (sig & childexit_sigmask)
			doexit = TRUE;

		if (doexit)
		{
			if (progdone || !exitbufptr)
				childsuicide();

			bufptr = exitbufptr;
			progdone = TRUE;
			parentsignalled = doexit = FALSE;
		}

		switch (b = *bufptr++)
		{
			case 0: /* end */
				if (!parentsignalled)
					Signal(syspictask,  SIGBREAKF_CTRL_C);

				if (progdone || !exitbufptr)
					childsuicide();

				bufptr = exitbufptr;
				progdone = TRUE;
				childwait(0);
				parentsignalled = FALSE;
				break;

			case TOK_SHOW:
				if (!parentsignalled)
				{
					Signal(syspictask,  SIGBREAKF_CTRL_C);
					parentsignalled = TRUE;
				}
				break;

			case TOK_WAIT:
				doexit = childwait(readword(bufptr));
				bufptr += 2;
				break;

			//------------------------------------------------

			case TOK_JAM1:
			case TOK_JAM2:
				obtain();
				SetDrMd(rp, b == TOK_JAM1 ? JAM1 : JAM2);
				release();
				break;

			case TOK_SETAPEN:
				obtain();
				SetAPen(rp, apen = *bufptr++);
				release();
				break;

			case TOK_SETBPEN:
				obtain();
				SetBPen(rp, *bufptr++);
				release();
				break;

			case TOK_SETFONT:
				obtain();
				SetFont(rp, font = fontarray[*bufptr++]);
				release();

				baseline = 0; //font->tf_Baseline;
				fontprop = font->tf_Flags & FPF_PROPORTIONAL;
				break;

			case TOK_TYPERATE:
				typerate = *bufptr++;
				break;

			case TOK_TYPESPACE:
				typespace = *(BYTE *)bufptr++;
				break;

			case TOK_LINESPACE:
				linespace = *(BYTE *)bufptr++;
				break;

			case TOK_SHADOW:
				shadowpen = *(BYTE *)bufptr++;
				shadowlen = *(BYTE *)bufptr++;
				break;

			case TOK_LEFTMARGIN:
				x = lmargin = readword(bufptr); bufptr += 2;
				break;

			case TOK_RIGHTMARGIN:
				rx = rmargin = readword(bufptr); bufptr += 2;
				break;

			case TOK_CR:
				x = lmargin;
				rx = rmargin;
				y += font->tf_YSize + linespace;
				break;

			case TOK_CENTER:
				center = *bufptr++;
				break;

			case TOK_GOTOXY:
				lmargin = x = readword(bufptr); bufptr += 2;
				y = readword(bufptr); bufptr += 2;
				break;	

			case TOK_TEXT:
			case TOK_TEXTXY:

				if (!typerate)
				{
					if (b == TOK_TEXTXY)
					{
						x = readword(bufptr); bufptr += 2;
						y = readword(bufptr) + baseline; bufptr += 2;
					}

					if (!(len = *bufptr++))
						break;

					if (center)
						x = (scrwidth - TextLength(rp, bufptr, len)) >> 1;

					obtain();

					if (shadowlen)
					{
						SetAPen(rp, shadowpen);
						Move(rp, x + shadowlen, y + shadowlen);
						// wait for vblank only if we've the picture shown
						if (parentsignalled)
							WaitTOF();
						Text(rp, bufptr, len); 
						SetAPen(rp, apen);
					}

					Move(rp, x, y);
					// wait for vblank only if we've the picture shown
					if (parentsignalled && !shadowlen)
						WaitTOF();
					Text(rp, bufptr, len); x = rp->cp_x;
					release();
					bufptr += len;
				}
				else
				{
					if (b == TOK_TEXTXY)
					{
						x = readword(bufptr); bufptr += 2;
						y = readword(bufptr) + baseline; bufptr += 2;
					}

					if (!(len = *bufptr++))
						break;
	
					if (center)
						x = (scrwidth - TextLength(rp, bufptr, len)) >> 1;
	
					c = *bufptr++;
					while (len--)
					{
						if (fontprop)
							x += ((WORD *)(font->tf_CharKern))[c - font->tf_LoChar];
	
						obtain();

						if (shadowlen)
						{
							SetAPen(rp, shadowpen);
							Move(rp, x + shadowlen, y + shadowlen);
//							WaitTOF();
							Text(rp, &c, 1); 
							SetAPen(rp, apen);
						}

						Move(rp, x, y);
//						WaitTOF();
						Text(rp, &c, 1);
						release();
	
						if (fontprop)
							x += ((WORD *)(font->tf_CharSpace))[c - font->tf_LoChar];
						else
							x += font->tf_XSize;
	
						x += typespace;
						if (len) c = *bufptr++;
	
						if (childwait(typerate))
						{
							doexit = TRUE;
							break;
						}
					}

					obtain();
					x = rp->cp_x;
					release();
				}
				break;

			case TOK_RTEXT:
			case TOK_RTEXTXY:

				if (!typerate)
				{
					if (b == TOK_RTEXTXY)
					{
						rx = readword(bufptr); bufptr += 2;
						y = readword(bufptr) + baseline; bufptr += 2;
					}

					if (!(len = *bufptr++))
						break;

					rx -= TextLength(rp, bufptr, len);
	
					obtain();

					if (shadowlen)
					{
						SetAPen(rp, shadowpen);
						Move(rp, rx + shadowlen, y + shadowlen);
						// wait for vblank only if we've the picture shown
						if (parentsignalled)
							WaitTOF();
						Text(rp, bufptr, len); 
						SetAPen(rp, apen);
					}

					Move(rp, rx, y);
					// wait for vblank only if we've the picture shown
					if (parentsignalled && !shadowlen)
						WaitTOF();
					Text(rp, bufptr, len);
					release();
					bufptr += len;
				}
				else
				{
					if (b == TOK_RTEXTXY)
					{
						rx = readword(bufptr); bufptr += 2;
						y = readword(bufptr) + baseline; bufptr += 2;
					}

					if (!(len = *bufptr++))
						break;

					tx = (rx -= TextLength(rp, bufptr, len));

					c = *bufptr++;
					while (len--)
					{
						if (fontprop)
							rx += ((WORD *)(font->tf_CharKern))[c - font->tf_LoChar];

						obtain();

						if (shadowlen)
						{
							SetAPen(rp, shadowpen);
							Move(rp, rx + shadowlen, y + shadowlen);
//							WaitTOF();
							Text(rp, &c, 1); 
							SetAPen(rp, apen);
						}

						Move(rp, rx, y);
//						WaitTOF();
						Text(rp, &c, 1);
						release();

						if (fontprop)
							rx += ((WORD *)(font->tf_CharSpace))[c - font->tf_LoChar];
						else
							rx += font->tf_XSize;

						rx += typespace;
						if (len) c = *bufptr++;

						if (childwait(typerate))
						{
							doexit = TRUE;
							break;
						}
					}

					rx = tx;
				}
				break;

			case TOK_BOX:
				x = readword(bufptr); bufptr += 2;
				y = readword(bufptr); bufptr += 2;
				x2 = readword(bufptr); bufptr += 2;
				y2 = readword(bufptr); bufptr += 2;

				obtain();
				RectFill(rp, x, y, x2, y2);
				release();
				break;
		}

	}
}

static BOOL childwait(ULONG ticks)
{
	ULONG tmpsigmask = sigmask, sig;
	BOOL	timedone = FALSE,
			exitpart = FALSE,
			kill = FALSE;

	/* do we have to do time request? */
	if (ticks)
	{
		timeio->tr_node.io_Command = TR_ADDREQUEST;
		tv->tv_secs = ticks / 100;
		tv->tv_micro = (ticks % 100) * 10000;
		tmpsigmask |= timeport_sigmask;
		SendIO(timeio);
	}

	do
	{
		sig = Wait(tmpsigmask);

		/* timer? */
		if (sig & timeport_sigmask)
			if (GetMsg(timeport))
				timedone = TRUE;

		/* signalled by parent to kill? */
		if (sig & childkill_sigmask)
			kill = TRUE;

		/* signalled by parent to initiate "exitpart"? */
		if (sig & childexit_sigmask)
			exitpart = TRUE;			
	} while (!(kill || timedone || exitpart));

	if (!timedone && ticks)
	{
		AbortIO(timeio);
		WaitIO(timeio);
		SetSignal(0, timeport_sigmask);
	}

	if (kill)
		childsuicide();

	return exitpart;
}

static void childsuicide(void)
{
	if (sigkill != -1) FreeSignal(sigkill);
	if (sigexit != -1) FreeSignal(sigexit);

	if (devopened)
		CloseDevice((struct IORequest *)timeio);

	DeleteIORequest(timeio);
	DeleteMsgPort(timeport);	

	if (!parentsignalled)
		Signal(syspictask,  SIGBREAKF_CTRL_C);

	DeleteTask(FindTask(0L));
}

static __inline void obtain(void)
{
	if (AttemptSemaphoreShared(sema))
		return;

	childsuicide();	
}

static __inline void release(void)
{
	ReleaseSemaphore(sema);
}


/*****************************************************************************/

STRPTR ver2rel(UBYTE ver)
{
	STRPTR s;

	switch (ver)
	{
		case 39: s = "3.0"; break;
		case 40: s = "3.1"; break;
		case 43: s = "3.2"; break;
		default: s = "???"; break;
	}
	return s;
}

void megaconvert(UWORD kb, STRPTR buf)
{
	UBYTE tmp[8], tmp2[8], mb;

	SPrintF(tmp, "%ld.", mb = kb >> 10);
	SPrintF(tmp2, "%ld", (kb % 1024) / 100);

	strcpy(buf, tmp);
	strcat(buf, tmp2);
}

static void getsysinfo(void)
{
	UBYTE  *s;
	UWORD	 f;
	struct timerequest tr;
	struct timeval tv;
	struct Library *verlib;

	// kickstart & workbench

	sysinfo->KickVersion = SysBase -> LibNode.lib_Version;
	sysinfo->KickRevision = SysBase->SoftVer;

	strcpy(sysinfo->KickRelease, ver2rel(sysinfo->KickVersion));

	if (verlib = OpenLibrary("version.library", 0))
	{
		sysinfo->WBVersion = verlib->lib_Version;
		sysinfo->WBRevision = verlib->lib_Revision;
		CloseLibrary(verlib);
	}
	else
		sysinfo->WBVersion = sysinfo->WBRevision = -1;

	strcpy(sysinfo->WBRelease, ver2rel(sysinfo->WBVersion));

	// memory

	sysinfo->bchip = AvailMem(MEMF_CHIP|MEMF_TOTAL);

	if (sysinfo->bchip > 0x1f4000)
		sysinfo->bchip = 0x200000;
	else
		if (sysinfo->bchip > 0xfa000)
			sysinfo->bchip = 0x100000;
		else
			if (sysinfo->bchip > 0x7d000)
				sysinfo->bchip = 0x80000;

	sysinfo->bfast = AvailMem(MEMF_FAST|MEMF_TOTAL);
	sysinfo->btotal = sysinfo->bchip + sysinfo->bfast;

	sysinfo->kchip = sysinfo->bchip >> 10;
	sysinfo->kfast = sysinfo->bfast>> 10;
	sysinfo->ktotal = sysinfo->kchip + sysinfo->kfast;

	megaconvert(sysinfo->kchip,  sysinfo->mchip);
	megaconvert(sysinfo->kfast,  sysinfo->mfast);
	megaconvert(sysinfo->ktotal,  sysinfo->mtotal);

	// cpu and fpu

	f = SysBase->AttnFlags;

	if (f & AFF_68060) s = "60";
	else if (f & AFF_68040)	s = "40";
		else if (f & AFF_68030)	s = "30";
			else if (f & AFF_68020)	s = "20";
				else if (f & AFF_68010)	s = "10";
					else s = "00";

	strcpy(sysinfo->cpu, "680"); strcat(sysinfo->cpu, s);

	if (f & AFF_68040 && f & AFF_FPU40)
		strcpy(sysinfo->fpu, sysinfo->cpu);
	else
	{
		strcpy(sysinfo->fpu, "68888");

		if (f & AFF_68882)
			sysinfo->fpu[4] = '2';
		else
			if (f & AFF_68881)
				sysinfo->fpu[4] = '1';
			else
				strcpy(sysinfo->fpu, "NONE");
	}

	// gfx chipset

	f = GfxBase->ChipRevBits0;

	if (f & GFXF_AA_ALICE && f & GFXF_AA_LISA)
		s = "AGA";
	else
		if (f & GFXF_HR_AGNUS && f & GFXF_HR_DENISE)
			s = "ECS";
		else
			s = "OCS";

	strcpy(sysinfo->chipset, s);

	// date & time

	if (!TimerBase)
	{
		memset(&tr, 0, sizeof(tr));
		if (OpenDevice(TIMERNAME, UNIT_VBLANK, (struct IORequest *)&tr, 0))
			fail(ERR_FAILED);
		TimerBase = (struct Library *)tr.tr_node.io_Device;
		GetSysTime(&tv);
		CloseDevice(&tr);
	}
	else
		GetSysTime(&tv);

	Amiga2Date(tv.tv_secs, &sysinfo->clockdata);

	SPrintF(sysinfo->day, "%02lu", sysinfo->clockdata.mday);
	SPrintF(sysinfo->month, "%02lu", sysinfo->clockdata.month);
	SPrintF(sysinfo->year, "%04lu", sysinfo->clockdata.year);

	strncpy(sysinfo->wdayname, weekdays[sysinfo->clockdata.wday], 3);
	strncpy(sysinfo->monthname, months[sysinfo->clockdata.month], 3);
	sysinfo->wdayname[3] = sysinfo->monthname[3] = 0;
}

/*****************************************************************************/

#define DBLID(a,b) ((UWORD)(a)<<8 | (UWORD) (b))

#define ID_os DBLID('o','s')
#define ID_wb DBLID('w','b')
#define ID_ov DBLID('o','v')
#define ID_wv DBLID('w','v')
#define ID_tc DBLID('t','c')
#define ID_tf DBLID('t','f')
#define ID_tt DBLID('t','t')
#define ID_Tc DBLID('T','c')
#define ID_Tf DBLID('T','f')
#define ID_Tt DBLID('T','t')
#define ID_TC DBLID('T','C')
#define ID_TF DBLID('T','F')
#define ID_TT DBLID('T','T')
#define ID_pr DBLID('p','r')
#define ID_cp DBLID('c','p')
#define ID_cs DBLID('c','s')
//#define ID_cd DBLID('c','d')
//#define ID_ca DBLID('c','a')
#define ID_dt DBLID('d','t')
#define ID_Dt DBLID('D','t')
#define ID_DT DBLID('D','T')
#define ID_t1 DBLID('t','1')
#define ID_t2 DBLID('t','2')
#define ID__d DBLID('_','d')
#define ID__m DBLID('_','m')
#define ID__y DBLID('_','y')
#define ID__Y DBLID('_','Y')
#define ID_wn DBLID('w','n')
#define ID_Wn DBLID('W','n')
#define ID_mn DBLID('m','n')
#define ID_Mn DBLID('M','n')

static UWORD processtext(STRPTR src, STRPTR dest, UWORD destlen)
{
	UBYTE	 c,c2,c3, buf[16];
	STRPTR s, t, t2;
	UWORD	 cnt = 0, id, len, tmp;
	ULONG	 num;

	s = src; --destlen;
	while (c = *s++)
	{
		if (c == '%')
		{
			if (!(c2 = *s++))
				return cnt;

			if (c2 == '%')
				if (cnt++ < destlen)
				{
					*dest++ = c2;
					continue;
				}

			if (!(c3 = *s++))
				return cnt;
		
			t = NULL;
			switch (id = (UWORD)c2 << 8 | c3)
			{
				case ID_os:
					t = sysinfo->KickRelease;
					break;
				case ID_wb:
					t = sysinfo->WBRelease;
					break;
				case ID_ov:
					t = SPrintF(buf, "%lu.%lu", sysinfo->KickVersion, sysinfo->KickRevision);
					break;
				case ID_wv:
					t = SPrintF(buf, "%lu.%lu", sysinfo->WBVersion, sysinfo->WBRevision);
					break;
				case ID_tc:
					num = sysinfo->bchip;
					break;
				case ID_tf:
					num = sysinfo->bfast;
					break;
				case ID_tt:
					num = sysinfo->btotal;
					break;
				case ID_Tc:
					num = sysinfo->kchip;
					break;
				case ID_Tf:
					num = sysinfo->kfast;
					break;
				case ID_Tt:
					num = sysinfo->ktotal;
					break;
				case ID_TC:
					t = sysinfo->mchip;
					break;
				case ID_TF:
					t = sysinfo->mfast;
					break;
				case ID_TT:
					t = sysinfo->mtotal;
					break;
				case ID_pr:
					t = sysinfo->cpu;
					break;
				case ID_cp:
					t = sysinfo->fpu;
					break;
				case ID_cs:
					t = sysinfo->chipset;
					break;

				case ID_dt:
					t = SPrintF(buf, "%s-%s-%s", sysinfo->day, sysinfo->month, sysinfo->year + 2);
					break;

				case ID_Dt:
					t = SPrintF(buf, "%s/%s/%s", sysinfo->month, sysinfo->day, sysinfo->year + 2);
					break;

				case ID_DT:
					t = SPrintF(buf, "%s-%s-%s", sysinfo->day, sysinfo->monthname, sysinfo->year + 2);
					break;

				case ID_t1:
					t = SPrintF(buf, "%02lu:%02lu", sysinfo->clockdata.hour, sysinfo->clockdata.min);
					break;

				case ID_t2:
					if ((tmp =  sysinfo->clockdata.hour) > 11)
					{
						tmp -= 12; t2 = "PM";
					}
					else
						t2 = "AM";
					t = SPrintF(buf, "%02lu:%02lu %s", tmp, sysinfo->clockdata.min, t2);
					break;

				case ID__d:
					t = sysinfo->day;
					break;

				case ID__m:
					t = sysinfo->month;
					break;

				case ID__y:
					t = sysinfo->year + 2;
					break;

				case ID__Y:
					t = sysinfo->year;
					break;

				case ID_wn:
					t = sysinfo->wdayname;
					break;

				case ID_Wn:
					t = weekdays[sysinfo->clockdata.wday];
					break;

				case ID_mn:
					t = sysinfo->monthname;
					break;

				case ID_Mn:
					t = months[sysinfo->clockdata.month];
					break;

				default:
					goto skip;
			}

			if (!t)
				t = SPrintF(buf, "%ld", num);

			len = strlen(t);
			if (cnt + len > destlen)
				len = destlen - cnt;

			strncpy(dest, t, len);
			cnt += len;
			dest += len;
			skip:
			continue;
		}
		else
			if (cnt++ < destlen)
				*dest++ = c;
	}
	*dest = 0;

	return cnt;
}
