/***             analog 4.14             http://www.analog.cx/             ***/
/*** This program is copyright (c) Stephen R. E. Turner 1995 - 2001 except as
 *** stated otherwise. Distribution, usage and modification of this program is
 *** subject to the conditions of the Licence which you should have received
 *** with it. This program comes with no warranty, expressed or implied.   ***/

/*** output2.c; subsiduary output functions ***/

#include "anlghea3.h"

extern unsigned int *rep2lng;

void pagetop(FILE *outf, Outchoices *od, Dateman *dman) {
  extern timecode_t starttimec;
  char *datestr;
  double t0;
  int t1, t2;

  if (od->cgi) {
    if (od->outstyle == HTML)
      fprintf(outf, "Content-Type: text/html; charset=%s\n\n",
	      od->lngstr[charset_]);
    else
      fprintf(outf, "Content-Type: text/plain\n\n");
  }
  if (od->outstyle == HTML) {
    fputs("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n", outf);
    fputs("<html>\n<head>\n", outf);
    fprintf(outf, "<meta http-equiv=\"Content-Type\" "
	    "content=\"text/html; charset=%s\">\n", od->lngstr[charset_]);
    if (od->norobots)
      fputs("<meta name=\"robots\" content=\"noindex,nofollow\">\n", outf);
    fprintf(outf, "<meta name=\"GENERATOR\" content=\"analog %s\">\n",
	    VERSION);
    fprintf(outf, "<title>%s ", od->lngstr[webstatsfor_]);
    htmlfprintf(outf, od->outstyle, od->multibyte, od->hostname, &(od->html),
		TRUE);
    fputs("</title>\n", outf);
    if (!strcaseeq(od->stylesheet, "none")) {
      fputs("<link href=\"", outf);
      htmlfprintf(outf, od->outstyle, FALSE, od->stylesheet, &(od->html),
		  FALSE);
      fputs("\" rel=\"stylesheet\">\n", outf);
    }
    fputs("</head>\n", outf);
    fputs("<body>\n<h1><a NAME=\"Top\">", outf);
    if (!strcaseeq(od->logo, "none")) {
      fputs("<IMG src=\"", outf);
      if (od->logo[0] != '/' && strstr(od->logo, "://") == NULL)
	htmlfprintf(outf, od->outstyle, FALSE, od->imagedir, &(od->html),
		    FALSE);
      htmlfprintf(outf, od->outstyle, FALSE, od->logo, &(od->html), FALSE);
      fputs("\" alt=\"\"> ", outf);
    }
    if (strcaseeq(od->hosturl, "none")) {
      fprintf(outf, "%s</a> ", od->lngstr[webstatsfor_]);
      htmlfprintf(outf, od->outstyle, od->multibyte, od->hostname, &(od->html),
		  TRUE);
    }
    else {
      fprintf(outf, "%s</a> <a HREF=\"", od->lngstr[webstatsfor_]);
      htmlfprintf(outf, od->outstyle, FALSE, od->hosturl, &(od->html), FALSE);
      fputs("\">", outf);
      htmlfprintf(outf, od->outstyle, od->multibyte, od->hostname, &(od->html),
		  TRUE);
      fputs("</a>", outf);
    }
    fputs("</h1>\n\n", outf);
  }
  else if (od->outstyle == ASCII) {
    fprintf(outf, "%s %s\n", od->lngstr[webstatsfor_], od->hostname);
    matchlength(outf, od->outstyle, od->hostname, '=');
    matchlength(outf, od->outstyle, od->lngstr[webstatsfor_], '=');
    fputs("=\n\n", outf);
  }
  if (!strcaseeq(od->headerfile, "none"))
    include_file(outf, od->outstyle, od->headerfile, 'h', od->pagewidth,
		 &(od->html));
  if (od->outstyle == COMPUTER) {
    fprintf(outf, "x%sVE%sanalog %s\n", od->compsep, od->compsep, VNUMBER);
    fprintf(outf, "x%sHN%s%s\n", od->compsep, od->compsep, od->hostname);
    if (!strcaseeq(od->hosturl, "none"))
      fprintf(outf, "x%sHU%s%s\n", od->compsep, od->compsep, od->hosturl);
  }

  datestr = (char *)xmalloc((size_t)datefmtlen(od->lngstr[datefmt2_],
       od->plainmonthlen, od->plaindaylen, od->compsep,
       (logical)(od->outstyle == COMPUTER)) + 1);
  if (od->runtime) {
    if (od->outstyle == COMPUTER)
      fprintf(outf, "x%sPS%s%s\n", od->compsep, od->compsep,
	      datesprintf(datestr, od->lngstr[datefmt2_], od->outstyle,
			  starttimec / 1440, (starttimec % 1440) / 60,
			  starttimec % 60, 0, 0,
			  NULL, NULL, 0, 0, od->compsep, TRUE));
    else
      fprintf(outf, "%s %s.\n", od->lngstr[progstart_],
	      datesprintf(datestr, od->lngstr[datefmt2_], od->outstyle,
			  starttimec / 1440, (starttimec % 1440) / 60,
			  starttimec % 60, 0, 0,
			  od->monthname, od->dayname, 0, 0, NULL, FALSE));
  if (od->outstyle == HTML)
    fputs("<br>", outf);
  }
  if (dman->firsttime <= dman->lasttime) {
    if (od->outstyle == COMPUTER) {
      fprintf(outf, "x%sFR%s%s\n", od->compsep, od->compsep,
	      datesprintf(datestr, od->lngstr[datefmt2_], od->outstyle,
			  dman->firsttime / 1440,
			  (dman->firsttime % 1440) / 60,
			  dman->firsttime % 60, 0, 0, NULL, NULL, 0, 0,
			  od->compsep, TRUE));
      fprintf(outf, "x%sLR%s%s\n", od->compsep, od->compsep,
	      datesprintf(datestr, od->lngstr[datefmt2_], od->outstyle,
			  dman->lasttime / 1440, (dman->lasttime % 1440) / 60,
			  dman->lasttime % 60, 0, 0, NULL, NULL, 0, 0,
			  od->compsep, TRUE));
    }
    else {
      mprintf(outf, od->pagewidth, "%s %s ", od->lngstr[reqstart_],
	      datesprintf(datestr, od->lngstr[datefmt2_], od->outstyle,
			  dman->firsttime / 1440,
			  (dman->firsttime % 1440) / 60,
			  dman->firsttime % 60, 0, 0, od->monthname,
			  od->dayname, 0, 0, NULL, FALSE));
      mprintf(outf, od->pagewidth, "%s %s", od->lngstr[to_],
	      datesprintf(datestr, od->lngstr[datefmt2_], od->outstyle,
			  dman->lasttime / 1440, (dman->lasttime % 1440) / 60,
			  dman->lasttime % 60, 0, 0, od->monthname,
			  od->dayname, 0, 0, NULL, FALSE));
      t0 = (dman->lasttime - dman->firsttime) / 1440.0 + 0.005;
      t1 = (int)t0;
      t2 = (int)(100 * (t0 - (double)t1));
      mprintf(outf, od->pagewidth, " (%d%c%02d %s).", t1, od->decpt, t2,
	      od->lngstr[days_]);
      mprintf(outf, 0, NULL);
    }
  }
  if (od->outstyle == HTML && od->gotos == FEW)
    gotos(outf, INT_MAX, od->repq, od->lngstr, od->reporder);
  hrule(outf, od->outstyle, od->pagewidth);
}

