/* *******************************************************************
 *                                                                   *
 * Copyright (c) L-DGO/MIT/JGOFS                                     *
 *                                                                   *
 *                                                                   *
 * File             : list.c                                         *
 *                                                                   *
 * Revision History :                                                *
 *                                                                   *
 * Date                              Developer                       *
 * ----                              ---------                       *
 */
char id[] = "list version 1.3b 08 Sep 1999";
/*
 * Sep 09, 1999  v 1.3b		    Christine Hammond                *
 *	get widely used constants from default.h                     *
 *	add conditional for DATUMSIZE, as value in default.h         *
 *	is typically defined as 80, was 120 here previously          *
 * Aug 17, 1999  v 1.3a		    Warren Sass                      *
 *	Bug fix:  was attempting stty reset even if -n was set	     *
 *	Change name of nflag to reflect what it asserts		     *
 * Aug 14, 1999  v 1.3		    Warren Sass                      *
 *		Misc bug fixes & minor improvements		     *
 *	Bug fix:  prettyprint failed if width > strlen(var) + 10     *
 *	Bug fix:  olen should be terminal width for "user viewing";  *
 *	  max buf else.  When max buf raised, wrong olen changed     *
 *	Bug fix: -m would change a variable name that happened to be *
 *	  "nd" to -9999.0					     *
 *	Bug fix: "more" capability seemed broken on Bob's Solaris    *
 *	   box.  Improved there.  Seemed to work on Chris' IRIX box  *
 *	   before and after.  Doesn't work on my VMS box either way  *
 *	Rename/resize appropriate constants to their default.h state *
 *	   Buffers ended up WAYY bigger.			     *
 *	Add id string; print it during help			     *
 *	prettyprint is now really a right justifier - name it that.  *
 *	  Nobody used its return values - eliminate them	     *
 *	  Don't right justify if not enough room in buffer	     *
 *	fillsize is no longer that; it's a fill/nofill flag.	     *
 *	  Make a flag variable and a width variable to distinguish   *
 *	Make static temp buffer - saves reallocation		     *
 *	Declare jdb functions					     *
 *	#include errno; stdlib					     *
 *	Minimize use of #ifndef; remove #if's from runtime portion   *
 *	  of code						     *
 *	Add some error message text; remove redundant maxlevel test  *
 *	Reformat some comments relating to revision history	     *
 *	Reformat some program text				     *
 *		Unaddressed issues				     *
 *	1) Separator in data					     *
 *	     If a user of list chooses a separator character that    *
 *	  appears as part of the data displayed, there may be a      *
 *	  problem visually, and there will certainly be a problem    *
 *	  if list's output is going on to another program.	     *
 *	     We could diagnose this condition in list and abort.     *
 *	  We could couple the diagnosis to the -z flag if we assume  *
 *	  -z "data consumers" are programs and not people.  We could *
 *	  make the diagnostic itself switch-controllable.	     *
 *	2) Blanks in data					     *
 *	     Leading blank issues are probably moot, since iovalstr_ *
 *	  (at least the one in def), which feeds outer, which feeds  *
 *	  us, removes leading blanks unconditionally.  Theoretically *
 *	  however, we have an issue in this program		     *
 *	    The column alignment logic will treat the blanks as sig- *
 *	  nificant data, hence the output could look funny.  However *
 *	  maybe it SHOULD look funny because somebody took the trou- *
 *	  ble to be sure blanks were data in that particular object  *
 *	3) What should be affected by the -m flag?		     *
 *	    -m affects data that are exactly the 2 characters nd.    *
 *	  It does not affect, say, nd's embedded in fields whose     *
 *	  other characters are blanks, or nd's that have been quo-   *
 *	  ted to invoke outer's NOQUOTELINK capability.		     *
 *	    Similar to blank discussion, question becomes whether    *
 *	  it's up to list, a data displayer, to make data transform- *
 *	  ation decisions.  For example, somebody might use n/d to   *
 *	  represent no data.					     *
 *	4) PATH_INFO 						     *
 *	    If there's a PATH_INFO in the environment when list	     *
 *	  runs, it will foul things up if the object listed is	     *
 *	  local.  Effects include "object not found" errors and SEGV *
 *	  dumps							     *
 *	    This is an old issue w/ anything that uses the jdb	     *
 *	  routines.   In general, "system" has issued "unsetenv      *
 *	  PATH_INFO" commands.  However, this has gradually caused   *
 *	  down-the-line problems w/objects that themselves create    *
 *	  other processes to get the data.			     *
 *	    We could "unsetenv" here.  Most elegant soln is to use   *
 *	  path info routines to modify PATH_INFO minimally (clear    *
 *	  the "protocol" (.html, .flat) field, then reset it after   *
 *	  jdb use (paranoid, but mindlessly safe).  If we like this  *
 *	  idea, then I think we could default list's input (eg, no   *
 *	  object on command line) to "the object spec'd in	     *
 *	  PATH_INFO".  The idea is to arrive at an OO server	     *
 *	  environment where the shells need to do minimal parsing of *
 *	  PATH_INFO.						     *
 *		[begin v 1.3]					     *
 *                                                                   *   
 * Aug 11, 1999  v 1.2c		    Warren Sass                      *
 *	Essentially the same as 1.3, except I introduced a bug.      *
 *	Given to Bob, who put it in his /bin directory.  While test- *
 *    ing bug fix, found another bug.  Decided to do more code re-   *
 *    arrangement, and decided that a renumbering would be in order  *
 *    even though no major functionality was added		     *
 *		[begin v 1.2c]					     *
 *                                                                   *   
 * June 1, 1999 v 1.2b		Christine Hammond		     *
 *          per WJS and CLC - see comment in code re print of msg    *
 *		[begin v 1.2b]					     *
 *                                                                   *   
 * Nov 9, 1998 	v 1.2a		Christine Hammond                    *   
 *	    ver 1.2a resolves diffs between ver 1.2 and 1.1a 	     *
 *	     developed independently from same code		     *
 *		[begin v 1.2a]					     *
 *                                                                   *   
 * Aug 29, 1995 12.00           Christine Hammond                    * 
 *          ver 1.2 attribute for width determines width of field    *  
 *          option '-m' converts missing value from 'nd' to -9999.0  *
 *		[begin v 1.2]					     *
 *                                                                   *   
 * Oct. 8, 1996	v 1.1a		    Warren Sass                      *
 *    Change number of char per flatfile output line before new line *
 *       is inserted from 255 to 511                                 *
 *		[begin v 1.1a]					     *
 *                                                                   *   
 * Sat Oct 17  1992 10.00           Glenn Flierl                     *
 *                                                                   *   
 ******************************************************************* */

