/*
   rp

   Running Performance Predictor
   by Joseph M. Knapp

*/

#include <stdio.h>
#include <math.h>

#define MAXEVENTS 50   /* maximum # of events to analyze */
#define MAXSTRING 15   /* maximum length of time string */
#define MAXUSTR 20     /* maximum length of distance-unit strings */
#define MILE 1609      /* meters per mile */
#define MAXU   50      /* maximum number of distance units */
#define UNK -1         /* unknown string flag */
#define AMB -2         /* ambiguous string flag */
#define LOWCORR .90    /* low-correlation message threshold */
#define MAXV0  12      /* maximum v0 before error warning */
#define MINV1  -4      /* minimum v1 before error warning */
#define MAXLINE 80     /* maximum line length in event file */
#define TRUE   1
#define FALSE  0

/* this structure will be used to hold the unit/value pairs */
struct ascfloat {
   char string[MAXUSTR] ;  /* ascii representation of unit */
   float value ;           /* value of unit (meters) */
   } ;

/* this structure will hold an event read in from the event file */
struct event {
   float s ;   /* distance of event */
   float t ;   /* time of event */
   } ;

/* this structure will hold the predicted performances */
struct pp {
   float logs ; /* log10 of prediction distance */
   float v ;    /* predicted average speed */
   } ;

/* uval() - returns the  value of a distance-unit string */
float uval(tunit,utypes,no)
char tunit[] ;              /* string containing test unit specification */
struct ascfloat utypes[] ;  /* .string, .value pairs */
int no ;                    /* number of pairs in structure */
{
   int uind ; /* index into utypes structure */

   uind = strmtch(tunit,utypes,no) ; /* returns index of match or err code */
   if (uind == UNK)
   {
      fprintf(stderr,"distance-file error: '%s' unknown\n",tunit) ;
      exit(0) ;
   }
   else if (uind == AMB)
   {
      fprintf(stderr,"distance-file error: '%s' ambiguous\n",tunit) ;
      exit(0) ;
   }
   else return(utypes[uind].value) ;  /* successful match, return value */
}

/* realtime() take a time string (e.g. 8:20:13) and returns the value in sec */
double realtime(str)
char *str ;  /* time string */
{
   char *sptr ;              /* pointer into substring */
   char substr[MAXSTRING] ;  /* substring */
   static int level ;        /* 0=sec 1=min 2=hr */
   static float cumtime ;    /* cumulative time */
   double realtime() ;       /* recursive */
   float tfield ;            /* value of time field */

   sptr = substr ;  /* point to substring (now empty) */

   level = 0 ;
   cumtime = 0 ;

   /* copy time string into substr[] until ':' or end of string */
   while ((*sptr = *str) != '\0' &&
           *str !=  ':' ) 
   {
      sptr++ ;
      str++ ;
   }

   /* if now pointing to ':' ther are more fields, call again */
   if (*sptr == ':') cumtime = realtime(++str) ;

   /* bottomed out, ripple back */
   *sptr = '\0' ;
   if (sscanf(substr,"%f",&tfield))
   {
      return(cumtime + tfield * pow((double)60,(double)(level++))) ;
   }
   else
   {
      fprintf(stderr,"event-file error: bad time string\n") ;
      exit(0) ;
   }
}

/* return TRUE if line is not a comment */
int comf(tline)
char *tline ;
{
   char ch ;

   sscanf(tline," %c ",&ch) ;

   if (ch == '*') return(FALSE) ;
      else return(TRUE) ;
}

/* return TRUE if line is not all whitespace */
int notwhite(tline)
char *tline ;
{
   int wflg ;
   int ich ;

   wflg = FALSE ;
   for (ich = 0 ; ich < strlen(tline) ; ich++)
      if (!isspace(*(tline+ich))) wflg = TRUE ;
   return(wflg) ;
}

