/*
 *    applef.c - output filter for postscript to printer
 *              Also figures out if it should be an ascii filter
 *              instead, if a -l or -w swich is given.
 *
 *    Copyright 1985 Massachusetts Institute of Technology
 *    Author: CJL@OZ
 *
 *
 *    TODO:
 *    Page ordering so they will come out right using structuring
 *      conventions defined by adobe.
 *
 */

#include <stdio.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>

#define MAXTFP    1000		/* Maximim # of pages in temp file */
#define MAXWAIT    900		/* Maximum time to wait for resync (sec) */
#define TOPY	   760
#define BOTTOMY     35
#define LEFTX       60
#define YINCR       11
#define MIN(a,b)    (((a) < (b))? (a):(b))
#define ALARM_TIME   5		/* Time between alarm signals (sec) */
#define PAPER_TIME 600		/* Time between paper out complaints (sec) */
#define STATUS_TIME 60		/* Time between printer status checks (sec) */

/* constants */
int infinity = 10000000;
char qsend[] = "/usr/local/bin/send";
char msg_tail[] = " ]%%\r\n";
char printer_error[] = "%%[ PrinterError: ";
char status_msg[] = "%%[ status: ";

/* variables */
int length = 66;
int width = 89;
long pprtim = 0;
long ststim = 0;
int ascii = 0;			/* Poscript filter by default */
char *user,*host;
int curry,charpos,nchars;
long tfpos[MAXTFP],tfp;
FILE *tf;
char tfn[256];
char logbuf[BUFSIZ],*logbp;
char last_status[BUFSIZ];

/* First, all the code for just sending chars to the printer */

int synched = 0;

timeout()
{
  long t;

  time(&t);
  alarm(ALARM_TIME);
  if (t - ststim > STATUS_TIME) {
    write(1,"\024",1); 		/* Control-T */
    ststim = t;
  }
  getlog();
}

getlog()
{
  register int i;
  int n;
  char buf[BUFSIZ],ch;

  while ((ioctl(1,FIONREAD,&n) >= 0) && n) {
    if (n > sizeof(buf)) n = sizeof(buf);
    n = read(1,buf,n);
    if (n > 0) {
      for (i=0; i<n; i++) {
	if ((ch = buf[i]) == 4) synched++;
	else {
	  *logbp++ = ch;
	  if ((ch == '\n') || (logbp >= logbuf+BUFSIZ)) flushlog();
	}
      }
    } else {
      perror("read from the printer");
      return;
    }
  }
  fflush(stderr);
}

dopaperout()
{
  char cmdline[256];
  FILE *f,*popen();
  long t;

  time(&t);
  if (t - pprtim > PAPER_TIME) {
    syslog(LOG_CRIT,"out of paper");
    if (user != NULL && host != NULL) {
      sprintf(cmdline,"exec %s %s@%s",qsend,user,host);
      if ((f = popen(cmdline,"w")) != NULL) {
	fprintf(f,"You need to add paper to the printer.");
	pclose(f);
      }
    }
    pprtim = t;
  }
}

dostatusmsg(msg)
     char *msg;
{
  if (strcmp(msg,last_status)) {
    syslog(LOG_WARNING,"status: %s",msg);
    strcpy(last_status,msg);
  }
  if (!strcmp(msg,"out of paper")) dopaperout();
}

doprintererror(msg)
     char *msg;
{
  syslog(LOG_WARNING,"printer error: %s",msg);
  if (!strcmp(msg,"out of paper")) dopaperout();
}

flushlog()
{
  register char *p;

  if ((logbp - logbuf > sizeof(printer_error)-1 + sizeof(msg_tail)-1) &&
      (!strncmp(logbuf,printer_error,sizeof(printer_error)-1))) {
    *(logbp-sizeof(msg_tail)+1) = 0;
    doprintererror(logbuf+sizeof(printer_error)-1);
  } else if ((logbp - logbuf > sizeof(status_msg)-1 + sizeof(msg_tail)-1) &&
	     (!strncmp(logbuf,status_msg,sizeof(status_msg)-1))) {
    *(logbp-sizeof(msg_tail)+1) = 0;
    dostatusmsg(logbuf+sizeof(status_msg)-1);
  } else for (p = logbuf; p < logbp; p++) {
    if (ascii || (tf == NULL)) putc(*p,stderr);
    else dochar(*p);
  }
  logbp = logbuf;
}

resync()
{
  int seconds = 0;
  struct stat *buf;

  fflush(stdout);
  fstat(stdout,buf);
  if (buf->st_mode&S_IFCHR){
    synched = 0;
    write(1,"\004",1);
    while (!synched && ((seconds += ALARM_TIME) < MAXWAIT)) pause();
    if (!synched) syslog(LOG_CRIT,"synchronization timeout");
  }
  flushlog();
}

init_printer()
{
  strcpy(last_status,"idle");
  logbp = logbuf;
  signal(SIGIO,getlog);
  signal(SIGALRM,timeout);
  fcntl(1,F_SETFL,FAPPEND|FASYNC);
  alarm(ALARM_TIME);

  resync();
  
  /* Open a temp file for sending the data 
   * for reversing the order of output
   */
  tf = fopen(mktemp(strcpy(tfn,"/usr/tmp/applef.XXXXXX")),"w+");
  if (tf == NULL) {
    perror(tfn);
    exit(1);
  }
  tfp = 0;
  unlink(tfn);
};

