/*
 * Copyright (c) 1994 David I. Bell
 * Permission is granted to use, distribute, or modify this source,
 * provided that this copyright notice remains intact.
 *
 * "Object" handling primatives.
 * This simply means that user-specified routines are called to perform
 * the indicated operations.
 */

#include "calc.h"
#include "opcodes.h"
#include "func.h"
#include "symbol.h"
#include "string.h"


/*
 * Types of values returned by calling object routines.
 */
#define A_VALUE	0	/* returns arbitrary value */
#define A_INT	1	/* returns integer value */
#define A_UNDEF	2	/* returns no value */

/*
 * Error handling actions for when the function is undefined.
 */
#define E_NONE	0	/* no special action */
#define E_PRINT	1	/* print element */
#define E_CMP	2	/* compare two values */
#define E_TEST	3	/* test value for nonzero */
#define E_POW	4	/* call generic power routine */
#define E_ONE	5	/* return number 1 */
#define E_INC	6	/* increment by one */
#define E_DEC	7	/* decrement by one */
#define E_SQUARE 8	/* square value */


static struct objectinfo {
	short args;	/* number of arguments */
	short retval;	/* type of return value */
	short error;	/* special action on errors */
	char *name;	/* name of function to call */
	char *comment;	/* useful comment if any */
} objectinfo[] = {
	{1, A_UNDEF, E_PRINT, "print",	"print value, default prints elements"},
	{1, A_VALUE, E_ONE,   "one",	"multiplicative identity, default is 1"},
	{1, A_INT,   E_TEST,  "test",	"logical test (false,true => 0,1), default tests elements"},
	{2, A_VALUE, E_NONE,  "add",	NULL},
	{2, A_VALUE, E_NONE,  "sub",	NULL},
	{1, A_VALUE, E_NONE,  "neg",	"negative"},
	{2, A_VALUE, E_NONE,  "mul",	NULL},
	{2, A_VALUE, E_NONE,  "div",	"non-integral division"},
	{1, A_VALUE, E_NONE,  "inv",	"multiplicative inverse"},
	{2, A_VALUE, E_NONE,  "abs",	"absolute value within given error"},
	{1, A_VALUE, E_NONE,  "norm",	"square of absolute value"},
	{1, A_VALUE, E_NONE,  "conj",	"conjugate"},
	{2, A_VALUE, E_POW,   "pow",	"integer power, default does multiply, square, inverse"},
	{1, A_INT,   E_NONE,  "sgn",	"sign of value (-1, 0, 1)"},
	{2, A_INT,   E_CMP,   "cmp",	"equality (equal,nonequal => 0,1), default tests elements"},
	{2, A_INT,   E_NONE,  "rel",	"inequality (less,equal,greater => -1,0,1)"},
	{2, A_VALUE, E_NONE,  "quo",	"integer quotient"},
	{2, A_VALUE, E_NONE,  "mod",	"remainder of division"},
	{1, A_VALUE, E_NONE,  "int",	"integer part"},
	{1, A_VALUE, E_NONE,  "frac",	"fractional part"},
	{1, A_VALUE, E_INC,   "inc",	"increment, default adds 1"},
	{1, A_VALUE, E_DEC,   "dec",	"decrement, default subtracts 1"},
	{1, A_VALUE, E_SQUARE,"square",	"default multiplies by itself"},
	{2, A_VALUE, E_NONE,  "scale",	"multiply by power of 2"},
	{2, A_VALUE, E_NONE,  "shift",	"shift left by n bits (right if negative)"},
	{2, A_VALUE, E_NONE,  "round",	"round to given number of decimal places"},
	{2, A_VALUE, E_NONE,  "bround",	"round to given number of binary places"},
	{3, A_VALUE, E_NONE,  "root",	"root of value within given error"},
	{2, A_VALUE, E_NONE,  "sqrt",	"square root within given error"},
	{0, 0, 0, NULL}
};


