/* *******************************************************************
 *                                                                   *
 * Copyright (c) MIT/JGOFS                                           *
 *                                                                   *
 *                                                                   *
 * File             : nm.c                                           *
 *                                                                   *
 * Purpose          :                                                *         
 *                                                                   *
 * Version Number   : 1.7c                                           *
 *                                                                   *
 * Revision History :                                                *
 *                                                                   *
 * Date                              Developer                       *
 * ----                              ---------                       *
*/

#define NM_VERSION "nm version 1.7c   5 Mar 2010"
/*
 *  5 Mar 10 v 1.7c  		    WJS				     *
 *    Straighten out some naming, etc irregularities		     *
 *	Because of a makefile mod, 1.7a source went into the	     *
 *	  1.7b release.  Accordingly, we're skipping 1.7b as a	     *
 *	  source version.					     *
 *	1.7a comments said 2004, while version said 2005.  Pretty    *
 *	  sure 2005 was correct.  Making a new source release gets   *
 *	  around that problem!					     *
 *    Bug fix:  ioclose_ closed w/o checking that file was ever	     *
 *	opened.  Problem since all error routines call ioclose_	     *
 *	    [Begin 1.7c [sic]]					     *

 *    4 Jun 05 v 1.7a  		    WJS				     *
 *	Need some "void"s & some "putting functions at the top"	     *
 *	    [Begin 1.7a]					     *
 *   13 Jul 04 v 1.7   		    WJS				     *
 *	Account for defgb_utils->utils				     *
 *	defgb.h->utils.h & path_info_routines.h			     *
 *	startchild_defgb->startchild				     *
 *	Recode err to use add_id_to_err; expect errn from library    *
 *	Add "version function"					     *
 *	Add iovaldouble_ entry					     *
 *	    [Needs utils 1.9; utils.h 1.1]			     *
 *   16 Jan 04 v 1.7   		    WJS				     *
 *	Add "exec" capability (if "input file" is enclosed in	     *
 *	  parens, assume input file is command w/params; open pipe   *
 *	  to it, etc).  Steal most code from defgb, hence need for   *
 *	  many new routines in make file...			     *
 *	Emit "big" error messages				     *
 *	Parametrize a few things like "nd"			     *
 *	Put comments in inverse time order.  If dates same, invert   *
 *	  order.						     *
 *	Change version to 1.7.  Version that was released with	     *
 *	  "JGOFS 1.5 release" was numbered 1.5.  So was version	     *
 *	  extant BEFORE "JGOFS 1.5 release". ? Allow one of them to  *
 *	  be considered 1.6?  Anyway...				     *
 *	    [Needs defgb_utils 1.5]				     *
 *	    [Needs defgb.h 5.0 (which needs core.h & path_info.h)]   *
 *	    [Begin 1.7 - in use on globec as testnm]		     *
 *                                                                   *
 * June 1997 grf                                                     *
 *       changed sub names to in_xxxx, fixes for v 1.5               *
 *                                                                   *
 * May 1997                         Glenn Flierl                     *
 *       changed error calls to use error_ in outer.c                *
 *                                                                   *
 * November  1996                        Christine Hammond           *
 *       proclabel change - place separating ';' AFTER first attrib  *
 *                                                                   *
 * November  1996                   Warren Sass, Christine Hammond   *
 *       ioattrout change - remove code after getwidth() call whose  *
 *       comment stated:  don't show the width attribute             *
 *       code was:        str = '\0';                                *
 *       we want outer to determine what gets shown.                 *
 *                                                                   *
 * March  1996                      Warren Sass, Christine Hammond   * 
 *       use a .h include file to set sizes of buffers used          *
 *       NOTE:  'minbufsize.h' also included in outer.c, to achieve  *
 *       some correspondence.  User-specified include file allowed   *
 *       (see minbufsize.h) as a compile switch IOBUF_INCLUDE        * 
 *                                                                   *
 * March  1996                      Christine Hammond                * 
 *       fix in_proclabel (width sensing) to ignore attributes with a*
 *       substring 'width=', ex: 'inpwidth=N', and to properly deal  *
 *       with a 'width=N' attr when it occurs after a ';', as in     *
 *       a series of attributes, ex: 'meters;color=pink;width=12'    *
 *                                                                   *
 * January 1996                     Christine Hammond                * 
 *       'type' the functions that outer method calls to avoid       *
 *        warnings on ANSI compilers - void() used for functions     * 
 *        returning no value  (e.g., return;)                        * 
 *                                                                   *
 * July 1995                        Christine Hammond                *
 *        change to add field width determination based on width of  *
 *        the label in the data.  Preparer must determine max width  *
 *        of column from inspection of data column width.            *
 *        Ex:  event___ , sta__ ,bot                                 *
 *        will give lengths of 8, 5, and 3 for the respective cols   *
 *                                                                   *
 * Oct 1993                         attr[][]                         *
 * Sat Oct 17  1992 10.00           Glenn Flierl                     *
 ******************************************************************* */

