/* 
   units.c   Copyright (c) 1993 by Adrian Mariano

   This program can be freely distributed as long as no money is
   charged for it. 
*/


#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <ctype.h>

#if defined(__SASC_650) && defined(_AMIGA)
#include <dos.h>
char __stdiowin[] = "CON:0/0/640/200/Units";
#endif

#define VERSION "$VER: units 1.0 (14.7.93)"

#ifndef UNITSFILE
#define UNITSFILE "units.lib"
#endif

#define MAXUNITS 1000
#define MAXPREFIXES 50

#define MAXSUBUNITS 500

#define PRIMITIVECHAR '!'

/* prototypes as desired by SAS/C 6.51 for Amiga (Ron Charlton) */
#include "units_protos.h"
#include "getopt_protos.h"

char *powerstring="^";

struct {
   char *uname;
   char *uval;
} unittable[MAXUNITS];

struct unittype {
   char *numerator[MAXSUBUNITS];
   char *denominator[MAXSUBUNITS];
   double factor;
};

struct {
   char *prefixname;
   char *prefixval;
} prefixtable[MAXPREFIXES];


char *NULLUNIT="";

int unitcount;
int prefixcount;


char *dupstr(char *str)
{
  char *ret;
  ret=malloc(strlen(str)+1);
  if (!ret) {
    fprintf(stderr,"Memory allocation error\n");
    exit(3);
  }
  strcpy(ret,str);
  return(ret);
} 


void readerror(int linenum)
{
  fprintf(stderr,"Error in units file '%s' line %d\n",UNITSFILE,linenum);
}


void readunits(char *userfile)
{
  FILE *unitfile;
  char line[80],*lineptr;
  int len,linenum,i;

  unitcount=0;
  linenum=0;

  if (userfile){
    unitfile=fopen(userfile,"rt");
    if (!unitfile) {
      fprintf(stderr,"Unable to open units file '%s'\n",userfile);
      exit(1);
    }
  } else {
    unitfile=fopen(UNITSFILE,"rt");
    if (!unitfile) { 
      char *direc,*env;
      char filename[1000];
      char separator[2];
      env=getenv("PATH");
      if (env){
        if (strchr(env,';')) strcpy(separator,";");
        else strcpy(separator,":");
        direc=strtok(env,separator);
        while(direc){
          strcpy(filename,"");
          strncat(filename,direc,999);
          strncat(filename,"/",999-strlen(filename));
          strncat(filename,UNITSFILE,999-strlen(filename));
          unitfile=fopen(filename,"rt");
          if (unitfile) break;
          direc=strtok(NULL,separator);
        }
      }
      if (!unitfile){
        fprintf(stderr,"Can't find units file '%s'\n",UNITSFILE);
        exit(1);
      }
    }
  }
  while (!feof(unitfile)){
    if (!fgets(line,79,unitfile)) break;
    linenum++;
    lineptr=line;
    if (*lineptr=='/') continue;
    lineptr+=strspn(lineptr," \n\t");
    len = strcspn(lineptr, " \n\t");
    lineptr[len]=0;
    if (!strlen(lineptr)) continue;
    if (lineptr[strlen(lineptr)-1]=='-'){   /* it's a prefix */
      if (prefixcount==MAXPREFIXES) {
        fprintf(stderr,"Memory for prefixes exceeded in line %d\n",linenum);
        continue;
      }
      lineptr[strlen(lineptr)-1]=0;
      prefixtable[prefixcount].prefixname=dupstr(lineptr);
      for(i=0;i<prefixcount;i++) 
        if (!strcmp(prefixtable[i].prefixname,lineptr)){
        fprintf(stderr,"Redefinition of prefix '%s' on line %d ignored\n",
            lineptr,linenum);
	continue;
      }
      lineptr+=len+1;
      if (!strlen(lineptr)) { readerror(linenum); continue;}
      lineptr+=strspn(lineptr," \n\t");
      len = strcspn(lineptr, "\n\t");
      lineptr[len]=0;
      prefixtable[prefixcount++].prefixval=dupstr(lineptr);
    } else {  /* it's not a prefix */
      if (unitcount==MAXUNITS) {
        fprintf(stderr,"Memory for units exceeded in line %d\n",linenum);
        continue;
      }
      unittable[unitcount].uname=dupstr(lineptr);
      for(i=0;i<unitcount;i++) if (!strcmp(unittable[i].uname,lineptr)){
        fprintf(stderr,"Redefinition of unit '%s' on line %d ignored\n",
            lineptr,linenum);
	continue;
      }
      lineptr+=len+1;
      lineptr+=strspn(lineptr," \n\t");
      if (!strlen(lineptr)) { readerror(linenum); continue;}
      len = strcspn(lineptr, "\n\t");
      lineptr[len]=0;
      unittable[unitcount++].uval=dupstr(lineptr);
    }
  }
  fclose(unitfile);
}