void pagebot(FILE *outf, Outchoices *od) {
  extern time_t origstarttime;
  time_t stoptime;
  long secs;

  if (od->outstyle == HTML)
    fprintf(outf, "<i>%s <a HREF=\"%s\">analog %s</a>.\n", od->lngstr[credit_],
	    ANALOGURL, VNUMBER);
  else if (od->outstyle == ASCII)
    fprintf(outf, "%s analog %s.\n", od->lngstr[credit_], VNUMBER);
  if (od->runtime && (od->outstyle == HTML || od->outstyle == ASCII)) {
    if (od->outstyle == HTML)
      fprintf(outf, "<br><b>%s:</b> ", od->lngstr[runtime_]);
    else
      fprintf(outf, "%s: ", od->lngstr[runtime_]);
    time(&stoptime);
    secs = (long)difftime(time((time_t *)NULL), origstarttime);
    if (secs == 0)
      fprintf(outf, "%s %s.\n", od->lngstr[lessone_], od->lngstr[second_]);
    else if (secs < 60)
      fprintf(outf, "%ld %s.\n", secs, 
	      (secs == 1)?(od->lngstr[second_]):(od->lngstr[seconds_]));
    else
      fprintf(outf, "%ld %s, %ld %s.\n", secs / 60,
	      (secs < 120)?(od->lngstr[minute_]):(od->lngstr[minutes_]),
	      secs % 60,
	      (secs % 60 == 1)?(od->lngstr[second_]):(od->lngstr[seconds_]));
  }
  if (od->outstyle == HTML) {
    fputs("</i>\n", outf);
    if (od->gotos != FALSE)
      gotos(outf, INT_MAX, od->repq, od->lngstr, od->reporder);
  }
  if (!strcaseeq(od->footerfile, "none"))
    include_file(outf, od->outstyle, od->footerfile, 'f', od->pagewidth,
		 &(od->html));
  if (od->outstyle == HTML && od->html) {
    fputs("<p><a href=\"http://validator.w3.org/\">\n", outf);
    fputs("<img src=\"", outf);
    htmlfprintf(outf, od->outstyle, FALSE, od->imagedir, &(od->html), FALSE);
    fputs("html2.gif\"\n", outf);
    fputs("alt=\"HTML 2.0 Conformant!\"></a>\n", outf);
  }
  if (od->outstyle == HTML)
    fputs("</body>\n</html>\n", outf);
}

/* Print "goto"s. Assume outstyle == HTML and want-gotos already tested. */
void gotos(FILE *outf, int code, logical *repq, char **lngstr,
	   choice *reporder) {
  extern char *anchorname[];
  int i;

  fprintf(outf, "<p>(<b>%s</b>", lngstr[goto_]);
  fprintf(outf, ": <a HREF=\"#Top\">%s</a>", lngstr[top_]);
  for (i = 0; reporder[i] != -1; i++) {
    if (reporder[i] == code)
      fprintf(outf, "%s %s", lngstr[colon_], lngstr[rep2lng[reporder[i]]]);
    else if (repq[reporder[i]])
      fprintf(outf, "%s <a HREF=\"#%s\">%s</a>", lngstr[colon_],
	      anchorname[reporder[i]], lngstr[rep2lng[reporder[i]]]);
  }
  fputs(")\n", outf);
}

void report_title(FILE *outf, char *name, int code, choice gotoq,
		  logical *repq, char **lngstr, choice *reporder,
		  choice outstyle) {
  extern char *anchorname[];

  if (outstyle == HTML) {
    fprintf(outf, "<h2><a NAME=\"%s\">%s</a></h2>\n", anchorname[code],
	    name);
    if (gotoq == TRUE)
      gotos(outf, code, repq, lngstr, reporder);
  }
  else if (outstyle == ASCII) {
    fprintf(outf, "%s\n", name);
    matchlength(outf, outstyle, name, '-');
    fputc('\n', outf);
  }
}

size_t htmlstrlen(char *s, choice outstyle) {
  /* Assume string contains no &'s except as markup */
  /* NB This may not work well for multibyte character sets? */
  logical f;
  size_t i;

  if (outstyle != HTML)
    return(strlen(s));
  for (f = TRUE, i = 0; *s != '\0'; s++) {
    if (*s == '&')
      f = FALSE;
    else if (*s == ';')
      f = TRUE;
    if (f)
      i++;
  }
  return(i);
}

void matchlength(FILE *outf, choice outstyle, char *s, char c) {
  size_t i;

  for (i = htmlstrlen(s, outstyle); i > 0; i--)
    myputc(outf, c, outstyle);
}

void myputc(FILE *outf, char c, choice outstyle) {
  if (outstyle != HTML)
    putc(c, outf);
  else if (c == '<')
    fputs("&lt;", outf);
  else if (c == '>')
    fputs("&gt;", outf);
  else if (c == '&')
    fputs("&amp;", outf);
  else if (c == '"')
    fputs("&quot;", outf);
  else
    putc(c, outf);
}

/* quicker than using myputc() */
void htmlfprintf(FILE *outf, choice outstyle, logical multibyte, char *s,
		 logical *html, logical userinput) {
  char w1[64];
  char *c;
  char *w = w1;
  int len = 0;

  if (outstyle != HTML || multibyte)
    fputs(s, outf);
  else {
    for (c = s; *c != '\0'; c++) {
      if (*c == '<') {
	PUTs(w, "&lt;", 0);
	len += 4;
      }
      else if (*c == '>') {
	PUTs(w, "&gt;", 0);
	len += 4;
      }
      else if (*c == '&') {
	PUTs(w, "&amp;", 0);
	len += 5;
      }
      else if (*c == '"') {
	PUTs(w, "&quot;", 0);
	len += 6;
      }
      else if (userinput && *c == '\\' && *(c + 1) != '\0') {
	*html = FALSE;
	PUTc(w, *(++c));
	len += 1;
      }
      else {
	PUTc(w, *c);
	len += 1;
      }
      if (len > 57) {
	*w = '\0';
	fputs(w1, outf);
	w = w1;
	len = 0;
      }
    }
    *w = '\0';
    fputs(w1, outf);
  }
}

void escfprintf(FILE *outf, char *name) {
  /* Escape names for use in hyperlinks. As with htmlfprintf(), don't try and
     print character by character. Assume outstyle == HTML already tested. */
#ifdef EBCDIC
  extern unsigned char os_toascii_strictly[];
#endif
  char w1[64];
  char *w = w1;
  int len = 0;

  for ( ; *name != '\0'; name++) {
    if (ISALNUM(*name) || *name == '/' || *name == '.' || *name == ':' ||
	*name == '-' || *name == '~' || *name == '_' || *name == '?' ||
	*name == '%' || *name == '=' || *name == '+' ||
	*name == ';' ||	*name == '@' || *name == '$' || *name == ',') {
      /* All reserved and some unreserved chars from RFC 2396 Sec 2. */
      /* Reserved chars are not escaped because if they are in the logfile they
	 must have their special meanings (path delimiters etc.), and escaping
	 them would change the semantics of the URL. */
      PUTc(w, *name);
      len += 1;
    }
    else if (*name == '&') {
      PUTs(w, "&amp;", 0);
      len += 5;
    }
    else {
#ifdef EBCDIC
      sprintf(w, "%%%.2X", os_toascii_strictly[*name]);
#else
      sprintf(w, "%%%.2X", (unsigned char)(*name));
#endif
      w += 3;
      len += 3;
    }
    if (len > 58) {
      *w = '\0';
      fputs(w1, outf);
      w = w1;
      len = 0;
    }
  }
  *w = '\0';
  fputs(w1, outf);
}

void hrule(FILE *outf, choice outstyle, unsigned int pagewidth) {
  unsigned int i;

  if (outstyle == HTML)
    fputs("<hr>\n", outf);
  else if (outstyle == ASCII) {
    for (i = 0; i < pagewidth; i++)
      putc('-', outf);
    fputs("\n\n", outf);
  }
}

