/*  Convert - unit conversion module
    This module handles unit conversion & manipulation.

    Copyright (C) 1990  Marty White

    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, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include <stdio.h>
#include <math.h>
#include "compiler.h"

#ifdef __STDHEADERS__
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#endif

#include "physcalc.h"
#include "physdecl.h"

#ifdef __PROTOTYPES__
LOCAL void getline(FILE *fp, char *buf);
LOCAL int evalxpon(char *s);
LOCAL void fixplural(char *s);
LOCAL int output_dims(FILE *fp, int const d[MAXDIM]);
LOCAL void putunit(char *s, int d, int p);
#endif

#define RESERVESIZE 10000	/* size of buffer for loaded data */
#define PREFIXES	25	/* Number of prefixes */

						/* Prefixes are private to this file */
LOCAL struct {			/* Prefixes data */
	char *name;
	double scale;
} prefix[PREFIXES] = {
	"EXA",	1E18,
	"PETA",	1E15,
	"TERA",	1E12,
	"GIGA",	1E9,
	"MEGA",	1E6,
	"MYRIA",1E4,
	"KILO",	1000,
	"HECTO",100,
	"DECA",	10,
	"DECI",	0.1,
	"CENTI",0.01,
	"MILLI",0.001,
	"MICRO",1E-6,
	"NANO",	1E-9,
	"PICO",	1E-12,
	"FEMTO",1E-15,
	"ATTO",	1E-18,
	"E",	1E18,		/* Abbreviations (M not used - too ambiguous) */
	"T",	1E12,
	"G",	1E9,
	"K",	1000,
	"C",	0.01,
	"N",	1E-9,
	"F",	1E-15,
	"A",	1E-18
};

LOCAL struct unitstruct {		/* Unit list (private to this file) */
	struct unitstruct *next;
	char *name;
	double factor;					/* conversion factor */
	int dimension[ MAXDIM ];		/* Dimensionality */
} *units=NULL, *lastunit=NULL;

EXPORT struct dimstruct *dimension_list=NULL,*last_fundamental_dim=NULL,
					*last_compound_dim=NULL;

/*-----Functions-----*/

LOCAL void getline(fp,buf)	/* Get a line from the file & doctor it up */
FILE *fp;
char *buf;
{
	char *c;
	int noerr;

	do {
		noerr = (int) fgets(buf,198,fp);
		c=buf;
		while (*c) {
			if (*c==';') {		/* Chop line off at a semicolon */
				*c='\0';
				break;
			}
			c++;
		}
		trimspc(buf);
	} while (*buf=='\0' && noerr);
#if 0
	if (noerr)
		strupr(buf);
	else
		*buf = '\0';
#endif
}

EXPORT void erasednum(n)		/* erase number & it's dimensions */
DNUM *n;
{
	int i;

	n->num = 1.0;
	for (i=0; i<MAXDIM; i++)
		n->di[i]=0;
}

EXPORT void define_dimension(s)
char const *s;
{	/* Define a dimension contained in string s.  One of 2 kinds:
		Fundamental - just a name
		Compound - a name followed by a definition in terms of previously
			defined dimensions */
	/* Spaces have been trimmed from s */

	static char memerr[]="Out of memory.  Dimension not defined.\n";
	struct dimstruct *newdim;
	char buf[SMALLBUF];
	int j;
	static int fundamental_cnt = 0;

	strupr(s);
	scan_symbol(&s,buf);
	newdim = dimension_list;
	while (newdim) {
		if (!strcmp(buf,newdim->name)) {
			if (echo) printf("Duplicate dimension name.\n");
			return;
		}
		newdim = newdim->next;
	}
	if ( (newdim=malloc(sizeof(struct dimstruct))) == NULL ) {
		printf(memerr);
		return;
	}
	if ( (newdim->name=strdup(buf))==NULL ) {
		printf(memerr);
		free(newdim);
		return;
	}
	skipspc(s);
	if (*s) {	/* This must be a compound dimension definition */
		DNUM n;
		erasednum(&n);
		evaldim(&s,1,&n);
		for (j=0; j<MAXDIM; j++)
			newdim->dimension[j] = n.di[j];
		newdim -> next = NULL;
		if (last_compound_dim)
			last_compound_dim->next = newdim;
		last_compound_dim = newdim;
		if (last_fundamental_dim && !last_fundamental_dim->next)
			last_fundamental_dim->next = newdim;
	} else {	/* This is a simple definition */
		if (fundamental_cnt == MAXDIM) {
			printf("There are too many fundamental dimensions. Dimension not defined.\n");
			free(newdim->name);
			free(newdim);
			return;
		}
		for (j=0; j<MAXDIM; j++)				/* set dimension */
			newdim->dimension[j] = 0;
		newdim->dimension[fundamental_cnt++] = 1;
		if (last_fundamental_dim) {
			newdim->next = last_fundamental_dim->next;
			last_fundamental_dim->next = newdim;
		} else
			newdim->next = NULL;
		last_fundamental_dim = newdim;
	}
	if (!dimension_list)
		dimension_list = newdim;
	if (echo) printf("Dimension defined.\n");
}

