/* ts=2;
**  $RCSfile: lp.c,v $
**  $Release$
**  $Revision: 1.18 $
**  $Date: 93/01/18 01:27:15 $
**  $Author: tf $
**
**  (c) Copyright 1991,92 Tobias Ferber, All Rights Reserved.
*/

#include <exec/types.h>
#include <stdio.h>
#include <exec/memory.h>

/* instead of "memblock.h" */

extern char *alloc_mem(long);
extern void free_mem(char *);
extern void free_all(void);

char *whoami;  /* who am I? (copy of argv[0]) */

#include <stdarg.h>

void perror(const char *fmt, ...)
{
  va_list argp;
  va_start(argp,fmt); /* name the last known argument */
  fprintf(stdout,"\r%s: ",whoami);
  vfprintf(stdout,fmt,argp);
  fprintf(stdout,"\n");
  fflush(stdout);
  va_end(argp); /* just to be sure... */
}

void _abort(void)
{ printf("^C\n\n***BREAK\n");
  free_all();
  exit(1);
}

#include "strfn.h"

static char rcs_id[] = "$VER: $Id: lp.c,v 1.18 93/01/18 01:27:15 tf Exp $";

#define BANNER &rcs_id[6]

struct jobparm
{ int indent;       /* left margin */
  int charct;       /* characters per line = bufsize */
  int linect;       /* #of lines per page */
  int tabsize;      /* 0 = don't convert tabs -> spaces */
  int columns;      /* #of columns: width= charct/columns */
  long flags;       /* see JPFLAG_XXXX */
  char *leader;     /* output line leader */
  int firstpage;    /* pageno to start numbering with */
  int lineskip;     /* number of lines to be skiped at tof */
  char *infile;     /* input filename */
  char *outfile;    /* output filename */
  char *tocfile;    /* list file filename (table of contents) */
  char *argmem;     /* argument buffer */
  long membytes;    /* size of allocated argument buffer */
  struct jobparm *next; /* next job to submit */
};

#define JPFLAG_APPEND       (1L)      /* append output to print destination */
#define JPFLAG_REPLACE      (1L<<1)   /* replace input file by output file */
#define JPFLAG_PLAIN        (1L<<2)   /* don't ENV:InitPrinter */
#define JPFLAG_AUTOLF       (1L<<3)   /* send CR, no LF for new lines */
#define JPFLAG_MINI         (1L<<4)   /* print superscript, subscript... */
#define JPFLAG_USEFF        (1L<<5)   /* send FF, not a sequence of LF */
#define JPFLAG_CLEANUP      (1L<<6)   /* delete SPC/TAB at the end of a line */
#define JPFLAG_HEADING      (1L<<7)   /* print a page header */
#define JPFLAG_FOOTING      (1L<<8)   /* print page footings */
#define JPFLAG_DOUBLESIDED  (1L<<9)   /* flag for type of page numbering */
#define JPFLAG_FEEDOUT      (1L<<10)  /* feed out the last sheet of a file */

#define JP_APPEND(f)        (((f)&JPFLAG_APPEND)==JPFLAG_APPEND)
#define JP_REPLACE(f)       (((f)&JPFLAG_REPLACE)==JPFLAG_REPLACE)
#define JP_PLAIN(f)         (((f)&JPFLAG_PLAIN)==JPFLAG_PLAIN)
#define JP_AUTOLF(f)        (((f)&JPFLAG_AUTOLF)==JPFLAG_AUTOLF)
#define JP_MINI(f)          (((f)&JPFLAG_MINI)==JPFLAG_MINI)
#define JP_USEFF(f)         (((f)&JPFLAG_USEFF)==JPFLAG_USEFF)
#define JP_CLEANUP(f)       (((f)&JPFLAG_CLEANUP)==JPFLAG_CLEANUP)
#define JP_HEADING(f)       (((f)&JPFLAG_HEADING)==JPFLAG_HEADING)
#define JP_FOOTING(f)       (((f)&JPFLAG_FOOTING)==JPFLAG_FOOTING)
#define JP_DOUBLESIDED(f)   (((f)&JPFLAG_DOUBLESIDED)==JPFLAG_DOUBLESIDED)
#define JP_SINGLESIDED(f)   (!(JP_DOUBLESIDED(f)))
#define JP_FEEDOUT(f)       (((f)&JPFLAG_FEEDOUT)==JPFLAG_FEEDOUT)

/*
 * \a = $07 [BELL]
 * \b = $08 [BACKSPACE]
 * \t = $09 [TAB]
 * \n = $0a [LF]
 * \v = $0b
 * \f = $0c [FF]
 * \r = $0d [CR]
 * \x = $ff [EOF]
 */

#define TAB '\t'
#define LF  '\n'
#define CR  '\r'
#define FF  '\f'

#define SUPERSCRIPT "\15\33[2v"
#define SUBSCRIPT   "\12\33[4v"

void newline(FILE *fp, long flags)
{ static BOOL ss=FALSE;  /* first newline changes to subscript */
  if(JP_MINI(flags))
  { fprintf(fp,(ss)?(SUPERSCRIPT):(SUBSCRIPT));
    ss=!ss;
  }
  else if(JP_AUTOLF(flags))
    fputc(CR,fp);
  else
    fputc(LF,fp);
}

#define term_write(num)                   \
        do                                \
        { fprintf(stdout," %ld\r",num);   \
          fflush(stdout);                 \
        } while(0);

#define term_clear                        \
        do                                \
        { fprintf(stdout,"       \r");    \
          fflush(stdout);                 \
        } while(0);

#define ENV_VOLUME    "ENV:"
#define ENV_LPOPTS    "ENV:LPOPTS"
#define INITPRINTER   "ENV:InitPrinter"
#define INITMINI      "\33c\33#4\33[2w\33[4w\33[0z\33[2v"
#define INITNOMINI    "\33#1"   /* turn off all special modi */
#define PRINTERNAME   "PRT:"
#define LOGFILENAME   "lp.log"
#define DEFOUT        "lp.out"
#define TEMPNAME      "lp$col.tmp"
#define USAGE         "USAGE: lp [options] [infile] [@tocfile]"

