/*
 *	Name:				util.c
 *
 * Description:	Various useful functions for xferq.library
 *
 * Copyright:		1992-1993 by David Jones.
 *
 * Distribution:
 *		This program 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 of the License, or
 *		(at your option) any later version.
 *
 *		This program 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 this program; if not, write to:
 *
 *				The Free Software Foundation		David Jones
 *				675 Mass Ave							6730 Tooney Drive
 *				Cambridge, MA							Orleans, Ontario
 *				02139										K1C 6R4
 *				USA										Canada
 *
 *	Usenet:	gnu@prep.ai.mit.edu					aa457@freenet.carleton.ca
 *	Fidonet:												1:163/109.8
 *
 *		$Log: $
 *
 */

#include <string.h>
#include <clib/alib_protos.h>
#include <proto/exec.h>
#include <proto/utility.h>
#include "xferq.h"
#include "xferqint.h"
#include "execlists.h"
#include "xferq_pragmas.h"

struct MakeStringData {
	char *string;						/* Pointer to base of string */
	int len;								/* Pointer to end of string */
	struct VarList *varList;		/* Pointer to varlist */
};


struct Node *FindNameNoCase(struct MinList *list, UBYTE *name)
/*
 *	In:	list				Pointer to list to search
 *			name				Name to find
 *
 * Does:	This function works like exec.library/FindName(), except that
 *			upper/lowercase is irrelevant.
 */
{
struct Node *node, *tnode;

	node = FIRST(list);
	while (tnode = NEXT(node)) {
		if (!Stricmp(node->ln_Name, name)) {
			return node;
		}
		node = tnode;
	}
	return NULL;
}


void NewSNVal(struct SNVal *snv)
/*
 *	In:	snv				Pointer to SNVal item
 *
 * Does:	Initializes the SNVal item.
 */
{

	snv->flags = 0;
}


void SetSNValNum(struct SNVal *snv, long num)
/*
 *	In:	snv				Pointer to SNVal structure
 *			num				Number to write in
 *
 * Does:	Sets the SNVal item to the given number.  The string value is
 *			invalidated.
 */
{

	if (snv->flags & VAR_STRING) {
		LibDropObject(snv->str);
	}
	snv->num = num;
	snv->flags = VAR_NUMERIC;
}


BOOL SetSNValStr(struct SNVal *snv, char *str, int len)
/*
 *	In:	snv				Pointer to SNVal structure
 *			str				String to write in
 *
 * Does:	Sets the SNVal item to the given string.  The numeric value is
 *			invalidated.  Function returns TRUE if operation succeeded.
 */
{

	if (snv->flags & VAR_STRING) {
		LibDropObject(snv->str);
	}
	snv->str = CopyStringN(str, len);
	snv->flags = VAR_STRING;
	if (snv->str) {
		return TRUE;
	}
	else {
		return FALSE;
	}
}


BOOL GetSNValNum(struct SNVal *snv, long *num)
/*
 *	In:	snv				Pointer to SNVal structure
 *
 * Does:	Reads the numeric value of the SNVal item.  If the item is
 *			given as a string, then its value is converted.
 */
{

	if (!(snv->flags & VAR_NUMERIC)) {
		if (!GetNumber(snv->str, 10, 0, &snv->num)) {
			return FALSE;
		}
		snv->flags |= VAR_NUMERIC;
	}
	*num = snv->num;
	return TRUE;
}


char *GetSNValStr(struct SNVal *snv)
/*
 *	In:	snv				Pointer to SNVal structure
 *
 * Does:	Reads the string value of the SNVal item.  If the item is
 *			given as a number, then its value is converted.
 */
{

	if (!(snv->flags & VAR_STRING)) {
		snv->str = AllocObject(16, XQO_STRING);
		if (!snv->str) {
			return NULL;
		}
		PutNumber(snv->str, 10, 0, snv->num);
		snv->flags |= VAR_STRING;
	}
	return snv->str;
}