void include_file(FILE *outf, choice outstyle, char *name, char type,
		  unsigned int pagewidth, logical *html) {
  extern char *block_start, *block_end;
  FILE *inf;

  if ((inf = my_fopen(name, (type == 'h')?"header file":"footer file")) !=
      NULL) {
    *html = FALSE;
    if (type == 'f' || outstyle == HTML)
      hrule(outf, outstyle, pagewidth);
    while (getmoredata(inf, block_start, BLOCKSIZE) != EOF)
      fwrite((void *)block_start, 1,
		   (size_t)((char *)block_end - (char *)block_start), outf);
    if (type == 'h')
      hrule(outf, outstyle, pagewidth);
    (void)my_fclose(inf, name, (type == 'h')?"header file":"footer file");
  }
}

/*** Date printing routine ***/

unsigned int datefmtlen(char *fmt, unsigned int monthlen, unsigned int daylen,
			char *compsep, logical allowmonth) {
  /* Assume no HTML accents in fmt. */
  unsigned int i = 0;
  char *c;

  for (c = fmt; *c != '\0'; c++) {
    if (*c == '%' && *(c + 1) != '\0') {
      c++;
      if (*c == '%')
	i += 1;
      else if (*c == 'd' || *c == 'D' || *c == 'y' || *c == 'h' || *c == 'H' ||
	       *c == 'i' || *c == 'I' || *c == 'n' || *c == 'o')
	i += 2;
      else if (*c == 'Y')
	i += 4;
      else if (*c == 'm')
	i += monthlen;
      else if (*c == 'w')
	i += daylen;
      else if (*c == '\b')
	i += (compsep == NULL)?0:strlen(compsep);
      else if (*c == 'M' && allowmonth)
	i += 2;
    }
    else
      i += 1;
  }
  return(i);
}

char *datesprintf(char *ans, char *fmt, choice outstyle, datecode_t date,
		  unsigned int hr,
		  unsigned int min, unsigned int newhr, unsigned int newmin,
		  char **monthname, char **dayname, unsigned int monthlen,
		  unsigned int daylen, char *compsep, logical allowmonth) {
  /* Puts date into ans. Calling fn. must ensure enough space in ans. */
  /* monthlen & daylen should be set to 0 in running (not formatted) text. */
  /* outstyle == OUT_NONE is used when a non-output function calls this one. */
  unsigned int d, m, y, i;
  char *s, *c;

  s = ans;
  if (date == 0) {
    if (outstyle == COMPUTER && compsep != NULL) {
      for (i = chrn(fmt, '\b'); i > 0; i--)
	PUTs(s, compsep, 0);
    }
    *s = '\0';
    return(ans);
  }
  code2date(date, &d, &m, &y);
  for (c = fmt; *c != '\0'; c++) {
    if (*c == '%' && *(c + 1) != '\0') {
      c++;
      switch (*c) {
      case '%':
	PUTc(s, '%');
	break;
      case 'd':
	PUT2d(s, d);
	break;
      case 'D':
	PUT02d(s, d);
	break;
      case 'm':
	if (monthname != NULL)
	  PUTs(s, monthname[m],    /* assume contains no &'s in not HTML */
	       (int)monthlen - (int)htmlstrlen(monthname[m], HTML));
	break;
      case 'M':
	if (allowmonth)
	  PUT02d(s, m + 1);
	break;
      case '\b':  /* \b only used internally */
	if (compsep != NULL)
	  PUTs(s, compsep, 0);
	break;
      case 'y':
	PUT02d(s, y % 100);
	break;
      case 'Y':
	PUT04d(s, y);
	break;
      case 'h':
	PUT2d(s, hr);
	break;
      case 'H':
	PUT02d(s, hr);
	break;
      case 'i':
	PUT2d(s, newhr);
	break;
      case 'I':
	PUT02d(s, newhr);
	break;
      case 'n':
	PUT02d(s, min);
	break;
      case 'o':
	PUT02d(s, newmin);
	break;
      case 'w':
	if (dayname != NULL)
	  PUTs(s, dayname[DAYOFWEEK(date)],
	       (int)daylen - (int)htmlstrlen(dayname[DAYOFWEEK(date)], HTML));
	break;
      }
    }
    else
      PUTc(s, *c);
  }
  *s = '\0';
  return(ans);
}

int f3printf(FILE *outf, choice outstyle, double x, unsigned int width,
	     char sepchar) {
  /* return number of characters printed, but counting e.g. &amp; as one */
  int ans, i;

  x += EPSILON;   /* just to make sure rounding down works OK */
  if (sepchar == '\0')
    return(fprintf(outf, "%*.0f", width, x));

  for (i = 0; x >= 1000; i++)
    x /= 1000;  /* find out how big x is to get number of leading spaces */
  ans = fprintf(outf, "%*d", MAX((int)width - 4 * i, 0), (int)x);
  ans += 4 * i;
  /* now run down again, printing each clump */
  for ( ; i > 0; i--) {
    myputc(outf, sepchar, outstyle);
    x -= (int)x;
    x *= 1000;
    fprintf(outf, "%03d", (int)x);
  }
  return(ans);
}

void printbytes(FILE *outf, choice outstyle, double bytes, unsigned int bmult,
		unsigned int width, char sepchar, char decpt) {
  int by1, by2;
  unsigned int j;

  if (bmult == 0)
    (void)f3printf(outf, outstyle, bytes, width, sepchar);
  else {
    for (j = 0; j < bmult; j++)
      bytes /= 1024;
    by1 = (int)bytes;
    by2 = (int)(1000 * (bytes - (double)by1));
    width = MAX(width, 3);
    fprintf(outf, "%*d", width - 4, by1);
    myputc(outf, decpt, outstyle);
    fprintf(outf, "%03d", by2);
  }
}

void doublemprintf(FILE *outf, choice outstyle, unsigned int pagewidth,
		   double x, char decpt) {
  unsigned int prec;
  double d;

  /* first calculate how many decimal places we need */

  for (prec = 0, d = x - (double)((int)(x));
       d - (double)((int)(d + 0.000005)) > 0.00001; d *= 10)
    prec++;

  /* now print it */

  if (pagewidth == 0 || outstyle == HTML) {  /* just fprintf not mprintf */
    if (prec > 0) {
      fprintf(outf, "%d", (int)x);
      myputc(outf, decpt, outstyle);
      fprintf(outf, "%0*d", prec, (int)(d + EPSILON));
    }
    else
      fprintf(outf, "%d", (int)(x + EPSILON));
  }
  else if (prec > 0)
    mprintf(outf, pagewidth, "%d%c%0*d", (int)x, decpt, prec,
	    (int)(d + EPSILON));
  else
    mprintf(outf, pagewidth, "%d", (int)(x + EPSILON));
}


double findunit(double n, unsigned int pagewidth, unsigned int width[],
		unsigned int mingraphwidth, choice *cols) {
  int w;
  double unit;
  int c;
  int i, j;

  w = (int)pagewidth - (int)width[COL_TITLE] - 2;
  for (c = 0; cols[c] != COL_NUMBER; c++)
    w -= (int)width[cols[c]] + 2;
  w = MAX(w, (int)mingraphwidth);
  /* unit must be nice amount: i.e., {1, 1.5, 2, 2.5, 3, 4, 5, 6, 8} * 10^n */
  unit = ((n - 1) / (double)w);
  j = 0;
  while (unit > 24.) {
    unit /= 10.;
    j++;
  }
  unit = (double)((int)unit);
  if (unit == 6.)
    unit = 7.;
  else if (unit == 8.)
      unit = 9.;
  else if (unit >= 20.)
    unit = 24.;
  else if (unit >= 15.)
    unit = 19.;
  else if (unit >= 10.)
    unit = 14.;
  unit += 1.;
  for (i = 0; i < j; i++) {
    unit *= 10.;
  }
  return(unit);
}