#define DEFCHARCT   80L
#define DEFINDENT   0L
#define DEFTABSIZE  8L
#define DEFLINECT   66L
#define DEFREFRESH  DEFLINECT
#define DEFJPFLAGS  JPFLAG_CLEANUP


static char *jobreport[]=   /* not used */
{ "Print job terminated without errors.",                     /* 0 */
  "Fatal-Error: AllocMem() failed, ran out of memory.",       /* 1 */
  "Nothing to print.",                                        /* 2 */
  "Unable to open input file.",                               /* 3 */
  "Error: Illegal or unknown directive.",                     /* 4 */
  "Unable to open print destination.",                        /* 5 */
  "Fatal-Error: source of error unknown.",                    /* 6 */
};

#define STATE_OKAY    0L
#define STATE_NOMEM   1L
#define STATE_NOFILE  2L
#define STATE_ERRFILE 3L
#define STATE_ERROPT  4L
#define STATE_ERRPRT  5L
#define STATE_FATAL   6L

struct jobstate
{ long inln;     /* lines of input */
  long outln;    /* lines of output */
  long wraps;    /* #of performed word wraps */
  long cracks;   /* #of broken lines */
  long cleanups; /* #of invisible chars removed */
  long fillups;  /* #of empty lines added */
};

char *alloc_argmem(jobparm,bytesize)
struct jobparm *jobparm;
long bytesize;
{ char *argmem;
  argmem= (char *)alloc_mem(bytesize);
  if(argmem)
  { jobparm->argmem= argmem;
    jobparm->membytes= bytesize;
  }
  return(argmem);
}

char *load_argfile(jobparm,filename)
struct jobparm *jobparm;
char *filename;
{ FILE *fp;
  char *argmem=(char *)NULL;
  if(filename)
  { long fsize=0;
    fp=fopen(filename,"r");
    if(fp)
    { fseek(fp, 0L, 2L);  /* OFFSET_END = 2L */
      fsize=ftell(fp);
      if(fsize)
      { fseek(fp, 0L, 0L);
        argmem= alloc_argmem(jobparm, 1+fsize); /* + EOF */
        if(argmem)
        { fread(argmem, sizeof(char), fsize, fp);
          argmem[fsize]= EOF;
        }
      }
      fclose(fp);
    }
  }
  return(argmem);
}

/*
 * Amiga options:   /S Switch
 *                  /K Keyword
 *                  /N Number
 *                  /T Toggle
 *                  /A Required
 *                  /F Rest of line
 *                  /M Multiple strings
 *
 * Note that there *MUST* be a space character following the option string!
 */

static char *howtouse[] = {

  "INDENT",      "-i", "<indent> ", "set indention, left margin (default: 0)",
  "WIDTH",       "-w", "<charct> ", "set page width, right margin (default: 80)",
  "HEIGHT",      "-h", "<linect> ", "set page height in #of lines per page (default: 66)",
  "FILE",        "-u", "<infile> ", "force next argument to be the input filename",
  "TO",          "-o", "<outfile>", "set output device or file (default is PRT:)",
  "APPEND",      "-a", "         ", "append output to print destination (default for @tocfiles)",
  "NOAPPEND",    "+a", "         ", "overwrite print destination (default)",
  "REPLACE",     "-r", "         ", "replace the input file by the output file",
  "TABSIZE",     "-t", "[tabsize]", "set tabsize and convert tabs to spaces",
  "USETAB",      "+t", "         ", "send tabs, don't convert tabs to spaces",
  "CRONLY",      "-e", "         ", "print with AUTO CR/LF option (send CR only)",
  "LFONLY",      "+e", "         ", "turn off AUTO CR/LF option (send normal LF)",
  "USEFF",       "-f", "         ", "use formfeed (FF) characters for new pages",
  "NOFF",        "+f", "         ", "convert a formfeed into a sequece of linefeeds (default)",
  "PLAIN",       "-p", "         ", "print plain, don't (ENV:) init printer",
  "NOPLAIN",     "+p", "         ", "add ENV:InitPrinter to the top of the file (default)",
  "MINI",        "-m", "         ", "print one line superscript, next subscript...",
  "NOMINI",      "+m", "         ", "turn off MINI option '-m' (default)",
  "SKIPTOPAGE",  "-b", "<pageno> ", "begin printing with the first line of page <pageno>",
  "SKIPTOLINE",  "-s", "<lineno> ", "start with line <lineno> of the input file (default: 1)",
  "NOCLEANUP",   "-d", "         ", "don't delete spaces or tabs at the end of a line",
  "CLEANUP",     "+d", "         ", "delete spaces or tabs at the end of a line (default)",
  "LEADER",      "-l", "<string> ", "print <string> in front of each output line",
  "NOLEADER",    "+l", "         ", "do not print any leading string",
  "HEADER",      "-n", "         ", "print page header (filename, date)",
  "NOHEADER",    "+n", "         ", "turn off HEADER option '-n', don't print any header (default)",
  "SINGLESIDED", "-1", "[pageno] ", "single sided page numbering beginning with page [pageno]",
  "DOUBLESIDED", "-2", "[pageno] ", "page numbering for double sided output.",
  "NOFOOTINGS",  "+0", "         ", "no page numbering, no footings (default)",
  "COLUMNS",     "-c", "<columns>", "set #of columns of equal width (default: 1, max: 2)",
  "FEEDOUT",     "-x", "         ", "expand each file via LF to fill up its last sheet",
  "NOFEEDOUT",   "+x", "         ", "do not add anything to the end of file (default)",
  "WITH",        "@",  "<tocfile>", "print a list of files (each with it's arguments)",
  NULL, NULL, NULL, NULL
};