void initializeunit(struct unittype *theunit)
{
  theunit->factor=1.0;
  theunit->numerator[0]=theunit->denominator[0]=NULL;
}


int addsubunit(char *product[], char *toadd)
{
  char **ptr;

  for(ptr=product;*ptr && *ptr!=NULLUNIT;ptr++);
  if (ptr>=product+MAXSUBUNITS){
    fprintf(stderr,"Memory overflow in unit reduction\n");
    return 1;
  }
  if (!*ptr) *(ptr+1)=0;
  *ptr=dupstr(toadd);
  return 0;
}


void showunit(struct unittype *theunit)
{
  char **ptr;
  int printedslash;
  int counter=1;

  printf("\t%.8g",theunit->factor);
  for(ptr=theunit->numerator;*ptr;ptr++){
    if (ptr>theunit->numerator && **ptr &&
       !strcmp(*ptr,*(ptr-1))) counter++;
      else {
        if (counter>1) printf("%s%d",powerstring,counter);
        if (**ptr) printf(" %s",*ptr);
	counter=1;
      }
  }
  if (counter>1) printf("%s%d",powerstring,counter);
  counter=1;
  printedslash=0;
  for(ptr=theunit->denominator;*ptr;ptr++){
    if (ptr>theunit->denominator && **ptr && 
         !strcmp(*ptr,*(ptr-1))) counter++;
      else {
        if (counter>1) printf("%s%d",powerstring,counter);
        if (**ptr) { 
	  if (!printedslash) printf(" /");
	  printedslash=1;
          printf(" %s",*ptr);
	}
	counter=1;
      }
  }
  if (counter>1) printf("%s%d",powerstring,counter);
  printf("\n");
}


void zeroerror()
{
   fprintf(stderr,"Unit reduces to zero\n");
}   

/* 
   Adds the specified string to the unit. 
   Flip is 0 for adding normally, 1 for adding reciprocal.
 
   Returns 0 for successful addition, nonzero on error.
*/

int addunit(struct unittype *theunit,char *toadd,int flip)
{
  char *scratch,*savescr;
  char *item;
  char *divider,*slash;
  int doingtop;

  savescr=scratch=dupstr(toadd);
  for(slash=scratch+1;*slash;slash++) 
    if (*slash=='-' && 
      (tolower(*(slash-1))!='e' || !strchr(".0123456789",*(slash+1))))
      *slash=' ';
  slash=strchr(scratch,'/');
  if (slash) *slash=0;
  doingtop=1;
  do{
  item=strtok(scratch," *\t\n/");
  while(item){
    if (strchr("0123456789.",*item)){  /* item is a number */
      double num;

      divider=strchr(item,'|');
      if (divider){
	*divider=0;
	num=atof(item);
	if (!num) {
          zeroerror();
          return 1;
        }
	if (doingtop^flip) theunit->factor *= num;
	     else  theunit->factor /=num;
	num=atof(divider+1);
	if (!num)  {
          zeroerror();
          return 1;
        }
	if (doingtop^flip) theunit->factor /= num;
	 else theunit->factor *= num;
      } else {
	num=atof(item);
	if (!num)  {
          zeroerror();
          return 1;
        }
	if (doingtop^flip) theunit->factor *= num;
	else theunit->factor /= num;

      }
    } else {                         /* item is not a number */
      int repeat=1;
      if (strchr("23456789",item[strlen(item)-1])){
	repeat=item[strlen(item)-1]-'0';
	item[strlen(item)-1]=0;
      }
      for(;repeat;repeat--)
	if (addsubunit(doingtop^flip?theunit->numerator:theunit->denominator,item))
  return 1;
    }
    item=strtok(NULL," *\t/\n");
  }
  doingtop--;
  if (slash){
    scratch=slash+1;
  } else doingtop--;
  }while (doingtop>=0 );
  free(savescr);
  return 0;
}