/* #define DEBUG */

#include INNEROPTIONS
#include "utils.h"
  /*  There are a zillion default separator strings defined 		*/
  /*  None of them are this one.  Sigh.  No real problem.  Using defgb	*/
  /*  as an example, since defgb ultimately allows user to spec the	*/
  /*  string, so we can pretend	this is the string user spec'd		*/
#define SEPARATOR " ,=\t\n"

  /*  outer routine							*/
void error_();

  /*  utils routines							*/
void analy_source();
char *buildstring();
char *strdupl();
Logical add_id_to_err();

#if READ_COMMANDS
char *startchild();
#endif

char names[NVAR][VARNAMESIZE];
char values[NVAR][DATUMSIZE];
char attr[NVAR][ATTRSIZE];
struct fileinfo file;

int fldwidths[NVAR];
int map[NVAR];
int nheader, ndata;
FILE *fl;
int maxlev,newlev;
int eofflag=0;
char str[INBUFSIZE],*strp;
char tabcomments[COMMENTSIZE];

/* * * * * *     end of declarations    * * * * * */

char *nm_return_vers()
/*  Dummy routine.  Exists only to force .h file version string into	*/
/*  this module.  Note string must not be global or we'll have con-	*/
/*  flicts if another routine similarly includes the version string	*/
{
  static char version[] = NM_VERSION"/"FULL_UTILSH_VERSION;
  return version;
}

void err(s,t)
char *s,*t;
{
  char *ss,*tt;
  add_id_to_err(&ss,&tt,s,t,NM_VERSION);
  error_(ss,tt);
  return;		/*  Not that it should ever get here...		*/
}

void in_getwidth(vn, ptr)
int *vn;
char *ptr;
{
  char *p;
  int i,len;

  i = *vn;
  p = strchr(ptr,'=');

  sscanf(p+1,"%d", &len);

  fldwidths[i] = len;
  return;
}

void in_striptoken(p)
char *p;
{
  char *tp;
  int i;

/* if there are extending characters at end of string, strip them */

  i = strlen(p);
  while ( (tp = strrchr(p,W_EXTEND)) == p+i-1) {
     *tp = '\0';        /* shorten string by that one extend char */
     i = strlen(p);
  };
  return;
}

void in_proclabel(n, ptr)
int n;
char *ptr;
/* 
attr[] is the global array.
routine to determine the length of the title string indexed at 'n' and
pointed to by 'ptr', and based on the idea that the user preparing the
data has placed W_EXTEND characters to pad the title to the desired
width.  An attribute is constructed and saved in attr[], either by
appending to existing attr's or saving as the sole attribute, beginning
with 'width='.  Then, the W_EXTEND characters must be stripped from the
names.  Same name separators are used as before (space, tab, comma,
newline).  If an explicit 'width=' attribute is found, it is used.
*/