void CopySNVal(struct SNVal *dest, struct SNVal *src)
/*
 *	In:	dest				Pointer to SNVal to write to
 *			src				Pointer to SNVal to write from
 *
 * Does:	Copies the value from src to dest.
 */
{

	*dest = *src;
	if (dest->flags & VAR_STRING) {
		LibCopyObject(dest->str);
	}
}


struct VarList *NewVarList(void)
/*
 *	Does:	Creates a new varlist.
 */
{
struct VarList *vl;

	vl = AllocObject(sizeof(struct VarList), XQO_VARLIST);
	if (vl) {
		NEWLIST(&vl->list);
	}
	return vl;
}


void DropSNVal(struct SNVal *snv)
/*
 *	In:	snv				Pointer to SNVal item
 *
 * Does:	Frees string storage in the SNVal if required.
 */
{

	if (snv->flags & VAR_STRING) {
		LibDropObject(snv->str);
		snv->flags &= ~VAR_STRING;
	}
}


void EmptyVarList(struct VarList *vl)
/*
 *	In:	vl					Pointer to varlist to empty
 *
 *	Does:	Returns all memory associated with the varlist to the system.
 *			The varlist is made empty and ready for re-use.
 */
{
struct VarListEntry *ve, *tve;

	ve = FIRST(&vl->list);
	while (tve = NEXT(ve)) {
		LibDropObject(ve->node.ln_Name);
		DropSNVal(&ve->val);
		FreeObject(ve);
		ve = tve;
	}
	NEWLIST(&vl->list);
}


void DropVarList(struct VarList *vl)
/*
 *	In:	vl					Pointer to varlist to drop
 *
 *	Does:	Returns all memory associated with the varlist to the system.
 */
{

	EmptyVarList(vl);
	FreeObject(vl);
}


struct VarListEntry *FindVarList(struct VarList *vl, char *name, int nameLen)
/*
 *	In:	vl					Pointer to varlist to search
 *			name				Name of data to find
 *			nameLen			Length of name
 *
 * Does:	Searches the varlist for the given entry and returns it if
 *			found and NULL otherwise.
 */
{
struct VarListEntry *ve, *tve;

	ve = FIRST(&vl->list);
	while (tve = NEXT(ve)) {
		if (ve->nameLen == nameLen &&
			!strncmp(name, ve->node.ln_Name, nameLen)) {
			return ve;
		}
		ve = tve;
	}
	return NULL;
}


BOOL GetVarListNum(struct VarList *vl, char *name, int nameLen, long *num)
/*
 *	In:	vl					Pointer to varlist to get from
 *			name				Name of variable to get
 *			nameLen			Length of name
 *			num				Pointer to write number into
 *
 * Does:	Searches the varlist and writes the numeric value into num.
 *			Function returns TRUE if all went well, FALSE otherwise.
 */
{
struct VarListEntry *ve;

	ve = FindVarList(vl, name, nameLen);
	if (!ve) {
		return FALSE;
	}
	return GetSNValNum(&ve->val, num);
}


char *GetVarListStr(struct VarList *vl, char *name, int nameLen)
/*
 *	In:	vl					Pointer to varlist to get from
 *			name				Name of variable to get
 *			nameLen			Length of name
 *
 * Does:	Searches the varlist and returns contents of variable or NULL
 *			if error.
 */
{
struct VarListEntry *ve;

	ve = FindVarList(vl, name, nameLen);
	if (!ve) {
		return NULL;
	}
	return GetSNValStr(&ve->val);
}


struct VarListEntry *AddVarListEntry(struct VarList *vl, char *name,
	int nameLen)
/*
 *	In:	vl					Pointer to varlist to modify
 *			name				Name of variable to create
 *			nameLen			Length of name
 *
 * Does:	Searches for an entry with the given name, and creates it if
 *			it cannot be found.  Function returns NULL on error.
 */
{
struct VarListEntry *ve;

	ve = FindVarList(vl, name, nameLen);
	if (!ve) {
		ve = AllocObject(sizeof(struct VarListEntry), XQO_VLENTRY);
		if (!ve) {
			return NULL;
		}
		ve->node.ln_Name = CopyStringN(name, nameLen);
		if (!ve->node.ln_Name) {
			FreeObject(ve);
			return NULL;
		}
		ve->nameLen = nameLen;
		NewSNVal(&ve->val);
		ADDHEAD(&vl->list, ve);
	}
	return ve;
}