int compare(const void *item1, const void *item2)
{
  return strcmp(*(char **) item1, * (char **) item2);
}


void sortunit(struct unittype *theunit)
{
  char **ptr;
  int count;

  for(count=0,ptr=theunit->numerator;*ptr;ptr++,count++);
  qsort(theunit->numerator, count, sizeof(char *), compare);
  for(count=0,ptr=theunit->denominator;*ptr;ptr++,count++);
  qsort(theunit->denominator, count, sizeof(char *), compare);
}


void cancelunit(struct unittype *theunit)
{
  char **den,**num;
  int comp;

  den=theunit->denominator;
  num=theunit->numerator;

  while(*num && *den){
    comp=strcmp(*den,*num);
    if (!comp) {
/*      if (*den!=NULLUNIT) free(*den);
      if (*num!=NULLUNIT) free(*num);*/
      *den++=NULLUNIT;
      *num++=NULLUNIT;
    }
    else if (comp<0) den++;
    else num++;
  }
}




/*
   Looks up the definition for the specified unit.  
   Returns a pointer to the definition or a null pointer
   if the specified unit does not appear in the units table.
*/

static char buffer[100];  /* buffer for lookupunit answers with prefixes */

char *lookupunit(char *unit)
{
  int i;
  char *copy;

  for(i=0;i<unitcount;i++){
    if (!strcmp(unittable[i].uname,unit)) return unittable[i].uval;
  }

  if (unit[strlen(unit)-1]=='^') {
    copy=dupstr(unit);
    copy[strlen(copy)-1]=0;
    for(i=0;i<unitcount;i++){
      if (!strcmp(unittable[i].uname,copy)) {
        strcpy(buffer,copy);
        free(copy);
        return buffer;
      }
    } 
    free(copy);
  }
  if (unit[strlen(unit)-1]=='s') {
    copy=dupstr(unit);
    copy[strlen(copy)-1]=0;
    for(i=0;i<unitcount;i++){
      if (!strcmp(unittable[i].uname,copy)) {
        strcpy(buffer,copy);
        free(copy);
        return buffer;
      }
    }
    if (copy[strlen(copy)-1]=='e') {
      copy[strlen(copy)-1]=0;
      for(i=0;i<unitcount;i++){
        if (!strcmp(unittable[i].uname,copy)){
          strcpy(buffer,copy);
          free(copy);
          return buffer;
        }
      }
    }
    free(copy);
  }
  for(i=0;i<prefixcount;i++){
    if (!strncmp(prefixtable[i].prefixname,unit,
        strlen(prefixtable[i].prefixname))) {
      unit += strlen(prefixtable[i].prefixname);
      if (!strlen(unit) || lookupunit(unit)){
        strcpy(buffer,prefixtable[i].prefixval);
        strcat(buffer," ");
        strcat(buffer,unit);
        return buffer;
      }
    }
  }
  return 0;
}



/*
   reduces a product of symbolic units to primitive units. 
   The three low bits are used to return flags:

     bit 0 (1) set on if reductions were performed without error.
     bit 1 (2) set on if no reductions are performed.
     bit 2 (4) set on if an unknown unit is discovered.
*/


#define ERROR 4

int reduceproduct(struct unittype *theunit, int flip)
{

  char *toadd;
  char **product;
  int didsomething=2;

  if (flip) product=theunit->denominator;
  else product=theunit->numerator;

  for(;*product;product++){
    
    for(;;){
      if (!strlen(*product)) break;
      toadd=lookupunit(*product);
      if (!toadd) { 
        printf("unknown unit '%s'\n",*product);
        return ERROR;
      }
      if (strchr(toadd,PRIMITIVECHAR)) break;
      didsomething=1;
      if (*product!=NULLUNIT) {
        free (*product);
        *product=NULLUNIT;
      }
      if (addunit(theunit, toadd, flip)) return ERROR;
    }
  }
  return didsomething;
}