/*
 * arglist2jobparm() parses argc arguments of given arglist argv[]
 * and fills the jobparm structure accordingly. (no checking!)
 */

void arglist2jobparm(jobparm, argc, argv)
struct jobparm *jobparm;
int argc;
char *argv[];
{ char *arg;
  if(jobparm)
  { while(argc>0)
    { arg=argv[0];

      if(isalpha(*arg))
      { char **aopt= &howtouse[0];
        /*while(*aopt && strnicmp(arg,*aopt,strlen(*aopt)))*/
        while(*aopt && stricmp(arg,*aopt))
          aopt= &aopt[4];
        if(*aopt) arg= aopt[1];
        /*if(*aopt) printf("AmigaDOS option %s changed to '%s'\n",aopt[0],aopt[1]);*/
      }

      if(*arg=='-')
      { arg++;
        switch(*arg)
        {
/* -1 */  case '1':
            if(arg[1]) jobparm->firstpage= atol(arg+1);
            else if('0'<=argv[1][0] && argv[1][0]<='9') /* isdigit(n) ? */
            { argv++;
              argc--;
              jobparm->firstpage= atol(argv[0]);
            }
            jobparm->flags |= JPFLAG_FOOTING;
            jobparm->flags &= ~JPFLAG_DOUBLESIDED;
            break;
/* -2 */  case '2':
            if(arg[1]) jobparm->firstpage= atol(arg+1);
            else if('0'<=argv[1][0] && argv[1][0]<='9') /* isdigit(n) ? */
            { argv++;
              argc--;
              jobparm->firstpage= atol(argv[0]);
            }
            jobparm->flags |= JPFLAG_FOOTING;
            jobparm->flags |= JPFLAG_DOUBLESIDED;
            break;
/* -a */  case 'a': case 'A':
            jobparm->flags |= JPFLAG_APPEND;
            break;
/* -b */  case 'b': case 'B':
            if(arg[1]) jobparm->lineskip= -atol(arg+1);
            else
            { argv++;
              argc--;
              jobparm->lineskip= -atol(argv[0]);
            }
            break;  /* negative lineskip will be converted to pageskip later */
/* -c */  case 'c': case 'C':
            if(arg[1]) jobparm->columns= atol(arg+1);
            else
            { argv++;
              argc--;
              jobparm->columns= atol(argv[0]);
            }
            break;
/* -d */  case 'd': case 'D':
            jobparm->flags &= ~JPFLAG_CLEANUP;
            break;
/* -e */  case 'e': case 'E':
            jobparm->flags |= JPFLAG_AUTOLF;
            break;
/* -f */  case 'f': case 'F':
            jobparm->flags |= JPFLAG_USEFF;
            break;
/* -h */  case 'h': case 'H':
            if(arg[1]) jobparm->linect= atol(arg+1);
            else
            { argv++;
              argc--;
              jobparm->linect= atol(argv[0]);
            }
            break;
/* -i */  case 'i': case 'I':
            if(arg[1]) jobparm->indent= atol(arg+1);
            else
            { argv++;
              argc--;
              jobparm->indent= atol(argv[0]);
            }
            break;

/* -l */  case 'l': case 'L':
            if(arg[1]) jobparm->leader= &arg[1];
            else
            { argv++;
              argc--;
              jobparm->leader= argv[0];
            }
            break;
/* -m */  case 'm': case 'M':
            jobparm->flags |= JPFLAG_MINI;
            break;
/* -n */  case 'n': case 'N':
            jobparm->flags |= JPFLAG_HEADING;
            break;
/* -o */  case 'o': case 'O':
            if(arg[1]) jobparm->outfile= &arg[1];
            else
            { argv++;
              argc--;
              jobparm->outfile= argv[0];
            }
            break;
/* -p */  case 'p': case 'P':
            jobparm->flags |= JPFLAG_PLAIN;
            break;
/* -r */  case 'r': case 'R':
            jobparm->outfile= DEFOUT;
            jobparm->flags &= ~JPFLAG_APPEND;
            jobparm->flags |= JPFLAG_REPLACE;
            break;
/* -s */  case 's': case 'S':
            if(arg[1]) jobparm->lineskip= atol(arg+1);
            else
            { argv++;
              argc--;
              jobparm->lineskip= atol(argv[0]);
            }
            break;
/* -t */  case 't': case 'T':
            if(arg[1]) jobparm->tabsize= atol(arg+1);
            else if('0'<=argv[1][0] && argv[1][0]<='9') /* isdigit(n) ? */
            { argv++;
              argc--;
              jobparm->tabsize= atol(argv[0]);
            }
            else jobparm->tabsize= DEFTABSIZE;
            break;
/* -u */  case 'u': case 'U':
            if(arg[1]) jobparm->infile= &arg[1];
            else
            { argv++;
              argc--;
              jobparm->infile= argv[0];
            }
            break;
/* -w */  case 'w': case 'W':
            if(arg[1]) jobparm->charct= atol(arg+1);
            else
            { argv++;
              argc--;
              jobparm->charct= atol(argv[0]);
            }
            break;
/* -x */  case 'x': case 'X':
            jobparm->flags |= JPFLAG_FEEDOUT;
            break;
          default:
            perror("Warning: Unknown option `-%c' ignored.",*arg);
        }
      }
      else if(*arg=='+')
      { arg++;
        switch(*arg)
        {
/* +0 */  case '0':
            jobparm->flags &= ~JPFLAG_FOOTING;
            break;
/* +a */  case 'a': case 'A':
            jobparm->flags &= ~JPFLAG_APPEND;
/* +d */  case 'd': case 'D':
            jobparm->flags |= JPFLAG_CLEANUP;
            break;
/* +e */  case 'e': case 'E':
            jobparm->flags &= ~JPFLAG_AUTOLF;
            break;
/* +f */  case 'f': case 'F':
            jobparm->flags &= ~JPFLAG_USEFF;
            break;
/* +l */  case 'l': case 'L':
            jobparm->leader = (char *)NULL;
            break;
/* +m */  case 'm': case 'M':
            jobparm->flags &= ~JPFLAG_MINI;
            break;
/* +n */  case 'n': case 'N':
            jobparm->flags &= ~JPFLAG_HEADING;
            break;
/* +p */  case 'p': case 'P':
            jobparm->flags &= ~JPFLAG_PLAIN;
            break;
/* +t */  case 't': case 'T':
            jobparm->tabsize= 0;
            break;
/* +x */  case 'x': case 'X':
            jobparm->flags &= ~JPFLAG_FEEDOUT;
            break;
          default:
            perror("Warning: Unknown option '+%c' ignored.",*arg);
        }
      }
      else if(*arg=='@')
      { if(arg[1])
          jobparm->tocfile= &arg[1];
        else
        { argv++;
          argc--;
          jobparm->tocfile= argv[0];
        }
        jobparm->flags |= JPFLAG_APPEND;
      }
      else if(*arg > ' ')
        jobparm->infile= arg;
      argc--;
      argv++;
    }
  }
}