static STRINGHEAD objectnames;	/* names of objects */
static STRINGHEAD elements;	/* element names for parts of objects */
static OBJECTACTIONS *objects[MAXOBJECTS]; /* table of actions for objects */


static VALUE objpowi MATH_PROTO((VALUE *vp, NUMBER *q));
static BOOL objtest MATH_PROTO((OBJECT *op));
static BOOL objcmp MATH_PROTO((OBJECT *op1, OBJECT *op2));
static void objprint MATH_PROTO((OBJECT *op));


/*
 * Show all the routine names available for objects.
 */
void
showobjfuncs()
{
	register struct objectinfo *oip;

	printf("\nThe following object routines are definable.\n");
	printf("Note: xx represents the actual object type name.\n\n");
	printf("Name	Args	Comments\n");
	for (oip = objectinfo; oip->name; oip++) {
		printf("xx_%-8s %d	%s\n", oip->name, oip->args,
			oip->comment ? oip->comment : "");
	}
	printf("\n");
}


/*
 * Call the appropriate user-defined routine to handle an object action.
 * Returns the value that the routine returned.
 */
VALUE
objcall(action, v1, v2, v3)
	int action;
	VALUE *v1, *v2, *v3;
{
	FUNC *fp;		/* function to call */
	static OBJECTACTIONS *oap; /* object to call for */
	struct objectinfo *oip;	/* information about action */
	long index;		/* index of function (negative if undefined) */
	VALUE val;		/* return value */
	VALUE tmp;		/* temp value */
	char name[SYMBOLSIZE+1];	/* full name of user routine to call */

	if ((unsigned)action > OBJ_MAXFUNC)
		math_error("Illegal action for object call");
	oip = &objectinfo[action];
	if (v1->v_type == V_OBJ)
		oap = v1->v_obj->o_actions;
	else if (v2->v_type == V_OBJ)
		oap = v2->v_obj->o_actions;
	else
		math_error("Object routine called with non-object");
	index = oap->actions[action];
	if (index == 0) {
		strcpy(name, oap->name);
		strcat(name, "_");
		strcat(name, oip->name);
		index = adduserfunc(name);
		oap->actions[action] = index;
	}
	fp = NULL;
	if (index > 0)
		fp = findfunc(index);
	if (fp == NULL) {
		switch (oip->error) {
			case E_PRINT:
				objprint(v1->v_obj);
				val.v_type = V_NULL;
				break;
			case E_CMP:
				val.v_type = V_INT;
				if (v1->v_type != v2->v_type) {
					val.v_int = 1;
					return val;
				}
				val.v_int = objcmp(v1->v_obj, v2->v_obj);
				break;
			case E_TEST:
				val.v_type = V_INT;
				val.v_int = objtest(v1->v_obj);
				break;
			case E_POW:
				if (v2->v_type != V_NUM)
					math_error("Non-real power");
				val = objpowi(v1, v2->v_num);
				break;
			case E_ONE:
				val.v_type = V_NUM;
				val.v_num = qlink(&_qone_);
				break;
			case E_INC:
				tmp.v_type = V_NUM;
				tmp.v_num = &_qone_;
				val = objcall(OBJ_ADD, v1, &tmp, NULL_VALUE);
				break;
			case E_DEC:
				tmp.v_type = V_NUM;
				tmp.v_num = &_qone_;
				val = objcall(OBJ_SUB, v1, &tmp, NULL_VALUE);
				break;
			case E_SQUARE:
				val = objcall(OBJ_MUL, v1, v1, NULL_VALUE);
				break;
			default:
				math_error("Function \"%s\" is undefined", namefunc(index));
		}
		return val;
	}
	switch (oip->args) {
		case 0:
			break;
		case 1:
			++stack;
			stack->v_addr = v1;
			stack->v_type = V_ADDR;
			break;
		case 2:
			++stack;
			stack->v_addr = v1;
			stack->v_type = V_ADDR;
			++stack;
			stack->v_addr = v2;
			stack->v_type = V_ADDR;
			break;
		case 3:
			++stack;
			stack->v_addr = v1;
			stack->v_type = V_ADDR;
			++stack;
			stack->v_addr = v2;
			stack->v_type = V_ADDR;
			++stack;
			stack->v_addr = v3;
			stack->v_type = V_ADDR;
			break;
		default:
			math_error("Bad number of args to calculate");
	}
	calculate(fp, oip->args);
	switch (oip->retval) {
		case A_VALUE:
			return *stack--;
		case A_UNDEF:
			freevalue(stack--);
			val.v_type = V_NULL;
			break;
		case A_INT:
			if ((stack->v_type != V_NUM) || qisfrac(stack->v_num))
				math_error("Integer return value required");
			index = qtoi(stack->v_num);
			qfree(stack->v_num);
			stack--;
			val.v_type = V_INT;
			val.v_int = index;
			break;
		default:
			math_error("Bad object return");
	}
	return val;
}