void calcsizes(choice outstyle, unsigned int width[], unsigned int *bmult,
	       double *unit, unsigned long maxr, unsigned long maxp,
	       double maxb, unsigned long howmany, unsigned int pagewidth,
	       unsigned int mingraphwidth, char graphby, char repsepchar,
	       logical rawbytes, choice *cols, unsigned int monthlen,
	       unsigned int daylen, char **lngstr) {
  /* width[COL_TITLE] should be set before calling this function */
  /* 0 signifies that the title is last and this function should calculate the
     remaining width */
  extern unsigned int *col2colhead;
  int w;
  unsigned int i;

  if (outstyle == COMPUTER) {
    width[COL_REQS] = 0;
    width[COL_PAGES] = 0;
    width[COL_BYTES] = 0;
    width[COL_PREQS] = 0;
    width[COL_PPAGES] = 0;
    width[COL_PBYTES] = 0;
    width[COL_DATE] = 0;
    width[COL_TIME] = 0;
    width[COL_INDEX] = 0;
    width[COL_TITLE] = 0;
    *bmult = 0;
  }
  else {
    width[COL_REQS] = MAX(LEN3(log10i(maxr) + 1, repsepchar),
			  htmlstrlen(lngstr[col2colhead[COL_REQS]], outstyle));
    width[COL_PAGES] = MAX(LEN3(log10i(maxp) + 1, repsepchar),
			   htmlstrlen(lngstr[col2colhead[COL_PAGES]],
				      outstyle));
    if (rawbytes || maxb < 1024.0) {
      width[COL_BYTES] = MAX(LEN3(log10x(maxb) + 1, repsepchar),
			     htmlstrlen(lngstr[col2colhead[COL_BYTES]],
					outstyle));
      *bmult = 0;
    }
    else {
      width[COL_BYTES] = MAX(7, htmlstrlen(lngstr[col2colhead[COL_BYTES]],
					   outstyle) + 1);
      *bmult = findbmult(maxb);             /* +1 for k, M etc. prefix */
    }
    width[COL_PREQS] = MAX(6, htmlstrlen(lngstr[col2colhead[COL_PREQS]],
					 outstyle));
    width[COL_PPAGES] = MAX(6, htmlstrlen(lngstr[col2colhead[COL_PPAGES]],
					  outstyle));
    width[COL_PBYTES] = MAX(6, htmlstrlen(lngstr[col2colhead[COL_PBYTES]],
					  outstyle));
    width[COL_DATE] = MAX(datefmtlen(lngstr[genrepdate_], monthlen, daylen,
				     NULL, FALSE),
			  htmlstrlen(lngstr[col2colhead[COL_DATE]], outstyle));
    width[COL_TIME] = MAX(datefmtlen(lngstr[genreptime_], monthlen, daylen,
				     NULL, FALSE),
			  htmlstrlen(lngstr[col2colhead[COL_TIME]], outstyle));
    width[COL_INDEX] = MAX(LEN3(log10i(howmany) + 1, repsepchar),
			   htmlstrlen(lngstr[col2colhead[COL_INDEX]],
				      outstyle));
    if (*unit == 0) {  /* unit != 0 is used as a marker for not a timegraph */
      if (graphby == 'R' || graphby == 'r')
	*unit = findunit((double)maxr, pagewidth, width, mingraphwidth, cols);
      else if (graphby == 'P' || graphby == 'p')
	*unit = findunit((double)maxp, pagewidth, width, mingraphwidth, cols);
      else {
	for (i = 0; i < *bmult; i++)
	  maxb /= 1024;
	if (*bmult > 0)
	  maxb *= 1000;
	*unit = findunit(maxb, pagewidth, width, mingraphwidth, cols);
	if (*bmult > 0)
	  *unit /= 1000;
      }
    }
    if (width[COL_TITLE] == 0) {
      w = (int)pagewidth;
      for (i = 0; cols[i] != COL_NUMBER; i++)
	w -= (int)width[cols[i]] + 2;
      width[COL_TITLE] = (unsigned int)MAX(0, w);
    }
  }
}

unsigned int alphatreewidth(choice rep, Hashtable *tree, choice outstyle,
			    unsigned int level, Strlist *partname, 
			    Alias *aliashead) {
  /* Calculate width needed for Organisation Report.
     Constructing the name is basically the same code as printtree(). */
  extern char *workspace;   /* see top of alias.c */

  char *name;
  size_t need = (size_t)level + 3;
  Strlist *pn, s;
  Hashindex *p;
  unsigned int tw = 0, tmp;

  if (tree == NULL || tree->head[0] == NULL)
    return(0);
  for (p = tree->head[0]; p != NULL; TO_NEXT(p)) {
    name = maketreename(partname, p, &pn, &s, need, rep, TRUE);
    if (name[0] != '\0' && name[1] != '\0') {   /* i.e. ignore special */
      strcpy(workspace, name);                  /* left-aligned codes */
      do_aliasx(workspace, aliashead);
      /* Only use do_aliasx() not do_aliasrep(), because it's just for
	 measuring width. */
      tmp = htmlstrlen(workspace, outstyle) + 2 * level;
                       /* will be printed with 2 trailing spaces per level */
      tw = MAX(tw, tmp);
      tmp = alphatreewidth(rep, (Hashtable *)(p->other), outstyle, level + 1,
			   pn, aliashead);
      tw = MAX(tw, tmp);
      /* The second tmp will of course be bigger unless there are aliases
	 (if there are any children at all). */
    }
  }
  return(tw);
}

void declareunit(FILE *outf, choice outstyle, unsigned int pagewidth,
		 char graphby, double unit, unsigned int bmult, char barstyle,
		 char markchar, char sepchar, char decpt, char *imagedir,
		 char **lngstr) {
  /* NB Number, or string kbytes, can still overflow PAGEWIDTH, but only if
     PAGEWIDTH is small, and will wrap straight after. PAGEWIDTH is never
     guaranteed anyway. */
  extern char *byteprefix;
  extern unsigned int ppcol;

  if (outstyle == HTML || outstyle == ASCII) {
    if (outstyle == HTML)
      fputs("<p>\n", outf);
    mprintf(outf, pagewidth, "%s (", lngstr[eachunit_]);
    if (outstyle == ASCII)
      mprintf(outf, pagewidth, "%c", markchar);
    else if (ISLOWER(graphby))
      mprintf(outf, pagewidth, "<tt>%c</tt>", markchar);
    else
      mprintf(outf, pagewidth, "<img src=\"%sbar%c1.gif\" alt=\"%c\">",
	      imagedir, barstyle, markchar);
    mprintf(outf, pagewidth, ") %s ", lngstr[represents_]);
    if (graphby == 'R' || graphby == 'r') {
      ppcol += f3printf(outf, outstyle, unit, 0, sepchar);
      mprintf(outf, pagewidth, " ");
      if (unit == 1.)
	mprintf(outf, pagewidth, "%s.", lngstr[request_]);
      else
	mprintf(outf, pagewidth, "%s %s.", lngstr[requests_],
		lngstr[partof_]);
    }
    else if (graphby == 'P' || graphby == 'p') {
      ppcol += f3printf(outf, outstyle, unit, 0, sepchar);
      mprintf(outf, pagewidth, " ");
      if (unit == 1.)
	mprintf(outf, pagewidth, "%s.", lngstr[pagereq_]);
      else
	mprintf(outf, pagewidth, "%s %s.", lngstr[pagereqs_],
		lngstr[partof_]);
    }
    else {
      if (bmult > 0) {
	doublemprintf(outf, outstyle, pagewidth, unit, decpt);
	mprintf(outf, pagewidth, " ");
	mprintf(outf, pagewidth, "%c", byteprefix[bmult]);
      }
      else {
	ppcol += f3printf(outf, outstyle, unit, 0, sepchar);
	mprintf(outf, pagewidth, " ");
      }
      mprintf(outf, pagewidth, "%s %s.", lngstr[bytes_], lngstr[partof_]);
    }
    mprintf(outf, 0, NULL);
  }
}

