#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "LeoLib.h"

#define VERSION "1.11"

#if !defined _AMIGA && !defined AMIGA
typedef short BOOL;
#endif
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif

/* The following are for ANSI C compilers with bad stdio.h */
#if !defined _IOFBF
#define _IOFBF 0
#endif

static void SetStupidTerminal(BOOL Stupid);
static void Bold(FILE *OutFile);
static void Italics(FILE *OutFile);
static void Reverse(FILE *OutFile);
static void Underline(FILE *OutFile);
static void BoldOff(FILE *OutFile);
static void ItalicsOff(FILE *OutFile);
static void ReverseOff(FILE *OutFile);
static void UnderlineOff(FILE *OutFile);
static void FGColor(FILE *OutFile, int Color);
static void BGColor(FILE *OutFile, int Color);
static void Normal(FILE *OutFile);
static void StoreTextMode(void);
static void RestoreTextMode(FILE *OutFile);

static void ReadAGFile(FILE *InFile);
static void WordAddChar(char NewC);
static void WordSend(void);

static void SkipSpaces(FILE *InFile);
static void SkipUntilSpace(FILE *InFile);
static void SkipToEOL(FILE *InFile);
static int ReadToUpper(FILE *InFile, char *string, int n);

static void PutInit(FILE *OutFile);
static void PutWord(char *Str);
static void PutEOL(void);
static void PutEnd(void);
static void PutTab(void);
static void PutSpace(void);
static void PutSetWidth(int Width);
static void PutSetWrap(void);
static void PutPurgeSpace(void);

static void NodeInit(void);
static int NodeNumber(char *Str);
static void NodeEnd(void);

static int AGParseCommand(char *Command, FILE *InFile);
static int AGArgs(void);
static char *AGArg(int Arg);
static char *AGArgU(int Arg);


#if defined _AMIGA || defined AMIGA
static char *Ver = "$VER:GuideCat " VERSION
#if defined __SASC
	" " __AMIGADATE__ " ©1994-6 Leopold-Soft"
#endif
;
#endif

static BOOL NodeItalics = TRUE,  NodeBold = TRUE,  NodeReverse = TRUE,
	LinkItalics = FALSE, LinkBold = FALSE, LinkReverse = TRUE,
	IndexNumbering = TRUE, fileArguments = FALSE, Plain = FALSE,
	Brackets = FALSE;
static int NodeColor = 1, LinkColor = 1, ColColor = 2;
static BOOL ColReverse = FALSE;
static void *InBuff = NULL;




/***********************************\
*									*
* Main - command line parser		*
*									*
\***********************************/