BOOL AddVarListNum(struct VarList *vl, char *name, int nameLen, 
	long num)
/*
 *	In:	vl					Pointer to varlist to modify
 *			name				Name of variable to modify
 *			nameLen			Length of name
 *			num				Value of numeric argument
 *
 * Does:	Sets a variable to a value.  All memory management is performed
 *			as necessary.  Function returns TRUE if all went well, FALSE
 *			otherwise.
 */
{
struct VarListEntry *ve;

	ve = AddVarListEntry(vl, name, nameLen);
	if (!ve) {
		return FALSE;
	}
	SetSNValNum(&ve->val, num);
	return TRUE;
}


BOOL AddVarListStr(struct VarList *vl, char *name, int nameLen,
	char *str, int strLen)
/*
 *	In:	vl					Pointer to varlist to modify
 *			name				Name of variable to modify
 *			nameLen			Length of name
 *			str				Value of string argument
 *			strLen			Length of string
 *
 * Does:	Sets a variable to a string value.  Function returns TRUE if
 *			all went well, FALSE otherwise.
 */
{
struct VarListEntry *ve;

	ve = AddVarListEntry(vl, name, nameLen);
	if (!ve) {
		return FALSE;
	}
	return SetSNValStr(&ve->val, str, strLen);
}

 
struct VarList *CopyVarList(struct VarList *oldVl)
/*
 *	In:	oldVl					Pointer to varlist to copy
 *
 * Does:	Creates a copy of oldVl and returns it or NULL if that cannot
 *			be done.
 */
{
struct VarList *newVl;
struct VarListEntry *ve, *tve, *nve;

	newVl = NewVarList();
	if (!newVl) {
		return NULL;
	}
	ve = FIRST(&oldVl->list);
	while (tve = NEXT(ve)) {
		if (nve = AddVarListEntry(newVl, ve->node.ln_Name, ve->nameLen)) {
			CopySNVal(&nve->val, &ve->val);
		}
		else {
			DropVarList(newVl);
			return NULL;
		}
		ve = tve;
	}
	return newVl;
}


short GetNumber(char *string, short base, short maxLen, long *num)
/*
 *	In:	string			Pointer to string containing number
 *			base				Number base to convert
 *			maxLen			Maximum number of characters to read
 *			num				Pointer to numeric value
 *
 * Does:	Converts the number from ASCII to binary representation.
 *			At most maxLen digits are read (minus signs don't count).
 *			Function returns number of characters consumed.
 */
{
int sign;
long ndigits, work, digit;
char *oldStr;

	sign = 1;
	oldStr = string;
	if (*string == '-') {
		sign = -1;
		string++;
	}
	if (!maxLen) {
		maxLen = 32767;
	}
	ndigits = work = 0;
	while (ndigits < maxLen) {
		if (*string >= '0' && *string <= '9') {
			digit = *string - '0';
		}
		else if (*string >= 'A' && *string <= 'F') {
			digit = *string - ('A' - 10);
		}
		else if (*string >= 'a' && *string <= 'f') {
			digit = *string - ('a' - 10);
		}
		else {
			break;
		}
		if (digit >= base) {
			break;
		}
		work = work * base + digit;
		string++;
		ndigits++;
	}
	*num = work * sign;
	return (short)(string - oldStr);
}


short PutNumber(char *string, short base, short maxLen, long num)
/*
 *	In:	string			Pointer to destination buffer (must be big enough)
 *			base				Number base to output in
 *			maxLen			If nonzero, number of digits to write
 *			num				The number to write
 *
 * Does:	Writes the number into the buffer.  If maxLen is zero, then only
 *			as many digits as required are written.  Otherwise, maxLen
 *			digits are written (possibly using leading zeros).  Function
 *			returns number of characters written.
 *
 */
{
char digBuf[32];
int ndigits;
char *oldStr;

static char digits[] = "0123456789ABCDEF";

	ndigits = 0;
	oldStr = string;
	if (num < 0) {
		*string++ = '-';
		num = -num;
	}
	do {
		digBuf[ndigits++] = num % base;
		num /= base;
	} while (num);
	if (maxLen) {
		while (ndigits < maxLen) {
			digBuf[ndigits++] = 0;
		}
	}
	while (--ndigits >= 0) {
		*string++ = digits[digBuf[ndigits]];
	}
	return (short)(string - oldStr);
}