#define MAXARGS 50   /* maximum #of arguments per line */

/*
 * make_arglist() converts given argument string `args' into a list of argv[]
 * and a #of args `argc'.
 */

char *make_arglist(args, argc, argv, line)
char *args;
int *argc;
char *argv[];
long *line;
{ int ac=0,         /* argument counter */
      smode=0;      /* scanner mode:  0= outer arg mode
                     *                1= inner argument mode
                     *                2= inner quote mode
                     *                3= EOL found
                     *                4= EOF reached
                     */
  char *t=args;     /* pointer to the beginning of the current arg string */

  while(smode<3)
  {
    switch(*args)
    {  case ';':
        if(smode<2)
        { *args='\0';
          while(*args!='\n' && *args!=EOF)   /* ';' == EOL comment leader */
            args++;
        }
        else ++args;
        break;
      case '\n': case EOF:
        (*line)++; /* since our caller begins counting with line=0 */
        if(smode==2)
        { perror("Warning: Unmatched quote found.");
          ac= -1;     /* error! */
          *args= EOF; /* stop scanner (return NULL) */
        }
        else if(smode==1)
          argv[ac++]=t;
        if(*args==EOF) smode=4;
        else
        { if(ac>0) smode=3;
        }
        *args++='\0';
        t=args;
        break;
      case '\"':
        if(smode>0)
        { argv[ac++]=t;
          *args='\0';
          if(smode==1) smode=2;
          else smode=0;
        }
        else smode=2;
        t=++args;
        break;
      case ' ': case '\t':
        if(smode<2)
        { if(smode==1)
          { argv[ac++]=t;
            *args='\0';
          }
          t=&args[1];
          smode=0;
        }
        ++args;
        break;
      default:
        if(smode==0) smode=1;
        ++args;
        break;
    }
  }

  *argc=ac;
  return (smode==4) ? (char *)NULL : args;
}

/*
 * check the settings in a jobparm structure
 */

int check_jobparm(jobparm, line, from)
struct jobparm *jobparm;
int line;
char *from;
{ int state= STATE_OKAY;
  char loc[256];  /* error location */

  if(line>0)
    sprintf(loc,"Error line %ld \"%s\"",line,from);
  else
    sprintf(loc,"Error in %s",from);

  if(jobparm->charct<1)
  { perror("%s Illegal page width %ld  (option -w).",
      loc,jobparm->charct );
    state= STATE_ERROPT;
  }
  if(jobparm->linect<1)
  { perror("%s Illegal page height %d  (option -h).",
      loc,jobparm->linect );
    state= STATE_ERROPT;
  }
  if(jobparm->indent<0)
  { perror("%s Negative indention (%ld) not supported.",
      loc,jobparm->indent );
    state= STATE_ERROPT;
  }
  if(jobparm->charct <= jobparm->indent)
  { perror("%s Page width must be > indention!",loc);
    state= STATE_ERROPT;
  }
  if(!(jobparm->outfile && *(jobparm->outfile)))
  { perror("%s No output (device) specified.",loc);
    state= STATE_ERRPRT;
  }
  return(state);
}

/*
 * tail() returns a pointer to the last jobparm structure in the job-list
 * using a while loop instead of recursion to increase efficiency.
 */

struct jobparm *tail(node)
struct jobparm *node;
{ while(node->next)
    node= node->next;
  return(node);
}

/*
 * file2jobparm() converts each line of given file(name) into a jobparm
 * structure and chains the jobparm structures to a list.
 * The head of this chained list (i.e. first line's jobparm structure)
 * will then be chained to the given jobparm structure.
 *
 * NOTE:
 *   Given jobparm structure contains the default settings for the whole
 * file.  Each new allocated jobparm structure will be initilalized with
 * it's values.
 */

struct jobparm *alloc_jobparm(); /* pre-declaration */