int main(int argc, char **argv) {
	static int CurrArg = 0;
	static FILE *InFile;
	static char *ColumnsStr;
	static int Columns = 0;

	if ((ColumnsStr = getenv("COLUMNS")) && (Columns = atoi(ColumnsStr)))
		PutSetWidth(Columns);

	while(++CurrArg < argc) {
								/* NODE OPTIONS */
		if (!strcmp(argv[CurrArg],"-nI")) {
			NodeItalics = TRUE;
			Plain = FALSE;
		} else if (!strcmp(argv[CurrArg],"-ni")) {
			NodeItalics = FALSE;
			Plain = FALSE;
		} else if (!strcmp(argv[CurrArg],"-nB")) {
			NodeBold = TRUE;
			Plain = FALSE;
		} else if (!strcmp(argv[CurrArg],"-nb")) {
			NodeBold = FALSE;
			Plain = FALSE;
		} else if (!strcmp(argv[CurrArg],"-nR")) {
			NodeReverse = TRUE;
			Plain = FALSE;
		} else if (!strcmp(argv[CurrArg],"-nr")) {
			NodeReverse = FALSE;
			Plain = FALSE;
		} else if (!strcmp(argv[CurrArg],"-nC")) {
			if (argc > CurrArg+1) {
				NodeColor = atoi(argv[++CurrArg]);
				if (NodeColor < 1 || NodeColor > 7) {
					NodeColor = 1;
					CurrArg--;
				}
			}
			Plain = FALSE;
		} else if (!strcmp(argv[CurrArg],"-nc")) {
			NodeColor = 1;
			Plain = FALSE;
								/* LINK OPTIONS */
		} else if (!strcmp(argv[CurrArg],"-lI")) {
			LinkItalics = TRUE;
			Plain = FALSE;
		} else if (!strcmp(argv[CurrArg],"-li")) {
			LinkItalics = FALSE;
			Plain = FALSE;
		} else if (!strcmp(argv[CurrArg],"-lB")) {
			LinkBold = TRUE;
			Plain = FALSE;
		} else if (!strcmp(argv[CurrArg],"-lb")) {
			LinkBold = FALSE;
			Plain = FALSE;
		} else if (!strcmp(argv[CurrArg],"-lR")) {
			LinkReverse = TRUE;
			Plain = FALSE;
		} else if (!strcmp(argv[CurrArg],"-lr")) {
			LinkReverse = FALSE;
			Plain = FALSE;
		} else if (!strcmp(argv[CurrArg],"-lC")) {
			if (argc > CurrArg+1) {
				LinkColor = atoi(argv[++CurrArg]);
				if (LinkColor < 1 || LinkColor > 7) {
					LinkColor = 1;
					CurrArg--;
				}
			}
			Plain = FALSE;
		} else if (!strcmp(argv[CurrArg],"-lc")) {
			LinkColor = 1;
			Plain = FALSE;
								/* COLOR OPTIONS */
		} else if (!strcmp(argv[CurrArg],"-cR")) {
			ColReverse = TRUE;
			ColColor = 1;
			Plain = FALSE;
		} else if (!strcmp(argv[CurrArg],"-cr")) {
			ColReverse = FALSE;
			Plain = FALSE;
		} else if (!strcmp(argv[CurrArg],"-cC")) {
			if (argc > CurrArg+1) {
				ColColor = atoi(argv[++CurrArg]);
				if (ColColor < 1 || ColColor > 7) {
					ColColor = 1;
					CurrArg--;
				}
			}
			ColReverse = FALSE;
			Plain = FALSE;
		} else if (!strcmp(argv[CurrArg],"-cc")) {
			ColColor = 1;
								/* OTHER OPTIONS */
		} else if (!strcmp(argv[CurrArg],"-I")) {
			IndexNumbering = TRUE;
		} else if (!strcmp(argv[CurrArg],"-i")) {
			IndexNumbering = FALSE;
		} else if (!strcmp(argv[CurrArg],"-S")) {
			SetStupidTerminal(TRUE);
		} else if (!strcmp(argv[CurrArg],"-s")) {
			SetStupidTerminal(FALSE);
		} else if (!strcmp(argv[CurrArg],"-B")) {
			Brackets = TRUE;
		} else if (!strcmp(argv[CurrArg],"-b")) {
			Brackets = FALSE;
		} else if (!strcmp(argv[CurrArg],"-P")) {
			Plain = TRUE;
		} else if (!strcmp(argv[CurrArg],"-p")) {
			Plain = FALSE;
		} else if (!strcmp(argv[CurrArg],"-A")) {
			Brackets = TRUE;
		} else if (!strcmp(argv[CurrArg],"-a")) {
			Brackets = FALSE;
		} else if (!strcmp(argv[CurrArg],"-w") || !strcmp(argv[CurrArg],"-W")) {
			if (argc > CurrArg+1) {
				static int Tmp;
				Tmp = atoi(argv[++CurrArg]);
				if (Tmp == 0) {
					CurrArg--;
				} else {
					PutSetWidth(Tmp);
				}
			}
		} else if (!strcmp(argv[CurrArg],"-d0")) {
			NodeItalics = TRUE;  NodeBold = TRUE;  NodeReverse = TRUE;
			LinkItalics = FALSE; LinkBold = FALSE; LinkReverse = TRUE;
			IndexNumbering = TRUE; fileArguments = FALSE; Plain = FALSE;
			Brackets = FALSE; NodeColor = 1; LinkColor = 1;
			ColReverse = FALSE; ColColor = 2;
			SetStupidTerminal(TRUE);
		} else if (!strcmp(argv[CurrArg],"-d1")) {
			NodeItalics = FALSE;  NodeBold = TRUE;  NodeReverse = FALSE;
			LinkItalics = FALSE; LinkBold = TRUE; LinkReverse = FALSE;
			IndexNumbering = TRUE; fileArguments = FALSE; Plain = FALSE;
			Brackets = FALSE; NodeColor = 3; LinkColor = 3;
			ColReverse = FALSE; ColColor = 2;
			SetStupidTerminal(FALSE);
		} else if (!strcmp(argv[CurrArg],"-d2")) {
			NodeItalics = FALSE; NodeBold = FALSE; NodeReverse = FALSE;
			LinkItalics = FALSE; LinkBold = FALSE; LinkReverse = FALSE;
			IndexNumbering = TRUE; fileArguments = FALSE; Plain = TRUE;
			Brackets = TRUE; NodeColor = 1; LinkColor = 1;
			ColReverse = FALSE; ColColor = 1;
			SetStupidTerminal(TRUE);
		} else if (!strcmp(argv[CurrArg],"-h") || !strcmp(argv[CurrArg],"?")) {
			printf("\nGuideCat " VERSION " ©1994-6 Henrik Herranen\n\n"
				"Usage: %s [?|-h] | [-n[I|i]] | [-n[B|b]] | [-n[R|r]] | [-l[I|i]] | [-l[B|b]] | [-l[R|r]] | [filename]\n\n"
				"-h or ?      Show this help page.\n"
				"                                             def: 0   1   2\n"
				"-nI or -ni   Turns Italics on/off in node headers on  off off\n"
				"-nB or -nb   Turns Bold on/off in node headers    on  off off\n"
				"-nR or -nr   Turns Reverse on/off in node headers on  off off\n"
				"-nC x or -nc Sets color on/off in node headers    off 3   off\n"
				"-lI or -li   Turns Italics on/off in links        off off off\n"
				"-lB or -lb   Turns Bold on/off in links           off off off\n"
				"-lR or -lr   Turns Reverse on/off in links        on  off off\n"
				"-lC x or -lc Sets color on/off in links           off 3   off\n"
				"-cR or -cr   Turns Reverse on/off for colors      off off off\n"
				"-cC x or -cc Sets colors on+hilite/off for colors 2   2   off\n"
				"-I or -i     Switches node index numbering on/off on  on  on\n"
				"-S or -s     Switches stupid terminal mode on/off on  off on\n"
				"-A or -a     Brackets around nodes+links on/off   off off on\n"
				"-P pr -p     plain output on/off                  off off on\n"
				"-w x (x>=20) Set display width if WORDWRAP used   78  78  78\n"
				"-d0 .. -d2   Use defaults 0..2\n"
				"filename     the named file will be read as an AmigaGuide file.\n\n"
				,argv[0]);
			fileArguments = TRUE;
		} else {
			if (InFile = fopen(argv[CurrArg], "ra")) {
				if (!InBuff) InBuff = malloc(16384);
				if (InBuff) setvbuf(InFile, InBuff, _IOFBF, (size_t) 16384);
				ReadAGFile(InFile);
				fclose(InFile);
			} else {
				fprintf(stderr,"%s: *** ERROR: Couldn't open file '%s'!\n", argv[0], argv[CurrArg]);
			}
			fileArguments = TRUE;
		}
	}

	if (!fileArguments) {
		fprintf(stderr,"%s: Reading from standard input. Press "
#if defined _AMIGA || defined AMIGA
				"ctrl-\\"
#else
				"ctrl-D"
#endif
				" to stop if running interactively.\n", argv[0]);
		ReadAGFile(stdin);
	}

	return EXIT_SUCCESS;
}