short ParseTemplate(char *template,
	BOOL (*argFunc)(char *, int, int, char, char, void *),
	BOOL (*charFunc)(char, void *),
	void *data)
/*
 *	In:	template			Pointer to xferq template
 *			argfunc			Pointer to function to call after parsing arg
 *			charfunc			Pointer to function to call after regular char
 *			data				Data to pass to callbacks
 *
 * Does:	Parses the template.  For each non-special character, charfunc
 *			is called with data as an argument.  For special constructs,
 *			argfunc is called.
 *
 *			Function returns number of characters scanned or zero if error.
 */
{
char *str;
char *name;
int nameLen;
int width;
char type;

	str = template;
	
	/* Exit at end of string */
	while (*str) {
		if (*str != '$') {
			if (!(*charFunc)(*str, data)) {
				break;
			}
			str++;
		}
		else {
			str++;
			if (*str++ != '(') {
				XfqSetLocalError(XQERROR_BADPARSE);
				return 0;
			}
			
			/* parse name */
			name = str;
			nameLen = 0;
			while (*str != ':') {
				if (!*str) {
					XfqSetLocalError(XQERROR_BADPARSE);
					return 0;
				}
				str++;
				nameLen++;
			}
			str++;	/* skip colon */
			if (!nameLen) {
				/* No name! */
				XfqSetLocalError(XQERROR_BADPARSE);
				return 0;
			}
			width = 0;
			while (*str >= '0' && *str <= '9') {
				width = width * 10 + *str - '0';
				str++;
			}
			type = *str++;
			if (!type) {
				XfqSetLocalError(XQERROR_BADPARSE);
				return 0;
			}
			if (*str++ != ')') {
				XfqSetLocalError(XQERROR_BADPARSE);
				return 0;
			}
			if (!(*argFunc)(name, nameLen, width, type, *str, data)) {
				return 0;
			}
		}
	}
	return (short)(str - template);
}


BOOL MakeStringCharFunc(char c, struct MakeStringData *msd)
/*
 *	In:	c					Character encountered in template.
 *			msd				Pointer to data required for MakeString
 *
 * Does:	This function is called when a non-special character is found
 *			in the template.  It copies the character to the destination
 *			string, making sure there's room.
 */
{

	msd->string = NeedString(msd->string, 1);
	if (msd->string) {
		msd->string[msd->len++] = c;
		msd->string[msd->len] = '\0';
		return TRUE;
	}
	else {
		return FALSE;
	}
}


BOOL MakeStringArgFunc(char *name, int nameLen, int width, char type,
	char next, struct MakeStringData *msd)
/*
 *	In:	name				Name of argument variable.
 *			nameLen			Length of name
 *			width				Field width
 *			type				Argument type
 *			next				Next char in template
 *			msd				Pointer to working data
 *
 * Does:	Adds the string representation of the argument to the output
 *			string.
 */
{
char buffer[12];
char *arg;
int argLen;
long num;

	switch (type) {
	case 'P':
		/* later */
		break;
	case 'D':
	case 'S':
		arg = GetVarListStr(msd->varList, name, nameLen);
		
		if (!arg) {
			XfqSetLocalError(XQERROR_NOEXIST);
			return FALSE;
		}
		argLen = strlen(arg);
		break;
	case 'X':
		if (!GetVarListNum(msd->varList, name, nameLen, &num)) {
			XfqSetLocalError(XQERROR_NOEXIST);
			return FALSE;
		}
		argLen = PutNumber(buffer, 16, width, num);
		buffer[argLen] = '\0';
		arg = buffer;
		break;
	case 'O':
		if (!GetVarListNum(msd->varList, name, nameLen, &num)) {
			XfqSetLocalError(XQERROR_NOEXIST);
			return FALSE;
		}
		argLen = PutNumber(buffer, 8, width, num);
		buffer[argLen] = '\0';
		arg = buffer;
		break;
	default:
		XfqSetLocalError(XQERROR_BADPARSE);
		return FALSE;
	}
	msd->string = NeedString(msd->string, argLen);
	if (msd->string) {
		strcpy(msd->string + msd->len, arg);
		msd->len += argLen;
		return TRUE;
	}
	else {
		return FALSE;
	}
}
		
		
char *LibBuildString(char *template,
	void *object, struct TagItem *tags)