usage()
{
      printf("\nusage:\n") ;
      printf("rp [-q|-12][-d <dist-file>][-u <unit-file>] [<event-file>]\n") ;

      printf("    where:\n") ;
      printf("       <events> is a file of events:<distance><time> pairs\n") ;
      printf("           e.g., 10 k  36:16   * this is a comment\n") ;
      printf("       <unit-file> is a file of known distance units\n") ;
      printf("           where a unit is a <string> <meters> pair\n") ;
      printf("           e.g., miles 1609\n") ;
      printf("           (default file: dunits)\n") ;
      printf("       <dist-file> is a file of prediction distances\n") ;
      printf("           where a distance is a <number> <unit> pair\n") ;
      printf("           e.g., 0.5 marathon\n") ;
      printf("           (default file: dlist)\n") ;
      printf("       -12 -> simulate a 12-minute fitness test\n") ;
      printf("       -q  -> print list of runs of same quality as a\n") ;
      printf("              specified standard run\n\n") ;
      printf("\n   rp without args reads events from keyboard\n") ;
      exit(0) ;
}

main(argc,argv)
int argc ;
char *argv[] ;
{

   FILE *fp, *fopen() ;

   struct event events[MAXEVENTS] ;    /* distance/time pairs */
   struct pp perfline[MAXEVENTS] ;     /* speed/log-distance pairs */

   float vbar ;                  /* average speed of events */
   float v0 ;
   float sbar ;                  /* average ditance of events */
   char time[MAXSTRING] ;        /* time string xx:xx:xx.x */
   char unit[MAXSTRING] ;        /* distance unit */
   float scalar ;                /* scalar distance */
   int eventno ;                 /* event counter */
   int ev ;
   int stdineof ;
   int nunits ;
   int option, optind ;
   int age, isex ;
   char sex[10] ;
   float cvar ;
   float var1 ;
   float var2 ;
   float m12, m21 ;
   float b12, b21 ;
   float rho ;
   float s, v, t ;
   float smx, smn ;

   char ufile[50], dfile[50], pfile[50] ;
   char evline[MAXLINE] ;

   float uval() ;
   double realtime() ;

   struct ascfloat utypes[MAXU] ;

   sprintf(ufile,"%s","dunits") ;
   sprintf(dfile,"%s","dlist") ;

   /* process command-line options */
   option = 0 ;
   for (optind = 1 ; optind < argc-1 ; optind++) 
   {
      if (strcmp(argv[optind],"-12" ) == 0) option = 1 ;
      else if (strcmp(argv[optind],"-q" ) == 0) option = 2 ;
      else if (strcmp(argv[optind],"-u") == 0)
         sprintf(ufile,"%s",argv[++optind]) ;
      else if (strcmp(argv[optind],"-d") == 0)
         sprintf(dfile,"%s",argv[++optind]) ;
      else if (strcmp(argv[optind],"-h") == 0)
         usage() ;

      else
      {
         printf("unknown option '%s'\n",argv[optind]) ;
         usage() ;
         exit(0) ;
      }
   }

   nunits = uload(ufile,utypes) ; /* load unit file into 'utypes' struct */

   /* get fp of event file */
   if (argc > 1)
   {
      sprintf(pfile,"%s",argv[argc-1]) ;
      fp = fopen(pfile,"r") ;
      if (fp == NULL)
      {
         fprintf(stderr,"event-file error: can't open '%s'\n",pfile) ;
         exit(0) ;
      }
   }
   else
   {
      fp=stdin ;  /* only one arg, use stdin */
      sprintf(pfile,"%s","stdin") ;
   }

   /* get events and store them in 'events' struct */
   eventno = 0 ;
   stdineof = 1 ;  /* will go to 0 on empty line from stdin */
   if (fp == stdin)
      printf("enter events: <distance> <time> , end with empty line\n") ;
   while ((fp != stdin || stdineof) && fgets(evline,MAXLINE,fp) != NULL)
   {
      /* skip if comment */
      if(comf(evline))
      {
         if (*evline != '\n')  /* only do if length > 0 */
         {
            /* read event line and check for scanf format error */
            if (notwhite(evline))  /* is not all whitespace? */
            {
               if (sscanf(evline," %f %s %s ",&scalar,unit,time))
               {
                  events[eventno].s = scalar * uval(unit,utypes,nunits) ;
                  events[eventno].t = realtime(time) ;
                  eventno++ ;
               }
               else
               {
                  fprintf(stderr,"event-file error: field 1 not a number\n") ;
                  exit(0) ;
               }
            }
         }
         else stdineof = 0 ;  /* if length = 0 terminate stdin */
      }
   }
   if (fp != stdin) fclose(fp) ;

   if (eventno < 2)
   {
      fprintf(stderr,"event-file error: not enough events\n") ;
      exit(0) ;
   }

   smx = 0 ;
   smn = 1e6 ;
   for (ev = 0 ; ev < eventno ; ev++)
   {
      s = events[ev].s ;
      if (s < smn) smn = s ;
      if (s > smx) smx = s ;
   }
   if (smx - smn == 0)
   {
      fprintf(stderr,"event-file error: all events are same distance\n") ;
      exit(0) ;
   }

   for (ev = 0; ev < eventno; ev++)
   {
      perfline[ev].v = events[ev].s/events[ev].t ;
      perfline[ev].logs = log10(events[ev].s) ;
   }

   vbar = 0 ;
   sbar = 0 ;
   for (ev = 0 ; ev < eventno ; ev++)
   {
      vbar += perfline[ev].v ;
      sbar += perfline[ev].logs ;
   }
   vbar = vbar/eventno ;
   sbar = sbar/eventno ;

   cvar = 0 ;
   for (ev = 0 ; ev < eventno ; ev++)
      cvar += (perfline[ev].v - vbar) * (perfline[ev].logs - sbar) ;
   cvar = cvar/(eventno - 1) ;

   var1 = 0 ;
   var2 = 0 ;
   for (ev = 0 ; ev < eventno ; ev++)
   {
      var1 += (perfline[ev].logs - sbar) * (perfline[ev].logs - sbar) ;
      var2 += (perfline[ev].v - vbar) * (perfline[ev].v - vbar) ;
   }
   var1 = var1/(eventno - 1) ;
   var2 = var2/(eventno - 1) ;

   m12 = var2/cvar ;
   b12 = vbar - m12 * sbar ;
   m21 = cvar/var1 ;
   b21 = vbar - m21 * sbar ;

   rho = cvar/(sqrt(var1)*sqrt(var2)) ;

   switch(option) {
   case 0:
      grid(b21,m21,dfile,utypes,nunits,rho,pfile,eventno) ;
      break ;

   case 1:
      printf("\n>>> 12-Minute Fitness Test (after Dr. Kenneth Cooper) <<<\n") ;
      printf("\n") ;

      do
      {
         printf("enter sex (m/f): ") ;
         scanf("%s",sex) ;
      }
      while (sex[0] != 'm' && sex[0] !='f') ;

      if (sex[0] == 'm') isex=0 ;
         else isex=1 ;

      do
      {
         printf("input age: ") ;
         scanf("%d",&age) ;
      }
      while (age < 1 || age > 99) ;

      test12(b21,m21,isex,age) ;
      break  ;

   case 2:
      printf("Input standard run <distance> <time>: ") ;
      scanf(" %f %s %s ", &scalar, unit, time) ;
      t = realtime(time) ;
      s = scalar * uval(unit,utypes,nunits) ;
      v = s/t ;
      v0 = b21 + m21*log10(s) - v ;
      grid(b21-v0,m21,dfile,utypes,nunits,rho,pfile,eventno) ;
   }
}