/***********************************\
*									*
* Text formatting functions			*
*									*
\***********************************/

struct TextMode {
	BOOL Bold, Italics, Reverse, Underline;
	int FGColor, BGColor;
};

static struct TextMode CurrTextMode = {FALSE, FALSE, FALSE, FALSE, 1, 0}, StoredTextMode;
static BOOL StupidTerminal = TRUE;

static void SetStupidTerminal(BOOL Stupid) {
	StupidTerminal = Stupid;
}

static void Bold(FILE *OutFile) {
	if (!CurrTextMode.Bold) {
		fprintf(OutFile,"\033[1m");
		CurrTextMode.Bold = TRUE;
	}
}

static void Italics(FILE *OutFile) {
	if (!CurrTextMode.Italics) {
		fprintf(OutFile,"\033[3m");
		CurrTextMode.Italics = TRUE;
	}
}

static void Reverse(FILE *OutFile) {
	if (!CurrTextMode.Reverse) {
		fprintf(OutFile,"\033[7m");
		CurrTextMode.Reverse = TRUE;
	}
}

static void Underline(FILE *OutFile) {
	if (!CurrTextMode.Underline) {
		fprintf(OutFile,"\033[4m");
		CurrTextMode.Underline = TRUE;
	}
}

/* The following function is only for internal use	*/
/*		-> no prototype provided					*/
static void SetTextModeOnStupidTerminal(FILE *OutFile) {
	struct TextMode TmpTextMode = CurrTextMode;
	Normal(OutFile);
	if (TmpTextMode.Bold) Bold(OutFile);
	if (TmpTextMode.Italics) Italics(OutFile);
	if (TmpTextMode.Reverse) Reverse(OutFile);
	if (TmpTextMode.Underline) Underline(OutFile);
	if (TmpTextMode.FGColor != 1) FGColor(OutFile, StoredTextMode.FGColor);
	if (TmpTextMode.BGColor != 0) BGColor(OutFile, StoredTextMode.BGColor);
}