{

   int len, docalc, others;
   char attrib[ATTRSIZE], *attrptr;

/* default value for flag indicating attributes other than 'width=' */

   others = FALSE;

/* calculate a field width, if no attributes are present or, 
   if none of the attributes are exactly == 'width=N'       */

   docalc = TRUE;
   attrptr = attr[n];
   while (*attrptr) {
        if (strncmp(attrptr,"width=",6) == 0) docalc = FALSE;
        if ((attrptr = strchr(attrptr,';')) != NULL){
          attrptr++; 
          others = TRUE;
        }
   }

   if (docalc == TRUE) {

     len = strlen(ptr);           /* length of (possibly) extended label */
     sprintf(attrib, "width=%d", len);
     if (others == TRUE) {         /* add to end of other attributes? */
        strcat(attr[n],";");
        strcat(attr[n],attrib);
     } else
        strcpy(attr[n],attrib);

  };   /* end docalc loop */
  
/* strip off the column extenders from the label */

  in_striptoken(ptr);

/* and save label name without the extra W_EXTEND characters */

  strcpy(names[n], ptr);
  return;
}

int in_fg(str)
char *str;
{

  *str = 0;
  while(*str == 0){
    if(fgets(str,INBUFSIZE-1,fl) == NULL) return 1;
    if(strspn(str,SEPARATOR)==strlen(str))*str = 0;
  };
  return 0;
}

int in_readdata(flag)
int flag;
{
int state,nn,i;
char *pstr;

state=1;
nn=0;
if(*str == 0)
  if(in_fg(str))return -1;

  if (str[0] == '#'){
    *tabcomments=0;
    while (str[0] == '#') {
      strcat(tabcomments,str+1);
      if (in_fg(str)) return -1;
      };
    };

if(flag<=1) while(strchr(str,'=')==NULL) if(in_fg(str))return -1;

while(strchr(str,'=')){
  state=2;
  strp=strtok(str,SEPARATOR);
  while(strp){
    if(flag==0){
      if(pstr=strchr(strp,'[')){
	*(pstr+strlen(pstr)-1)=0;
	strcpy(attr[nheader],pstr+1);
	*pstr=0;
      } else attr[nheader][0]=0;

/* routine to determine field width and assign field name */

      in_proclabel(nheader, strp);
      nn=nheader++;
    } else {
      if(pstr=strchr(strp,'['))*pstr=0;
      for(nn=0;nn<nheader;nn++){
           in_striptoken(strp);
           if(strcmp(names[nn],strp)==0) break;
      };
      if(nn==nheader)  
        err("unknown header variable name found - ",strp);
    };
    strp=strtok(NULL,SEPARATOR);
    if(strp==NULL)err("no value assigned - ",names[nn]);
    strcpy(values[nn],strp);
    strp=strtok(NULL,SEPARATOR);
  };
  if(in_fg(str))return -1;
};

if (state==2 || flag<=1){
  strp=strtok(str,SEPARATOR);
  i=0;
  while(strp){
    if(flag==0){
      if(pstr=strchr(strp,'[')){
	*(pstr+strlen(pstr)-1)=0;
	strcpy(attr[nheader+ndata],pstr+1);
	*pstr=0;
      } else attr[nheader+ndata][0]=0;
/*      strcpy(names[nheader+ndata],strp); */
      in_proclabel(nheader+ndata, strp);
      nn=nheader+ndata;
      ndata++;
    } else {
      if(pstr=strchr(strp,'['))*pstr=0;
      for(nn=nheader;nn<nheader+ndata;nn++) {
        in_striptoken(strp);
	if(strcmp(names[nn],strp)==0)break;
      }
      if(nn==(nheader+ndata))err("unknown data variable - ",strp);
    };
    map[i++]=nn;
    strp=strtok(NULL,SEPARATOR);
  };
  *str=0;
  return 1;
};

strp=strtok(str,SEPARATOR);
for(nn=0;nn<ndata;nn++){
  if(strp==NULL){
    if(in_fg(str)) return -1;
    strp=strtok(str,SEPARATOR);
  };
  strcpy(values[map[nn]],strp);
  strp=strtok(NULL,SEPARATOR);
};
*str = 0;
return 2;
}