#ifndef PC
#define PC 0
#endif
#ifndef VMS
#define VMS 0
#endif

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

#if ! PC
#include <signal.h>
#endif
          
  /*  Function re-definitions						*/
#if HP || IBM
#define jdbopen_ jdbopen
#define jdbreada_ jdbreada
#define jdbread_ jdbread
#define jdbclose_ jdbclose
#define jdbcomments_ jdbcomments
#define jdblevel_ jdblevel
#define jdbattributes_ jdbattributes
#elif PC
#define getchar getch
#endif

  /*  Sizes are defgb's; = default.h except for datumsize, up from 80	*/
  /*  compile-time definition of OPTIONS will point to default.h        */
#include OPTIONS
#define NVALS 250
#ifdef DATUMSIZE < 120
#undef DATUMSIZE
#define DATUMSIZE 120
#endif

#if PC
#define CR '\r'
#else
#define CR '\n'
#endif
#define SPACE ' '
#define MISSING_VAL_STRING "nd"
#define MISSING_VAL_NUMERIC "-9999.0"
#define DEFAULT_FIELD_WIDTH 7
#define SCREEN_COLS 80
#define SCREEN_ROWS 24
#define OUTLINEBUF 4096

#if SUN || ULTRIX
  char *setup_term_command = "/bin/stty cbreak";
  char *reset_term_command = "/bin/stty -cbreak";
#elif IRIX || IBM || OSF || SOL || HP
  char *setup_term_command = "/bin/stty -icanon min 1 time 0";
  char *reset_term_command = "/bin/stty icanon";