static void BoldOff(FILE *OutFile) {
	if (CurrTextMode.Bold) {
		CurrTextMode.Bold = FALSE;
		if (StupidTerminal) SetTextModeOnStupidTerminal(OutFile);
		else fprintf(OutFile,"\033[22m");
	}
}

static void ItalicsOff(FILE *OutFile) {
	if (CurrTextMode.Italics) {
		CurrTextMode.Italics = FALSE;
		if (StupidTerminal) SetTextModeOnStupidTerminal(OutFile);
		else fprintf(OutFile,"\033[23m");
	}
}

static void ReverseOff(FILE *OutFile) {
	if (CurrTextMode.Reverse) {
		CurrTextMode.Reverse = FALSE;
		if (StupidTerminal) SetTextModeOnStupidTerminal(OutFile);
		else fprintf(OutFile,"\033[27m");
	}
}

static void UnderlineOff(FILE *OutFile) {
	if (CurrTextMode.Underline) {
		CurrTextMode.Underline = FALSE;
		if (StupidTerminal) SetTextModeOnStupidTerminal(OutFile);
		else fprintf(OutFile,"\033[24m");
	}
}

static void FGColor(FILE *OutFile, int Color) {
	fprintf(OutFile,"\033[3%dm", Color & 7);
	CurrTextMode.FGColor = Color & 7;
}

static void BGColor(FILE *OutFile, int Color) {
	fprintf(OutFile,"\033[4%dm", Color & 7);
	CurrTextMode.BGColor = Color & 7;
}

static void Normal(FILE *OutFile) {
	CurrTextMode.Bold = FALSE;
	CurrTextMode.Italics = FALSE;
	CurrTextMode.Reverse = FALSE;
	CurrTextMode.Underline = FALSE;
	CurrTextMode.FGColor = 1;
	CurrTextMode.BGColor = 0;
	fprintf(OutFile,"\033[0m");
}

static void StoreTextMode(void) {
	StoredTextMode = CurrTextMode;
}

static void RestoreTextMode(FILE *OutFile) {
	if (CurrTextMode.Bold != StoredTextMode.Bold ||
				CurrTextMode.Italics != StoredTextMode.Italics ||
				CurrTextMode.Reverse != StoredTextMode.Reverse ||
				CurrTextMode.Underline != StoredTextMode.Underline ||
				CurrTextMode.FGColor != StoredTextMode.FGColor ||
				CurrTextMode.BGColor != StoredTextMode.BGColor) {
		if (CurrTextMode.Bold || CurrTextMode.Italics ||
			CurrTextMode.Reverse || CurrTextMode.Underline ||
			CurrTextMode.FGColor != 1 || CurrTextMode.BGColor != 0) {
			Normal(OutFile);
			if (StoredTextMode.Bold) Bold(OutFile);
			if (StoredTextMode.Italics) Italics(OutFile);
			if (StoredTextMode.Reverse) Reverse(OutFile);
			if (StoredTextMode.Underline) Underline(OutFile);
			if (StoredTextMode.FGColor != 1) FGColor(OutFile, StoredTextMode.FGColor);
			if (StoredTextMode.BGColor != 0) BGColor(OutFile, StoredTextMode.BGColor);
		}
	}
}




static int ReadToUpper(FILE *InFile, char *string, int n) {
	static int i, InC;

	for (i=0; i<n; i++) {
		if ((InC = fgetc(InFile)) != -1) {
			*(string++) = ToUpper(InC);
		} else {
			return i;
		}
	}
	return n;
}

static void SkipSpaces(FILE *InFile) {
	static int InC;
	while (IsSpace(InC = fgetc(InFile)));
	if (InC != -1) ungetc(InC, InFile);
}

static void SkipUntilSpace(FILE *InFile) {
	static int InC;
	while (!IsSpace(InC = fgetc(InFile)) && InC != -1);
	if (InC != -1) ungetc(InC, InFile);
}

static void SkipToEOL(FILE *InFile) {
	static int InC;
	while ((InC = fgetc(InFile)) != -1 && InC != '\n');
	if (InC != -1) ungetc(InC, InFile);
}