void whatincluded(FILE *outf, choice rep, choice outstyle, unsigned long n,
		  unsigned int pagewidth, Floor *floor, choice sortby,
		  char decpt, char *compsep, char *gens, char *genp,
		  char gender, choice requests, choice date,
		  timecode_t firsttime, char **monthname, char **dayname,
		  unsigned int monthlen, unsigned int daylen, char **lngstr) {
  extern unsigned int *method2sing, *method2pl, *method2date, *method2pc;
  extern unsigned int *method2relpc, *method2sort;
  extern char repcodes[];
  int firsts, firstds, alls, sorted, alphsort, unsort;
  char *datestr;
  unsigned long temp = 0;
  timecode_t tempd;

  if (outstyle == HTML || outstyle == ASCII) {
    if (gender == 'm') {
      firsts = firstsm_;
      firstds = firstdsm_;
      alls = allsm_;
      sorted = sortedm_;
      alphsort = STREQ(gens, lngstr[codegs_])?numsortm_:alphasortm_;
      unsort = unsortedm_;            /* quickest kludge for only one report */
    }
    else if (gender == 'f') {
      firsts = firstsf_;
      firstds = firstdsf_;
      alls = allsf_;
      sorted = sortedf_;
      alphsort = STREQ(gens, lngstr[codegs_])?numsortf_:alphasortf_;
      unsort = unsortedf_;
    }
    else { /* gender == 'n' */
      firsts = firstsn_;
      firstds = firstdsn_;
      alls = allsn_;
      sorted = sortedn_;
      alphsort = STREQ(gens, lngstr[codegs_])?numsortn_:alphasortn_;
      unsort = unsortedn_;
    }

    /* see also report_floor() in settings.c */
    if (outstyle == HTML)
      fputs("<p>\n", outf);
    if (floor->min < 0 && n < (unsigned long)(-floor->min + EPSILON))
      floor->min = 0;  /* not enough items for requested -ve floor */
    if (floor->min < 0) {
      temp = (unsigned long)(-floor->min + EPSILON);
      if (temp == 1)
	mprintf(outf, pagewidth, lngstr[firsts], gens);
      else
	mprintf(outf, pagewidth, lngstr[firstds], temp, genp);
      mprintf(outf, pagewidth, " %s ", lngstr[floorby_]);
      if (floor->floorby == REQUESTS)
	mprintf(outf, pagewidth, lngstr[method2sort[requests]]);
      else if (floor->floorby == DATESORT)
	mprintf(outf, pagewidth, lngstr[method2sort[date]]);
      else
	mprintf(outf, pagewidth, lngstr[method2sort[floor->floorby]]);
    }
    else {   /* floor->min >= 0 */
      mprintf(outf, pagewidth, lngstr[alls], genp);
      if (floor->floorby == DATESORT) {
	tempd = (timecode_t)(floor->min + EPSILON);
	if (tempd > firsttime) {
	  mprintf(outf, pagewidth, " %s ", lngstr[method2date[date]]);
	  datestr = (char *)xmalloc((size_t)datefmtlen(lngstr[whatincfmt_],
						       monthlen, daylen, NULL,
						       FALSE) + 1);
	  mprintf(outf, pagewidth,
		  datesprintf(datestr, lngstr[whatincfmt_], outstyle,
			      tempd / 1440,  (tempd % 1440) / 60, tempd % 60,
			      0, 0, monthname, dayname, 0, 0, NULL, FALSE));
	  free((void *)datestr);
	}
      }
      else if (floor->min > EPSILON) {
	mprintf(outf, pagewidth, " %s ", lngstr[atleast_]);
	if (floor->qual == '\0') {
	  temp = (unsigned long)(floor->min + EPSILON);
	  mprintf(outf, pagewidth, "%lu ", temp);
	  if (floor->floorby == REQUESTS)
	    mprintf(outf, pagewidth, (temp == 1)?lngstr[method2sing[requests]]:
		    lngstr[method2pl[requests]]);
	  else
	    mprintf(outf, pagewidth, (temp == 1)?
		    lngstr[method2sing[floor->floorby]]:
		    lngstr[method2pl[floor->floorby]]);
	}
	else {  /* floor->qual != '\0' */
	  doublemprintf(outf, outstyle, pagewidth, floor->min, decpt);
	  if (floor->qual == '%') {
	    if (floor->floorby == REQUESTS)
	      mprintf(outf, pagewidth, lngstr[method2pc[requests]]);
	    else
	      mprintf(outf, pagewidth, lngstr[method2pc[floor->floorby]]);
	  }
	  else if (floor->qual == ':') {
	    if (floor->floorby == REQUESTS)
	      mprintf(outf, pagewidth, lngstr[method2relpc[requests]]);
	    else
	      mprintf(outf, pagewidth, lngstr[method2relpc[floor->floorby]]);
	  }
	  else { /* if qual is anything else, must be (k|M|G|T)bytes */
	    mprintf(outf, pagewidth, " ");
	    mprintf(outf, pagewidth, "%c%s", floor->qual, lngstr[bytes_]);
	  }
	}   /* end floor->qual != '\0' */
      }     /* end floor->min > EPSILON */
    }       /* end floor->min > 0 */
    /* That completes the floor; now we are just left with the sortby */
    if (floor->min >= 0 || temp != 1) { /* else only one item, so no sort */
      if (floor->min < 0 && sortby == RANDOM)
	sortby = floor->floorby;
      mprintf(outf, pagewidth, ", ");
      if (sortby == ALPHABETICAL)
	mprintf(outf, pagewidth, lngstr[alphsort]);
      else if (sortby == RANDOM)
	mprintf(outf, pagewidth, lngstr[unsort]);
      else {
	mprintf(outf, pagewidth, lngstr[sorted]);
	mprintf(outf, pagewidth, " ");
	if (sortby == REQUESTS)
	  mprintf(outf, pagewidth, lngstr[method2sort[requests]]);
	else if (sortby == DATESORT)
	  mprintf(outf, pagewidth, lngstr[method2sort[date]]);
	else
	  mprintf(outf, pagewidth, lngstr[method2sort[sortby]]);
      }
    }
    mprintf(outf, pagewidth, ".");
    mprintf(outf, 0, NULL);
  }
  else {  /* outstyle == COMPUTER */
    fprintf(outf, "%c%sf%s", repcodes[rep], compsep, compsep);
    if (floor->min < 0)
      fprintf(outf, "-%lu", (unsigned long)(-floor->min + EPSILON));
    else if (floor->floorby == DATESORT) {
      datestr = (char *)xmalloc((size_t)datefmtlen("%Y%M%D:%H%n", monthlen,
						   daylen, NULL, TRUE) + 1);
      tempd = (timecode_t)(floor->min + EPSILON);
      fputs(datesprintf(datestr, "%Y%M%D:%H%n", outstyle, tempd / 1440,
			(tempd % 1440) / 60, tempd % 60, 0, 0, monthname,
			dayname, 0, 0, NULL, TRUE), outf);
      free((void *)datestr);
    }
    else if (floor->qual == '\0')
      fprintf(outf, "%lu", (unsigned long)(floor->min + EPSILON));
    else
      fprintf(outf, "%f", floor->min);
    if (floor->qual != '\0')
      putc(floor->qual, outf);
    if (floor->floorby == REQUESTS)
      putc('R', outf);
    else if (floor->floorby == PAGES)
      putc('P', outf);
    else if (floor->floorby == BYTES)
      putc('B', outf);
    else /* floor->floorby == DATESORT */
      putc('D', outf);

    /* now the sortby */
    if (floor->min < 0 && sortby == RANDOM)
      sortby = floor->floorby;
    fprintf(outf, "%s", compsep);
    if (sortby == ALPHABETICAL)
      putc('a', outf);
    else if (sortby == BYTES)
      putc('b', outf);
    else if (sortby == DATESORT)
      putc('d', outf);
    else if (sortby == PAGES)
      putc('p', outf);
    else if (sortby == REQUESTS)
      putc('r', outf);
    else if (sortby == RANDOM)
      putc('x', outf);
    putc('\n', outf);
  }
}