#elif VMS
  char *setup_term_command = "SET TERM/PASTHRU";
  char *reset_term_command = "SET TERM/NOPASTHRU";
#else
  char *setup_term_command = "";
  char *reset_term_command = "";
#endif

  /*  Buffer sizing considerations.					*/
  /*    1)  Although the name and value buffers are logically different	*/
  /*	    sizes, we use them to hold right-justified fields, and we	*/
  /*	    right justify both to the same size.  Accordingly the	*/
  /*	    buffers need to be the same size (or the application needs	*/
  /*	    to be recoded from "right justify all, output all" logic	*/
  /*	    to "right justify 1, output 1" logic which would permit 1	*/
  /*	    buffer, reused for every right-justified field)		*/
  /*	2)  We append the attribute string to the name in the name	*/
  /*	    buffer before right justifying, so the buffer needs to be	*/
  /*	    big enough for that						*/
  /*	3)  There is no logical connection between the output field	*/
  /*	    width to which we are right justifying, and the data field	*/
  /*	    width.  For example, somebody could request that data	*/
  /*	    fields that never exceed 10 bytes in size should always be	*/
  /*	    displayed in 20 character fields.  We don't know the widths	*/
  /*	    until we call jdbopen, but we must supply jdbopen with a	*/
  /*	    buffer.  We should provide a temporary buffer big enough	*/
  /*	    to hold the jdbopen data, find the widths, then dynamically	*/
  /*	    allocate buffers of the correct size and copy the temp	*/
  /*	    buffer into that.  We don't do this.  Aside from eating my	*/
  /*	    hat if the max requested width exceeds this buffer size, we	*/
  /*	    fail safe by leaving that column left justified		*/
  /*	4)  Each buffer needs to be at least DEFAULT_FIELD_WIDTH in	*/
  /*	    size.  They are.						*/
  /*	5)  The value buffer needs to be big enough to hold		*/
  /*	    MISSING_VAL_NUMERIC.  It is					*/
#define MAXVALBUFSIZE DATUMSIZE + 1
  /* + 2 in next line is for the []s we add to bracket the attrib list	*/
#define MAXNAMEBUFSIZE VARNAMESIZE + TOTATTRSIZE + 2 + 1
#if MAXVALBUFSIZE > MAXNAMEBUFSIZE
#define BUFSIZE MAXVALBUFSIZE
#else
#define BUFSIZE MAXNAMEBUFSIZE
#endif

int valsize = BUFSIZE;
int namebufsize = BUFSIZE;
char namebuf[NVALS][BUFSIZE], vals[NVALS][BUFSIZE];

int levels[NVALS], widths[NVALS];
char outline[OUTLINEBUF];

int unit,maxlevel,firstpass,num,level;
char one_char_buf;

int outcount = 0;
int moreflag = 1;
int fflag = 0;
int mflag = 0;
int hflag = 1;
int cflag = 1;
int fillflag = 1;
int olen = SCREEN_COLS - 1;
char osep[] = ",";

FILE *ofile;

int jdbopen_();
int jdbreada_();
int jdbread_();
void jdbclose_();
int jdbcomments_();
int jdblevel_();
int jdbattributes_();

void rst_term();

/************************************************************************/

void writemln(outline)
char *outline;
{
  int len;

  len = strlen(outline);
  if (outline[len-1] == osep[0]) outline[len-1] = '\0';
  fprintf(ofile,"%s\n",outline);

  if ( ! moreflag) return;

    /*  Do "more" emulation						*/
  if ( ++outcount < SCREEN_ROWS - 1) return;

  printf("--More--");

  switch (getchar()) {
    case SPACE:
      printf("\n");
      outcount = 0;
      break;
    case CR:
      if (PC) printf("\n");
	/*  Arrange things so that we interrupt after next line of	*/
	/*  output.  Use -2 instead of -1 to account for the --More--	*/
	/*  we put out							*/
      outcount = SCREEN_ROWS - 2;
      break;
    default:
      printf("\n");
      jdbclose_(&unit);
      system("/bin/stty opost");
      if (*reset_term_command != '\0') system (reset_term_command);
      exit(1);
      break;
  }

  return;
}