/*
 * Routine called to clear the cache of known undefined functions for
 * the objects.  This changes negative indices back into positive ones
 * so that they will all be checked for existence again.
 */
void
objuncache()
{
	register int *ip;
	int i, j;

	i = objectnames.h_count;
	while (--i >= 0) {
		ip = objects[i]->actions;
		for (j = OBJ_MAXFUNC; j-- >= 0; ip++)
			if (*ip < 0)
				*ip = -*ip;
	}
}


/*
 * Print the elements of an object in short and unambiguous format.
 * This is the default routine if the user's is not defined.
 */
static void
objprint(op)
	OBJECT *op;		/* object being printed */
{
	int count;		/* number of elements */
	int i;			/* index */

	count = op->o_actions->count;
	math_fmt("obj %s {", op->o_actions->name);
	for (i = 0; i < count; i++) {
		if (i)
			math_str(", ");
		printvalue(&op->o_table[i], PRINT_SHORT | PRINT_UNAMBIG);
	}
	math_chr('}');
}


/*
 * Test an object for being "nonzero".
 * This is the default routine if the user's is not defined.
 * Returns TRUE if any of the elements are "nonzero".
 */
static BOOL
objtest(op)
	OBJECT *op;
{
	int i;			/* loop counter */

	i = op->o_actions->count;
	while (--i >= 0) {
		if (testvalue(&op->o_table[i]))
			return TRUE;
	}
	return FALSE;
}


/*
 * Compare two objects for equality, returning TRUE if they differ.
 * This is the default routine if the user's is not defined.
 * For equality, all elements must be equal.
 */
static BOOL
objcmp(op1, op2)
	OBJECT *op1, *op2;
{
	int i;			/* loop counter */

	if (op1->o_actions != op2->o_actions)
		return TRUE;
	i = op1->o_actions->count;
	while (--i >= 0) {
		if (comparevalue(&op1->o_table[i], &op2->o_table[i]))
			return TRUE;
	}
	return FALSE;
}


/*
 * Raise an object to an integral power.
 * This is the default routine if the user's is not defined.
 * Negative powers mean the positive power of the inverse.
 * Zero means the multiplicative identity.
 */