int file2jobparm(jp_base, filename)
struct jobparm *jp_base;
char *filename;
{
  static char *tgraph[256];  /* recursion depth limited to 256 !!! */
  static int tnodect= 0;
  int state= STATE_FATAL;

  if(jp_base && filename && *filename)
  { char *filemem;

    /* Check the tgraph for non-deterministic recursions. */

    int n;
    for(n=0; n<tnodect; n++)
    { if(!stricmp(filename, tgraph[n]))
      { perror("Fatal-Error with \"%s\"  Contents is non-deterministic!",
          filename);
        return(STATE_FATAL);
      }
    }
    if(tnodect>=225)
    { perror("Ran out of memory!  Recursion depth > 256");
      return(STATE_FATAL);
    }
    tgraph[tnodect++]= filename; /* add the tgraph node */

    filemem= load_argfile(jp_base, filename);
    if(filemem)
    { struct jobparm *jobparm= jp_base; /* working pointer */
      char *args=filemem;   /* pointer into filemem */
      int argc;             /* argument counter */
      char *argv[MAXARGS];  /* argument list */
      long line=0;          /* line number for error messages */
      state= STATE_OKAY;    /* everything seems okay now */

      while(args && (state==STATE_OKAY))
      { jobparm->next= alloc_jobparm(jp_base);
        if(jobparm->next)
        { jobparm= jobparm->next;
          args= make_arglist(args, &argc, argv, &line);
          if(argc<0)
          { perror("Error line %ld \"%s\"  Argument scan failed.",
              line,filename);
            return(STATE_FATAL);
          }
          arglist2jobparm(jobparm,argc,argv);
          state= check_jobparm(jobparm, line, filename);
          if(state==STATE_OKAY)
          { if(jobparm->tocfile && *(jobparm->tocfile))
            { 
              /* prevent lp from performing non-deterministric recursions */
              for(n=0; n<tnodect; n++)
              { if(!stricmp(jobparm->tocfile, tgraph[n]))
                { perror("Fatal-Error line %ld \"%s\"  Non-deterministic recursion!",
                    line,filename);
                  return(STATE_FATAL);
                }
              }
              state= file2jobparm(jobparm, jobparm->tocfile);
            }
            jobparm= tail(jobparm);
          }
        }
        else
        { perror("Error line %ld \"%s\"  Out of memory!",
            line,filename);
          state=STATE_NOMEM;
        }
      }
    }
    else
    { perror("File-Error: Unable to load \"%s\".",filename);
      state=STATE_NOMEM;
    }
    --tnodect; /* delete the tgraph node */
  }
  return(state);
}

void free_jobparm(struct jobparm *jobparm)
{ if(jobparm)
  { if(jobparm->next)
      free_jobparm(jobparm->next);
    if(jobparm->argmem && jobparm->membytes)
      free_mem(jobparm->argmem);
    free_mem(jobparm);
  }
}
/*
 * allocate jobparm structure and init default values.
 */

struct jobparm *alloc_jobparm(jp_default)
struct jobparm *jp_default;
{ struct jobparm *jobparm;

  jobparm= (struct jobparm *)alloc_mem(sizeof(struct jobparm));
  if(jobparm)
  {
    /* Just to be sure that this is unset: */

    jobparm->firstpage   = 0;             /* => continue with current # */
    jobparm->infile      = (char *)NULL;  /* which file to print? */
    jobparm->tocfile     = (char *)NULL;  /* list of files for this job */
    jobparm->argmem      = (char *)NULL;  /* not allocated yet */
    jobparm->membytes    = 0;             /* => not set yet */
    jobparm->next        = (struct jobparm *)NULL;

    if(jp_default)
    { jobparm->indent    = jp_default->indent;
      jobparm->charct    = jp_default->charct;
      jobparm->linect    = jp_default->linect;
      jobparm->tabsize   = jp_default->tabsize;
      jobparm->columns   = jp_default->columns;
      jobparm->flags     = jp_default->flags;
      jobparm->leader    = jp_default->leader;
      jobparm->lineskip  = jp_default->lineskip;
      jobparm->outfile   = jp_default->outfile;
    }
    else
    { jobparm->indent    = DEFINDENT;    /* left margin */
      jobparm->charct    = DEFCHARCT;    /* characters per line = bufsize */
      jobparm->linect    = DEFLINECT;    /* = don't fill pages */
      jobparm->tabsize   = 0;            /* = don't convert tabs -> spaces */
      jobparm->columns   = 1;            /* #of columns of equal width */
      jobparm->flags     = DEFJPFLAGS;   /* default flags */
      jobparm->outfile   = PRINTERNAME;  /* should be "PRT:" */

      if((dltype(ENV_VOLUME)!=DLT_UNKNOWN) && fexist(ENV_LPOPTS))
        file2jobparm(jobparm, ENV_LPOPTS);
    }
  }
  return(jobparm);
}

#define HEADERLINES 3L

/*
 * make the headerlines and return a pointer to it's static string
 */

#include <time.h>

char *make_heading(jobparm)
struct jobparm *jobparm;
{ static char header[512];
  int len, maxlen;

  /*len = stcgfn(header,jobparm->infile);*/
                              /* seperate filename's node.ext */

  len= strlen(jobparm->infile);
  strcpy(header,jobparm->infile);

  maxlen = jobparm->charct;

  if(len>maxlen && maxlen>3)
    strcpy(&header[maxlen-3],"...");
  else if(len+24< maxlen)
  { long t;
    char *s;
    time(&t);        /* get #of sec. since 00:00:00 GMT Jan 1, 1970 */
    s= ctime(&t);    /* convert to 26 characters ASCII string */
    s[24]='\0';      /* remove '\n' character */
    for(t=len; t<maxlen-24; t++)
      header[t]=' ';
    strcpy(&header[t],s);
  }
  else header[0]='\0';
  strcat(header,"\n\n\n");
  return(header);
}

/*
 * make the footer lines in the same way
 */

int numdigits(unsigned long x)  /* get #of digits in a long */
{ int d=0;
  do
  { x/=10;
    d++;
  } while(x>0);
  return(d);
}

#define FOOTERLINES 3L

/*
 * make page footings, return a pointer to a static string
 */

char *make_footing(jobparm, pageno)
struct jobparm *jobparm;
unsigned long pageno;
{ static char footer[512];
  char fmt[80];

  if(JP_SINGLESIDED(jobparm->flags))
  { sprintf(fmt,"\n\n%%%ldld\n",
      (jobparm->charct + numdigits(pageno) -1) /2 );
    sprintf(footer,fmt,pageno);
  }
  else if(pageno%2)
  { sprintf(fmt,"\n\n%%%ldld\n",jobparm->charct);
    sprintf(footer,fmt,pageno);
  }
  else
    sprintf(footer,"\n\n%ld\n",pageno);

  return(footer);
}

/*
 * seek given file pointer `numlines' lines towards the end of file
 */