void outstr(s)
char *s;
{
  if (  strlen(outline)+strlen(s) > olen ) {
    writemln (outline);
    outline[0] = '\0';
  }
  strcat(outline,s);
  strcat(outline,osep);
  return;
}  

void outflush()
{
  if (outline[0] != '\0') {
    writemln(outline);
    outline[0] = '\0';
  }
  return;
}

void writevar(level)
int level;
{
  int i,j;
  static char tmp[OUTLINEBUF],msg[COMMENTLINE+1];
  if (fflag) {
    if (firstpass) {
      if (cflag)
	while (jdbcomments_(&unit,msg)) {
	  strcpy(tmp,"# ");
	  strcat(tmp,msg);
	  strcat(tmp," ");
	  writemln(tmp);
        }
      if (hflag) {
	for (i=0 ;i<num; i++) outstr(namebuf[i]);
	outflush();
      }
      firstpass = 0;
    }
    for (i=0; i<num; i++) outstr(vals[i]);
    outflush();
  } else {
    if (  (level < maxlevel) || firstpass  ) {
	/* do comments */
      if (cflag)
	while (jdbcomments_(&unit,msg)) {
	  strcpy(tmp,"# ");
	  strcat(tmp,msg);
	  strcat(tmp," ");
	  writemln(tmp);
        }
	/* do headers */
      for (i=level; i<=maxlevel; i++) {
	writemln("======================= ");
	for (j=0; j<num; j++)
	  if (levels[j] == i) outstr(namebuf[j]);
	outflush();
        if (i < maxlevel)
          writemln("........................ ");
        else
          writemln("------------------------ ");
	for (j=0; j<num; j++)
	  if (levels[j] == i) outstr(vals[j]);
	outflush();
      }
    } else {
      for (j=0; j<num; j++)
	if (levels[j] == maxlevel) outstr(vals[j]);
      outflush();
    }
    firstpass = 0;
  }

  return;
}