grid(b,m,dfile,utypes,nunits,rho,pfile,no)
char dfile[] ;
struct ascfloat utypes[] ;
int nunits ;
float b,m,rho ;
char pfile[] ;
int no ;
{
   FILE *fp, *fopen() ;
   float s,v,t ;
   float scalar ;
   char pace[20],
        time[20] ;
   char unit[20] ;
   float uval() ;
   int scanres ;

   fp = fopen(dfile,"r") ;  /* open distance-list file */
   if (fp == NULL)
   {
      fprintf(stderr,"distance-file error: can't open '%s'\n",dfile) ;
      exit(0) ;
   }

   printf("\nanalysis of file '%s'  (%d events):\n",pfile,no) ;
   printf("\nmodel equation: v =%5.2f%6.2f*log10(s)",b,m) ;
   if (no > 2) printf("   correlation ~ %2.0f\n",fabs(rho*100.)) ;
      else printf("\n") ;

   if ( b > MAXV0 || m > 0 || m < MINV1 )
      printf("probable bogus model -- unusual parameters\n") ;

   if (fabs(rho) < LOWCORR)
      printf("low correlation\n") ;

   printf("\n") ;

   printf("%-19s %10s %10s %10s\n"," ",
          "time","speed","pace") ;
   printf("%-19s %10s %10s %10s\n","distance",
          "hh:mm:ss","meters/s","min/mile") ;
   printf("%-19s %10s %10s %10s\n","-----------------",
          "--------","--------","--------") ;
   while ((scanres = fscanf(fp," %f %s ",&scalar,unit)) != EOF )
     {
      if (!scanres)
      {
         fprintf(stderr,"distance-file error: unrecognizable distance\n") ;
         exit(0) ;
      }

      s = scalar * uval(unit,utypes,nunits) ;
      v = b + m*log10(s) ;
      t = s/v ;

      tstr(MILE/v,pace) ;
      tstr(t,time) ;

      if (fmod(scalar,1.0) > 0)
      printf("%6.1f %-12s %10s %10.2f %10s\n",
             scalar,unit,time,v,pace) ;
      else
      printf("%6.0f %-12s %10s %10.2f %10s\n",
             scalar,unit,time,v,pace) ;
       
      }
   printf("\n") ;
   fclose(fp) ;

}