void skip_lines(fp, numlines)
FILE *fp;
long numlines;
{ if(numlines > 0)
  { while((fgetc(fp)!=LF) && !feof(fp))
      ;
    if(!feof(fp))
      skip_lines(fp, numlines-1);
  }
}

/*
 * Here we do the post-processing (column merging)
 */

int join_columns(jobparm)
struct jobparm *jobparm;
{ FILE *fp1,    /* file pointer reading first column */
       *fp2,    /* file pointer reading second column */
       *fpo;    /* file pointer writing data */
  char *buf;    /* line buffer */

  /* open files */

  if(!(fp1=fopen(TEMPNAME,"r")))
  { perror("File-Error: Unable to open `%s'.",TEMPNAME);
    return(STATE_ERRFILE);
  }
  if(!(fp2=fopen(TEMPNAME,"r")))
  { perror("Error: Unable to open `%s'.",TEMPNAME);
    if(fp1) fclose(fp1);
    return(STATE_ERRFILE);
  }
  { char *mode= (JP_APPEND(jobparm->flags)) ? ("a") : ("w");
    if(!(fpo= fopen(jobparm->outfile,mode)))
    { perror("File-Error: Unable to open %s.",jobparm->outfile);
      if(fp1) fclose(fp1);
      if(fp2) fclose(fp2);
      return(STATE_ERRPRT);
    }
  }

  /* allocate line buffer */

  if(!(buf=(char *)alloc_mem(jobparm->charct)))
  { perror("Fatal-Error: Unable to allocate %ld bytes of memory.",
      jobparm->charct);
    if(fp1) fclose(fp1);
    if(fp2) fclose(fp2);
    if(fpo) fclose(fpo);
    return(STATE_NOMEM);
  }

  skip_lines(fp2, jobparm->linect);
  term_clear;

  if(!feof(fp2))
  { char fmt[20];
    char *s,
         *header=NULL,
         *footer=NULL;
    long l=0,
         p=jobparm->firstpage;

    int  linect= jobparm->linect;

    if(JP_HEADING(jobparm->flags))
    { linect-= HEADERLINES;
      header= make_heading(jobparm);
      fwrite(header, sizeof(char), strlen(header), fpo);
    }

    if(JP_FOOTING(jobparm->flags))
    { linect-= FOOTERLINES;
      footer= make_footing(jobparm,p);
    }
    sprintf(fmt,"%%-%lds | ",jobparm->charct/jobparm->columns);

    while(!(feof(fp1) || feof(fp2)))
    { for(s=buf;((*s=fgetc(fp1))!=LF) && !feof(fp1); s++)
        ;
      *s='\0';
      fprintf(fpo,fmt,buf);
      for(s=buf;((*s=fgetc(fp2))!=LF) && !feof(fp2); s++)
        ;
      *++s='\0';
      if(!feof(fp2))
      { fputs(buf,fpo);
        l++;
        if((l%linect)==0)
        { skip_lines(fp1, jobparm->linect);
          skip_lines(fp2, jobparm->linect);
          term_write(l);
          if(footer)
          { fwrite(footer,sizeof(char),strlen(footer),fpo);
            footer= make_footing(jobparm, ++p);
          }
          if(header) fwrite(header,sizeof(char),strlen(header),fpo);
        }
      }
    }
  }
  free_mem(buf);
  if(fp2) fclose(fp2);
  if(fp1) fclose(fp1);
  if(fpo) fclose(fpo);
  return(STATE_OKAY);
}

/*
 * print a file
 */

int print_file(jobparm,jobstate)
struct jobparm *jobparm;
struct jobstate *jobstate;
{ static unsigned long pageno=1;

  FILE *fpi,  /* input stream */
       *fpo;  /* output stream */

  char *buf;       /* line buffer */
  long b=0,        /* general counter */
       inln=0,     /* lines of input */
       outln=0,    /* lines of output */
       wraps=0,    /* #of word wraps */
       cracks=0,   /* #of line breaks */
       cleanups=0, /* #of invisible chars */
       fillups=0;  /* #of empty lines added */

  int  indent = jobparm->indent,
       charct = jobparm->charct,
       linect = jobparm->linect,
       tabsize= jobparm->tabsize,
       flags  = jobparm->flags;

  char *header= (char *)NULL;      /* page header */

  /* open input- and output stream */

  if(jobparm->infile && *(jobparm->infile))
  { if(!(fpi=fopen(jobparm->infile,"r")))
    { perror("Error: Unable to open `%s'.\n",jobparm->infile);
      return(STATE_ERRFILE);
    }
    if(jobparm->columns > 1)
    { if(!(fpo= fopen(TEMPNAME, "w")))
      { perror("Error: Unable to write temorary data to %s.\n",TEMPNAME);
        if(fpi) fclose(fpi);
        return(STATE_ERRPRT);
      }
      charct /= jobparm->columns;
      if(JP_HEADING(flags))
      { linect-= (HEADERLINES * (jobparm->columns -1) );
        flags&= ~JPFLAG_HEADING;
      }
      if(JP_FOOTING(flags))
      { linect-= (FOOTERLINES * (jobparm->columns -1) );
        flags&= ~JPFLAG_FOOTING;
      }
    }
    else
    { char *mode= (JP_APPEND(flags)) ? ("a") : ("w");
      if(!(fpo= fopen(jobparm->outfile,mode)))
      { perror("Error: Unable to open %s.\n",jobparm->outfile);
        if(fpi) fclose(fpi);
        return(STATE_ERRPRT);
      }
    }
  }
  else return(STATE_ERRFILE); /* nothing to print */

  /* init printer */

  if(JP_MINI(flags))
    fprintf(fpo,INITMINI);  /* begin with superscript */
  else if(!JP_PLAIN(flags))
  { if(dltype(ENV_VOLUME)!=DLT_UNKNOWN) /* env: exists */
    { FILE *fpx=fopen(INITPRINTER,"r");
      if(fpx)
      { char c;
        printf("%s found, printer installed.\n",INITPRINTER);
        while((c=fgetc(fpx))!=EOF && !feof(fpx))
        { fputc(c,fpo);
          if(c=='\n')
           outln++;
          }
        fclose(fpx);
      }
    }
  }