void busyprintf(FILE *outf, choice outstyle, char *datefmt,
		unsigned long reqs, unsigned long pages, double bys,
		datecode_t date, unsigned int hr, unsigned int min,
		unsigned int newhr, unsigned int newmin, char graphby,
		char sepchar, char decpt, logical rawbytes, char **lngstr,
		char **monthname, char **dayname, unsigned int monthlen,
		unsigned int daylen, char *busystr) {
  extern char *byteprefix;
  char *datestr;
  unsigned int bmult;

  if (outstyle == ASCII || outstyle == HTML) {
    if (outstyle == ASCII)
      putc('\n', outf);
    datestr = (char *)xmalloc((size_t)datefmtlen(datefmt, monthlen, daylen,
						 NULL, FALSE) + 1);
    fprintf(outf, "%s %s (", busystr,
	    datesprintf(datestr, datefmt, outstyle, date, hr, min,
			(newmin == 0)?(hr + 1):hr, (unsigned int)newmin,
			monthname, dayname, 0, 0, NULL, FALSE));
    free((void *)datestr);
    if (graphby == 'R' || graphby == 'r') {
      f3printf(outf, outstyle, (double)reqs, 0, sepchar);
      fprintf(outf, " %s).\n", (reqs == 1)?lngstr[request_]:lngstr[requests_]);
    }
    else if (graphby == 'P' || graphby == 'p') {
      f3printf(outf, outstyle, (double)pages, 0, sepchar);
      fprintf(outf, " %s).\n",
	      (pages == 1)?lngstr[pagereq_]:lngstr[pagereqs_]);
    }
    else /* graphby == 'B' or 'b' */ {
      if (rawbytes)
	bmult = 0;
      else
	bmult = findbmult(bys);
      printbytes(outf, outstyle, bys, bmult, 0, sepchar, decpt);
      putc(' ', outf);
      if (bmult >= 1)
	putc(byteprefix[bmult], outf);
      fprintf(outf, "%s).\n", lngstr[bytes_]);
    }
  }
}

void pccol(FILE *outf, choice outstyle, double n, double tot,
	   unsigned int width, char decpt) {
  double pc;
  unsigned int pc1, pc2;
  int i;

  if (outstyle == COMPUTER)
    fprintf(outf, "%.3f", (tot == 0)?0.0:(n * 100.0 / tot));
  else {
    for (i = 0; i < (int)width - 6; i++)
      putc(' ', outf);
    if (tot == 0)
      pc = 0.0;
    else
      pc = n * 10000.0 / tot;
    if (pc >= 9999.5)
      fputs("  100%", outf);
    else if (pc < 0.5)
      fputs("      ", outf);
    else {
      pc1 = ((int)(pc + 0.5)) / 100;
      pc2 = ((int)(pc + 0.5)) % 100;
      fprintf(outf, "%2d", pc1);
      myputc(outf, decpt, outstyle);
      fprintf(outf, "%02d%%", pc2);
    }
  }
}

void barchart(FILE *outf, choice outstyle, char graphby, unsigned long reqs,
	      unsigned long pages, double bys, double unit, char barstyle,
	      char markchar, char *imagedir) {
  int i, j;
  double x;
  int y;
  logical first = TRUE;

  if (graphby == 'P' || graphby == 'p')
    x = (double)pages - 0.5;
  else if (graphby == 'R' || graphby == 'r')
    x = (double)reqs - 0.5;
  else
    x = bys;
  x /= unit;
  x += 1;
  y = (int)x;
  if (ISLOWER(graphby) || outstyle != HTML) {
    for (i = 0; i < y; i++)
      myputc(outf, markchar, outstyle);
  }
  else {
    for (j = 32; j >= 1; j /= 2) {
      while (y >= j) {
	fprintf(outf, "<img src=\"%sbar%c%d.gif\" alt=\"", imagedir,
		barstyle, j);
	if (first) {
	  for (i = 0; i < y; i++)
	    myputc(outf, markchar, outstyle);
	  first = FALSE;
	}
	fputs("\">", outf);
	y -= j;
      }
    }
  }
}

void colheads(FILE *outf, choice *cols, choice outstyle, unsigned int width[],
	      unsigned int bmult, char *name, logical name1st, char **lngstr) {
  extern unsigned int *col2colhead;
  extern char *byteprefix;
  int len;
  unsigned int c, i;

  if (outstyle == HTML || outstyle == ASCII) {
    if (name1st)
      fprintf(outf, "%*s: ", width[COL_TITLE] + strlen(name)
	      - htmlstrlen(name, outstyle), name);
    for (c = 0; cols[c] != COL_NUMBER; c++) {
      if (cols[c] == COL_BYTES) {
	len = (int)htmlstrlen(lngstr[col2colhead[cols[c]]], outstyle) +
	  (int)(bmult != 0);
	for (i = width[cols[c]] - len; i > 0; i--)
	  putc(' ', outf);
	if (bmult != 0)
	  putc(byteprefix[bmult], outf);
	fprintf(outf, "%s: ", lngstr[col2colhead[cols[c]]]);
      }
      else
	fprintf(outf, "%*s: ", width[cols[c]]
		+ strlen(lngstr[col2colhead[cols[c]]])
		- htmlstrlen(lngstr[col2colhead[cols[c]]], outstyle),
		lngstr[col2colhead[cols[c]]]);
    }
    if (!name1st)
      fputs(name, outf);
    putc('\n', outf);
    if (name1st) {
      for (i = 0; i < width[COL_TITLE]; i++)
	putc('-', outf);
      fputs(": ", outf);
    }
    for (c = 0; cols[c] != COL_NUMBER; c++) {
      for (i = width[cols[c]] ; i > 0; i--)
	putc('-', outf);
      fputs(": ", outf);
    }
    if (!name1st)
      matchlength(outf, outstyle, name, '-');
    fputc('\n', outf);
  }
}