static VALUE
objpowi(vp, q)
	VALUE *vp;		/* value to be powered */
	NUMBER *q;		/* power to raise number to */
{
	VALUE res, tmp;
	long power;		/* power to raise to */
	FULL bit;		/* current bit value */

	if (qisfrac(q))
		math_error("Raising object to non-integral power");
	if (zge31b(q->num))
		math_error("Raising object to very large power");
	power = ztolong(q->num);
	if (qisneg(q))
		power = -power;
	/*
	 * Handle some low powers specially
	 */
	if ((power <= 2) && (power >= -2)) {
		switch ((int) power) {
			case 0:
				return objcall(OBJ_ONE, vp, NULL_VALUE, NULL_VALUE);
			case 1:
				res.v_obj = objcopy(vp->v_obj);
				res.v_type = V_OBJ;
				return res;
			case -1:
				return objcall(OBJ_INV, vp, NULL_VALUE, NULL_VALUE);
			case 2:
				return objcall(OBJ_SQUARE, vp, NULL_VALUE, NULL_VALUE);
		}
	}
	if (power < 0)
		power = -power;
	/*
	 * Compute the power by squaring and multiplying.
	 * This uses the left to right method of power raising.
	 */
	bit = TOPFULL;
	while ((bit & power) == 0)
		bit >>= 1L;
	bit >>= 1L;
	res = objcall(OBJ_SQUARE, vp, NULL_VALUE, NULL_VALUE);
	if (bit & power) {
		tmp = objcall(OBJ_MUL, &res, vp, NULL_VALUE);
		objfree(res.v_obj);
		res = tmp;
	}
	bit >>= 1L;
	while (bit) {
		tmp = objcall(OBJ_SQUARE, &res, NULL_VALUE, NULL_VALUE);
		objfree(res.v_obj);
		res = tmp;
		if (bit & power) {
			tmp = objcall(OBJ_MUL, &res, vp, NULL_VALUE);
			objfree(res.v_obj);
			res = tmp;
		}
		bit >>= 1L;
	}
	if (qisneg(q)) {
		tmp = objcall(OBJ_INV, &res, NULL_VALUE, NULL_VALUE);
		objfree(res.v_obj);
		return tmp;
	}
	return res;
}


/*
 * Define a (possibly) new class of objects.
 * The list of indexes for the element names is also specified here,
 * and the number of elements defined for the object.
 */
void
defineobject(name, indices, count)
	char *name;		/* name of object type */
	int indices[];		/* table of indices for elements */
	int count;
{
	OBJECTACTIONS *oap;	/* object definition structure */
	STRINGHEAD *hp;
	int index;

	hp = &objectnames;
	if (hp->h_list == NULL)
		initstr(hp);
	index = findstr(hp, name);
	if (index >= 0) {
		/*
		 * Object is already defined.  Give an error unless this
		 * new definition is exactly the same as the old one.
		 */
		oap = objects[index];
		if (oap->count == count) {
			for (index = 0; ; index++) {
				if (index >= count)
					return;
				if (oap->elements[index] != indices[index])
					break;
			}
		}
		math_error("Object type \"%s\" is already defined", name);
	}

	if (hp->h_count >= MAXOBJECTS)
		math_error("Too many object types in use");
	oap = (OBJECTACTIONS *) malloc(objectactionsize(count));
	if (oap)
		name = addstr(hp, name);
	if ((oap == NULL) || (name == NULL))
		math_error("Cannot allocate object type");
	oap->name = name;
	oap->count = count;
	for (index = OBJ_MAXFUNC; index >= 0; index--)
		oap->actions[index] = 0;
	for (index = 0; index < count; index++)
		oap->elements[index] = indices[index];
	index = findstr(hp, name);
	objects[index] = oap;
	return;
}


/*
 * Check an object name to see if it is currently defined.
 * If so, the index for the object type is returned.
 * If the object name is currently unknown, then -1 is returned.
 */
int
checkobject(name)
	char *name;
{
	STRINGHEAD *hp;

	hp = &objectnames;
	if (hp->h_list == NULL)
		return -1;
	return findstr(hp, name);
}


/*
 * Define a (possibly) new element name for an object.
 * Returns an index which identifies the element name.
 */
int
addelement(name)
	char *name;
{
	STRINGHEAD *hp;
	int index;

	hp = &elements;
	if (hp->h_list == NULL)
		initstr(hp);
	index = findstr(hp, name);
	if (index >= 0)
		return index;
	if (addstr(hp, name) == NULL)
		math_error("Cannot allocate element name");
	return findstr(hp, name);
}