  if(!(buf=(char *)alloc_mem(charct)))
  { perror("Fatal-Error: Unable to allocate %ld bytes of memory.",
      charct);
    if(fpi) fclose(fpi);
    if(fpo) fclose(fpo);
    return(STATE_NOMEM);
  }
  for(b=0;b<indent;b++) /* init indention */
    buf[b]=' ';
  if(jobparm->leader)
  { strcpy(&buf[indent],jobparm->leader);
    indent+=strlen(jobparm->leader);
  }
  b= indent;

  printf("        %s => %s\r",jobparm->infile,jobparm->outfile);

  /* skip lines */

  if(jobparm->lineskip>0)
  { int skip= jobparm->lineskip;
    if(skip<0)
      skip= -skip*linect; /* skip pages */
    if(skip>0)
      skip_lines(fpi,skip);
  }

  if(jobparm->firstpage != 0)
    pageno= jobparm->firstpage;
  else jobparm->firstpage= pageno;

  if(JP_HEADING(flags))
  { header= make_heading(jobparm);
    linect-= HEADERLINES;
  }
  if(JP_FOOTING(flags))
    linect-= FOOTERLINES;

  /* main loop */

  { BOOL pbrk, lbrk;  /* page break, line break read */
    char *rbuf;
    int refresh=(linect>1)?(linect):(DEFREFRESH);
                      /* when to print out the current line number */

    while(!feof(fpi))
    { rbuf=&buf[b]; /* b==indent */
      do
      { *rbuf=fgetc(fpi);
        if(*rbuf==TAB && tabsize>0)
        { do
          { *rbuf++=' ';
            b++;
          }
          while((b%tabsize) && b<charct);
          lbrk=pbrk=FALSE;
        }
        else
        { lbrk=(*rbuf==LF);
          pbrk=(*rbuf==FF);
          b++;
          rbuf++;
        }
      } while(b<charct && !(pbrk || lbrk || feof(fpi)));

      rbuf--; /* set to the character read last
               * b = #of chars in buf[]
               */

      if(lbrk)
      { inln++;  /* we've read a complete line */
        if(inln%refresh==0)
          term_write(inln);   /* we're alive ! */
      }

      if(b<charct || lbrk || pbrk || feof(fpi) )
      { --rbuf;  /* => don't write the character read last */
        --b;
      }

      if(JP_CLEANUP(flags)) /* => remove invisible chars */
      { while((*rbuf==' '||*rbuf=='\t') && b>0)
        { --rbuf;
          --b;
          cleanups++;
        }
      }

      if(header && (outln%linect)==0)
        fwrite(header, sizeof(char), strlen(header), fpo);

      if(b<charct)  /* => normal line */
      { if((feof(fpi) && b>indent) || (!feof(fpi) && b>0))
          fwrite(buf,sizeof(char),b,fpo);
        newline(fpo,flags);
        outln++;
        /*
         * We increase outln in any case, since the first line on a new
         * page terminated without '\n' would imply (outln%linect) to
         * be ==0 and this page would NOT be filled up if an EOF follows.
         * note that a file ending without a '\n' character will get it from
         * lp in any case.
         */
        b=indent;
      }
      else /* line too long */
      { int bp=b-1; /* break,wrap position */

        /* search for a word wrap position */
        while(buf[bp]!=' ' && buf[bp]!='\t' && --bp>indent);

        if(bp>indent) /* => confirm word wrap */
        { int i;
          fwrite(buf,sizeof(char),bp,fpo);
          newline(fpo,flags);
          outln++;
          bp++;  /* skip word terminating character */
          for(i=0;i<b-bp;i++) buf[indent+i]=buf[bp+i];
          b=indent+i;
          wraps++;
        }

        else /* => break line... we've got no choice */
        { fwrite(buf,sizeof(char),b,fpo);
          newline(fpo,flags);
          outln++;
          b=indent;
          cracks++;
        }
      }

      /* handle form feed character */
      if(pbrk && (outln%linect) > 0)
      { int al= linect-(outln%linect);  /* #of additional lines */
        /* Warning:
         * If (outln%lincet)==0 we would add another lincet lines to
         * the output without increasing/printing the page number and a
         * new header! So: If a header and/or footer were also toggled,
         * linect would not be enough to fill up a full page since we
         * decreased it by headerlines and/or footerlines!!!
         */
        if(JP_USEFF(flags))
          fputc(FF, fpo);
        else
        { int i;
          for(i=0;i<al;i++) newline(fpo,flags);
          fillups+=al;
        }
        outln+=al;
        
      }

      if((outln%linect)==0)
      { if(JP_FOOTING(flags))
        { char *s= make_footing(jobparm, pageno);
          fwrite(s,sizeof(char),strlen(s),fpo);
        }
        pageno++;
      }
    }
    if(JP_FEEDOUT(flags))
    { linect*= jobparm->columns;
      for(b=0;(outln%linect)!=0;outln++,b++)
        newline(fpo,flags); /* feed out last page */
      fillups+=b;

      /* take care: (here was a bug v1.16)
       * if(b==0) we've already added the footer ! */
      if(b>0)
      { if(JP_FOOTING(flags))
        { char *s= make_footing(jobparm, pageno);
          fwrite(s,sizeof(char),strlen(s),fpo);
        }
        pageno++;
      }
    }
  }
  if(JP_MINI(flags))
    fprintf(fpo, INITNOMINI);  /* turn off all special modi at the EOF */

  if(buf) free_mem(buf);

  if(fpi) fclose(fpi);
  if(fpo) fclose(fpo);