void printcols(FILE *outf, choice rep, choice *cols, choice outstyle,
	       logical multibyte, unsigned long reqs, unsigned long pages,
	       double bys, long index, int level,
	       unsigned long totr, unsigned long totp, double totb,
	       unsigned int width[], unsigned int bmult, char graphby,
	       double unit, char barstyle, char markchar, char repsepchar,
	       char decpt, char *compsep, logical name1st, logical timegraph,
	       logical rightalign,
	       char *imagedir, logical *html, char *name, logical dateasname,
	       logical ispage, unsigned int spaces, Alias *aliashead,
	       Include *linkhead, char *baseurl, char *datefmt, char *timefmt,
	       datecode_t date, unsigned int hr,
	       unsigned int min, unsigned int newhr, unsigned int newmin,
	       char **monthname, char **dayname, unsigned int monthlen,
	       unsigned int daylen, unsigned int plainmonthlen,
	       unsigned int plaindaylen) {
  /* NB multibyte only used if name1st == FALSE; so not needed for time reps */
  /* 'level' is -1 for time reports, 0 for other non-hierarchical reports
     (although both are currently ignored). */
  /* name == NULL means calling function supplies name and new line (used for
     "not listed" lines). Use name == "" (and dateasname == TRUE) if wanting
     date instead of name. */
  extern char *workspace;   /* see top of alias.c */
  extern char repcodes[];
  static char *datestr = NULL;
  static size_t len = 0, need;
  int c, i;

  if (outstyle == COMPUTER) {
    fprintf(outf, "%c%s", repcodes[rep], compsep);
    if (level >= 1)
      putc('l', outf);
    for (c = 0; cols[c] != COL_NUMBER; c++) {
      switch(cols[c]) {
      case COL_REQS:
	putc('R', outf);
	break;
      case COL_PREQS:
	putc('r', outf);
	break;
      case COL_PAGES:
	putc('P', outf);
	break;
      case COL_PPAGES:
	putc('p', outf);
	break;
      case COL_BYTES:
	putc('B', outf);
	break;
      case COL_PBYTES:
	putc('b', outf);
	break;
      case COL_DATE:
	putc('d', outf);
	break;
      case COL_TIME:
	putc('D', outf);
	break;
      case COL_INDEX:
	putc('N', outf);
	break;
      }
    }
    fputs(compsep, outf);
  }
  else if (name1st) {
    if (dateasname) {
      need = datefmtlen(datefmt, plainmonthlen, plaindaylen, NULL, FALSE) + 1;
      ENSURE_LEN(datestr, len, need);
      name = datesprintf(datestr, datefmt, outstyle, date, hr, min, newhr,
			 newmin, monthname, dayname, monthlen, daylen, NULL,
			 FALSE);
    }
    for (i = (int)width[COL_TITLE] - (int)htmlstrlen(name, outstyle); i > 0;
	 i--)
      putc(' ', outf);
    htmlfprintf(outf, outstyle, multibyte, name, html, FALSE);
    fputs(": ", outf);
  }

  if (outstyle == COMPUTER && level >= 1)
    fprintf(outf, "%d%s", level, compsep);
  for (c = 0; cols[c] != COL_NUMBER; c++) {
    switch(cols[c]) {
    case COL_REQS:
      f3printf(outf, outstyle, (double)reqs, width[cols[c]], repsepchar);
      break;
    case COL_PREQS:
      pccol(outf, outstyle, (double)reqs, (double)totr, width[cols[c]],
	    decpt);
      break;
    case COL_PAGES:
      f3printf(outf, outstyle, (double)pages, width[cols[c]], repsepchar);
      break;
    case COL_PPAGES:
      pccol(outf, outstyle, (double)pages, (double)totp, width[cols[c]],
	    decpt);
      break;
    case COL_BYTES:
      printbytes(outf, outstyle, bys, bmult, width[cols[c]], repsepchar,
		 decpt);
      break;
    case COL_PBYTES:
      pccol(outf, outstyle, bys, totb, width[cols[c]], decpt);
      break;
    case COL_DATE:
    case COL_TIME:
      need = datefmtlen((cols[c] == COL_DATE)?datefmt:timefmt,
			plainmonthlen, plaindaylen, compsep,
			(logical)(outstyle == COMPUTER)) + 1;
      ENSURE_LEN(datestr, len, need);
      (void)datesprintf(datestr, (cols[c] == COL_DATE)?datefmt:timefmt,
			outstyle, date, hr, min, newhr, newmin,
			monthname, dayname, monthlen, daylen, compsep,
			(logical)(outstyle == COMPUTER));
      for (i = (int)width[cols[c]] - (int)htmlstrlen(datestr, outstyle);
	   i > 0; i--)
	putc(' ', outf);
      fprintf(outf, "%s", datestr);
      break;
    case COL_INDEX:
      if (index > 0)
	f3printf(outf, outstyle, (double)index, width[cols[c]], repsepchar);
      else for (i = (int)width[cols[c]]; i > 0; i--)
	putc(' ', outf);
      break;
    }
    if (outstyle == HTML || outstyle == ASCII)
      fputs(": ", outf);
    else
      fputs(compsep, outf);
  }

  if (timegraph && (outstyle == HTML || outstyle == ASCII)) {
      for (i = 0; i < (int)bmult; i++)
	bys /= 1024;
      barchart(outf, outstyle, graphby, reqs, pages, bys, unit, barstyle,
	       markchar, imagedir);
  }
  else if (!name1st) {
    if (name == NULL)
      return;   /* calling function supplies name and newline */
    strcpy(workspace, name);
    do_aliasrep(workspace, aliashead, multibyte);
    if (outstyle != COMPUTER) {
      if (rightalign)
	i = (int)width[COL_TITLE] - (int)htmlstrlen(workspace, outstyle) -
	  (int)spaces;
      else
	i = (int)spaces;
      for ( ; i > 0; i--)
	putc(' ', outf);
    }
    if (linkhead != NULL && outstyle == HTML &&
	included(name, ispage, linkhead)) {
      /* We link to the unaliased name, because the OUTPUTALIAS is usually in
	 the nature of an annotation. */
      fputs("<a href=\"", outf);
      if (baseurl != NULL)
	htmlfprintf(outf, outstyle, FALSE, baseurl, html, TRUE);
      escfprintf(outf, name);
      fputs("\">", outf);
      htmlfprintf(outf, outstyle, multibyte, workspace, html, TRUE);
      fputs("</a>", outf);
    }
    else
      htmlfprintf(outf, outstyle, multibyte, workspace, html, TRUE);
  }
  /* The previous htmlfprintf's have userinput = TRUE because of aliases,
     particularly accents in domains: cf do_aliasr(). Note also that for
     multibyte charsets, some necessary conversions may not take place.
     There's not much we can do about this because the source of the name may
     be a URL etc. which we should convert, but may be a lngstr, OUTPUTALIAS
     etc. which we shouldn't. We play conservative, not converting entities,
     and setting html == FALSE (in finalinit(), init.c). */
  else if (outstyle == COMPUTER) {
    if (dateasname) {
      need = datefmtlen(datefmt, plainmonthlen, plaindaylen, compsep, TRUE) + 1;
      ENSURE_LEN(datestr, len, need);
      name = datesprintf(datestr, datefmt, outstyle, date, hr, min, newhr,
			 newmin, monthname, dayname, monthlen, daylen, compsep,
			 TRUE);
    }
    fprintf(outf, "%s", name);
  }
  fputc('\n', outf);
}

void lastseven(FILE *outf, choice outstyle, timecode_t last7to, char *compsep,
	       char **monthname, char **dayname, unsigned int monthlen,
	       unsigned int daylen, char **lngstr) {
  char *datestr;

  datestr = (char *)xmalloc((size_t)datefmtlen(lngstr[datefmt1_], monthlen,
					       daylen, compsep,
					       (logical)(outstyle == COMPUTER))
			    + 1);
  if (outstyle == HTML)
    fputs("<p>", outf);
  if (outstyle == COMPUTER)
    fprintf(outf, "x%sE7%s%s\n", compsep, compsep,
	    datesprintf(datestr, lngstr[datefmt1_], outstyle, last7to / 1440,
			(last7to % 1440) / 60, last7to % 60, 0, 0, NULL,
			NULL, 0, 0, compsep, TRUE));
  else
    fprintf(outf, "(%s %s %s).\n", lngstr[brackets_],
	    lngstr[sevendaysto_],
	    datesprintf(datestr, lngstr[datefmt1_], outstyle, last7to / 1440,
			(last7to % 1440) / 60, last7to % 60, 0, 0,
			monthname, dayname, 0, 0, NULL, FALSE));
  free((void *)datestr);
}