void open_datafile(file)
struct fileinfo *file;
/*  open_datafile opens file as a datafile. This structure was init'ed	*/
/*  when we processed the "next level file" item from the level above	*/
/*  Should only return if open succeeded				*/
/*	Code from defgb 4.1b						*/
{
  char *PATH_INFO_none_putenv = NULL;
  char *PATH_INFO_orig_putenv = NULL;
  char *tmp;
  char one_char_buf[2] = {'\0','\0'};
  int i;
  int varnamesize;
/*
  if (max_trace_level >= TRACE_PERFILE_ROUTINES) {
    tmp = buildstring("open ",file->source,NULL, " building trace msg");
    do_diag_trace (TRACE_PERFILE_ROUTINES,tmp);
    free (tmp);
  }
*/

  errno = 0;	/*  Be sure that errno errors are ours!			*/

  switch (file->source_type) {

    case COMMAND_FILE:
#if READ_COMMANDS
	/*  If a method is run by the command file, it will look	*/
	/*  at the PATH_INFO string.  Ideally, the command file it-	*/
	/*  self would set PATH_INFO appropriately.  However,		*/
	/*  we sometimes have a command itself, not a command file.	*/
	/*  Tough choice, since presumably command file might want	*/
	/*  to know PATH_INFO, but replace proto string with "none"	*/
	/*  (and drop options - see ioopen_ where none_putenv string	*/
	/*  is made).  My decision at this point... none has no		*/
	/*  meaning to system.						*/
	/*    For nm 1.7, short circuit this ...			*/
      if (PATH_INFO_none_putenv != NULL) 
	if (  (i = putenv(PATH_INFO_none_putenv)) != 0  )
	  err ("putenv failure for PATH_INFO: ",strerror(i));

      tmp = startchild (file->source, NULL, &file->stream);
	/*  Not sure if bad startchild result would always give		*/
	/*  bad return status						*/
      if (  (file->stream == NULL) && (tmp == NULL)  )  tmp = "Reason unknown";
      if (tmp != NULL)
	err ( buildstring("Error opening data script",
			  file->source,
			  " : \n	",
			  " open_datafile-script msg"),
              tmp					);
      file->open=TRUE;

	/*  Restore original PATH_INFO if we changed it			*/
      if (PATH_INFO_none_putenv != NULL) 
	if (  (i = putenv(PATH_INFO_orig_putenv)) != 0  )
	  err ("putenv failure restoring PATH_INFO: ",strerror(i));
#else
      err("Method not compiled with command-reading capability,\n\
needed to read data from script ",file->source);
#endif
      break;

    case DATA_FILE:
      if (  (file->stream = fopen(file->source,"r"))  == NULL   )
        err ( buildstring("Error opening data file ",
			  file->source,
			  " : \n	",
			  " open datafile msg"),
	      strerror(errno) 				);
      file->open=TRUE;
      break;

    default:
      tmp = buildstring(
		"Attempt to open illegal file type as data file\n   File type = ",
		file->descrip,
		"\n   File source type = ",
		" building open_datafile err string");
      *one_char_buf = file->source_type;
      err (tmp, one_char_buf);
      free (tmp);
      break;
  }

  file->eod=FALSE;
  file->eof=FALSE;
  file->nrecs=0;	/* A bit deceptive.  Counts between EODs.  If	*/
			/* >1 dataset comes from a single file, this is	*/
			/* NOT the count on the whole file		*/

  return;
}

int ioopen_(s,nparams,ntotal)
char *s[];
int *nparams,*ntotal;
{
char tmp[INBUFSIZE],*tok;
int ncnt,i,j;

file.stream=NULL;
file.source=NULL;
file.source_type=ILLEGAL_SOURCE;
file.descrip="datafile";
file.open=FALSE;
file.eof=FALSE;
file.eod=FALSE;
file.nrecs=0;
file.entry_separators=NULL;
file.item_separators="\n\t";	/* Now used only to parse input command	*/
file.item_alt_separators[0] = '\0';
/* at some point could allocate file.buf,.buf_size; 			*/
/* at same time switch to using file.eof				*/

strdupl(&file.source,s[0]," saving level 0 source info");
analy_source(&file,SEPARATOR,NULL);
open_datafile(&file);
fl = file.stream;

s[0][0] = 0;

str[0]=0;
strp=str;
nheader=0;
ndata=0;
*tabcomments=0;

if(in_readdata(0)<=0)err("file structure","");

*ntotal=nheader+ndata;

if (nheader>0){
  newlev=3;
  return 1;
} else {
  newlev=2;
  return 0;
}
}