tstr(t,str)
float t ;
char *str ;
{
   int h,m,s ;
   int it ;
   it = t ;
   h = it / 3600 ;
   m = (it/60)-h*60 ;
   s = it - h*3600 - m*60 ;

   if (h>0)
      sprintf(str,"%2d:%02d:%02d",h,m,s) ;
   else {
      if (m>0)
         sprintf(str,"%2d:%02d",m,s) ;
      else
      sprintf(str,"%2d",s) ;
      }
}

/* return position of matching string or UNK (unknown) or AMB (ambiguous) */
strmtch(ustr,utypes,no)
char *ustr ; /* test string */
struct ascfloat utypes[] ; /* reference unit strings (& values) */
int no ; /* number of reference strings */
{
   int m, nmatch ;
   int word, len, imtch ;

   nmatch = 0 ;
   word = 0 ;
   len = strlen(ustr) ;

   do
   {
      m = strncmp(ustr,utypes[word++].string,len) ;
      if (m == 0)
      {
         nmatch++ ;
         imtch = word - 1 ;
      }
   }
   while (word < no) ;

   if (nmatch > 1) word = AMB ;
   else if (nmatch == 0) word = UNK ;
   else word = imtch ;

   return(word) ;
}


int uload(ufile,utypes)
char ufile[] ; /* file containing <string> <meters> pairs */
struct ascfloat utypes[] ; /* to contain those pairs */
{
   int i ;
   int scanres ;
   FILE *fp, *fopen() ;

   fp = fopen(ufile,"r") ;
   if (fp == NULL)
   {
      fprintf(stderr,"unit-file error: can't open '%s'\n",ufile) ;
      exit(0) ;
   }