/***********************************\
*									*
* The AmigaGuide parser				*
*									*
\***********************************/

static void ReadAGFile(FILE *InFile) {
	static char TmpStr[81], TmpStr2[81];
	static int InC, i;
	BOOL LastNewLine = TRUE, InsideNode = FALSE, Command = FALSE;

	ReadToUpper(InFile,TmpStr,10);
	if (memcmp(TmpStr,"@DATABASE ",10)) {
		fprintf(stderr,"   *** ERROR: Not a valid AmigaGuide file!\n");
		return;
	}

	printf("\nDATABASE: ");
	while ((InC = fgetc(InFile)) != '\n') {
		if (InC == -1) {
			fprintf(stderr,"   *** ERROR: Unexpected end of file!\n");
			return;
		} else {
			fputc(InC, stdout);
		}
	}
	fputc('\n', stdout);

	PutInit(stdout);
	NodeInit();

	while (!feof(InFile)) {
		InC = fgetc(InFile);
		if (InC == '@') {			/* The command character @ */
			InC = fgetc(InFile);
			if (LastNewLine && InC != '{') {			/* If a command line */
				ungetc(InC, InFile);
				AGParseCommand(NULL, InFile);
				if (feof(InFile)) return;
				if (!strcmp(AGArgU(0), "NODE")) {	/* recognize commands */
					if (!InsideNode) {
						InsideNode = TRUE;
						Normal(stdout);
						printf("\n\n\n%sNODE: ", (Brackets ? "[" : ""));
						if (!Plain) {
							if (NodeItalics) Italics(stdout);
							if (NodeBold) Bold(stdout);
							if (NodeReverse) Reverse(stdout);
							if (NodeColor != 1) FGColor(stdout, NodeColor);
						}
						if (IndexNumbering) printf("(%d) ", NodeNumber(AGArgU(1)));
						if (AGArgs() > 2) {
							printf("%s", AGArg(2));
						} else {
							printf("*** Internal name: %s ***", AGArgU(1));
						}
						if ((NodeItalics || NodeBold || NodeReverse || NodeColor != 1)
									&& !Plain) Normal(stdout);
						printf("%s\n", (Brackets ? "]" : ""));
					}
				} else if (!strcmp(AGArgU(0), "ENDNODE")) {
					if (InsideNode) {
						InsideNode = FALSE;
					}
				} else if (!strcmp(AGArgU(0), "WORDWRAP")) {
					PutSetWrap();
				}	/* End of recognized commands */
				InC = '\n';
				Command = TRUE;
			} else if (InC == '{') {					/* If link or inline command	*/
				ungetc(InC, InFile);
				AGParseCommand(NULL, InFile);
				if (AGArgs() >= 3 && !strcmp(AGArgU(1), "LINK")) {	/* If link */
					WordSend();
					if (!Plain) {
						StoreTextMode();
						Normal(stdout);
						if (LinkItalics) Italics(stdout);
						if (LinkBold) Bold(stdout);
						if (LinkReverse) Reverse(stdout);
						if (LinkColor != 1) FGColor(stdout, LinkColor);
					}
					strncpy(TmpStr, AGArg(0), 60);
					if (IndexNumbering) {
						sprintf(TmpStr2, "(%d) %s", NodeNumber(AGArgU(2)), TmpStr);
						strcpy(TmpStr, TmpStr2);
					}
					if (Brackets) {
						sprintf(TmpStr2, "[%s]", TmpStr);
						strcpy(TmpStr, TmpStr2);
					}
					PutWord(TmpStr);
					if (!Plain) RestoreTextMode(stdout);
					InC = '}';
					Command = TRUE;
				} else {									/* else: Inline command */
					i = 0;
					if (!Plain) {
						if (!strcmp(AGArgU(0), "B")) {
							WordSend();
							Bold(stdout);
						} else if (!strcmp(AGArgU(0), "UB")) {
							WordSend();
							BoldOff(stdout);
						} else if (!strcmp(AGArgU(0), "I")) {
							WordSend();
							Italics(stdout);
						} else if (!strcmp(AGArgU(0), "UI")) {
							WordSend();
							ItalicsOff(stdout);
						} else if (!strcmp(AGArgU(0), "U")) {
							WordSend();
							Underline(stdout);
						} else if (!strcmp(AGArgU(0), "UU")) {
							WordSend();
							UnderlineOff(stdout);
						} else if (!strcmp(AGArgU(0), "FG")) {
							static int Tmp;
							WordSend();
							if (!strcmp(AGArgU(1), "TEXT")) {
								Tmp = 1;
							} else if (!strcmp(AGArgU(1), "HIGHLIGHT")) {
								Tmp = ColColor;
							} else if (!strcmp(AGArgU(1), "BACKGROUND")) {
								Tmp = 0;
							} else {
								Tmp = 1;
							}
							if (ColColor != 1) FGColor(stdout, Tmp);
							else if (Tmp != 1 && ColReverse) Reverse(stdout);
							else ReverseOff(stdout);
						} else if (!strcmp(AGArgU(0), "BG")) {
							static int Tmp;
							WordSend();
							if (!strcmp(AGArgU(1), "TEXT")) {
								Tmp = 1;
							} else if (!strcmp(AGArgU(1), "HIGHLIGHT")) {
								Tmp = ColColor;
							} else if (!strcmp(AGArgU(1), "BACKGROUND")) {
								Tmp = 0;
							} else {
								Tmp = 0;
							}
							if (ColColor != 1) BGColor(stdout, Tmp);
							else if (Tmp != 0 && ColReverse) Reverse(stdout);
							else ReverseOff(stdout);
						}
					}
					Command = TRUE;
				}
			} else {		/* There was an @ but no command, so @ must be displayed */
				ungetc(InC, InFile);
				InC = '@';
			}
		}

		if (!Command && InC != -1 && InsideNode) {		/* If no command	*/
			if (InC == '\\') InC = fgetc(InFile);	/* '\' is the escape character */
			if (InC == '\n' || InC == '\t' || InC == ' ') {
				WordSend();
				if (InC == '\n') PutEOL();
				else if (InC == '\t') PutTab();
				else PutSpace();
			} else {
				WordAddChar(InC);
			}
		}

		LastNewLine = (InC == '\n');
		Command = FALSE;
	}

	PutEnd();
	NodeEnd();
	if (!Plain) Normal(stdout);
}