main(argc,argv)
     int argc;
     char *argv[];
{
  char tmp[ATTRSIZE];
  int i;
  void in_getwidth();
  void rightjust();

  i = 1;
  if (i >= argc) {
    printf
      ("Usage: %s [-n] [-s] [-t] [-f] [-b] [-m] [-c] [-z] object [outfile]\n",
       argv[0]);
    printf("Options: \n     -n   nonstop\n     -s   space-separated\n");
    printf("     -t   tab-separated\n     -f   flat file output\n");
    printf("     -b   brief flat file (no header info)\n");
    printf("     -m   missing value converted to -9999.0\n");
    printf("     -c   no comments\n");
    printf("     -z   delete extra spaces\n");
    printf("%s\n",id);
    exit(1);
  }

    ofile = stdout;

  while (argv[i][0] == '-') {
    switch (argv[i++][1]) {
      case 'f':
	fflag = 1;
	olen = OUTLINEBUF;
	break;
      case 'm':
	mflag=1;
	break;
      case 'n':
	moreflag=0;
	break;
      case 's':
	osep[0]=' ';
	break;
      case 't':
	osep[0]='\t';
	break;
      case 'b':
	fflag = 1;
	hflag = 0;
	cflag = 0;
	olen = OUTLINEBUF;
	break;
      case 'c':
	cflag=0;
	break;
      case 'z':
	fillflag=0;
	break;
    }
  }
  strcpy(outline,argv[i]);
  if (++i < argc) {
    if (  (ofile = fopen(argv[i],"w")) == NULL  ) {
      printf ("Could not open %s for write: %s\n%s\n",
	      argv[i],strerror(errno),id);
      exit(1);
    }
    moreflag = 0;
  }
  
  unit = 1;
  num = -NVALS;
  maxlevel = jdbopen_(&unit,outline,namebuf,&namebufsize,&num);
  if (maxlevel < 0) {
    printf("jdbopen failure (Object %s not found?)\n%s\n",outline,id);
    exit(1);
  }

/* do levels, attributes & cleanup names*/
  for (i=0; i<num; i++) {
    levels[i] = jdblevel_(&unit,&i);
    strcpy(outline,"[");
    while (jdbattributes_(&unit,&i,tmp)) {
       if (strncmp(tmp,"width=",6) == 0) {
         in_getwidth(&i,tmp);
       } else {     
         strcat(outline,tmp);
         strcat(outline,";");
       }
    }
    if (outline[1] == '\0') outline[0] = '\0';
    else outline[strlen(outline)-1] = ']';
    strcat (namebuf[i],outline);
    if (fillflag) rightjust (namebuf[i],widths[i],namebufsize-1);
  }

  outline[0] = '\0';
  outcount = 0;
  
/*    Next test unnecessary since we test maxlevel above and don't	*/
/*    change it afterwards						*/
/*  if (maxlevel<0) {							*/
/*    printf("Object not found(1)\n");					*/
/*    exit(1);								*/
/*  };									*/

    /*  Set up terminals for "more" emulation				*/
  if (moreflag) {
    if (PC) setbuf(stdin,NULL);
    if ( ! PC) signal(SIGINT,rst_term);
    if (*setup_term_command != '\0') system (setup_term_command);
  }
  
  firstpass = 1;
  
  while (  (level = jdbreada_(&unit,vals,&valsize)) >= 0  ) {
    for (i=0; i<num; i++) {
      if (level <= levels[i]) {
	  /*  if the user wants to translate 'nd' vals to numeric */
	if (  mflag && (strcmp(vals[i],MISSING_VAL_STRING) == 0)  )
	  strcpy(vals[i],MISSING_VAL_NUMERIC);
        if (fillflag) rightjust(vals[i],widths[i],valsize-1);
      }
    }
    writevar(level);
  }
  if ( ! fflag) fprintf(ofile,"****** End of object ***\n");

/*   by wjs and clc that because msg was not initialized to any value, when */
/*   objects were seen with no data (or when selections were such that no */
/*   data was present, there was garbage printing.  we want nothing printed */
/*   if in flat mode so that programs reading data output will be able to */
/*   test for end-of-data/no-data, and humans will see only above message */
/* */
/*   if(firstpass==1) printf("%s\n",msg);	*/

  jdbclose_(&unit);
  if (  moreflag && (*reset_term_command != '\0')  )
						system (reset_term_command);
  exit(0);
}

void rst_term()
{
  if (*reset_term_command != '\0') system (reset_term_command);
  jdbclose_(&unit);
  exit(1);
}

void rightjust(s,fillwidth,maxwidth)
char *s;
int fillwidth,maxwidth;
/*  Right justify the string in s to a width of fillwidth, pre-filling  */
/*    with blanks.  Do nothing if s already wider than fillwidth or	*/
/*    if fillwidth > maxwidth (? might use maxwidth instead...)		*/
/*  Not clear what should be done if user says "fill" but no width	*/
/*  spec'd.   Duplicate 1.2b behavior of filling to width 7 if		*/
/*    1) fillsize > 0  but 2) widths[vn]= 0 (fillsize & widths[vn] were	*/
/*  variables in 1.2b source)						*/
{
  int i,chars_to_fill;

    /*  Who knows what < 0 would mean?  Too lazy to issue error msg	*/
  if (fillwidth <= 0) fillwidth = DEFAULT_FIELD_WIDTH;
  if (fillwidth > maxwidth) return;
  chars_to_fill = fillwidth - strlen(s);
  if (chars_to_fill <= 0) return;

    /*  Right justify.  Go backwards to deal w/overlap problem		*/
  for (i = fillwidth-1; i >= chars_to_fill; i--)
    s[i] = s[i-chars_to_fill];

    /*  Blank fill at the front						*/
  for (i = 0; i < chars_to_fill; i++)
    s[i] = ' ';

  s[fillwidth] = '\0';

  return;
}

void in_getwidth(vn, ptr)
/* read the width from the string 'ptr' (an attribute that has
        been tested prior to coming here for containing the 
        string 'width=') and write it to the widths array
        element '*vn'
*/
int *vn;
char *ptr;
{

  char *p;
  int i,len;

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

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

  widths[i] = len;
  return;
}