/*
 *	In:	template	A0		Pointer to template to use
 *			object	A1		Pointer to object to use
 *			tags		A2		Pointer to tags controlling operation
 *
 * Does:	Builds a string (which must be dropped) based on the template
 *			and the object.  No tags are currently defined.
 */
{
struct VarList *vl;
struct MakeStringData msd;

	vl = NewVarList();
	if (!vl) {
		return NULL;
	}
	msd.string = NeedString(NULL, 2);
	if (!msd.string) {
		return NULL;
	}
	*msd.string = '\0';
	msd.len = 0;
	msd.varList = vl;
	if (ExamObjectVarList(object, vl)) {
		ParseTemplate(template, MakeStringArgFunc, MakeStringCharFunc, &msd);
	}
	else {
		LibDropObject(msd.string);
		msd.string = NULL;
	}
	DropVarList(vl);
	return msd.string;
}


char *__saveds __asm LIBBuildString(register __a0 char *tpl,
	register __a1 void *obj, register __a2 struct TagItem *tags)
{
char *str;

	str = LibBuildString(tpl, obj, tags);
	SetErrorTags(tags);
	return str;
}


BOOL ScanStringCharFunc(char c, struct MakeStringData *msd)
/*
 *	In:	c					Current character in template
 *			msd				Pointer to working data
 *
 * Does:	Compares the current template character with the next character
 *			in the string.
 */
{

	if (ToUpper(*msd->string) != ToUpper(c)) {
		XfqSetLocalError(XQERROR_BADPARSE);
		return FALSE;
	}
	msd->string++;
	return TRUE;
}


BOOL ScanStringArgFunc(char *name, int nameLen, int width, char type,
	char next, struct MakeStringData *msd)
/*
 *	In:	name				Name of argument variable.
 *			nameLen			Length of name
 *			width				Field width
 *			type				Argument type
 *			next				Next char in template
 *			msd				Pointer to working data
 *
 * Does: Converts an argument to internal form.
 */
{
long num;
int len;

	switch (type) {
	case 'P':
		/* later */
		break;
	case 'D':
		len = GetNumber(msd->string, 10, width, &num);
		if (!len) {
			XfqSetLocalError(XQERROR_BADPARSE);
			return FALSE;
		}
		if (!AddVarListNum(msd->varList, name, nameLen, num)) {
			return FALSE;
		}
		break;
	case 'X':
		len = GetNumber(msd->string, 16, width, &num);
		if (!len) {
			XfqSetLocalError(XQERROR_BADPARSE);
			return FALSE;
		}
		if (!AddVarListNum(msd->varList, name, nameLen, num)) {
			return FALSE;
		}
		break;
	case 'O':
		len = GetNumber(msd->string, 8, width, &num);
		if (!len) {
			XfqSetLocalError(XQERROR_BADPARSE);
			return FALSE;
		}
		if (!AddVarListNum(msd->varList, name, nameLen, num)) {
			return FALSE;
		}
		break;
	case 'S':
		/*
		 *	If no width is given, then break out to
		 * next template character.
		 */
		if (!width) {
			while (msd->string[width] && msd->string[width] != next) {
				width++;
			}
		}
		len = width;
		if (!AddVarListStr(msd->varList, name, nameLen, msd->string, width)) {
			return FALSE;
		}
		break;
	default:
		XfqSetLocalError(XQERROR_BADPARSE);
		return FALSE;
	}
	msd->string += len;
	return TRUE;
}