void ioname_(vn,s)
int *vn;
char *s;
{
strcpy(s,names[*vn]);
}

int iovarlevel_(vn)
int *vn;
{
if(*vn<nheader) return 0;
return 1;
}

int ioattrout_(vn,str)
int *vn;
char *str;
{
char *at;
int j;
j= *vn;

if (attr[j][0]){
  at=strchr(attr[j],';');
  if(at){
    *at = '\0';
    strcpy(str,attr[j]);
    if (strncmp(str,"width=",6) == 0) {   /* width attr, use it for tabwidths */
      in_getwidth(&j,str);
    }
    strcpy(attr[j],at+1);
  } else {
    strcpy(str,attr[j]);
    if (strncmp(str,"width=",6) == 0) {
      in_getwidth(&j,str);
    }
    attr[j][0]='\0';
  };
  return 1;
} else return 0;
}

int iocommout_(str)
char *str;
{
char *at;
if (tabcomments[0]){
  at=strchr(tabcomments,'\n');
  if(at){
    *at = '\0';
    strcpy(str,tabcomments);
    strcpy(tabcomments,at+1);
  } else {
    strcpy(str,tabcomments);
    tabcomments[0]='\0';
  };
  return 1;
} else return 0;
}
  
void iovaldouble_(vn,df)
int *vn;
double *df;
/*	See iovalreal_							*/
{
/*     following line changed by clh, May 1996
       if(strspn(tabvalues[i],"0123456789.+-"))
       to one below, to allow for strings that begin with digits
*/

int i;
char *end_char_ptr;

i=*vn;
if(i<0){*df= MISSING_VALUE_REAL;return;};

/*     if strtod does not point to NULL, make the 'number' returned a 
       'missing' one.  Note that if the string conversion produces no
        number, end_char_ptr will point to tabvalues[i], which is not NULL
*/
 *df=strtod(values[i],&end_char_ptr);
 if (*end_char_ptr != '\0')
   *df= MISSING_VALUE_REAL;

#ifdef DEBUG
printf("iovaldouble %d %f\n",*vn,df);
#endif
return;
}

void iovalreal_(vn,f)
int *vn;
float *f;
/*	"Return real value (f) for variable indexed by vn. -9999"	*/
/*	"for strings"							*/
{
  double df;
  iovaldouble_(*vn,&df);
  *f = df;
  return;
}

void iovalstr_(vn,tmp)
int *vn;
char *tmp;
{
static char *s;
int i;
i= *vn;
if(i<0){strcpy(tmp,MISSING_VALUE_STRING);return;};
s=values[i];
s=s+strspn(s," ");
strcpy(tmp,s);
#ifdef DEBUG
printf("iovalstr %d %s\n",*vn,tmp);
#endif
return;
}

int ioreadrec_(level)
int *level;
{
if(eofflag)return 0;
switch(*level){
 case 0:
  if(nheader){
    if (newlev & 1) {newlev=0;return 1;};
    newlev=in_readdata(1);
    if(newlev<0){eofflag=1;return 0;};
    newlev=newlev-1;
    return 1;
  } else {
    if (newlev & 2){newlev=0;return 1;};
    newlev=in_readdata(2);
    if(newlev<0){eofflag=1;return 0;};
    newlev=newlev-1;
    return 1;
  };
 case 1:
  if(newlev & 2){newlev=newlev-2; return 1;};
  newlev=in_readdata(2);
  if(newlev<0){eofflag=1;return 0;};
  if(newlev & 1) return 0;
  newlev=newlev-2;
  return 1;
};
}

void ioclose_(maxlevel)
int *maxlevel;
{
if (fl != NULL) fclose(fl);
file.open = FALSE;
return;
}


int iowidth_(vn)
int *vn;
{
   int i;
   i = fldwidths[*vn];
return i;

}