EXPORT void define_unit(s)
char const *s;
	/* Define unit as contained in s:
		<name> <conversion factor> <dimension/unit specifier> */
{
	static char memerr[]="Out of memory.  Unit not defined.\n";
	struct unitstruct *newunit;
	char buf[SMALLBUF];
	int j;
	DNUM n;

	strupr(s);
	scan_symbol(&s,buf);
	newunit = units;
	while (newunit) {
		if (!strcmp(buf,newunit->name)) {
			if (echo) printf("Duplicate unit.\n");
			return;
		}
		newunit = newunit->next;
	}
	if ( (newunit=malloc(sizeof(struct unitstruct)))==NULL ) {
		printf(memerr);
		return;
	}
	if ( (newunit->name=strdup(buf))==NULL ) {
		printf(memerr);
		free(newunit);
		return;
	}
	skipspc(s);
	if (! *s) {
		if (echo) printf("Missing conversion factor.\n");
		free(newunit->name);
		free(newunit);
		return;
	}
	erasednum(&n);
	sscanf(s,"%lf%*s",&n.num);			/* read in factor */
    /* n.num = 1.0 / n.num; */  /* Oct-94 */
	while (!isspace(*s))
		s++;
	s++;
	if (! *s) {
		if (echo) printf("Missing dimension specifier.\n");
		free(newunit->name);
		free(newunit);
		return;
	}
	evaldim(&s,1,&n);
    newunit->factor = 1.0 / /* Oct-94 */ n.num;
	for (j=0; j<MAXDIM; j++)
		newunit->dimension[j] = n.di[j];
	newunit->next = NULL;
	if (lastunit) {
		lastunit->next = newunit;
		lastunit = newunit;
	} else
		lastunit = newunit;
	if (!units)
		units = newunit;
	if (echo) printf("Unit defined.\n");
}

EXPORT void load_data(s)
char const *s;
{	/* read in lines from fp and do each as if typed from console */
	FILE *fp;
	char buf[SMALLBUF];
	char *fullpath;
	int oldecho;
#ifdef TRACE
	int oldtrace;
#endif

	fullpath = malloc( strlen( SHAREDIR ) + strlen( s ) + 1 );
	bcopy(SHAREDIR, fullpath, strlen(SHAREDIR));
	(void) strcat(fullpath, s);
	if ((fp = fopen(fullpath,"r"))==NULL) {
		printf("Can't open %s\n",s);
		return;
	}
	oldecho = echo;
#ifdef TRACE
	oldtrace = trace;
	/*trace = FALSE;*/
#endif
	echo = FALSE;
	while (TRUE) {
		getline(fp,buf);
		if (!buf[0])
			break;
		do_cmd(buf);
	}
	echo = oldecho;
#ifdef TRACE
	trace = trace || oldtrace;
#endif
	fclose(fp);
}

/* These pointers are used to mark the end of auto-loaded data, so
that stuff defined since calling remember_old() can be saved
independent of previously loaded information. */

LOCAL struct dimstruct *old_last_fund_dim, *old_last_compound_dim;
LOCAL struct unitstruct *old_lastunit;
LOCAL struct varstruct *old_var;
LOCAL struct ufstruct *old_userfunc;

EXPORT void remember_old()
{	/* Set pointers to the current ends of the 4 data lists - dimensions,
	units, variables, and functions */

	struct varstruct *v;
	struct ufstruct *u;

	old_last_fund_dim = last_fundamental_dim;
	old_last_compound_dim = last_compound_dim;
	old_lastunit = lastunit;
	v = var;
	while (v->next)
		v = v->next;
	old_var = v;
	u = userfunc;
	while (u)
		u = u->next;
	old_userfunc = u;
}