   i = 0 ;
   while ((scanres=fscanf(fp," %s %f ",utypes[i].string,&utypes[i].value))!=EOF)
   {
      i++ ;
      if (!scanres)
      {
         fprintf(stderr,"unit-file error: illegal unit definition \n") ;
         exit(0) ;
      }
   }


   fclose(fp) ;

   if (i == 0)
   {
      fprintf(stderr,"unit-file error: no units recognized in '%s'\n",ufile) ;
      exit(0) ;
   }
   return(i) ;
}

test12(v0,v1,sex,age)
float v0 ;     /* one-meter speed */
float v1 ;   /* aerobic factor */
int sex ;    /* 0=male 1=female */
int age ;      /* years old */
{
   static float mu30[6] = { 0.00,1.00, 1.24, 1.49, 1.74, 3.5 } ;
   static float mu40[6] = { 0.00,0.95, 1.14, 1.39, 1.64, 3.5 } ;
   static float mu50[6] = { 0.00,0.85, 1.04, 1.29, 1.54, 3.5 } ;
   static float mu99[6] = { 0.00,0.80, 0.99, 1.24, 1.49, 3.5 } ;
   static float wu30[6] = { 0.00,0.95, 1.14, 1.34, 1.64, 3.5 } ;
   static float wu40[6] = { 0.00,0.85, 1.04, 1.24, 1.54, 3.5 } ;
   static float wu50[6] = { 0.00,0.75, 0.94, 1.14, 1.44, 3.5 } ;
   static float wu99[6] = { 0.00,0.65, 0.84, 1.04, 1.34, 3.5 } ;
   static float *man[4] = { mu30, mu40, mu50, mu99 } ;
   static float *woman[4] = { wu30, wu40, wu50, wu99 } ;

   static float vo2tab[6] = { 0.00,25.0,33.7,42.5,51.5,105.0 } ;
   static float stab[6] =  { 0,1.0,1.25,1.50,1.75,3.5 } ;

   int ageind ;
   int level ;
   int vind ;
   float minv,maxv ;
   float s ;
   float maxvo2, x ;

   level = 0 ;

   ageind = age/10 - 2 ;
   if (ageind < 0 ) ageind = 0 ;

   /* calculate 12-minute distance in miles */
   s = 160 ;  /* 160 meters = 0.1 mile */
   while (s/(v0+v1*log10(s)) < 720 )
      s = s + 160 ;

   while (s/(v0 + v1*log10(s)) > 720 )
      s = s - 16 ;

   /* convert meters to miles */
   s = s/(MILE) ;

   /* find fitness level (1-5) */
   if (sex == 0 ) /* woman */
      while (woman[ageind][level++] < s) ;
   else
      while (man[ageind][level++] < s) ;

   level-- ; /* correct level */
   /* interpolate to find max V02 */
   vind = 0 ;
   while(stab[vind] < s ) vind++;

   vind-- ;

   minv = vo2tab[vind] ;
   maxv = vo2tab[vind + 1] ;

   x = (s - stab[vind])/(stab[vind+1]-stab[vind]) ;

   maxvo2 = minv + x*(maxv-minv) ;


   printf("\nIn 12 minutes, you could run%5.2f miles.\n\n",s) ;
   printf("Of very poor, poor, fair, good, and excellent,\n") ;
   printf("your fitness level is " ) ; 
   switch(level) {
   case 1:
      printf( "very poor.\n\n") ;
      break ;
   case 2:
      printf( "poor.\n\n" );
      break ;
   case 3:
      printf( "fair.\n\n" );
      break ;
   case 4:
      printf( "good.\n\n" );
      break ;
   case 5:
      printf( "excellent.\n\n") ;
      break ;
   }
   printf("Your estimated maximum oxygen uptake (VO2max)\n") ; 
   printf("is%5.1f ml/kg/min.\n\n", maxvo2) ;
}