/*
 * Return the index which identifies an element name.
 * Returns minus one if the element name is unknown.
 */
int
findelement(name)
	char *name;		/* element name */
{
	if (elements.h_list == NULL)
		return -1;
	return findstr(&elements, name);
}


/*
 * Return the value table offset to be used for an object element name.
 * This converts the element index from the element table into an offset
 * into the object value array.  Returns -1 if the element index is unknown.
 */
int
objoffset(op, index)
	OBJECT *op;
	long index;
{
	register OBJECTACTIONS *oap;
	int offset;			/* offset into value array */

	oap = op->o_actions;
	for (offset = oap->count - 1; offset >= 0; offset--) {
		if (oap->elements[offset] == index)
			return offset;
	}
	return -1;
}


/*
 * Allocate a new object structure with the specified index.
 */
OBJECT *
objalloc(index)
	long index;
{
	OBJECTACTIONS *oap;
	OBJECT *op;
	VALUE *vp;
	int i;

	if ((unsigned) index >= MAXOBJECTS)
		math_error("Allocating bad object index");
	oap = objects[index];
	if (oap == NULL)
		math_error("Object type not defined");
	i = oap->count;
	if (i < USUAL_ELEMENTS)
		i = USUAL_ELEMENTS;
	if (i == USUAL_ELEMENTS)
		op = (OBJECT *) malloc(sizeof(OBJECT));
	else
		op = (OBJECT *) malloc(objectsize(i));
	if (op == NULL)
		math_error("Cannot allocate object");
	op->o_actions = oap;
	vp = op->o_table;
	for (i = oap->count; i-- > 0; vp++) {
		vp->v_num = qlink(&_qzero_);
		vp->v_type = V_NUM;
	}
	return op;
}


/*
 * Free an object structure.
 */
void
objfree(op)
	register OBJECT *op;
{
	VALUE *vp;
	int i;

	vp = op->o_table;
	for (i = op->o_actions->count; i-- > 0; vp++) {
		if (vp->v_type == V_NUM) {
			qfree(vp->v_num);
		} else
			freevalue(vp);
	}
	if (op->o_actions->count <= USUAL_ELEMENTS)
		free(op);
	else
		free((char *) op);
}


/*
 * Copy an object value
 */
OBJECT *
objcopy(op)
	OBJECT *op;
{
	VALUE *v1, *v2;
	OBJECT *np;
	int i;

	i = op->o_actions->count;
	if (i < USUAL_ELEMENTS)
		i = USUAL_ELEMENTS;
	if (i == USUAL_ELEMENTS)
		np = (OBJECT *) malloc(sizeof(OBJECT));
	else
		np = (OBJECT *) malloc(objectsize(i));
	if (np == NULL)
		math_error("Cannot allocate object");
	np->o_actions = op->o_actions;
	v1 = op->o_table;
	v2 = np->o_table;
	for (i = op->o_actions->count; i-- > 0; v1++, v2++) {
		if (v1->v_type == V_NUM) {
			v2->v_num = qlink(v1->v_num);
			v2->v_type = V_NUM;
		} else
			copyvalue(v1, v2);
	}
	return np;
}


/*
 * Show object types that have been defined.
 */
void
showobjtypes()
{
	STRINGHEAD *hp;
	OBJECTACTIONS *oap;
	STRINGHEAD *ep;
	int index, i;

	hp = &objectnames;
	ep = &elements;
	if (hp->h_count == 0) {
		printf("No object types defined\n");
		return;
	}
	for (index = 0; index < hp->h_count; index++) {
		oap = objects[index];
		printf("\t%s\t{", oap->name);
		for (i = 0; i < oap->count; i++) {
			if (i) printf(",");
			printf("%s", namestr(ep, oap->elements[i]));
		}
		printf("}\n");
	}

}


/* END CODE */