EXPORT void save_data(fname, all)
char const *fname;
int all;	/* if all, then save all data. If not, save only data
				entered after the default data */
{
	FILE *fp;
	struct dimstruct *d;
	struct unitstruct *u;
	int compound = FALSE;

	if ( (fp = fopen(fname,"w"))==NULL ) {
		printf("Can't open %s\n",fname);
		return;
	}

	/* Write Dimensions */
	d = all ? dimension_list : old_last_fund_dim->next;
	while (d) {
		if (d == last_fundamental_dim->next) {
			compound = TRUE;
			if (!all) {
				d = old_last_compound_dim -> next;
				continue;
			}
		}
		fprintf(fp,"\nDIMENSION %s",d->name);
		if (compound)
			output_dims(fp,d->dimension);
		d = d->next;
	}
	/* Write Units */
	u = all ? units : old_lastunit->next;
	while (u) {
		fprintf(fp,"\nUNIT %s %lg",u->name,u->factor);
		output_dims(fp,u->dimension);
		u = u->next;
	}
	fprintf(fp,"\n");
	/* Write Variables & functions */
	output_list(fp,
			all ? var->next : old_var->next,
			(all || !old_userfunc) ? userfunc : old_userfunc,
			TRUE);

	fclose(fp);
}


LOCAL int evalxpon(s)
char *s;
/* Returns integer exponent, & truncates string if there is one */
{
	int p;
	char *t;

	p=1;
	if (t=strchr(s,'^')) {
		sscanf(t+1,"%d",&p);
		*t = '\0';
	}
	return p;
}	/* end evalxpon */

LOCAL void fixplural(s)	/* Change word to singular */
char *s;
{	/* Change the given word to singular (if it is plural). Make
	exceptions for the words "INCHES" and "CALORIES" */
	int i;

	i=strlen(s);

	if ( (i>5) && (strcmp(&s[i-6],"INCHES")==0) ) {
		s[i-2]='\0';
		return;
	}
	if ( (i>3) && (strcmp(&s[i-3],"IES")==0) && strcmp(&s[i-8],"CALORIES")!=0) {
		s[i-3]='Y';
		s[i-2]='\0';
		return;
	}
	if (s[i-1]=='S' && s[i-2]!='S' && i>1)
		s[i-1]='\0';
}	/* end fixplural() */

EXPORT int evaldim(s,io,n)
                /* evaluate a unit or dimension, return error code */
char const **s;	/* Ptr to string ptr of str to eval */
int io;         /* flag for converting to or from given dimension (-1 or 1) */
DNUM *n;        /* adjust n accordingly */

{

/* This function is the guts of unit conversion.  It takes a handle to
a string and tries to determine what kind of unit or dimension was given.
It can evaluate METERS^2 PER SEC or DISTANCE PER TIME, but not a mixed
expression like METERS PER TIME.  When searching for a match to a word,
search for dimensions, then units, then prefixes. "PER" is a special
keyword which reverses whether units are multiplied or divided.
*/

    int q, per;
	char const *s2;

	q = -1;

    /* q is type of expression evaluated:
		0 = a specific unit (like meters),
		1 = a dimension (like distance per time)
	   -1 = an error occured */

    per = 1;                /* PER flag (1 or -1) */
	s2 = *s;

    for (;;) {              /* Outer dimension-processing loop */
        char t[NAMELEN];
        double pf, pwr;

        *s = s2;
		skipspc(*s);
		if (**s=='\0')		/* Check for end-of-string */
			return (q);

		/* Strip dimension off of s into t */
		scan_symbol(&s2,t);
		if (*s2=='^') {
			strcat(t,"^");
			s2++;
			scan_symbol(&s2,t+strlen(t));
		}
		strupr(t);

		if (strncmp(t,"PER",3)==0 /*|| t[0]=='/'*/) {	/* Evaluate 'PER' */
			per = -per;
			continue;
		}

		if ((pwr=evalxpon(t))==0) {				/* Evaluate exponent if any */
			printf("Err: Zero exponent.\n");
			return (-1);
		}

        pf = 1.0;

        for (;;) {                  /* inner prefix-proccessing loop */
            struct dimstruct *d;

            d = dimension_list;     /* search list of dimensions */
			while (d) {
				if (strcmp(t,d->name)==0)
					break;
				d = d->next;
			}
			if (d) {				/* found a dimension */
                int y;

				if (q==0) {
					printf("Err: Unit expected. [d]\n");
					return (-1);
				}
				q=1;
				for (y=0; y<MAXDIM; y++)
					n->di[y] += (int) ( pwr * per * io * d->dimension[y]);
				break;				/* continue with outer while loop */
			} else {
                struct unitstruct *u;
				char t1[NAMELEN]; /* t1 is depluraized version of t */

				strcpy(t1, t);
			    fixplural(t1);

                u = units;          /* search list of units */
				while (u) {
					if ( strcmp(t,u->name)==0 || strcmp(t1, u->name)==0 )
						break;
					u = u->next;
				}
				if (u) {				/* found a unit */
					double z1,z2,z3;
                    int y;

					if (q==1) {
						printf("Err: Dimension expected. [u]\n");
						return (-1);
					}
					q=0;
					z1 = u->factor / pf;
					z2 = pwr * per * io;
					z3 = pow(z1,z2);
					n->num /= z3;
					for (y=0; y<MAXDIM; y++)
						n->di[y] += (int)( pwr * per * io * u->dimension[y] );
					break;	/* continue with outer while loop */
				} else {						/* search list of prefixes */
                    int x;

					for (x=0; x<PREFIXES; x++)
						if ( strncmp(t,prefix[x].name,strlen(prefix[x].name)) ==0)
							break;
					if (x<PREFIXES) {
						if (q==1) {
							printf("Err: Dimension expected. [p]\n");
							return (-1);
						}
						q=0;
						strcpy(t, t + strlen(prefix[x].name));
							/* was midstr(t,strlen(prefix[x].name),strlen(t),t);*/
						pf *= prefix[x].scale;
					} else {	/* not a prefix or a unit */
							return (q);		/* unknown unit/prefix so stop */
					}	/* end else */
				}		/* end else */
			}			/* end else */
        }               /* end inner for */
    }                   /* end outer for */
	return -1;
}						/* end evaldim() */