term_printer()
{
  resync();
  alarm(0);
};    

copychars(infd,outfd,nchars)
{
  register int i,j;
  register char *p;
  char buf[BUFSIZ];

  i = 0;
  while ((nchars > 0) &&
	 (i = read(infd,p = buf,MIN(sizeof(buf),nchars))) > 0) {
    nchars -= i;
    while (i > 0) {
      if ((j = write(outfd,p,i)) > 0) {
	i -= j;
	p += j;
      } else {
	perror("applef");
	i = 0;
      }
    }
  }
  if (i < 0) perror("applef");
}

main(argc, argv) 
    int argc;
    char *argv[];
{
  register int ch;
  int i;
  char *rindex();
  char lognam[256];

  if (rindex(argv[0],'/')) argv[0] = rindex(argv[0],'/')+1;
  for (i = 1; i < argc; i++) {
    if (argv[i][0] == '-') switch (argv[i][1]) {
    case 'h':
      host = argv[++i];
      break;

    case 'l':
      ascii++;			/* So I must be an ascii filter */
      sscanf(argv[i]+2,"%d",&length);
      break;

    case 'n':
      user = argv[++i];
      break;

    case 'w':
      ascii++;			/* So I must be an ascii filter */
      sscanf(argv[i]+2,"%d",&width);
      break;

    default:
      break;
    }
  }
  if (user != NULL) sprintf(lognam,"%s (%s@%s)",argv[0],user,host);
  else sprintf(lognam,"%s",argv[0]);
  openlog(lognam,LOG_PID);
  init_printer();
  starttext();
  if (ascii) {
    while (!ferror(stdin) && (ch = getchar()) != EOF) switch (ch) {
    case '\031':
      /*
       * lpd needs to use a different filter to
       * print data so stop what we are doing and
       * wait for lpd to restart us.
       */
      if ((ch = getchar()) == '\1') {
	resync();
	kill(getpid(), SIGSTOP);
	resync();
	break;
      } else {
	ungetc(ch, stdin);
	ch = '\031';
      }
    default:
      dochar(ch);
    }
    if (ferror(stdin)) perror("applef stdin");
  } else {
    copychars(0,1,infinity);
    resync();
  }
  endtext();
  term_printer();
  exit(0);
}

/* Now code for converting text files to Postscript
 * and sending it to the printer. We need this stuff
 * to print printer responses after the print job,
 * and to print in ascii mode.
 */

starttext()
{
  newpage();
}

endtext()
{
  long end, curr;
  FILE *tempf;
  int tempfd;

  dochar('\f');
  tfpos[tfp] = ftell(tf);
  fflush(tf);
  tempf = tf;
  tf = NULL;
  tempfd = fileno(tempf);
  /* now send the pages to the printer in reverse order */
  while (--tfp >= 0) {
    curr = tfpos[tfp];
    end = tfpos[tfp + 1];
    lseek(tempfd,curr,0);
    copychars(tempfd,1,end-curr);
  };
  fclose(tempf);
}  

newpage()
{
  nchars = 0;
  charpos = 0;
  curry = TOPY;
}

pageinit()
{
  fflush(tf);
  if (tfp >= MAXTFP) {
    fprintf(stderr,"too many pages\n");
    exit(1);
  }
  tfpos[tfp++] = ftell(tf);
  fprintf(tf,"/Courier findfont 10 scalefont setfont\n"); /* font */
}

dostring(p)
char *p;
{
  register char ch;

  while (ch = *p++) dochar(ch);
}

dochar (ch)
int ch;
{
  register int i;

  if (ch < ' ') 
    switch (ch) {
    case '\b':
      if (charpos) { 
	fprintf(tf,") show\n");
	fprintf(tf,"(a) stringwidth pop neg 0 rmoveto\n");
	if (--charpos) fprintf(tf,"(");
      }
      break;

    case '\t':
      for (i = 8 - charpos%8; i > 0; i--) dochar(' ');
      break;

    case '\n':
      if (curry - YINCR < BOTTOMY) dochar('\f');
      else {
	if (charpos) fprintf(tf,") show\n");
	curry -= YINCR;
	charpos = 0;
      }
      break;

    case '\r':
      if (charpos) fprintf(tf,") show\n");
      charpos = 0;
      break;

    case '\f':
      if (charpos) fprintf(tf,") show ");
      if (nchars) fprintf(tf,"showpage\n");
      newpage();
      break;
    
    default:
      dochar('^'); dochar('\b'); dochar('|');
      dochar(ch+'A'-001);
      break;
    } else {
      if (!nchars++) pageinit();
      if (!charpos++) fprintf(tf,"%d %d moveto (",LEFTX,curry);
      if (charpos >= width) {
	putc('!',tf);
	dochar('\n');
	dochar(ch);
      } else switch (ch) {
      case '\\':
      case '/':
      case '(':
      case ')':
	putc('\\',tf);
    
      default:
	putc(ch,tf);
	break;
      }
    }
}