#define MAX_WORD_LENGTH		80

static char Word[MAX_WORD_LENGTH+1];
static int WordLen = 0;

static void WordAddChar(char NewC) {
	if (WordLen >= MAX_WORD_LENGTH) WordSend();
	Word[WordLen++] = NewC;
}

static void WordSend(void) {
	Word[WordLen] = '\000';
	PutWord(Word);
	WordLen = 0;
}



/***********************************\
*									*
*	Text displaying put-routines	*
*									*
\***********************************/

static FILE *PutFile=NULL;
static int PutPos, PutMaxColumn = 77;
static BOOL PutWrap;
static BOOL PutLastSpace;

static void PutInit(FILE *OutFile) {
	PutFile = OutFile;
	PutPos = 0;
	PutWrap = FALSE;
	PutLastSpace = FALSE;
}

static void PutWord(char *Str) {
	static int Len;

	if (PutFile) {
/*		printf("PW: Len=%d, Str=%s\n", strlen(Str), Str);*/
		Len = strlen(Str);
		if (PutWrap) {
			if (PutPos + Len >= PutMaxColumn) {
				fprintf(PutFile, "\n%s", Str);
				PutPos = Len - 1;
			} else {
				if (PutLastSpace) {
					fputc(' ', PutFile);
					PutPos++;
				}
				fprintf(PutFile, "%s", Str);
				PutPos += Len;
			}
		} else {
			PutPos += Len;
			if (PutLastSpace) {
				fputc(' ', PutFile);
				PutPos++;
			}
			fprintf(PutFile, "%s", Str);
		}
		PutLastSpace = FALSE;
	}
}

static void PutEOL(void) {
	if (PutFile) {
		fputc('\n', PutFile);
		PutPos = 0;
		PutLastSpace = FALSE;
	}
}

static void PutTab(void) {
	if (PutFile) {
		PutPurgeSpace();
		fputc('\t', PutFile);
		PutPos = (PutPos + 8) & (~7);
	}
}

static void PutSpace(void) {
	if (PutFile) {
		if (PutWrap) {
			if (PutLastSpace) {
				PutLastSpace = FALSE;
				PutWord(" ");
			}
			PutLastSpace = TRUE;
		} else {
			PutWord(" ");
		}
	}
}

static void PutPurgeSpace(void) {
	if (PutFile) {
		if (PutLastSpace) {
			if (PutPos + 1 >= PutMaxColumn) {
				fputc('\n', PutFile);
				PutPos = 0;
			} else {
				fputc(' ', PutFile);
				PutPos++;
			}
		}
		PutLastSpace = FALSE;
	}
}