EXPORT struct dimstruct *dimension_number(i)
int i;
{	/* look up dimension number i (0 is the first one) */
	struct dimstruct *d;

	d = dimension_list;
	while (d) {
		if (!(i--))
			return d;
		d = d->next;
	}
	return NULL;
}

LOCAL int output_dims(fp,d)	/* Output dimension list to a stream */
FILE *fp;
int const d[MAXDIM];
{
	int i,flag,flag2;

	flag = flag2 = FALSE;
	for (i=0; i<MAXDIM; i++) {	/* Display positive powers */
		if (d[i]<0)
			flag = flag2 = TRUE;	/* indicate a negative power exists */
		else if (d[i]>0) {
			fprintf(fp," %s",(dimension_number(i))->name);
			if (d[i]>1)
					fprintf(fp,"^%d",d[i]);
			flag2 = TRUE;
		}
	}
	if (flag) {						/* display negative powers */
		fprintf(fp," PER");
		for (i=0; i<MAXDIM; i++)
			if (d[i]<0) {
				fprintf(fp," %s",(dimension_number(i))->name);
				if (d[i]<(-1))
					fprintf(fp,"^%d",- d[i]);
			}
	}
	return flag2;
}

EXPORT void showdims(n) 	/* Display fundamental dimensions of a unit */
DNUM const *n;
{
		int j,flag,flag2;
		struct dimstruct *d;

		flag2 = output_dims(stdout,n->di);

		/* Display compund unit (if any) */
		if (d = last_fundamental_dim) {
			while (d = d->next) {
				for (j=flag=0; j<MAXDIM; j++)
					if (n->di[j]!=d->dimension[j]) {
						flag=1;
						break;
					}
				if (!flag) {
					printf("  (%s)",d->name);
					break;
				}
			}
		}
		if (!flag2)
			printf("No dimension.");
		printf("\n");
}

LOCAL void putunit(s,d,p)
/* Append the name for a unit of sole dimension d to
	string s (used only by generate_unit) */
char *s;	/* Buffer for name */
int d,p;	/* d = dimension, p = power */
{
	int j,flag;
	char buf[NAMELEN];
	struct unitstruct *u;

	u = units;
	while (u) {
		flag = TRUE;
		for (j=0; j<MAXDIM; j++)
			if (u->dimension[j] != (j==d) ) {
				flag = FALSE;
				break;
			}
		if (flag && u->factor==1.0)
			break;
		u = u->next;
	}
	if (!flag)
		printf("Error in putunit()\n");
	strcat(s, u->name);
	if (p>1) {
		sprintf(buf,"^%d",p);
		strcat(s,buf);
	}
	strcat(s," ");
}

EXPORT void generate_unit(n,s)
	/* Create a unit specifier of the given dimensions */
DNUM const *n;	/* Type of unit to generate */
char *s;	/* String to store results in */
{			/* Will return a null string if there are no dimensions */
	int i,j,flag;
	struct unitstruct *u;

	*s = '\0';
	u = units;
	while (u) {		/* Search for a compound unit that matches */
		flag = TRUE;
		for (j=0; j<MAXDIM; j++)
			if (n->di[j] != u->dimension[j]) {
				flag = FALSE;
				break;
			}
		if (flag && u->factor==1.0) {
			strcpy(s, u->name);
			return;
		}
		u = u->next;
	}
	flag = FALSE;
	for (i=0; i<MAXDIM; i++)	/* Generate a compound unit */
		if (n->di[i]>0)
			putunit(s,i,n->di[i]);
		else
			if (n->di[i]<0)
				flag = TRUE;
	if (flag) {
		strcat(s, "PER ");
		for (i=0; i<MAXDIM; i++)
			if (n->di[i]<0)
				putunit(s,i,- n->di[i]);
	}
}

