/*                        Copyright (c) 1988 Bellcore
**                            All Rights Reserved
**       Permission is granted to copy or use this program, EXCEPT that it
**       may not be sold for profit, the copyright notice must be reproduced
**       on copies, and credit should be given to Bellcore where it is due.
**       BELLCORE MAKES NO WARRANTY AND ACCEPTS NO LIABILITY FOR THIS PROGRAM.
*/


#ifndef lint
static char rcsid[]= "$Header: tol.c,v 1.1 88/09/15 11:33:59 daniel Rel $";
#endif

#include "misc.h"
#include "float.h"
#include "tol.h"
#include "token.h"

/*
**	storage for the default tolerances
*/
T_tol _T_gtol = _T_null;

/*
**	tolerances that can be set in the command script and attached to floating
**		point numbers at parse time
*/
static T_tol _T_tols[_T_TOLMAX];

/*
**	initialize the global tolerance
**	should be called only once at the beginning of the program
*/
void
T_initdefault()
{
	static int called_before = 0;

	if (called_before)
	{
		Z_fatal("T_initdefault called more than once\n");
	}

	/*
	**	if the default tolerance was set somewhere else
	**	don't set it here
	*/
	if (T_isnull(_T_gtol))
	{
		T_defatol(_T_ADEF);
		T_defrtol(_T_RDEF);
	}
	called_before = 1;
}

static void
_T_tolclear(addr)
T_tol *addr;
{
	*addr = _T_null;
}

/*
**	clear the parse time tolerances
*/
void
T_clear_tols()
{
	int i;
	for(i=0;i<_T_TOLMAX;i++)
	{
		_T_tolclear(&_T_tols[i]);
	}
}

static void
_T_defclear()
{
	_T_tolclear(&_T_gtol);
}

/*
**	take a series of specifiers and add them to the tolerance
*/
static void
_T_settol(toladdr,str)
T_tol *toladdr;
char *str;
{
	char typechar;
	while ('\0' != *str)
	{
		/*
		**	find the first non-whitespace character
		*/
		S_skipspace(&str);
		/*
		**	snarf up the type specifier
		*/
		typechar = *str;
		/*
		**	now skip the first char
		*/
		str++;
		/*
		**	skip any possibly intervening whitespace
		*/
		S_skipspace(&str);
		switch (typechar)
		{
			case 'a':
				_T_addtol(toladdr,T_ABSOLUTE,str);
				break;
			case 'r':
				_T_addtol(toladdr,T_RELATIVE,str);
				break;
			case 'i':
				_T_addtol(toladdr,T_IGNORE,(char*)0);
				break;
			case 'd':
				_T_appendtols(toladdr,_T_gtol);
				break;
			default:
				(void) sprintf(Z_err_buf,
				  "don't understand tolerance type '%c'\n",typechar);
				Z_fatal(Z_err_buf);
		}
		/*
		**	and skip to next tolerance
		*/
		S_nextword(&str);
	}
}

/*
**	set the default tolerance 
*/
void
T_setdef(str)
char *str;
{
	_T_defclear();
	_T_settol(&_T_gtol,str);
}


static char*
_T_nextspec(ptr)
char *ptr;
{
	/*
	**	find the end of the current spec
	*/
	for(;(_T_SEPCHAR != *ptr) && ('\0' != *ptr);ptr++)
	{
	}

	/*
	**	and step over the seperator if necessary
	*/
	if (_T_SEPCHAR == *ptr)
		ptr++;

	return(ptr);
}

/*
**	return just the next set of specs
**		ie the string up to end of line or
**			the first _T_SEPCHAR
**	returned string does not include the _T_SEPCHAR
*/
static char *
_T_getspec(from)
char *from;
{
	static char retval[Z_LINELEN];
	char *ptr = retval;

	while((_T_SEPCHAR != *from) && ('\0' != *from))
	{
		*ptr++ = *from++;
	}
	*ptr = '\0';	/* terminate the line */
	return(retval);
}

/*
**	parse a series of _T_SEPCHAR separated tolerance specifications
*/
void
T_tolline(str)
char *str;
{
	int nexttol;

	T_clear_tols();

	for(nexttol=0;'\0' != *str;nexttol++,str = _T_nextspec(str))
	{
		/*
		**	make sure we haven't run off the end
		*/
		if (nexttol >= _T_TOLMAX)
		{
			Z_fatal("too many tolerances per line");
		}

		/*
		**	and set the tolerance
		*/
		_T_settol(&_T_tols[nexttol],_T_getspec(str));
	}
}

T_moretols(next_tol)
{
	return((next_tol >= 0) &&
		(_T_TOLMAX-1 > next_tol) &&
		(!T_isnull( _T_tols[next_tol+1])));
}

T_tol
T_gettol(index)
int index;
{
	return(_T_tols[index]);
}

/*
**	chose which tolerance to use
**		 precidence is
**			first tolerance
**			second tolerance
**			default tolerance
*/
T_tol
T_picktol(p1,p2)
T_tol p1, p2;
{
	if (!(T_isnull(p1)))
		return(p1);

	if (!(T_isnull(p2)))
		return(p2);

	return(_T_gtol);
}

void
_T_appendtols(to,from)
T_tol *to,from;
{

	T_tol last;

	/*
	**	are there any elements on the list yet
	*/
	if (T_isnull(*to))
	{
		/*
		**	it's a null list, so allocat space for the
		**		first element and set pointer to it.
		*/

		*to = from;
	}
	else
	{
		/*
		**	find the last element on the list
		*/
		for(last= *to;!T_isnull(T_getnext(last));last = T_getnext(last))
		{
		}
		/*
		**	add an element on the end
		*/
		T_setnext(last,from);
	}
}

/*
**	add a tolerance to a list
*/
void
_T_addtol(listptr,type,str)
T_tol *listptr;
int type;
char *str;
{
	T_tol last;

	/*
	**	are there any elements on the list yet
	*/
	if (T_isnull(*listptr))
	{
		/*
		**	it's a null list, so allocat space for the
		**		first element and set pointer to it.
		*/

		last = *listptr = Z_ALLOC(1,_T_struct);
	}
	else
	{
		/*
		**	find the last element on the list
		*/
		for(last= *listptr;!T_isnull(T_getnext(last));last = T_getnext(last))
		{
		}
		/*
		**	add an element on the end
		*/
		T_setnext(last,Z_ALLOC(1,_T_struct));

		/*
		**	and point to the new element
		*/
		last = T_getnext(last);
	}

	T_settype(last,type);
	T_setnext(last,_T_null);

	/*
	**	set the float value only if necessary
	*/
	if (T_IGNORE == type)
	{
		T_setfloat(last,F_null);
	}
	else
	{
		T_setfloat(last,F_atof(str,NO_USE_ALL));

		/*
		**	test new tolerance for sanity
		*/
		if (F_getsign(T_getfloat(last)))
		{
			(void) sprintf(Z_err_buf,
			"%s : negative tolerances don't make any sense\n",str);
			Z_fatal(Z_err_buf);
		}
		/*
		**	check for excessively large relative tolerances
		*/
		if ((T_RELATIVE == type) &&
			 (F_floatcmp(T_getfloat(last),
				     F_atof("2.0",USE_ALL)) > 0))
		{
			(void) sprintf(Z_err_buf,
	"%s : relative tolerances greater than 2 don't make any sense\n",str);
			Z_fatal(Z_err_buf);
		}
	}
}