static void PutSetWidth(int Width) {
	if (Width < 20) Width = 20;

	PutMaxColumn = Width - 1;
}

static void PutSetWrap(void) {
	PutWrap = TRUE;
}

static void PutEnd(void) {
	PutFile = NULL;
}


struct NodeNode {
	struct NodeNode *Left;
	struct NodeNode *Right;
	long Index;
	char *Text;
};




/***********************************\
*									*
*	Node handling routines			*
*									*
\***********************************/

static int NodeAmount = -1;
static struct NodeNode *NodeNode=NULL;


/* The following function is only for internal use	*/
/*		-> no prototype provided					*/
static struct NodeNode *AddNode(char *Str) {
	static struct NodeNode *NewNode;

	NewNode = malloc(sizeof(struct NodeNode) + strlen(Str) + 1);
	if (NewNode) {
		NewNode -> Left = NULL;
		NewNode -> Right = NULL;
		NewNode -> Index = ++NodeAmount;
		NewNode -> Text = ((char *) NewNode) + sizeof(struct NodeNode);
		strcpy(NewNode -> Text, Str);
	} else {
		fprintf(stderr,"   *** PANIC: Couldn't allocate memory!\n");
		exit(EXIT_FAILURE);
	}

	return NewNode;
}



/* The following function is only for internal use	*/
/*		-> no prototype provided					*/
static int FindNode(struct NodeNode *CurrNode, char *Str) {
	int Tmp;

	if (CurrNode == NULL) {
		NodeNode = AddNode(Str);
		return 1;
	} else {
		Tmp = strcmp(Str, CurrNode->Text);
		if (Tmp < 0) {
			if (CurrNode->Left) {
				return FindNode(CurrNode->Left, Str);
			} else {
				CurrNode -> Left = AddNode(Str);
				return CurrNode -> Left -> Index;
			}
		} else if (Tmp>0) {
			if (CurrNode->Right) {
				return FindNode(CurrNode->Right, Str);
			} else {
				CurrNode -> Right = AddNode(Str);
				return CurrNode -> Right -> Index;
			}
		} else {
			return CurrNode -> Index;
		}
	}
}




static int NodeNumber(char *Str) {
	if (NodeAmount >= 0) return FindNode(NodeNode, Str);

	return -1;
}




static void NodeInit(void) {
	NodeEnd();
	NodeAmount = 0;
}



/* The following function is only for internal use	*/
/*		-> no prototype provided					*/
static void FreeNodes(struct NodeNode *CurrNode) {
	if (CurrNode -> Left) FreeNodes(CurrNode->Left);
	if (CurrNode -> Right) FreeNodes(CurrNode->Right);
	free(CurrNode);
}



static void NodeEnd(void) {
	if (NodeNode && NodeAmount >= 0) FreeNodes(NodeNode);
	NodeNode = NULL;
	NodeAmount = -1;
}




/***********************************\
*									*
*	AmigaGuide parsing routines		*
*									*
\***********************************/

#define AG_MAX_CHARS_PER_ARGUMENT		80
#define AG_MAX_ARGUMENTS_PER_COMMAND	4

static char AGOriginalArgument[AG_MAX_ARGUMENTS_PER_COMMAND][AG_MAX_CHARS_PER_ARGUMENT+1];
static char AGUppercaseArgument[AG_MAX_ARGUMENTS_PER_COMMAND][AG_MAX_CHARS_PER_ARGUMENT+1];
static int AGArguments;
static char AGNULL = '\000';

/*-----------------------------------------------------------------*\
|																	|
| AGParseCommand - read AmigaGuide command from a string or file	|
|	and parse it.													|
|																	|
| Synopsis:															|
|	int Args = AGParseCommand(char *Command, NULL);					|
|	int Args = AGParseCommand(NULL, FILE *InFile);					|
|																	|
| Function:															|
|	Reads an AmigaGuide command either from a string or a file and	|
|	parses it. AGParseCommand assumes that leading @ is not in the	|
|	string / has been read from the file. If the first character is	|
|	"{" the command is assumed to be an inline command, otherwise	|
|	it is assumed to be a full command line.						|
|																	|
| Inputs:															|
|	char *Command - if not NULL, this is taken to be the Command	|
|	FILE *InFile - if not NULL, this is the file where the Command	|
|		will be read.												|
|	Command and InFile are mutually exclusive.						|
|																	|
| Result:															|
|	Args - The amount of arguments read from the command			|
|																	|
\*-----------------------------------------------------------------*/