/* 
   Reduces numerator and denominator of the specified unit.
   Returns 0 on success, or 1 on unknown unit error.
*/

int reduceunit(struct unittype *theunit)
{
  int ret;
  ret=1;
  while(ret & 1){
    ret = reduceproduct(theunit,0) | reduceproduct(theunit,1);
    if (ret & 4) return 1;
  }
  return 0;
}


int compareproducts(char **one,char **two)
{
  while(*one || *two){
    if (!*one && *two!=NULLUNIT) return 1;
    if (!*two && *one!=NULLUNIT) return 1;
    if (*one==NULLUNIT) one++;
    else if (*two==NULLUNIT) two++;
    else if (strcmp(*one,*two)) return 1;
    else one++,two++;
  }
  return 0;
}


/* Return zero if units are compatible, nonzero otherwise */

int compareunits(struct unittype *first, struct unittype *second)
{
  return 
    compareproducts(first->numerator,second->numerator) ||
    compareproducts(first->denominator,second->denominator);
}


int completereduce(struct unittype *unit)
{
  if (reduceunit(unit)) return 1;
  sortunit(unit);
  cancelunit(unit);
  return 0;
}


void showanswer(struct unittype *have, struct unittype *want)
{
  if (compareunits(have,want)) {
    printf("conformability error\n");
    showunit(have);
    showunit(want);
  }
  else printf("\t* %.8g\n\t/ %.8g\n",have->factor/want->factor,
                           want->factor/have->factor);
}


void usage()
{
  fprintf(stderr,"\nunits [-f unitsfile] [-q] [-v] [from-unit to-unit]\n");
  fprintf(stderr,"\n    -f specify units file\n");
  fprintf(stderr,"    -q supress prompting (quiet)\n");
  fprintf(stderr,"    -v print version number\n");
  exit(3);
}


void main(int argc, char **argv){

  struct unittype have,want;
  char havestr[81],wantstr[81];
  char optchar;
  char *userfile=0;
  int quiet=0;

  extern char *optarg;
  extern int optind;

#if defined(__SASC_650) && defined(_AMIGA)
  if(argc == 0) {
      /* Invoked from WorkBench... use WorkBench arguments
       * Make sure <dos.h> was included earlier in order to
       * declare _WBArgc and _WBArgv.                      
       */
      argc = _WBArgc;
      argv = _WBArgv;
  }
#endif

  while (EOF != (optchar = getopt(argc,argv,"vqf:"))) {
    switch(optchar) {
      case 'f':
        userfile=optarg;
        break;
      case 'q':
        quiet=1;
        break;
      case 'v':
        fprintf(stderr,"\n  %s  Copyright (c) 1993 by Adrian Mariano\n",VERSION+6);
        fprintf(stderr,"                    This program may be freely distributed\n");
        usage();
      default:
        usage();
        break;
     }
  }
    
  if (optind != argc-2 && optind!=argc) usage();

  readunits(userfile);

  if (optind==argc-2){
    strcpy(havestr,argv[optind]);
    strcpy(wantstr,argv[optind+1]);
    initializeunit(&have);
    addunit(&have,havestr,0);
    completereduce(&have);
    initializeunit(&want);
    addunit(&want,wantstr,0);
    completereduce(&want);
    showanswer(&have,&want);
  } else {
    if (!quiet) printf("%d units, %d prefixes\n\n",unitcount,prefixcount);
    for(;;){
      do{
        initializeunit(&have);
        if (!quiet) printf("You have: ");
        if (!fgets(havestr,80,stdin)) { if (!quiet);putchar('\n');exit(0);}
        if (!strnicmp(havestr, "quit", 4)){ if (!quiet);putchar('\n');exit(0);}
      } while(addunit(&have,havestr,0) || completereduce(&have));
      do{
        initializeunit(&want);
        if (!quiet) printf("You want: ");
        if (!fgets(wantstr,80,stdin)) {if (!quiet) putchar('\n');exit(0);}
        if (!strnicmp(wantstr, "quit", 4)){if (!quiet);putchar('\n');exit(0);}
      } while(addunit(&want,wantstr,0) || completereduce(&want));
      showanswer(&have,&want);
    }
  }
}