  jobstate->inln     = inln;
  jobstate->outln    = outln;
  jobstate->wraps    = wraps;
  jobstate->cracks   = cracks;
  jobstate->cleanups = cleanups;
  jobstate->fillups  = fillups;

  if(jobparm->columns>1)
    join_columns(jobparm);

  return(STATE_OKAY);
}

/* write the statistics into our logfile */

void statistics(struct jobparm *jp,
                struct jobstate *js )
{ FILE *sf;
  sf=fopen(LOGFILENAME,"a");
  if(sf)
  { long flags= jp->flags;

    fprintf(sf,"%s (%d+%d lines, %d cleaned, %d wraped, %d broken)\n",
                                                   jp->infile,
                                                   js->inln,
                                                   js->fillups,
                                                   js->cleanups,
                                                   js->wraps,
                                                   js->cracks );

    fprintf(sf,"jobparm -i%d -w%d -h%d -t%d -c%d ",jp->indent,
                                                   jp->charct,
                                                   jp->linect,
                                                   jp->tabsize,
                                                   jp->columns );

    if(JP_PLAIN(flags))       fprintf(sf,"-p ");  else fprintf(sf,"+p ");
    if(JP_AUTOLF(flags))      fprintf(sf,"-e ");  else fprintf(sf,"+e ");
    if(JP_MINI(flags))        fprintf(sf,"-m ");  else fprintf(sf,"+m ");
    if(JP_USEFF(flags))       fprintf(sf,"-f ");  else fprintf(sf,"+f ");
    if(JP_CLEANUP(flags))     fprintf(sf,"+d ");  else fprintf(sf,"-d ");
    if(jp->leader)            fprintf(sf,"-l \"%s\" ",jp->leader);
    if(jp->lineskip > 0)      fprintf(sf,"-s %d ",jp->lineskip);
    else if(jp->lineskip < 0) fprintf(sf,"-n %d ",-(jp->lineskip));
    if(JP_REPLACE(flags))     fprintf(sf,"-r ");
    if(JP_APPEND(flags))      fprintf(sf,"-a ");  else fprintf(sf,"+a ");
    if(jp->outfile)           fprintf(sf,"-o \"%s\" ",jp->outfile);
    if(JP_HEADING(flags))     fprintf(sf,"-n ");  else fprintf(sf,"+n ");
    if(JP_FEEDOUT(flags))     fprintf(sf,"-x ");  else fprintf(sf,"+x ");
    if(JP_DOUBLESIDED(flags)) fprintf(sf,"-2 %d ",jp->firstpage);
    if(JP_SINGLESIDED(flags)) fprintf(sf,"-1 %d ",jp->firstpage);
    if(!JP_FOOTING(flags))    fprintf(sf,"+0 ");
    fprintf(sf,"\n\n");
    fclose(sf);
  }
}

/*
 * submit all print jobs in given jobparm list
 */

int submit_printjob(jobparm)
struct jobparm *jobparm;
{ int state= STATE_OKAY;
  struct jobstate jobstate;

  if(jobparm)
  { if(jobparm->infile && *(jobparm->infile) &&
      jobparm->outfile && *(jobparm->outfile) )
    { state= print_file(jobparm, &jobstate);
      if(state==STATE_OKAY)
      { if(JP_REPLACE(jobparm->flags))
        { DeleteFile(jobparm->infile);
          Rename(jobparm->outfile,jobparm->infile);
        }
        printf("   %s => %s [printed]\n",jobparm->infile, jobparm->outfile);
        statistics(jobparm, &jobstate);
      }
    }
    if(state==STATE_OKAY && jobparm->next)
      state= submit_printjob(jobparm->next);
  }
  return(state);
}

/*
 * here we build the whole jobparm-list out of main()'s arguments
 */

struct jobparm *build_joblist(int argc, char *argv[])
{ struct jobparm *mainjob;  /* head of the jobparm-list */
  int state= STATE_OKAY;
  mainjob= alloc_jobparm(NULL);
  if(mainjob)
  { struct jobparm *jobparm= mainjob; /* working pointer */
    jobparm= tail(jobparm);
    jobparm->next= alloc_jobparm(jobparm);
    jobparm= jobparm->next;
    if(jobparm)
    { arglist2jobparm(jobparm, argc, argv);
      check_jobparm(jobparm,0,"command-line");
      if(jobparm->tocfile)
        state= file2jobparm(jobparm, jobparm->tocfile);
    }
    else
    { perror("Not enough memory to parse command line!");
      state= STATE_NOMEM;
    }
  }
  else
  { perror("Not enough memory to allocate print job!");
    state= STATE_NOMEM;
  }

  if((state!=STATE_OKAY) && mainjob)
  { free_jobparm(mainjob);
    mainjob= (struct jobparm *)NULL;
  }

  return(mainjob);
}

void main(int argc, char *argv[])
{ int state;    /* job state after execution attempt */
  struct jobparm *jobparm;
  onbreak(_abort);  /* this is DICE */

  puts(BANNER); /* let me introduce myself... */
  if(argc==2 && (argv[1][0]=='?' || argv[1][0]=='.'))
  { char **s= howtouse;
    puts(USAGE);
    puts("\nLegal options are:\n");
    if(argv[1][0]=='?') while(*s)
    { printf("%-11s %s %s\n",s[0],s[2],s[3]);
      s= &s[4];
    }
    else while(*s)
    { printf("%-2s %s %s\n",s[1],s[2],s[3]);
      s= &s[4];
    }
    puts("\nALL THIS IS HIGHLY EXPERIMENTAL !!! TAKE GREAT CARE !!!");
    exit(0);
  }

  whoami=argv[0]; /* who am I ? */
  argc--;
  argv++; /* skip our name */

  jobparm= build_joblist(argc, argv);
  if(jobparm)
  { DeleteFile(LOGFILENAME);
    state= submit_printjob(jobparm);
  }
  free_jobparm(jobparm);
  exit((state==STATE_OKAY)?(0):(1));
}