static int AGParseCommand(char *Command, FILE *InFile) {
	int CPos = -1, ArgPos = 0;
	BOOL InQuotes = FALSE, GoQuotes = FALSE, CommandLine = FALSE, Quit = FALSE;
	static int Curr;

	AGArguments = 0;

	Curr = fgetc(InFile);
	if (Curr != '{') {
		CommandLine = TRUE;
		ungetc(Curr, InFile);
	}

	do {
		if (!InFile) Curr = (unsigned char) Command[++CPos];
		else Curr = fgetc(InFile);

		if (!Curr || Curr == -1 || Curr == '\n' ||
					!CommandLine && !InQuotes && Curr == '}') {
			Curr = ' ';
			Quit = TRUE;
		}

		if (Curr == '"') {
			Curr = ' ';
			if (!InQuotes) GoQuotes = TRUE;
			else InQuotes = FALSE;
		}

		if(IsSpace(Curr) && ArgPos && !InQuotes) {
			if (AGArguments <= AG_MAX_ARGUMENTS_PER_COMMAND) {
				AGOriginalArgument[AGArguments-1][ArgPos] = '\000';
				AGUppercaseArgument[AGArguments-1][ArgPos] = '\000';		
			}
			ArgPos = 0;
		} else {
			if (ArgPos < AG_MAX_CHARS_PER_ARGUMENT && (!IsSpace(Curr) || InQuotes)) {
				if (!ArgPos) AGArguments++;
				if (AGArguments <= AG_MAX_ARGUMENTS_PER_COMMAND) {
					AGOriginalArgument[AGArguments-1][ArgPos] = Curr;
					AGUppercaseArgument[AGArguments-1][ArgPos] = ToUpper(Curr);
				}
				ArgPos++;
			}
		}

		if (GoQuotes) {
			InQuotes = TRUE;
			GoQuotes = FALSE;
		}
	} while (!Quit);

	if (AGArguments > AG_MAX_ARGUMENTS_PER_COMMAND)
		AGArguments = AG_MAX_ARGUMENTS_PER_COMMAND;

	return AGArguments;
}


/*-----------------------------------------------------------------*\
|																	|
| AGArgs - returns last AmigaGuide command argument count			|
|																	|
| Synopsis:															|
|	int Args = AGArgs();											|
|																	|
| Function:															|
|	Returns argument count from last call of AGParseCommand()		|
|																	|
| Inputs:															|
|																	|
| Result:															|
|	Args - The amount of arguments read from the command			|
|																	|
\*-----------------------------------------------------------------*/

static int AGArgs(void) {
	return AGArguments;
}


/*-----------------------------------------------------------------*\
|																	|
| AGArg - returns desired argument from last AmigaGuide command		|
|																	|
| Synopsis:															|
|	char *Arg = AGArg(int ArgNo);									|
|																	|
| Function:															|
|	Returns desired argument from last call of AGParseCommand().	|
|																	|
| Inputs:															|
|	ArgNo - 0 .. AGArgs()-1											|
|																	|
| Result:															|
|	Arg - A pointer to the desired argument. If ArgNo was out of	|
|		range, a pointer to an empty string is returned.			|
|																	|
\*-----------------------------------------------------------------*/

static char *AGArg(int Arg) {
	if (Arg <= AG_MAX_ARGUMENTS_PER_COMMAND && Arg >= 0) return AGOriginalArgument[Arg];
	else return &AGNULL;
}

/*-----------------------------------------------------------------*\
|																	|
| AGArg - returns desired argument from last AmigaGuide command		|
|	converted to uppercase.											|
|																	|
| Synopsis:															|
|	char *Arg = AGArg(int ArgNo);									|
|																	|
| Function:															|
|	Returns desired argument from last call of AGParseCommand()		|
|	converted to uppercase letters.									|
|																	|
| Inputs:															|
|	ArgNo - 0 .. AGArgs()-1											|
|																	|
| Result:															|
|	Arg - A pointer to the desired argument. If ArgNo was out of	|
|		range, a pointer to an empty string is returned.			|
|																	|
\*-----------------------------------------------------------------*/

static char *AGArgU(int Arg) {
	if (Arg <= AG_MAX_ARGUMENTS_PER_COMMAND && Arg >= 0) return AGUppercaseArgument[Arg];
	else return &AGNULL;
}