long ParseVarList(char *string, char *template, struct VarList *vl)
/*
 *	In:	string			Pointer to string to parse
 *			template			Pointer to template to parse against
 *			vl					Pointer to varlist
 *
 * Does:	Parses a template, writing into the varlist.  Function returns
 *			number of characters scanned.
 */
{
struct MakeStringData msd;

	msd.string = string;
	msd.len = 0;
	msd.varList = vl;
	return ParseTemplate(template, ScanStringArgFunc, ScanStringCharFunc,
		&msd);
}


void *LibParseString(char *string,
	char *template, void *object,
	struct TagItem *tags)
/*
 *	In:	string	A0		Pointer to string to use
 *			template	A1		Pointer to template to use
 *			object	A2		Pointer to object to base parse on
 *			tags		A3		Tag list controlling operation
 *
 * Does:	Compares a string against the given template.  A copy of the
 *			object is returned, modified according to the template.
 *			Valid tags:
 *
 *			XQ_EndPos	-	Specifies where to write end marker
 */
{
long scanned;
char **endpos;
void *newObj, *copyObj;
struct VarList *vl;

	vl = NewVarList();
	if (!vl) {
		return NULL;
	}
	scanned = ParseVarList(string, template, vl);
	endpos = (char **)GetTagData(XQ_EndPos, NULL, tags);
	if (endpos) {
		*endpos = scanned + string;
	}
	copyObj = LibCopyObject(object);
	if (!XfqGetLocalError()) {
		newObj = ModifyObjectVarList(copyObj, vl);
	}
	else {
		newObj = NULL;
	}
	DropVarList(vl);
	if (!newObj) {
		LibDropObject(copyObj);
	}
	return newObj;
}


void *__saveds __asm LIBParseString(register __a0 char *str,
	register __a1 char *tpl, register __a2 void *obj,
	register __a3 struct TagItem *tags)
{
void *newobj;

	newobj = LibParseString(str, tpl, obj, tags);
	SetErrorTags(tags);
	return newobj;
}


struct GAEData {
	int flags;
	int type;
};


BOOL GAECharFunc(char c, struct GAEData *gae)
/*
 *	In:	c					Character from user
 *			gae				Pointer to data area
 *
 * Does:	Determines if c is valid and determines the type of address
 *			element.
 */
{

	if (c >= '0' && c <= '9') {
		return TRUE;
	}
	if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
		(c == '_') || (c == '-') || (c == '.' && gae->flags == GAE_DOTOK) ||
		(c == ' ' && gae->flags == GAE_SPACEOK)) {
		gae->type = ET_ALPHA;
		return TRUE;
	}
	return FALSE;
}


BOOL GAEArgFunc(char *name, int nameLen, int width, char type,
	char next, struct GAEData *gae)
/*
 *	In:	name				Name of variable element
 *			nameLen			Length of variable name
 *			width				Field width
 *			type				Type of field
 *			next				Next char in template
 *			gae				Pointer to private data
 *
 * Does:	Determines the type of the address element based on the
 *			template argument.
 */
{

	if (type == 'D' || type == 'X' || type == 'O') {
		return TRUE;
	}
	if (type == 'S') {
		gae->type = ET_ALPHA;
		return TRUE;
	}
	return FALSE;
}


int GetAddrElement(char **strp, int flags)
/*
 *	In:	strp				Pointer to string variable.
 *			flags				Flags giving permissible values
 *
 * Does:	Parses characters up to end of address component, which consists
 *			of letters, numbers, '-', '_' and optionally '.'.
 *			Advances strp past this string and returns its type.
 */
{
struct GAEData gae;
int len;

	gae.type = ET_NUMBER;
	gae.flags = flags;
	len = ParseTemplate(*strp, GAEArgFunc, GAECharFunc, &gae);
	if (!len) {
		return ET_ERROR;
	}
	while ((*strp)[len - 1] == ' ') {
		len--;
	}
	*strp += len;
	return gae.type;
}


