text: /* Copyright 2018, UCAR/Unidata and OPeNDAP, Inc.
     See the COPYRIGHT file for more information.
  */
  
  #include "config.h"
  
  #include <stdlib.h>
  #include <stdio.h>
  #ifdef HAVE_SYS_STAT_H
  #include <sys/stat.h>
  #endif
  
  #include "ocinternal.h"
  #include "ocdebug.h"
  
  #define MAXLEVEL 1
  
  /*Forward*/
  static void dumpocnode1(OCnode* node, int depth);
  static void dumpdimensions(OCnode* node);
  static void dumpattvalue(OCtype nctype, char** aset, int index);
  
  static const char* sindent = 
  	"                                                                                                     ";
  
  static const char*
  dent(int n)
  {
      if(n > 100) n = 100;
      return sindent+(100-n);
  }
  
  /* support [dd] leader*/
  static const char*
  dent2(int n) {return dent(n+4);}
  
  static void
  tabto(int pos, NCbytes* buffer)
  {
      int bol,len,pad;
      len = ncbyteslength(buffer);
      /* find preceding newline */
      for(bol=len-1;;bol--) {
  	int c = ncbytesget(buffer,(size_t)bol);
  	if(c < 0) break;
  	if(c == '\n') {bol++; break;}
      }
      len = (len - bol);
      pad = (pos - len);
      while(pad-- > 0) ncbytescat(buffer," ");
  }
  
  void
  ocdumpnode(OCnode* node)
  {
      if(node != NULL) {
          dumpocnode1(node,0);
      } else {
  	fprintf(stdout,"<NULL>\n");
      }
      fflush(stdout);
  }
  
  static void
  dumpocnode1(OCnode* node, int depth)
  {
      unsigned int n;
      switch (node->octype) {
      case OC_Atomic: {
          fprintf(stdout,"[%2d]%s ",depth,dent(depth));
  	if(node->name == NULL) OCPANIC("prim without name");
  	fprintf(stdout,"%s %s",octypetostring(node->etype),node->name);
  	dumpdimensions(node);
  	fprintf(stdout," &%p",node);
  	fprintf(stdout,"\n");
      } break;
  
      case OC_Dataset: {
          fprintf(stdout,"[%2d]%s ",depth,dent(depth));
  	fprintf(stdout,"dataset %s\n",
  		(node->name?node->name:""));
  	for(n=0;n<nclistlength(node->subnodes);n++) {
  	    dumpocnode1((OCnode*)nclistget(node->subnodes,n),depth+1);
  	}
      } break;
  
      case OC_Structure: {
          fprintf(stdout,"[%2d]%s ",depth,dent(depth));
  	fprintf(stdout,"struct %s",
  		(node->name?node->name:""));
  	dumpdimensions(node);
  	fprintf(stdout," &%p",node);
  	fprintf(stdout,"\n");
  	for(n=0;n<nclistlength(node->subnodes);n++) {
  	    dumpocnode1((OCnode*)nclistget(node->subnodes,n),depth+1);
  	}
      } break;
  
      case OC_Sequence: {
          fprintf(stdout,"[%2d]%s ",depth,dent(depth));
  	fprintf(stdout,"sequence %s",
  		(node->name?node->name:""));
  	dumpdimensions(node);
  	fprintf(stdout," &%p",node);
  	fprintf(stdout,"\n");
  	for(n=0;n<nclistlength(node->subnodes);n++) {
  	    dumpocnode1((OCnode*)nclistget(node->subnodes,n),depth+1);
  	}
      } break;
  
      case OC_Grid: {
  	unsigned int i;
          fprintf(stdout,"[%2d]%s ",depth,dent(depth));
  	fprintf(stdout,"grid %s",
  		(node->name?node->name:""));
  	dumpdimensions(node);
  	fprintf(stdout," &%p",node);
  	fprintf(stdout,"\n");
  	fprintf(stdout,"%sarray:\n",dent2(depth+1));
  	dumpocnode1((OCnode*)nclistget(node->subnodes,0),depth+2);
  	fprintf(stdout,"%smaps:\n",dent2(depth+1));
  	for(i=1;i<nclistlength(node->subnodes);i++) {
  	    dumpocnode1((OCnode*)nclistget(node->subnodes,i),depth+2);
  	}
      } break;
  
      case OC_Attribute: {
          fprintf(stdout,"[%2d]%s ",depth,dent(depth));
  	if(node->name == NULL) OCPANIC("Attribute without name");
  	fprintf(stdout,"%s %s",octypetostring(node->etype),node->name);
  	for(n=0;n<nclistlength(node->att.values);n++) {
  	    char* value = (char*)nclistget(node->att.values,n);
  	    if(n > 0) fprintf(stdout,",");
  	    fprintf(stdout," %s",value);
  	}
  	fprintf(stdout," &%p",node);
  	fprintf(stdout,"\n");
      } break;
  
      case OC_Attributeset: {
          fprintf(stdout,"[%2d]%s ",depth,dent(depth));
  	fprintf(stdout,"%s:\n",node->name?node->name:"Attributes");
  	for(n=0;n<nclistlength(node->subnodes);n++) {
  	    dumpocnode1((OCnode*)nclistget(node->subnodes,n),depth+1);
  	}
      } break;
  
      default:
  	OCPANIC1("encountered unexpected node type: %x",node->octype);
      }
  
      if(node->attributes != NULL) {
  	unsigned int i;
  	for(i=0;i<nclistlength(node->attributes);i++) {
  	    OCattribute* att = (OCattribute*)nclistget(node->attributes,i);
  	    fprintf(stdout,"%s[%s=",dent2(depth+2),att->name);
  	    if(att->nvalues == 0)
  		OCPANIC("Attribute.nvalues == 0");
  	    if(att->nvalues == 1) {
  		dumpattvalue(att->etype,att->values,0);
  	    } else {
  		int j;
  	        fprintf(stdout,"{");
  		for(j=0;j<att->nvalues;j++) {
  		    if(j>0) fprintf(stdout,", ");
  		    dumpattvalue(att->etype,att->values,j);
  		}
  	        fprintf(stdout,"}");
  	    }
  	    fprintf(stdout,"]\n");
  	}
      }
  }
  
  static void
  dumpdimensions(OCnode* node)
  {
      unsigned int i;
      for(i=0;i<node->array.rank;i++) {
          OCnode* dim = (OCnode*)nclistget(node->array.dimensions,i);
          fprintf(stdout,"[%s=%lu]",
  			(dim->name?dim->name:"?"),
  			(unsigned long)dim->dim.declsize);
      }
  }
  
  static void
  dumpattvalue(OCtype nctype, char** strings, int index)
  {
      if(nctype == OC_String || nctype == OC_URL) {
          fprintf(stdout,"\"%s\"",strings[index]);
      } else {
          fprintf(stdout,"%s",strings[index]);
      }
  }
  
  void
  ocdumpslice(OCslice* slice)
  {
      fprintf(stdout,"[");
      fprintf(stdout,"%lu",(unsigned long)slice->first);
      if(slice->stride > 1) fprintf(stdout,":%lu",(unsigned long)slice->stride);
      fprintf(stdout,":%lu",(unsigned long)(slice->first+slice->count)-1);
      fprintf(stdout,"]");
  }
  
  void
  ocdumpclause(OCprojectionclause* ref)
  {
      unsigned int i;
      NClist* path = nclistnew();
      occollectpathtonode(ref->node,path);
      for(i=0;i<nclistlength(path);i++) {
          NClist* sliceset;
  	OCnode* node = (OCnode*)nclistget(path,i);
  	if(node->tree != NULL) continue; /* leave off the root node*/
  	fprintf(stdout,"%s%s",(i>0?PATHSEPARATOR:""),node->name);
  	sliceset = (NClist*)nclistget(ref->indexsets,i);
  	if(sliceset != NULL) {
  	    unsigned int j;
  	    for(j=0;j<nclistlength(sliceset);j++) {
  	        OCslice* slice = (OCslice*)nclistget(sliceset,j);
  	        ocdumpslice(slice);
  	    }
  	}
      }
  }
  
  
  static void
  addfield(char* field, size_t llen, char* line, int align)
  {
      int len,rem;
      strlcat(line,"|",llen);
      strlcat(line,field,llen);
      len = strlen(field);
      rem = (align - len);
      while(rem-- > 0) strlcat(line," ",llen);
  }
  
  static void
  dumpfield(size_t index, char* n8, int isxdr)
  {
      char line[1024];
      char tmp[32];
  
      union {
  	unsigned int uv;
  	int sv;
  	char cv[4];
  	float fv;
      } form;
      union {
  	char cv[8];
          unsigned long long ll;
          double d;
      } dform;
  
      line[0] = '\0';
  
      /* offset */
      sprintf(tmp,"%6zd",index);
      addfield(tmp,sizeof(line),line,5);
  
      memcpy(form.cv,n8,4);
  
      /* straight hex*/
      sprintf(tmp,"%08x",form.uv);
      addfield(tmp,sizeof(line),line,8);
  
      if(isxdr) {swapinline32(&form.uv);}
  
      /* unsigned integer */
      sprintf(tmp,"%12u",form.uv);
      addfield(tmp,sizeof(line),line,12);
  
      /* signed integer */
      sprintf(tmp,"%12d",form.sv);
      addfield(tmp,sizeof(line),line,12);
  
      /* float */
      sprintf(tmp,"%#g",form.fv);
      addfield(tmp,sizeof(line),line,12);
  
      /* char[4] */
      {
          /* use raw form (i.e. n8)*/
          int i;
  	tmp[0] = '\0';
          for(i=0;i<4;i++) {
  	    char stmp[64];
  	    unsigned int c = (n8[i] & 0xff);
  	    if(c < ' ' || c > 126)
                  snprintf(stmp,sizeof(stmp),"\\%02x",c);
  	    else
                  snprintf(stmp,sizeof(stmp),"%c",c);
  	    strlcat(tmp,stmp,sizeof(tmp));
          }
      }
  
      addfield(tmp,sizeof(line),line,16);
  
      /* double */
      memcpy(dform.cv,n8,(size_t)(2*XDRUNIT));
      if(isxdr) xxdrntohdouble(dform.cv,&dform.d);
      sprintf(tmp,"%#g",dform.d);
      addfield(tmp,sizeof(line),line,12);
  
      fprintf(stdout,"%s\n",line);
  }
  
  static void
  typedmemorydump(char* memory, size_t len, int fromxdr)
  {
      unsigned int i,count,rem;
      char line[1024];
      char* pmem;
      char mem[8];
  
      assert(memory[len] == 0);
  
      /* build the header*/
      line[0] = '\0';
      addfield("offset",sizeof(line),line,6);
      addfield("hex",sizeof(line),line,8);
      addfield("uint",sizeof(line),line,12);
      addfield("int",sizeof(line),line,12);
      addfield("float",sizeof(line),line,12);
      addfield("char[4]",sizeof(line),line,16);
      addfield("double",sizeof(line),line,12);
      strlcat(line,"\n",sizeof(line));
      fprintf(stdout,"%s",line);
  
      count = (len / sizeof(int));
      rem = (len % sizeof(int));
  
      for(pmem=memory,i=0;i<count;i++,pmem+=4) {
  	memset(mem,0,8);
  	if(i<(count-1))
  	    memcpy(mem,pmem,8);
  	else
  	    memcpy(mem,pmem,4);
  	dumpfield(i*sizeof(unsigned int),mem,fromxdr);
      }
      if(rem > 0) {
  	memset(mem,0,8);
  	memcpy(mem,pmem,4);
  	dumpfield(i*sizeof(unsigned int),mem,fromxdr);
      }
      fflush(stdout);
  }
  
  static void
  simplememorydump(char* memory, size_t len, int fromxdr)
  {
      unsigned int i,count,rem;
      int* imemory;
      char tmp[32];
      char line[1024];
  
      assert(memory[len] == 0);
  
      /* build the header*/
      line[0] = '\0';
      addfield("offset",sizeof(line),line,6);
      addfield("XDR (hex)",sizeof(line),line,9);
      addfield("!XDR (hex)",sizeof(line),line,10);
      fprintf(stdout,"%s\n",line);
  
      count = (len / sizeof(int));
      rem = (len % sizeof(int));
      if(rem != 0)
  	fprintf(stderr,"ocdump: |mem|%%4 != 0\n");
      imemory = (int*)memory;
  
      for(i=0;i<count;i++) {
  	unsigned int vx = (unsigned int)imemory[i];
  	unsigned int v = vx;
  	if(!xxdr_network_order) swapinline32(&v);
          line[0] = '\0';
          sprintf(tmp,"%6d",i);
          addfield(tmp,sizeof(line),line,6);
          sprintf(tmp,"%08x",vx);
          addfield(tmp,sizeof(line),line,9);
          sprintf(tmp,"%08x",v);
          addfield(tmp,sizeof(line),line,10);
          fprintf(stdout,"%s\n",line);
      }
      fflush(stdout);
  }
  
  void
  ocdumpmemory(char* memory, size_t len, int xdrencoded, int level)
  {
      if(level > MAXLEVEL) level = MAXLEVEL;
      switch (level) {
      case 1: /* Do a multi-type dump */
          typedmemorydump(memory,len,xdrencoded);
  	break;
      case 0: /* Dump a simple linear list of the contents of the memory as 32-bit hex and decimal */
      default:
          simplememorydump(memory,len,xdrencoded);
  	break;
      }
  }
  
  static OCerror
  ocreadfile(FILE* file, off_t datastart, char** memp, size_t* lenp)
  {
      char* mem = NULL;
      size_t len;
      long red;
      struct stat stats;
      long pos;
      OCerror stat = OC_NOERR;
  
      pos = ftell(file);
      if(pos < 0) {
        fprintf(stderr,"ocreadfile: ftell error.\n");
        stat = OC_ERCFILE;
        goto done;
      }
  
      fseek(file,0,SEEK_SET);
      if(fseek(file,(long)datastart,SEEK_SET) < 0) {
  	fprintf(stderr,"ocreadfile: fseek error.\n");
  	stat = OC_ERCFILE;
  	goto done;
      }
  
      if(fstat(fileno(file),&stats) < 0) {
  	fprintf(stderr,"ocreadfile: fstat error.\n");
  	stat = OC_ERCFILE;
  	goto done;
      }
      len = stats.st_size;
      len -= datastart;
  
      mem = (char*)calloc(len+1,1);
      if(mem == NULL) {stat = OC_ENOMEM; goto done;}
  
      /* Read only the data part */
      red = fread(mem,1,len,file);
      if(red < len) {
  	fprintf(stderr,"ocreadfile: short file\n");
  	stat = OC_ERCFILE;
  	goto done;
      }
  
      if(fseek(file,pos,SEEK_SET) < 0) {; /* leave it as we found it*/
        fprintf(stderr,"ocreadfile: fseek error.\n");
        stat = OC_ERCFILE;
        goto done;
      }
      if(memp) {*memp = mem; mem = NULL;}
      if(lenp) *lenp = len;
  
   done:
      if(mem != NULL)
        free(mem);
      return OCTHROW(stat);
  }
  
  void
  ocdd(OCstate* state, OCnode* root, int xdrencoded, int level)
  {
      char* mem;
      size_t len;
      if(root->tree->data.file != NULL) {
          if(!ocreadfile(root->tree->data.file,
                         root->tree->data.bod,
                         &mem,
                         &len)) {
            /* ocreadfile allocates memory that must be freed. */
            if(mem != NULL) free(mem);
            fprintf(stderr,"ocdd could not read data file\n");
            return;
          }
          ocdumpmemory(mem,len,xdrencoded,level);
          free(mem);
      } else {
          mem = root->tree->data.memory;
          mem += root->tree->data.bod;
          len = root->tree->data.datasize;
          len -= root->tree->data.bod;
          ocdumpmemory(mem,len,xdrencoded,level);
      }
  }
  
  void
  ocdumpdata(OCstate* state, OCdata* data, NCbytes* buffer, int frominstance)
  {
      char tmp[1024];
      OCnode* pattern = data->pattern;
      char* smode = NULL;
  
      snprintf(tmp,sizeof(tmp),"%p:",data);
      ncbytescat(buffer,tmp);
      if(!frominstance) {
          ncbytescat(buffer," node=");
          ncbytescat(buffer,pattern->name);
      }
      snprintf(tmp,sizeof(tmp)," xdroffset=%ld",(unsigned long)data->xdroffset);
      ncbytescat(buffer,tmp);
      if(data->pattern->octype == OC_Atomic) {
          snprintf(tmp,sizeof(tmp)," xdrsize=%ld",(unsigned long)data->xdrsize);
          ncbytescat(buffer,tmp);
      }
      if(ociscontainer(pattern->octype)) {
          snprintf(tmp,sizeof(tmp)," ninstances=%d",(int)data->ninstances);
          ncbytescat(buffer,tmp);
      } else if(pattern->etype == OC_String || pattern->etype == OC_URL) {
          snprintf(tmp,sizeof(tmp)," nstrings=%d",(int)data->nstrings);
          ncbytescat(buffer,tmp);
      }
      ncbytescat(buffer," container=");
      snprintf(tmp,sizeof(tmp),"%p",data->container);
      ncbytescat(buffer,tmp);
      ncbytescat(buffer," mode=");
      ncbytescat(buffer,(smode=ocdtmodestring(data->datamode,0)));
      nullfree(smode);
  }
  
  /*
  Depth Offset   Index Flags Size Type      Name
  0123456789012345678901234567890123456789012345
  0         1         2         3         4
  [001] 00000000 0000  FS    0000 Structure person
  */
  static const int tabstops[] = {0,6,15,21,27,32,42};
  static const char* header =
  "Depth Offset   Index Flags Size Type     Name\n";
  
  void
  ocdumpdatatree(OCstate* state, OCdata* data, NCbytes* buffer, int depth)
  {
      size_t i,rank;
      OCnode* pattern;
      char tmp[1024];
      size_t crossproduct;
      int tabstop = 0;
      const char* typename;
      char* smode = NULL;
  
      /* If this is the first call, then dump a header line */
      if(depth == 0) {
  	ncbytescat(buffer,header);
      }
  
      /* get info about the pattern */
      pattern = data->pattern;
      rank = pattern->array.rank;
  
      /* get total dimension size */
      if(rank > 0)
          crossproduct = octotaldimsize(pattern->array.rank,pattern->array.sizes);
  
      /* Dump the depth first */
      snprintf(tmp,sizeof(tmp),"[%03d]",depth);
      ncbytescat(buffer,tmp);
  
      tabto(tabstops[++tabstop],buffer);
  
      snprintf(tmp,sizeof(tmp),"%08lu",(unsigned long)data->xdroffset);
      ncbytescat(buffer,tmp);
  
      tabto(tabstops[++tabstop],buffer);
  
      /* Dump the Index wrt to parent, if defined */
      if(fisset(data->datamode,OCDT_FIELD)
         || fisset(data->datamode,OCDT_ELEMENT)
         || fisset(data->datamode,OCDT_RECORD)) {
          snprintf(tmp,sizeof(tmp),"%04lu ",(unsigned long)data->index);
          ncbytescat(buffer,tmp);
      }
  
      tabto(tabstops[++tabstop],buffer);
  
      /* Dump the mode flags in compact form */
      ncbytescat(buffer,(smode=ocdtmodestring(data->datamode,1)));
      nullfree(smode);
  
      tabto(tabstops[++tabstop],buffer);
  
      /* Dump the size or ninstances */
      if(fisset(data->datamode,OCDT_ARRAY)
         || fisset(data->datamode,OCDT_SEQUENCE)) {
          snprintf(tmp,sizeof(tmp),"%04lu",(unsigned long)data->ninstances);
      } else {
          snprintf(tmp,sizeof(tmp),"%04lu",(unsigned long)data->xdrsize);
      }
      ncbytescat(buffer,tmp);
  
      tabto(tabstops[++tabstop],buffer);
  
      if(pattern->octype == OC_Atomic) {
  	typename = octypetoddsstring(pattern->etype);
      } else { /*must be container*/
  	typename = octypetoddsstring(pattern->octype);
      }
      ncbytescat(buffer,typename);
  
      tabto(tabstops[++tabstop],buffer);
  
      ncbytescat(buffer,pattern->name);
  
      if(rank > 0) {
  	snprintf(tmp,sizeof(tmp),"[%lu]",(unsigned long)crossproduct);
  	ncbytescat(buffer,tmp);
      }
      ncbytescat(buffer,"\n");
  
      /* dump the sub-instance, which might be fields, records, or elements */
      if(!fisset(data->datamode,OCDT_ATOMIC)) {
          for(i=0;i<data->ninstances;i++)
  	    ocdumpdatatree(state,data->instances[i],buffer,depth+1);
      }
  }
  
  void
  ocdumpdatapath(OCstate* state, OCdata* data, NCbytes* buffer)
  {
      int i;
      OCdata* path[1024];
      char tmp[1024];
      OCdata* pathdata;
      OCnode* pattern;
      int isrecord;
  
      path[0] = data;
      for(i=1;;i++) {
  	OCdata* next = path[i-1];
  	if(next->container == NULL) break;
  	path[i] = next->container;
      }
      /* Path is in reverse order */
      for(i=i-1;i>=0;i--) {
  	pathdata = path[i];
  	pattern = pathdata->pattern;
  	ncbytescat(buffer,"/");
  	ncbytescat(buffer,pattern->name);
  	/* Check the mode of the next step in path */
  	if(i > 0) {
  	    OCdata* next = path[i-1];
  	    if(fisset(next->datamode,OCDT_FIELD)
  		|| fisset(next->datamode,OCDT_ELEMENT)
  		|| fisset(next->datamode,OCDT_RECORD)) {
  		snprintf(tmp,sizeof(tmp),".%lu",(unsigned long)next->index);
  	        ncbytescat(buffer,tmp);
  	    }
  	}
  	if(pattern->octype == OC_Atomic) {
  	    if(pattern->array.rank > 0) {
  	        off_t xproduct = octotaldimsize(pattern->array.rank,pattern->array.sizes);
  	        snprintf(tmp,sizeof(tmp),"[0..%lu]",(unsigned long)xproduct-1);
  	        ncbytescat(buffer,tmp);
  	    }
  	}
  	isrecord = 0;
  	if(pattern->octype == OC_Sequence) {
  	    /* Is this a record or a sequence ? */
  	    isrecord = (fisset(pathdata->datamode,OCDT_RECORD) ? 1 : 0);
  	}
      }
      /* Add suffix to path */
      if(ociscontainer(pattern->octype)) {
          /* add the container type, except distinguish record and sequence */
  	ncbytescat(buffer,":");
  	if(isrecord)
  	    ncbytescat(buffer,"Record");
  	else
  	    ncbytescat(buffer,octypetoddsstring(pattern->octype));
      } else if(ocisatomic(pattern->octype)) {
  	/* add the atomic etype */
  	ncbytescat(buffer,":");
  	ncbytescat(buffer,octypetoddsstring(pattern->etype));
      }
      snprintf(tmp,sizeof(tmp),"->0x%p",pathdata);
      ncbytescat(buffer,tmp);
  }
  