/*** Now some stuff for the general summary ***/

void distcount(Hashindex *gooditems, Hashindex *baditems, unsigned long *tot,
	       unsigned long *tot7) {
  Hashindex *p;

  for (p = gooditems, *tot = 0, *tot7 = 0; p != NULL; TO_NEXT(p)) {
    if (p->own != NULL && p->own->data[REQUESTS] > 0) {
      (*tot)++;
      *tot7 += (unsigned long)(p->own->last7);
    }
  }
  for (p = baditems; p != NULL; TO_NEXT(p)) {
    if (p->own != NULL && p->own->data[REQUESTS] > 0) {
      (*tot)++;
      *tot7 += (unsigned long)(p->own->last7);
    }
  }
}

void gensumline(FILE *outf, choice outstyle, int namecode, unsigned long x,
		unsigned long x7, char sepchar, char *compsep, logical p,
		char **lngstr) {

  if ((x > 0 || namecode == succreqs_) && x != (unsigned long)UNSET) {
    if (outstyle == HTML) {
      if (p)
	fprintf(outf, "<p><b>%s%s</b> ", lngstr[namecode], lngstr[colon_]);
      else
	fprintf(outf, "<br><b>%s%s</b> ", lngstr[namecode], lngstr[colon_]);
    }
    else if (outstyle == ASCII)
      fprintf(outf, "%s%s ", lngstr[namecode], lngstr[colon_]);
    else
      fprintf(outf, "x%s%c%c%s", compsep, lngstr[namecode][0],
	      lngstr[namecode][1], compsep);
    f3printf(outf, outstyle, (double)x, 0, sepchar);
    if (x7 != (unsigned long)(-1)) {
      if (outstyle == COMPUTER) {
	fprintf(outf, "\nx%s%c%c%s", compsep, lngstr[namecode][2],
		lngstr[namecode][3], compsep);
	f3printf(outf, outstyle, (double)x7, 0, sepchar);
      }
      else {
	fputs(" (", outf);
	f3printf(outf, outstyle, (double)x7, 0, sepchar);
	putc(')', outf);
      }
    }
    putc('\n', outf);
  }
}

void gensumlineb(FILE *outf, choice outstyle, int namecode, double x,
		 double x7, logical rawbytes, char sepchar, char decpt,
		 char *compsep, char **lngstr) {
  /* same as gensumline() but for bytes */
  extern char *byteprefix;
  unsigned int j;

  if (x > 0) {
    if (outstyle == HTML)
      fprintf(outf, "<br><b>%s%s</b> ", lngstr[namecode], lngstr[colon_]);
    else if (outstyle == ASCII)
      fprintf(outf, "%s%s ", lngstr[namecode], lngstr[colon_]);
    else
      fprintf(outf, "x%s%c%c%s", compsep, lngstr[namecode][0],
	      lngstr[namecode][1], compsep);
    j = rawbytes?0:findbmult(x);
    printbytes(outf, outstyle, x, j, 0, sepchar, decpt);
    if (outstyle == HTML || outstyle == ASCII) {
      if (j > 0)
	fprintf(outf, " %c%s", byteprefix[j], lngstr[bytes_]);
      else
	fprintf(outf, " %s", lngstr[bytes_]);
    }
    if (x7 != UNSET) {
      if (outstyle == COMPUTER) {
	fprintf(outf, "\nx%s%c%c%s", compsep, lngstr[namecode][2],
		lngstr[namecode][3], compsep);
	f3printf(outf, outstyle, (double)x7, 0, sepchar);
      }
      else {
	fputs(" (", outf);
	j = rawbytes?0:findbmult(x7);
	printbytes(outf, outstyle, x7, j, 0, sepchar, decpt);
	if (j > 0)
	  fprintf(outf, " %c%s)", byteprefix[j], lngstr[bytes_]);
	else
	  fprintf(outf, " %s)", lngstr[bytes_]);
      }
    }
    putc('\n', outf);
  }
}

logical checkonerep(Hashindex *gp, choice requests) {
  for ( ; gp != NULL; TO_NEXT(gp)) {
    if (gp->own != NULL && gp->own->data[requests] > 0)
      return(TRUE);
  }
  return(FALSE);
}

logical checksearchrep(Hashindex *gp, choice requests, Strpair *list) {
  /* it's a bit complicated whether the Search Reports have anything in them,
     so the easiest thing is to let their routines check */
  char *name, *nameend;

  for ( ; gp != NULL; TO_NEXT(gp)) {
    if (gp->own != NULL && gp->own->data[requests] > 0) {
      name = NULL;
      nnextname(&name, &nameend, gp->name, list);
      if (name != NULL)
	return(TRUE);
    }
  }
  return(FALSE);
}

logical checktreerep(Hashtable *tp, choice requests) {
  unsigned long i;

  for (i = 0; i < tp->size; i++) {
    if (checkonerep(tp->head[i], requests))
      return(TRUE);
  }
  return(FALSE);
}

logical checkarrayrep(Arraydata *array) {
  choice i;

  for (i = 0; ; i++) {
    if (array[i].reqs > 0)
      return(TRUE);
    if (array[i].threshold < -0.5)
      return(FALSE);
  }
}

void checkreps(Outchoices *od, logical *repq, Dateman *dman, choice *alltrees,
	       choice *alldervs, Hashindex **gooditems, Tree **trees,
	       Derv **dervs, Arraydata **arraydata) {
  extern choice *rep2type, *rep2reqs;
  extern char *repname[];
  int i, j;
  choice ok;

  if (dman->currdp == NULL) {
    for (i = 0; i < DATEREP_NUMBER; i++) {
      if (repq[i]) {
	warn('R', TRUE, "Turning off empty time reports");
	for ( ; i < DATEREP_NUMBER; i++)
	  repq[i] = FALSE;
      }
    }
  }
  for (i = FIRST_GENREP; i <= LAST_NORMALREP; i++) {
    for (ok = 0, j = 0; alltrees[j] != REP_NUMBER; j++) {
      if (i == alltrees[j])
	ok = 1;
    }
    for (j = 0; alldervs[j] != REP_NUMBER; j++) {
      if (i == alldervs[j])
	ok = 2;
    }
    if (ok == 1) {
      if (!checktreerep(trees[G(i)]->tree, rep2reqs[G(i)])) {
	/* alltrees implies repq so don't have to check that */
	warn('R', TRUE, "Turning off empty %s", repname[i]);
	repq[i] = FALSE;
      }
    }
    else if (ok == 2) {
      if (!checktreerep(dervs[i - FIRST_DERVREP]->table, rep2reqs[G(i)])) {
	/* again, alldervs implies repq */
	warn('R', TRUE, "Turning off empty %s", repname[i]);
	repq[i] = FALSE;
      }
    }
    else if (i == REP_SEARCHREP || i == REP_SEARCHSUM) {
      if (repq[i] && !checksearchrep(gooditems[rep2type[i]], rep2reqs[G(i)],
		     *(od->derv[REP_SEARCHSUM - FIRST_DERVREP]->list))) {
	warn('R', TRUE, "Turning off empty %s", repname[i]);
	repq[i] = FALSE;
      }
    }
    else if (repq[i] && !checkonerep(gooditems[rep2type[i]], rep2reqs[G(i)])) {
      warn('R', TRUE, "Turning off empty %s", repname[i]);
      repq[i] = FALSE;
    }
  }
  for ( ; i < REP_NUMBER; i++) {
    if (repq[i] && !checkarrayrep(arraydata[i - FIRST_ARRAYREP])) {
      warn('R', TRUE, "Turning off empty %s", repname[i]);
      repq[i] = FALSE;
    }
  }
}