EXPORT void showunits(n)	/* display all units of type n */
DNUM const *n;
{
	int j,flag;
	struct unitstruct *u;

	u = units;
	while (u) {
		flag=0;
		for (j=0; j<MAXDIM; j++)
			if ( n->di[j] != u->dimension[j] ) {
				flag=1;
				break;
			}
		if (!flag)
			printf("%-20s",u->name);
		u = u->next;
	}
	printf("\n");
}

EXPORT void show_all_units()
	/* dump a list of all dimensions & units to screen */
{
	int j=0;
	struct dimstruct *d;
	struct unitstruct *u;

	printf("Dimensions:\n");
	d = dimension_list;
	while (d) {
		printf("%-20s",d->name);
		if (++j==88) {
			if (pause_for_user()==27)
				break;
			j=0;
		}
		d = d->next;
	}
	j += 8 - (j%4);
	printf("\nUnits:\n");
	u = units;
	while (u) {
		printf("%-20s",u->name);
		if (++j>=88) {		/* Pause for a screenfull */
			if (pause_for_user()==27)
				break;
			j=0;
		}
		u = u->next;
	}
	printf("\n");
}

EXPORT int has_dims(n)
	/* Does a dimensional number actually have dimension? */
DNUM const *n;
{
	int i;

	for (i=0; i<MAXDIM; i++)
		if (n->di[i])
			return TRUE;
	return FALSE;
}

EXPORT int check_equ_dim(a,b)	/* Are a & b the same dimension? */
DNUM const *a,*b;
{
	int i;

	for (i=0; i<MAXDIM; i++)
		if (a->di[i] != b->di[i])
			return FALSE;
	return TRUE;
}

EXPORT void copydnum(d,s)	/* copy the dimensions of s to d */
DNUM *d;
DNUM const *s;
{
	int i;

	d->num = s->num;
	for (i=0; i<MAXDIM; i++)
		d->di[i] = s->di[i];
}

EXPORT void querry(s)	/* Called when user types a '?' */
char const *s;		/* Display dimensions & units as requested */
{
	DNUM n;

	erasednum(&n);
	if (*s) {
		evaldim(&s,1,&n);
		showdims(&n);
		showunits(&n);
	} else
		show_all_units();
}

EXPORT void printanswer(n,s)
	/* Print answer, asking for unit conversion if needed */
NODEP n;
char const *s;
{
/* Use regular expression printer except for dimensioned numbers */

	if (n->type==NNODE) {
		if (has_dims((DNUM *)(n->data))) {
            int i,flag;
            char buf[SMALLBUF],*t;
            DNUM d;

            for (;;) {
                copydnum(&d,&n->data->dmn);
                if (s) {
					strcpy(buf,s);
					s=NULL;
				} else {
					showdims(&d);
					printf("Convert to: ");
					fgets(buf, sizeof(buf), stdin);
					trimspc(buf);
					if (buf[0]=='?') {
						showdims(&d);
						showunits(&d);
						generate_unit(&d,buf);
						printf("Default unit is %s\n",buf);
						continue;
					}
				}
				if (buf[0]=='\0') {			/* use default */
					generate_unit(&d,buf);
					break;
				} else {					/* convert to specified unit */
					t = buf;
					i = evaldim(&t,-1,&d);	/* Evaluates destination unit & ajusts d */
					skipspc(t);
					if (i || *t) {
						printf("Invalid unit specifier.\n");
						continue;
					}
					flag = TRUE;
					for (i=0; i<MAXDIM; i++)
						if (d.di[i]!=0) {
							printf("%s DIMENSION INCOMPATABILITY.\n",(dimension_number(i))->name);
							flag = FALSE;
						}
					if (flag)	/* if conversion succesfull, exit while */
						break;
				}
			}
            printf(/*"Answer: "*/ "%lg %s\n",d.num,buf);
			return;
		}	/* end if has dim's */
	}		/* if NNODE, but no dims, fall thru */
    /*printf("Answer: ");*/     /* Not an NNODE */
	printexpr(n);
	printf("\n");
}


syntax highlighted by Code2HTML, v. 0.9.1