      program alpaid

c     Copyright 2012 Leon van Dommelen.  This software is made available
c     under the GNU Public License version 3.

c     Create a list of alphanumeric student Activity ID (AID) values.

c     First  character is a base 16 digit with symbols 'ABDEGHIJKMPRSUWY'.
c     Second character is a base 16 digit with symbols '734568CFLNOQTVXZ'
c     (if the class checksum, or rather symbol, offset is zero).
c
c     The final third character is a 1-3 base 16 checksum guarding
c     against mistyping or misreading a single character.  Its symbols
c     are like for the first character.
c
c     The two sets of digit symbols are chosen so as to minimize the
c     effect of commonly confused alphanumeric characters.  As much as
c     possible, symbols a symbol may be confused with are in the other
c     set.  The data for which characters are commonly confused is from:
c     https://www.ismp.org/resources/misidentification-alphanumeric-symbols
c
c     The number of 256 AID values is reduced to 232 to guard against
c     misreading a character that occurs two times, and against the
c     student putting the characters in the wrong order.

c     To give different classes non-overlapping sets of AID values, give
c     them different checksum offsets when prompted for a "distinction
c     number".

c     Programmers Note:
c        https://community.canvaslms.com/docs/DOC-16546-4152813649
c     and
c        https://resourcecenter.odee.osu.edu/carmencanvas/
c                                      importing-csv-files-canvas-grades
c     give completely contradictory reports on what columns there should
c     be in a CSV file uploaded to Canvas.  However, both agree that
c     "Section" must be there.  The first says "Student" (Name) and "ID"
c     must be there, the second says you could leave out both if you
c     provide a "SIS Login ID", which the first reference says you will
c     leave out if not used.  Since leaving out "Student" does not make
c     any sense, even if it is legal as the second reference says, this
c     program requires that both "Student" and "Section" are present, in
c     the order that the first reference requires.

c     Programmers Note:
c     The Blackboard docs at:
c        https://help.blackboard.com/Learn/Instructor/Grade/
c                             Grading_Tasks/Work_Offline_With_Grade_Data
c     say that the student's username is used as their unique ID.
c     There is one header row in the downloaded file:
c        "Last Name", "First Name", "Username", "Student ID", ...
c     Uploaded grade columns without a Blackboard generated ID (of the
c     form |NNNNNN?) will create new *text* columns.
c     Quoting in .CSV files is required, but in .XLS files is not.  But
c     the downloaded .xls file is completely quoted anyway.
c     WARNING: quoted double quotes are apparently not doubled but
c     preceded by backslash.
c     Note: the following link seems to confirm the format of the
c     comment columns as described in the blackboard docs:
c     http://blackboard.tlc.aston.ac.uk/help-support/assessing-learners/
c                                                  grade-center/grading/
c       uploading-or-downloading-grade-center-items-for-working-offline/

c     avoid typos
      implicit none

c common

c     input file encoding and backslash escape
      integer enc,besc
c     utf-8 bom
      character*3 bom
      common/cmenc/enc,besc,bom

c     saved buffer data
      integer savdim
      parameter (savdim=200000)
      integer savmax
      character*1 savchr(savdim)
      common/cmsav/savmax,savchr

c declarations

c     declared number of arguments
      integer argdim
      parameter (argdim=7)
c     number of arguments
      integer argmx
c     argument index
      integer arg
c     arguments
      character*256 argt(argdim)
      integer argtl(argdim)
c     descriptive names for the input and output files
      character*256 fin,fout
      integer finlt,foutlt
      equivalence (fin,argt(1)),(finlt,argtl(1))
      equivalence (fout,argt(2)),(foutlt,argtl(2))
c     descriptive name for the position of the AID column
      integer coladd
c     descriptive names for the header line count and column contents in them
      character*256 head
      integer headlt
      integer headct,hnamof(9),hnamlt(9)
      equivalence (head,argt(4))
      equivalence (headlt,argtl(4))
c     descriptive names for the separator character and decimal separator
      character*256 sep
      integer seplt
      character*1 sepchr
      equivalence (sep,argt(5))
      equivalence (seplt,argtl(5))
      equivalence (sepchr,argt(5)(1:1))
c     descriptive name for the quote character
      character*1 quot
      equivalence (quot,argt(6)(1:1))
c     descriptive name for the seed
      integer seed
      data seed/1/
c     set argtl after its equivalences
      data argtl/argdim*1/

c     class checksum offset (or really, a second digit symbol shift)
      integer cco
c     class ID
      character*4 cid
      integer cidlt

c     whether we are in csv mode
      logical csv
      data csv/.false./
c     whether we have a Canvas input file
      logical canvas
      data canvas/.false./
c     whether we have a Blackboard input file
      logical bb
      data bb/.false./
c     whether we are creating a new set of AID strings, not adding more
      logical new
      data new/.true./
c     whether we kill columns beyond the AID one (must be false for coladd=1)
      logical kill
      data kill/.false./
c     value of coladd actually found in the file
      integer colold
c     key columns besides coladd
      integer colnam,colfnm,collog,colacc,colfin,colnot
      data colnot/0/
c     offset of the Notes column
      integer bufnot
      data bufnot/0/

c     size of the buffer to hold the current line in the input file
      integer bufdim
      parameter (bufdim=100000)
c     number of bytes in the current line in the input file
      integer bufmax
c     offset at which to insert the new AID column
      integer bufadd
c     buffer
      character*1 bufchr(bufdim)
      character*(bufdim) bufstr
      equivalence (bufchr(1),bufstr(1:1))
c     length of a field in the buffer
      integer fldlt,fldlt2
c     decoded found field in the buffer or field or file to find
      character*256 fnd,fnd2
      integer fndlt,fndlt2
c     the offset of the next field (actually existing or not)
      integer bufnxt

c     the base 32 digits:
c     0, 1, 2, and 9 left out for similarity with O, I/l, Z, and 4/g/Q/q.
c     7 (Z/F/T) will be left out too, while avoiding duplicated characters.
c     5 (S/Y) has been kept, as it is in a different set than S and Y.
c     Characters ordered to minimize character confusions as documented in:
c     https://www.ismp.org/resources/misidentification-alphanumeric-symbols
      character*32 digs
      data digs/'ABDEGHIJKMPRSUWY734568CFLNOQTVXZ'/

c     declared number of AID strings
      integer iddim
      parameter (iddim=256)
c     actual number of AID strings
      integer idcnt
c     id number
      integer id,id0,id2
      data id0/0/
c     restriction on id values if any, already used id values, new id values
      integer idlim,idused,idnew
      data idlim/0/,idused/0/,idnew/0/
c     check sum multipliers
      integer m1,m2
      parameter (m1=1,m2=3)
c     the digits of the number
      integer d1,d2,d2s,d3
c     AID strings
      character*4 idstr(0:iddim-1),idstr0
      integer*4 idint(0:iddim-1),idint0
      equivalence (idstr,idint),(idstr0,idint0)
c     original id and inverse mapping
      integer idorg(0:iddim-1),orgid(0:iddim-1)
c     AID strings and (start of) record assigned to it
      character*155 idrec(0:iddim-1)
      integer idrecl(0:iddim-1)
      data idrecl/iddim*0/

c     whether to write the AID sort number instead of the AID
      logical dosort
      data dosort/.false./

c     whether to quote fields even if not really needed (0: no, 1: yes)
      integer doquot
      data doquot/0/

c     whether the column header has a star wildcard appended (0: no, 1: yes)
      integer star
      data star/0/

c     email domain
      character*256 dom,dom0
      integer domlt,dom0lt

c     alternative separators
      integer asepsd
      parameter (asepsd=7)
      character*(asepsd) aseps
      data aseps/'_cdpstv'/

c     backslash
      character*1 bsl

c     date and time
      character*25 dattim

c     input line terminator (0: EOF, 1: LF, 2: CR LF)
      integer term

c     number of lines written to the output file
      integer outcnt

c     output row and column indices
      integer rowcnt,row0,row,col

c     output strings
      character*3 outst1(0:9)
      character*7 outst2(0:9)

c     generic character word
      character*4 word

c     integer as a string
      character*12 cint
      integer cintl

c     whether a file exists
      logical exists,havlcs

c     shift
      integer shft

c     extension type
      integer exttyp

c     generic character number
      integer ichr,ichr0

c     generic indices
      integer i,i1,i2

c executable

c     usage
      goto 5
    1 print2,
     & 'Usage: alpaid INFILE OUTFILE POS HEADER SEP QUOT SEED'
      print3,
     & 'INFILE: input file (CSV file names should end in .csv or .xls)',
     & '        default (or -): alpaid[_?sv].{csv|xls} or alpaid.txt',
     & 'OUTFILE: output file (may not be the same as the input file)',
     & '         default (or -): new_INFILE if INFILE has no path',
     & 'POS: if CSV: column number in which to put the AID or B or C',
     & '             column number 1 puts the AID at the start',
     & '             column number 0 puts the AID at the end',
     & '             end POS with K to kill remaining columns',
     & '             default (or -): if .xls file: B: Blackboard mode',
     & '                             if .csv file: CK: Canvas mode',
     & '     if not: S: puts the AID at the start of each line',
     & '             E: puts the AID at the  end  of each line',
     & '             default (or -): S',
     & 'HEADER: number of header lines (0-9) followed by field content',
     & '        default (or -): if CSV: 1AID* or 2AIDsort*,0.00 Canvas',
     & '                                (AIDsort puts in sort numbers)',
     & '                        if not: 0',
     & 'SEP: if CSV: single byte separating CSV fields (like comma)',
     & '             default (or -): if .xls file: Tab, else: comma',
     & '                             [but { ,:;Tab|} if ?={_CDSTV}]',
     & '     if not: string between the AID and the rest of the line',
     & '             default (or -): ": "',
     & '     aliases: _=Space  C=,  D=:  S=;  T=Tab  V=|  H=-  N=NIL',
     & 'QUOT: if CSV: single byte used to quote CSV fields',
     & '              follow QUOT with F to force quoting added fields',
     & '              default (or -): double-quote',
     & '      if not: must be omitted or set to -',
     & '      aliases: D="  S=''  H=-  N=NUL',
     & 'SEED: seed to use for the random number generator (if not 1)',
     & '      default: 1; creates an unpredictable set of AID strings'
      write(3,2,err=9003)
     & 'Usage: alpaid INFILE OUTFILE POS HEADER SEP QUOT SEED'
      write(3,3,err=9003)
     & 'INFILE: input file (CSV file names should end in .csv or .xls)',
     & '        default (or -): alpaid[_?sv].{csv|xls} or alpaid.txt',
     & 'OUTFILE: output file (may not be the same as the input file)',
     & '         default (or -): new_INFILE if INFILE has no path',
     & 'POS: if CSV: column number in which to put the AID or B or C',
     & '             column number 1 puts the AID at the start',
     & '             column number 0 puts the AID at the end',
     & '             end POS with K to kill remaining columns',
     & '             default (or -): if .xls file: B: Blackboard mode',
     & '                             if .csv file: CK: Canvas mode',
     & '     if not: S: puts the AID at the start of each line',
     & '             E: puts the AID at the  end  of each line',
     & '             default (or -): S',
     & 'HEADER: number of header lines (0-9) followed by field content',
     & '        default (or -): if CSV: 1AID* or 2AIDsort*,0.00 Canvas',
     & '                                (AIDsort puts in sort numbers)',
     & '                        if not: 0',
     & 'SEP: if CSV: single byte separating CSV fields (like comma)',
     & '             default (or -): if .xls file: Tab, else: comma',
     & '                             [but { ,:;Tab|} if ?={_CDSTV}]',
     & '     if not: string between the AID and the rest of the line',
     & '             default (or -): ": "',
     & '     aliases: _=Space  C=,  D=:  S=;  T=Tab  V=|  H=-  N=NIL',
     & 'QUOT: if CSV: single byte used to quote CSV fields',
     & '              follow QUOT with F to force quoting added fields',
     & '              default (or -): double-quote',
     & '      if not: must be omitted or set to -',
     & '      aliases: D="  S=''  H=-  N=NUL',
     & 'SEED: seed to use for the random number generator (if not 1)',
     & '      default: 1; creates an unpredictable set of AID strings'
    2 format(a)
    3 format('       ',a)
    4 format('    ',a)
      call exit(1)
    5 continue

c     key formats
   10 format(a,a,a,a,a)
   20 format(a,i4,a)
   40 format(a79)
   50 format(a,$)

c     initialize backslash
      bsl=char(92)
c     initialize the encoding and backslash escape
      enc=0
      besc=0
c     initialize the utf-8 bom
      bom=char(239)//char(187)//char(191)
c     initialize the saved buffer data
      savmax=0

c     open the log file
      call openw('alpaid.log',3)

c     find date and time and log it
      dattim='ccyymmdd hhmmss.sss Shhmm'
      call date_and _time(dattim(1:8),dattim(10:19),dattim(21:25))
      print10,'Run date and time: ',dattim
      write(3,10,err=9003)'Run date and time: ',dattim
      print*
      write(3,*,err=9003)

c read and check the command arguments

c     get the arguments
      call args(argdim,argmx,argt,argtl)

c     check the number of arguments
      if(argmx.gt.argdim)goto 1

c     process argument 1, INFILE

c     default value
      if(argmx.lt.1 .or. argt(1)(1:argtl(1)).eq.'-')then
         finlt=0
         inquire(file='alpaid.csv',exist=exists,err=9000)
         if(exists)then
            finlt=10
            fin(1:finlt)='alpaid.csv'
         endif
         inquire(file='alpaid.xls',exist=exists,err=9000)
         if(exists)then
            if(finlt.gt.0)then
               print10,'Potential input file 1: ', fin(1:finlt)
               print10,'Potential input file 2: alpaid.xls'
               call fatal('Remove/rename all but the correct file')
            endif
            finlt=10
            fin(1:finlt)='alpaid.xls'
         endif
         fndlt=14
         fnd(1:fndlt)='alpaid_?sv.ext'
         do 55 i=1,asepsd
            fnd(8:8)=aseps(i:i)
            fnd(fndlt-2:fndlt)='csv'
            inquire(file=fnd(1:fndlt),exist=exists,err=9000)
            if(exists)then
               if(finlt.gt.0)then
                  print10,'Potential input file 1: ',fin(1:finlt)
                  print10,'Potential input file 2: ',fnd(1:fndlt)
                  call fatal('Remove/rename all but the correct file')
               endif
               finlt=fndlt
               fin(1:finlt)=fnd(1:fndlt)
            endif
            fnd(fndlt-2:fndlt)='xls'
            inquire(file=fnd(1:fndlt),exist=exists,err=9000)
            if(exists)then
               if(finlt.gt.0)then
                  print10,'Potential input file 1: ',fin(1:finlt)
                  print10,'Potential input file 2: ',fnd(1:fndlt)
                  call fatal('Remove/rename all but the correct file')
               endif
               finlt=fndlt
               fin(1:finlt)=fnd(1:fndlt)
            endif
   55    continue
         inquire(file='alpaid.txt',exist=exists,err=9000)
         if(exists)then
            if(finlt.gt.0)then
               print10,'Potential input file 1: ', fin(1:finlt)
               print10,'Potential input file 2: alpaid.txt'
               call fatal('Remove/rename all but the correct file')
            endif
            finlt=10
            fin(1:finlt)='alpaid.txt'
         endif
         if(finlt.le.0)then
            print2,'*** No alpaid... input file found!',' '
            write(3,2,err=9003)'*** No alpaid... input file found!',' '
            goto 1
         endif
      endif
c     check the value
      if(fin(1:finlt).eq.' ')then
         print2,'*** ERROR: Missing input file name!',' '
         write(3,2,err=9003)'*** ERROR: Missing input file name!',' '
         goto 1
      endif
c     see whether it exists
      inquire(file=fin(1:finlt),exist=exists,err=9000)
      if(.not.exists)then
         if(argmx.eq.1 .and. finlt.gt.1 .and.
     &    (fin(1:1).eq.'/' .or. fin(1:1).eq.'-'))goto 1
         print2,'*** Input file not found: "'//fin(1:finlt)//'"',' '
         write(3,2,err=9003)
     &    '*** Input file not found: "'//fin(1:finlt)//'"',' '
         goto 1
      endif

c     process argument 2, OUTFILE

c     default value
      if(argmx.lt.2 .or. argt(2)(1:argtl(2)).eq.'-')then
c        use new_INFILE if INFILE is free of DOS and unix path characters
         if(
     &    index(fin(1:finlt),':').gt.0 .or.
     &    index(fin(1:finlt),'/').gt.0 .or.
     &    index(fin(1:finlt),bsl).gt.0)then
            print10,
     &       '*** ERROR: output file name must be specified if the'
            print2,
     &       '    input file name contains :, /, or '//bsl//'!',' '
            write(3,10,err=9003)
     &       '*** ERROR: output file name must be specified if the'
            write(3,2,err=9003)
     &       '    input file name contains :, /, or '//bsl//'!',' '
            goto 1
         endif
         foutlt=finlt+4
         if(foutlt.gt.256)
     &    call fatal2('File name too long: ',fin(1:finlt))
         fout(1:4)='new_'
         fout(5:foutlt)=fin(1:finlt)
      endif
c     disallow spaces
      if(index(fout(1:foutlt),' ').ne.0)then
         print2,
     &    '*** ERROR: Spaces in the output file name are not allowed!',
     &    ' '
         write(3,2,err=9003)
     &    '*** ERROR: Spaces in the output file name are not allowed!',
     &    ' '
         goto 1
      endif
c     check that it does not equal the input file name
      if(finlt.eq.foutlt)then
         if(fin(1:finlt).eq.fout(1:foutlt))call fatal(
     &    'Output file may not equal the input one')
      endif

c     convert all remaining arguments except HEADER to upper case

c     convert case
      do 60 arg=3,argmx
         if(arg.eq.4)goto 60
         do 59 i=1,argtl(arg)
            ichr=ichar(argt(arg)(i:i))
            ichr0=ichr
            if(ichr.ge.ichar('a') .and. ichr.le.ichar('z'))ichr=ichr-32
c           shorthands for SEP
            if(arg.eq.5)then
               if(ichr.eq.ichar('_'))ichr=32
               if(ichr.eq.ichar('C'))ichr=ichar(',')
               if(ichr.eq.ichar('D'))ichr=ichar(':')
               if(ichr.eq.ichar('P'))ichr=ichar('|')
               if(ichr.eq.ichar('S'))ichr=ichar(';')
               if(ichr.eq.ichar('T'))ichr=9
               if(ichr.eq.ichar('V'))ichr=ichar('|')
c           shorthands for QUOT
            else if(arg.eq.6)then
               if(ichr.eq.ichar('D'))ichr=ichar('"')
               if(ichr.eq.ichar('S'))ichr=ichar('''')
               if(ichr.eq.ichar('N'))ichr=0
            endif
            if(ichr.ne.ichr0)argt(arg)(i:i)=char(ichr)
   59    continue
   60 continue

c     process argument 3, POS

c     see if if the input file name ends in .csv or .xls
      exttyp=0
      i=finlt
      if(i.ge.4)then
         if(fin(i-3:i-3).eq.'.')then
            if(
     &       (fin(i-2:i-2).eq.'X' .or. fin(i-2:i-2).eq.'x') .and.
     &       (fin(i-1:i-1).eq.'L' .or. fin(i-1:i-1).eq.'l') .and.
     &       (fin(i:i)    .eq.'S' .or. fin(i:i)    .eq.'s'))exttyp=1
            if(
     &       (fin(i-2:i-2).eq.'C' .or. fin(i-2:i-2).eq.'c') .and.
     &       (fin(i-1:i-1).eq.'S' .or. fin(i-1:i-1).eq.'s') .and.
     &       (fin(i:i)    .eq.'V' .or. fin(i:i)    .eq.'v'))exttyp=2
         endif
      endif
      if(exttyp.ne.0)csv=.true.
c     default value
      if(argmx.lt.3 .or. argt(3)(1:argtl(3)).eq.'-')then
         if(exttyp.eq.1)then
            argtl(3)=1
            argt(3)(1:argtl(3))='B'
         else if(exttyp.eq.2)then
            argtl(3)=2
            argt(3)(1:argtl(3))='CK'
         else
            argtl(3)=1
            argt(3)(1:argtl(3))='S'
         endif
      endif
c     set kill
      if(argtl(3).gt.1 .and. argt(3)(argtl(3):argtl(3)).eq.'K')then
         if(index('SE',argt(3)(1:1)).gt.0)then
            print2,'*** Cannot kill columns in a plain text file!',' '
            write(3,2,err=9003)
     &       '*** Cannot kill columns in a plain text file!',' '
            goto 1
         endif
         argtl(3)=argtl(3)-1
         kill=.true.
      endif
c     put each field at the start of the line
      if(argt(3)(1:argtl(3)).eq.'S')then
         coladd=-1
         if(csv)then
            print10,' ** WARNING: CSV mode turned off.'
            write(3,10,err=9003)' ** WARNING: CSV mode turned off.'
            csv=.false.
         endif
c     put each field at the end of the line
      else if(argt(3)(1:argtl(3)).eq.'E')then
         coladd=-2
         if(csv)then
            print10,' ** WARNING: CSV mode turned off.'
            write(3,10,err=9003)' ** WARNING: CSV mode turned off.'
            csv=.false.
         endif
c     use Blackboard mode
      else if(argt(3)(1:argtl(3)).eq.'B')then
         bb=.true.
         if(exttyp.ne.1)call fatal2(
     &    'Blackboard requires a .xls input file, not ',fin(1:finlt))
         print10,' ** WARNING: ',
     &    'Expecting a Blackboard XLS file as this program knows it.'
         write(3,10,err=9003)' ** WARNING: ',
     &    'Expecting a Blackboard XLS file as this program knows it.'
         coladd=4
c     use Canvas mode
      else if(argt(3)(1:argtl(3)).eq.'C')then
         canvas=.true.
         if(exttyp.ne.2)call fatal2(
     &    'Canvas mode requires a .csv input file, not ',fin(1:finlt))
         print10,' ** WARNING: ',
     &    'Expecting a Canvas CSV file as this program knows it.'
         write(3,10,err=9003)' ** WARNING: ',
     &    'Expecting a Canvas CSV file as this program knows it.'
         coladd=3
c     read a numeric column position
      else
         coladd=0
         do 70 i=1,argtl(3)
            i2=ichar(argt(3)(i:i))-48
            if(i2.lt.0 .or. i2.gt.9)then
               print2,
     &          '*** ERROR: bad digit in POS: "'//argt(3)(i:i)//'"!',' '
               write(3,2,err=9003)
     &          '*** ERROR: bad digit in POS: "'//argt(3)(i:i)//'"!',' '
               goto 1
            endif
            if(coladd.gt.123456789)call fatal('POS is too big')
            coladd=coladd*10+i2
   70    continue
         if(.not.csv)then
            print10,' ** WARNING: CSV mode turned on.'
            write(3,10,err=9003)' ** WARNING: CSV mode turned on.'
            csv=.true.
         endif
      endif
c     we cannot kill from column 1 and beyond the final column is none
      if(kill .and. coladd.le.1)then
         print10,' ** WARNING: Unsupported kill turned off.'
         write(3,10,err=9003)' ** WARNING: Unsupported kill turned off.'
         kill=.false.
      endif

c     process argument 4, HEADER

c     default value
      if(argmx.lt.4 .or. argt(4)(1:argtl(4)).eq.'-')then
         if(bb)then
            headlt=5
            head(1:headlt)='1AID*'
         else if(canvas)then
            headlt=14
            head(1:headlt)='2AIDsort*,0.00'
         else if(csv)then
            headlt=5
            head(1:headlt)='1AID*'
         else
            headlt=1
            head(1:headlt)='0'
         endif
      endif
c     read number of header lines
      headct=ichar(head(1:1))-48
      if(headct.lt.0 .or. headct.gt.9 .or. (headlt.gt.1 .and.
     & head(2:2).ge.'0' .and. head(2:2).le.'9'))then
         print2,
     &    '*** ERROR: HEADER line count must be from 0 to 9!',' '
         write(3,2,err=9003)
     &    '*** ERROR: HEADER line count must be from 0 to 9!',' '
         goto 1
      endif
c     read the column name for each header line, if any
      i2=1
      do 80 i=1,headct
         hnamof(i)=i2
         hnamlt(i)=headlt-i2
         if(hnamlt(i).le.0)goto 80
         i1=index(head(i2+1:headlt),',')
         if(i1.le.0)then
            i2=headlt
         else
            hnamlt(i)=i1-1
            i2=i2+i1
         endif
   80 continue
c     in Blackboard mode we must have 1 header line
      if(headct.ne.1 .and. bb)
     & call fatal('Must have 1 header line in Blackboard mode')
c     in Canvas mode we must have 2 header lines
      if(headct.ne.2 .and. canvas)
     & call fatal('Must have 2 header lines in Canvas mode')
c     check for a trailing star in the column name
      if(headct.gt.0 .and. hnamlt(1).gt.0)then
         i=hnamof(1)+hnamlt(1)
         if(head(i:i).eq.'*')then
            star=1
            hnamlt(1)=hnamlt(1)-1
         endif
      endif
c     must have a column name if there are header lines
      if(headct.gt.0 .and. hnamlt(1).le.0)
     & call fatal('Must have a column name in the header')
c     see whether to write sort numbers instead of AID strings
      if(headct.gt.0 .and. hnamlt(1).eq.7)then
         i=hnamof(1)
         if(head(i+1:i+7).eq.'AIDsort')then
            if(.not.csv)call fatal('Currently AID sort numbers '//
     &       'are only implemented for CSV files')
            dosort=.true.
         endif
      endif

c     process argument 5, SEP

c     default value
      if(argmx.lt.5 .or. argt(5)(1:argtl(5)).eq.'-')then
         if(csv)then
            seplt=1
            sepchr=','
            if(exttyp.eq.1)sepchr=char(9)
            if(finlt.ge.8)then
               i=finlt-8
               if(
     &          fin(i+1:i+1).eq.'_' .and. fin(i+5:i+5).eq.'.'  .and.
     &          (fin(i+3:i+3).eq.'s' .or. fin(i+3:i+3).eq.'S') .and.
     &          (fin(i+4:i+4).eq.'v' .or. fin(i+4:i+4).eq.'V')
     &          )then
                  i=i+2
                  if(fin(i:i).eq.'_')sepchr=' '
                  if(fin(i:i).eq.'c' .or. fin(i:i).eq.'C')sepchr=','
                  if(fin(i:i).eq.'d' .or. fin(i:i).eq.'D')sepchr=':'
                  if(fin(i:i).eq.'p' .or. fin(i:i).eq.'P')sepchr='|'
                  if(fin(i:i).eq.'s' .or. fin(i:i).eq.'S')sepchr=';'
                  if(fin(i:i).eq.'t' .or. fin(i:i).eq.'T')sepchr=char(9)
                  if(fin(i:i).eq.'v' .or. fin(i:i).eq.'V')sepchr='|'
               endif
            endif
         else
            seplt=2
            sep(1:seplt)=': '
         endif
      endif
c     translate a leading H into -
      if(sep(1:1).eq.'H')sep(1:1)='-'
c     allow NIL
      if(sep(1:seplt).eq.'N')seplt=0
c     check the value
      if(csv)then
         if(seplt.ne.1)
     &    call fatal('The CSV separator must be a single byte')
         if(sepchr.eq.char(10))
     &    call fatal('The CSV separator cannot be LF')
         if(sepchr.eq.char(13))
     &    call fatal('The CSV separator cannot be CR')
         if(sepchr.eq.'"')
     &    call fatal('The CSV separator cannot be double-quote')
      endif

c     process argument 6, QUOT

c     default value
      if(argmx.lt.6 .or. argt(6)(1:argtl(6)).eq.'-')then
         if(csv)then
            argtl(6)=1
            argt(6)(1:argtl(6))='"'
            if(bb)then
               argtl(6)=2
               argt(6)(1:argtl(6))='"F'
            endif
         else
            argtl(6)=1
            argt(6)(1:argtl(6))='-'
         endif
      endif
c     in csv mode analyze QUOT
      if(csv)then
c        see whether there is backslash escaping
         if(argtl(6).ge.2)then
            if(argt(6)(argtl(6):argtl(6)).eq.'B')then
c              may not be CR or LF or nonASCII
               besc=92
               argtl(6)=argtl(6)-1
            endif
         endif
c        see whether quoting is forced
         if(argtl(6).eq.2)then
            if(argt(6)(2:2).eq.'F')then
               doquot=1
               argtl(6)=1
            endif
         endif
c        translate H into -
         if(argt(6)(1:1).eq.'H')argt(6)(1:1)='-'
c        check the value
         if(argtl(6).ne.1)
     &    call fatal('The CSV quote character must be a single byte')
         if(quot.eq.char(10))
     &    call fatal('The CSV quote character cannot be LF')
         if(quot.eq.char(13))
     &    call fatal('The CSV quote character cannot be CR')
         if(quot.eq.sepchr)call fatal(
     &    'The CSV quote character cannot be the separator')
         if(ichar(quot).eq.besc)
     &    call fatal('The CSV quote character cannot be the escape one')
         if(ichar(sepchr).eq.besc)
     &    call fatal('The CSV separator cannot be the escape character')
c     in plain text mode, QUOT must be undefined
      else
         if(argt(6)(1:argtl(6)).ne.'-')then
            print2,'*** ERROR: a CSV quote character '//
     &       'may only be specified in CSV mode!',' '
            write(3,2,err=9003)'*** ERROR: a CSV quote character '//
     &       'may only be specified in CSV mode!',' '
            goto 1
         endif
      endif

c     process argument 7, SEED

c     read any given value
      if(argmx.ge.7 .and. argt(7)(1:argtl(7)).ne.'-')
     & read(argt(7)(1:argtl(7)),*,err=1)seed

c     done processing the arguments

c     end of the argument list
      print*
      write(3,*,err=9003)

c look for the e-mail domain and for the class ID and for old counts and seeds

c     set the default e-mail domain
      dom0lt=1
      dom0(1:dom0lt)='@'
      inquire(file='email.txt',exist=exists,err=9000)
      if(exists)then
         open(12,file='email.txt',status='old',err=9012)
         read(12,10,err=9012)dom0
         dom0lt=len(dom0)
         call trimlt(dom0,dom0lt)
         if(dom0lt.lt.1)goto 9012
         if(dom0(dom0lt:dom0lt).eq.char(13))dom0lt=dom0lt-1
         if(dom0lt.lt.1)goto 9012
         if(dom0(1:1).ne.'@')then
            print10,'E-mail domain must start with "@"!'
            goto 9012
         endif
         close(12,err=9012)
         print10,'Default e-mail domain from file email.txt: '
         print10,dom0(1:dom0lt)
         print*
         write(3,10,err=9003)
     &    'Default e-mail domain from file email.txt: '
         write(3,10,err=9003)dom0(1:dom0lt)
         write(3,*,err=9003)
      endif

c     read alpaid.cid
      inquire(file='alpaid.cid',exist=exists,err=9000)
      if(.not.exists)then
         print2,
     &    'You need to select a class distinction number from 0 to 14.',
     &    'This is in case students in your class are also in another',
     &    'class that uses AID strings.  Assuming that the other',
     &    'class uses a different class distinction number, there will',
     &    'not be a problem if a student uses the AID string from the',
     &    'other class in yours, or vice versa.  In particular the',
     &    'credit will not go to a wrong student.  If no other classes',
     &    'use AID strings just enter 0.'
  100    print50,'Enter your class distinction number from 0 to 14 '//
     &    '(q to quit): '
  101    format(i2)
  102    format(a4)
         read102,cid
         cidlt=4
         call trimlt(cid,cidlt)
         if(cidlt.le.0)goto 100
         if(cid(1:cidlt).eq.'q' .or. cid(1:cidlt).eq.'Q')
     &    call fatal('User quit')
         if(cidlt.eq.1 .and. cid(1:1).ge.'0' .and. cid(1:1).le.'9')then
            cco=ichar(cid(1:1))-48
         else if(cidlt.eq.2 .and. cid(1:1).eq.'1'
     &       .and. cid(2:2).ge.'0' .and. cid(2:2).le.'4')then
            cco=10+ichar(cid(2:2))-48
         else
            print2,'*** Bad number.  Please reenter.'
            goto 100
         endif
         print2,' ',
     &    'You may also want to select a class ID string.',
     &    'This may discourage students from entering the AID of the',
     &    'wrong class in the first place.  The ID string will be',
     &    'prepended before the 3 character AID.'
  103    print50,'Enter no more than 3 lowercase letters and digits '//
     &    'as a Class ID: '
         read102,cid
         cidlt=4
         call trimlt(cid,cidlt)
         if(cidlt.ge.4)goto 103
         if(cidlt.le.0)goto 106
         if(cid(1:cidlt).eq.'q' .or. cid(1:cidlt).eq.'Q')
     &    call fatal('User quit')
         do 105 i=1,cidlt
            ichr=ichar(cid(i:i))
            if(ichr.lt.32 .or. ichr.eq.127)then
               print*,'*** ASCII control character',ichr,' is illegal'
               goto 103
            endif
            if(ichr.eq.32 .or. ichr.eq.ichar(':'))goto 104
            if(ichr.eq.ichar(sepchr) .or. ichr.eq.ichar(quot))goto 104
            if(ichr.ge.ichar('A') .and. ichr.le.ichar('Z'))goto 104
            if(ichr.eq.ichar('0') .and. i.eq.1)goto 104
            if(i.lt.cidlt)goto 105
            if(ichr.ge.ichar('0') .and. ichr.le.ichar('9'))goto 105
            if(ichr.ge.ichar('a') .and. ichr.le.ichar('z'))goto 105
            if(ichr.eq.ichar('@') .or.  ichr.eq.ichar('_'))goto 105
  104       print10,'*** Character "',cid(i:i),'" is illegal.'
            goto 103
  105    continue
  106    open(2,file='alpaid.cid',status='new',err=9002)
         write(2,101,err=9002)cco
         write(2,102,err=9002)cid
         close(2,err=9002)
      endif
      open(1,file='alpaid.cid',status='old',err=9001)
      read(1,101,err=9001)cco
      if(cco.lt.0 .or. cco.gt.14)goto 9001
      print20,'Class distinction number:',cco
      write(3,20,err=9003)'Class distinction number:',cco
      read(1,102,err=9001)cid
      close(1,err=9001)
      cidlt=4
      call trimlt(cid,cidlt)
      if(cidlt.gt.3)goto 9001
      if(cidlt.le.0)then
         print10,'No class ID'
         write(3,10,err=9003)'No class ID'
      else
         print10,'Class ID: "',cid(1:cidlt),'"'
         write(3,10,err=9003)'Class ID: "',cid(1:cidlt),'"'
      endif
      print*
      write(3,*,err=9003)

c     check limit, count, seed file alpaid.lcs
      inquire(file='alpaid.lcs',exist=havlcs,err=9000)
      if(havlcs)then
         print10,'Reading file alpaid.lcs'
         write(3,10,err=9003)'Reading file alpaid.lcs'
         open(4,file='alpaid.lcs',status='old',err=9004)
         idused=-1
         term=1
  110    bufstr(1:79)=' '
         read(4,40,err=9004,end=112)bufstr(1:79)
         if(bufstr(1:3).eq.bom)then
            bufstr(101:179)=bufstr(1:79)
            bufstr(1:79)=bufstr(104:179)//'   '
         endif
         if(bufstr(1:79).eq.' ')goto 110
  111    print10,bufstr(1:79)
         write(3,10,err=9003)bufstr(1:79)
         if(index(bufstr(1:2),char(254)).gt.0)call fatal(
     &    'Error: File alpaid.lcs is illegal: UTF-16 ("Unicode")')
         i=index(bufstr(1:79),':')
         if(i.ne.1 .and. i.ne.4 .and. i.ne.38)goto 9004
         if(i.eq.4 .and.
     &    bufstr(1:i).ne.'new:' .and. bufstr(1:i).ne.'New:')goto 9004
         new=.true.
         if(i.ne.4)new=.false.
         read(bufstr(i+1:79),*,err=9004)idlim,idused,seed
         if(term.eq.0)goto 113
         goto 110
  112    term=0
         if(bufstr(1:79).ne.' ')goto 111
  113    if(idused.lt.0)goto 9004
         close(4,err=9004)
         if(new)goto 115
         print2,
     &    ' ** Program alpaid has already run before in this folder,',
     &    '    based on limit-count-seed file alpaid.lcs.'
         call inttos(idused,cint,cintl)
         print4,
     &    'Alpaid will merely add AID data where still missing.',
     &    'Please make absolutely sure that input file',
     &       '   '//fin(1:finlt),
     &    'is fully up to date and has the already assigned '
     &    //cint(1:cintl)//' AID',
     &    'strings in the appropriate column.  Enter the letter "C" to',
     &    'confirm that the input file is up to date or "Q" to quit.'
  114    print50,'    Enter "C" or "Q" as described above: '
         read*,word
         if(word.eq.'C' .or. word.eq.'c')then
            write(3,10,err=9003)
     &       ' ** User confirmed that the input file is up to date: ',
     &       fin(1:finlt)
            print*
            write(3,*,err=9003)
         else if(word.eq.'Q' .or. word.eq.'q')then
            call fatal2(
     &       'User did not confirm the input file ',fin(1:finlt))
         else
            goto 114
         endif
  115    print*
         write(3,*,err=9003)
      endif

c check the input file

c     try opening the input file
      call openru(fin(1:finlt),1)
      print10,'Reading file ',fin(1:finlt)
      print*
      write(3,10,err=9003)'Reading file ',fin(1:finlt)
      write(3,*,err=9003)

c     check and write any header lines in CSV mode
      if(headct.gt.0 .and. csv)then
         call readcv(fin,finlt,1,sepchr,quot,coladd,
     &    bufdim,bufmax,bufadd,fldlt,bufchr,term)
         if(term.eq.0 .and. bufmax.le.0)
     &    call fatal2('Input file is empty: ',fin(1:finlt))
         col=1
         if(bb)then
            call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,col,
     &       9,'Last Name',fldlt,bufnxt,colnam)
            if(colnam.le.0)call fatal2(
     &       'Column ''Last Name'' not found in ',fin(1:finlt))
            call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,col,
     &       10,'First Name',fldlt,bufnxt,colfnm)
            if(colfnm.le.0)call fatal2(
     &       'Column ''First Name'' not found in ',fin(1:finlt))
            call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,col,
     &       8,'Username',fldlt,bufnxt,collog)
            if(collog.le.0)call fatal2(
     &       'Column ''Username'' not found in ',fin(1:finlt))
            coladd=max(colnam,colfnm,collog)
            if(coladd.gt.collog)call fatal2(
     &       'Column ''Username'' in the wrong place in ',fin(1:finlt))
            bufadd=bufnxt-1
            call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,col,
     &       10,'Student ID',fldlt,bufnxt,colfin)
            if(colfin.gt.coladd)then
               coladd=colfin
               bufadd=bufnxt-1
            endif
            call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,col,
     &       11,'Last Access',fldlt,bufnxt,colfin)
            if(colfin.gt.coladd)then
               coladd=colfin
               bufadd=bufnxt-1
            endif
            call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,col,
     &       12,'Availability',fldlt,bufnxt,colfin)
            if(colfin.gt.coladd)then
               coladd=colfin
               bufadd=bufnxt-1
            endif
            coladd=coladd+1
            if(dosort)
     &       call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,col,
     &       16,'Feedback to User',fldlt,bufnxt,colnot)
         endif
         if(canvas)then
            call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,col,
     &       7,'Student',fldlt,bufnxt,colnam)
            if(colnam.le.0)call fatal2(
     &       'Column ''Student'' not found in ',fin(1:finlt))
            call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,col,
     &       12,'SIS Login ID',fldlt,bufnxt,collog)
            if(collog.le.0)
     &       print2,' ** Warning: Column ''SIS Login ID'' not found'
            call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,col,
     &       12,'Root Account',fldlt,bufnxt,colacc)
            if(colacc.le.0)
     &       print2,' ** Warning: Column ''Root Account'' not found'
            call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,col,
     &       7,'Section',fldlt,bufnxt,colfin)
            if(colfin.le.0)call fatal2(
     &       'Column ''Section'' not found in ',fin(1:finlt))
            coladd=max(colnam,collog,colacc,colfin)
            if(coladd.gt.colfin)call fatal2(
     &       'Column ''Section'' in the wrong place in ',fin(1:finlt))
            coladd=colfin+1
            bufadd=bufnxt-1
            call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,col,
     &       5,'Notes',fldlt,bufnxt,colnot)
            if(colnot.gt.colfin)then
               coladd=colnot+1
               bufadd=bufnxt-1
            endif
            if(.not.dosort)colnot=0
         endif
         i1=hnamof(1)
         i2=hnamlt(1)+star
         call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,col,
     &    i2,head(i1+1:i1+i2),fldlt,bufnxt,colold)
         if(new .and. colold.gt.0)then
            print2,'*** There is already a column '''//
     &       head(i1+1:i1+i2)//''' in input file'
            print4,
     &       '   '//fin(1:finlt),
     &       'To create a completely new set of AID strings, you must',
     &       'delete this column.  Then move the input file into a new',
     &       'folder and run alpaid again.',
     &       'To add instead missing AID strings to the current ones,',
     &       'it is *essential* to have a valid file alpaid.lcs that',
     &       'was created when the current AID strings were created',
     &       'without program errors.'
            write(3,2,err=9003)'*** There is already a column '''//
     &       head(i1+1:i1+i2)//''' in input file'
            write(3,4,err=9003)
     &       '   '//fin(1:finlt),
     &       'To create a completely new set of AID strings, you must',
     &       'delete this column.  Then move the input file into a new',
     &       'folder and run alpaid again.',
     &       'To add instead missing AID strings to the current ones,',
     &       'it is *essential* to have a valid file alpaid.lcs that',
     &       'was created when the current AID strings were created',
     &       'without program errors.'
            call fatal('Must abort for the above reason')
         else if(.not.new .and. colold.le.0)then
            print2,'*** Column '''//head(i1+1:i1+i2)//
     &       ''' not found in input file'
            print4,
     &       '   '//fin(1:finlt),
     &       'You cannot add more AID strings without an existing',
     &       'AID column.  Check that you are using the latest',
     &       'version of the input file.',
     &       'Otherwise you may need to create new AID strings from',
     &       'scratch.  Make sure to create a new folder for that.'
            write(3,2,err=9003)'*** Column '''//head(i1+1:i1+i2)//
     &       ''' not found in input file'
            write(3,4,err=9003)
     &       '   '//fin(1:finlt),
     &       'You cannot add more AID strings without an existing',
     &       'AID column.  Check that you are using the latest',
     &       'version of the input file.',
     &       'Otherwise you may need to create new AID strings from',
     &       'scratch.  Make sure to create a new folder for that.'
            call fatal('Must abort for the above reason')
         else if(.not.new .and. coladd.ne.colold .and. coladd.ne.0)then
            print2,'*** Column '''//head(i1+1:i1+i2)//
     &       ''' in input file'
            print4,
     &       '   '//fin(1:finlt),
     &       'is in the wrong place:'
            call inttos(coladd,cint,cintl)
            print4,'Expected column number: '//cint(1:cintl)
            call inttos(colold,cint,cintl)
            print4,'Found at column number: '//cint(1:cintl),
     &       'Will proceed using column number '//cint(1:cintl)
  140       print50,
     &       '    Enter the letter "A" to accept that or "Q" to quit: '
            read*,word
            if(word.eq.'A' .or. word.eq.'a')then
               write(3,10,err=9003)
     &          ' ** User accepted AID column number '//cint(1:cintl)
            else if(word.eq.'Q' .or. word.eq.'q')then
               call fatal('User quit on column number error')
            else
               goto 140
            endif
         endif
         if(.not.new)then
            coladd=colold
            bufadd=bufnxt-fldlt-2
         endif
         if(coladd.le.1)kill=.false.
         if(new)then
            if(coladd.eq.1)then
               if(doquot.ne.0)call savbuf(1,quot)
               call savbuf(hnamlt(1),head(hnamof(1)+1:))
               if(doquot.ne.0)call savbuf(1,quot)
               call savbuf(1,sepchr)
               call savbuf(bufmax,bufchr)
            else
               call savbuf(bufadd,bufchr)
               call savbuf(1,sepchr)
               if(doquot.ne.0)call savbuf(1,quot)
               call savbuf(hnamlt(1),head(hnamof(1)+1:))
               if(doquot.ne.0)call savbuf(1,quot)
               if(kill)then
                  call savbuf(term,bufchr(bufmax-term+1))
               else
                  call savbuf(bufmax-bufadd,bufchr(bufadd+1))
               endif
            endif
            do 141 i=2,headct
               call readcv(fin,finlt,1,sepchr,quot,coladd,
     &          bufdim,bufmax,bufadd,fldlt,bufchr,term)
               if(term.eq.0 .and. bufmax.le.0)
     &          call fatal2('Missing header line in ',fin(1:finlt))
               if(coladd.eq.1)then
                  if(doquot.ne.0)call savbuf(1,quot)
                  call savbuf(hnamlt(i),head(hnamof(i)+1:))
                  if(doquot.ne.0)call savbuf(1,quot)
                  call savbuf(1,sepchr)
                  call savbuf(bufmax,bufchr)
               else
                  call savbuf(bufadd,bufchr)
                  call savbuf(1,sepchr)
                  if(doquot.ne.0)call savbuf(1,quot)
                  call savbuf(hnamlt(i),head(hnamof(i)+1:))
                  if(doquot.ne.0)call savbuf(1,quot)
                  if(kill)then
                     call savbuf(term,bufchr(bufmax-term+1))
                  else
                     call savbuf(bufmax-bufadd,bufchr(bufadd+1))
                  endif
               endif
  141       continue
         else
            if(kill)then
               call savbuf(bufnxt-1,bufchr)
               call savbuf(term,bufchr(bufmax-term+1))
            else
               call savbuf(bufmax,bufchr)
            endif
            do 142 i=2,headct
               call readcv(fin,finlt,1,sepchr,quot,coladd+1,
     &          bufdim,bufmax,bufadd,fldlt,bufchr,term)
               if(term.eq.0 .and. bufmax.le.0)
     &          call fatal2('Missing header line in ',fin(1:finlt))
               if(kill)then
                  call savbuf(bufadd,bufchr)
                  call savbuf(term,bufchr(bufmax-term+1))
               else
                  call savbuf(bufmax,bufchr)
               endif
  142       continue
         endif

c     check and write any header lines in plain text mode
      else if(headct.gt.0)then
         call readfu(fin,finlt,1,bufdim,bufmax,bufchr,term)
         if(term.eq.0 .and. bufmax.le.0)
     &    call fatal2('Input file is empty: ',fin(1:finlt))
         colold=0
         i1=hnamof(1)
         i2=hnamlt(1)
         if(bufmax-term.ge.i2+seplt)then
            if(coladd.eq.-1)then
               if(bufstr(1:i2).ne.head(i1+1:i1+i2))goto 160
               colold=1
               if(seplt.le.0)goto 160
               if(bufstr(i2+1:i2+seplt).eq.sep(1:seplt))goto 160
               colold=0
            else
               i=bufmax-term-seplt-i2
               if(bufstr(i+seplt+1:i+seplt+i2).ne.head(i1+1:i1+i2))
     &          goto 160
               colold=2
               if(seplt.le.0)goto 160
               if(bufstr(i+1:i+seplt).eq.sep(1:seplt))goto 160
               colold=0
            endif
         endif
  160    if(new .and. colold.ne.0)then
            print2,'*** There is already a column '''//
     &       head(i1+1:i1+i2)//''' in input file'
            print4,
     &       '   '//fin(1:finlt),
     &       'To create a completely new set of AID strings, you must',
     &       'delete this column.  Then move the input file into a new',
     &       'folder and run alpaid again.',
     &       'To add instead missing AID strings to the current ones,',
     &       'it is *essential* to have a valid file alpaid.lcs that',
     &       'was created when the current AID strings were created',
     &       'without program errors.'
            write(3,2,err=9003)'*** There is already a column '''//
     &       head(i1+1:i1+i2)//''' in input file'
            write(3,4,err=9003)
     &       '   '//fin(1:finlt),
     &       'To create a completely new set of AID strings, you must',
     &       'delete this column.  Then move the input file into a new',
     &       'folder and run alpaid again.',
     &       'To add instead missing AID strings to the current ones,',
     &       'it is *essential* to have a valid file alpaid.lcs that',
     &       'was created when the current AID strings were created',
     &       'without program errors.'
            call fatal('Must abort for the above reason')
         else if(.not.new .and. colold.eq.0)then
            print2,'*** Column '''//head(i1+1:i1+i2)//
     &       ''' not found in input file'
            print4,
     &       '   '//fin(1:finlt),
     &       'You cannot add more AID strings without an existing',
     &       'AID column.  Check that you are using the latest',
     &       'version of the input file.',
     &       'Otherwise you may need to create new AID strings from',
     &       'scratch.  Make sure to create a new folder for that.'
            write(3,2,err=9003)'*** Column '''//head(i1+1:i1+i2)//
     &       ''' not found in input file'
            write(3,4,err=9003)
     &       '   '//fin(1:finlt),
     &       'You cannot add more AID strings without an existing',
     &       'AID column.  Check that you are using the latest',
     &       'version of the input file.',
     &       'Otherwise you may need to create new AID strings from',
     &       'scratch.  Make sure to create a new folder for that.'
            call fatal('Must abort for the above reason')
         endif
         if(new)then
            if(coladd.eq.-1)then
               call savbuf(hnamlt(1),head(hnamof(1)+1:))
               call savbuf(seplt,sep)
               call savbuf(bufmax,bufchr)
            else
               call savbuf(bufmax-term,bufchr)
               call savbuf(seplt,sep)
               call savbuf(hnamlt(1),head(hnamof(1)+1:))
               call savbuf(term,bufchr(bufmax-term+1))
            endif
            do 161 i=2,headct
               call readfu(fin,finlt,1,bufdim,bufmax,bufchr,term)
               if(term.eq.0 .and. bufmax.le.0)
     &          call fatal2('Missing header line in ',fin(1:finlt))
               if(coladd.eq.-1)then
                  call savbuf(hnamlt(i),head(hnamof(i)+1:))
                  call savbuf(seplt,sep)
                  call savbuf(bufmax,bufchr)
               else
                  call savbuf(bufmax-term,bufchr)
                  call savbuf(seplt,sep)
                  call savbuf(hnamlt(i),head(hnamof(i)+1:))
                  call savbuf(term,bufchr(bufmax-term+1))
               endif
  161       continue
         else
            call savbuf(bufmax,bufchr)
            do 162 i=2,headct
               call readfu(fin,finlt,1,bufdim,bufmax,bufchr,term)
               if(term.eq.0 .and. bufmax.le.0)
     &          call fatal2('Missing header line in ',fin(1:finlt))
               call savbuf(bufmax,bufchr)
  162       continue
         endif
      endif

c create the list of AID strings

c     create the AID strings
      idcnt=0
      do 250 d1=0,15
         do 240 d2=0,15
c           modified second digit symbol location
            d2s=d2
            if(d2.gt.0)d2s=1+mod(d2-1+cco,15)
c           check digit
            d3=mod(d1*m1+d2*m2,16)
c           add the id to the list
            idcnt=idcnt+1
c           id number
            id=d1*16+d2s
c           the actual AID
            idstr(id)=
     &       ' '//digs(d1+1:d1+1)//digs(d2s+17:d2s+17)//digs(d3+1:d3+1)
c           mark AID strings with the same start and end digits
            if(d1.eq.d3)idstr(id)(1:1)='*'
c           mark AID strings that invert to a valid ID
            if(d1.gt.d3 .and. d1.eq.mod(m1*d3+m2*d2,16))
     &       idstr(id)(1:1)='*'
c           initialize the id sorting order to trivial
            idorg(id)=id
  240    continue
  250 continue

c     print the AID strings out 16 a line
      print*
      write(3,*,err=9003)
      print10,'Sorted AID strings:'
      write(3,10,err=9003)'Sorted AID strings:'
      rowcnt=(idcnt+15)/16
      row0=rowcnt-(rowcnt*16-idcnt)
      i=0
      do 300 row=0,rowcnt-1
         print310,(idstr(row+col*rowcnt),col=0,15-i)
         write(3,310,err=9003)(idstr(row+col*rowcnt),col=0,15-i)
         if(row+1.eq.row0)i=1
  300 continue
  310 format(
     & a4,' ',a4,' ',a4,' ',a4,' ',a4,' ',a4,' ',a4,' ',a4,' ',
     & a4,' ',a4,' ',a4,' ',a4,' ',a4,' ',a4,' ',a4,' ',a4)
      print*
      write(3,*,err=9003)

c     remove the IDs marked with a star
      id0=-1
      do 400 id=0,idcnt-1
         if(idstr(id)(1:1).eq.'*') goto 400
         id0=id0+1
         idint(id0)=idint(id)
  400 continue
      idcnt=id0+1
      print20,
     & 'Eliminated the starred AID strings.  AID strings left: ',idcnt
      write(3,20,err=9003)
     & 'Eliminated the starred AID strings.  AID strings left: ',idcnt
      print*
      write(3,*,err=9003)

c     summarize the status
      print2,
     & 'For a class size of 90 students, the probability for a random',
     & 'string of 3 case-insensitive alphanumeric characters to be a',
     & 'valid AID is about 0.2%.  However, hundreds of errors might be',
     & 'made in a whole class.  To reduce the chance of problems, the',
     & 'AID strings are chosen such that the following errors in an',
     & 'AID string never produce a valid other AID:',
     & '- Getting any single character wrong, by student or grader,',
     & '  for whatever reason.',
     & '- Getting multiple instances of the same character wrong,',
     & '  like a grader misreading a weirdly written character.',
     & '- Putting the characters in the wrong order.',
     & 'But doing two or more of the above things at the same time',
     & 'might produce a wrong but valid AID.',
     & 'Note further that the modified order of the characters is',
     & 'chosen to minimize the effect of commonly confused characters.'
      write(3,2,err=9003)
     & 'For a class size of 90 students, the probability for a random',
     & 'string of 3 case-insensitive alphanumeric characters to be a',
     & 'valid AID is about 0.2%.  However, hundreds of errors might be',
     & 'made in a whole class.  To reduce the chance of problems, the',
     & 'AID strings are chosen such that the following errors in an',
     & 'AID string never produce a valid other AID:',
     & '- Getting any single character wrong, by student or grader,',
     & '  for whatever reason.',
     & '- Getting multiple instances of the same character wrong,',
     & '  like a grader misreading a weirdly written character.',
     & '- Putting the characters in the wrong order.',
     & 'But doing two or more of the above things at the same time',
     & 'might produce a wrong but valid AID.',
     & 'Note further that the modified order of the characters is',
     & 'chosen to minimize the effect of commonly confused characters.'
      print*
      write(3,*,err=9003)

c put the AID strings in random order

c     seed the random number generator
      if(seed.eq.1)then
         call system_clock(i)
         i2=getpid()
         i2=ishftc(i2,16,31)
         seed=xor(i,i2)
         if(seed.eq.1)seed=0
      endif
      print*,'Seed used:',seed
      write(3,*,err=9003)'Seed used:',seed
      call srand(seed)
      print*
      write(3,*,err=9003)

c     randomize the AID order
      do 1200 id=0,idcnt-1
         id2=id+int(rand(0)*(idcnt-id))
         if(id2.lt.id .or. id2.ge.idcnt)
     &    call fatal('Bad random id number')
         id0=idorg(id)
         idorg(id)=idorg(id2)
         idorg(id2)=id0
 1200 continue
      do 1210 id=0,idcnt-1
         id0=idorg(id)
         orgid(id0)=id
 1210 continue

c     print the resorted AID strings out 10 a line
      print10,
     & 'Randomly resorted AID strings preceded by sort numbers:'
      write(3,10,err=9003)
     & 'Randomly resorted AID strings preceded by sort numbers:'
      rowcnt=(idcnt+9)/10
      word=' '
      if(cidlt.gt.0)word(5-cidlt:4)=cid(1:cidlt)
      do 1300 row=0,rowcnt
         write(3,10,err=9003)
     &    '-------------------------------------',
     &    '-------------------------------------'
         if(row.eq.rowcnt)goto 1300
         i=max(row*10+9-idcnt+1,0)
         print1310,
     &    (idorg(row*10+col)+1,col=0,9-i)
         print1320,
     &    (word//idstr(idorg(row*10+col))(2:4),col=0,9-i)
         write(3,1310,err=9003)
     &    (idorg(row*10+col)+1,col=0,9-i)
         write(3,1320,err=9003)
     &    (word//idstr(idorg(row*10+col))(2:4),col=0,9-i)
 1300 continue
 1310 format('Num:',10i7)
 1320 format('AID:',10a7)
      print*
      write(3,*,err=9003)

c     limit on the assigned id values
      if(idlim.le.0)idlim=idcnt

c create the output and AID list files

c     open the output file and dump any saved data
      call openwu(fout(1:foutlt),2)
      call writfu(fout,foutlt,2,savmax,savchr)
      savmax=0

c     open the AID list files
      call openw('alpaid.nam.txt',7)
      call openw('alpaid.aid.txt',8)
      if(canvas .or. bb)then
         word(1:1+cidlt)='='//cid
         write(7,10,err=9007)' AID',word(1:1+cidlt),
     &    'A00: Ideal Student (instructor email)'
         write(8,10,err=9008)' AID',word(1:1+cidlt),
     &    'A00:   0 Ideal Student (instructor email)'
      endif

c process the data records of the input file

c     initialize the id number and line count
      id=idused-1
      outcnt=headct

c     loop on the lines in the input file
 2000 continue

c     read the csv input file and write to the output files
      if(csv)then
c        read the input file
         call readcv(fin,finlt,1,sepchr,quot,coladd,
     &    bufdim,bufmax,bufadd,fldlt,bufchr,term)
         if(term.eq.0 .and. bufmax.le.0)goto 8000
         outcnt=outcnt+1
c        normalize the Notes column if any
         if(colnot.gt.0)then
            fndlt=-1
            call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,colnot,
     &       fndlt,fnd,fldlt2,bufnxt,col)
            if(fldlt2.lt.0 .or. bufnxt.lt.0)
     &       call fatal2('Lost Notes column in ',fin(1:finlt))
            bufnot=bufnxt-1-fldlt2+doquot
            shft=3+4-fldlt2+2*doquot
            if(shft.gt.0)then
               if(bufmax+shft.gt.bufdim)
     &          call fatal('Out of record storage')
               do 2110 i=bufmax,bufnxt,-1
                  bufchr(i+shft)=bufchr(i)
 2110          continue
            else if(shft.lt.0)then
               do 2111 i=bufnxt,bufmax
                  bufchr(i+shft)=bufchr(i)
 2111          continue
            endif
            bufmax=bufmax+shft
            if(coladd.gt.colnot)then
               if(coladd.eq.colnot+1)fldlt=fldlt+shft
               bufadd=bufadd+shft
            endif
            bufstr(bufnot+1:bufnot+7)='A00 000'
            if(doquot.ne.0)then
               bufstr(bufnot:bufnot)=quot
               bufstr(bufnot+8:bufnot+8)=quot
            endif
         endif
c        case that we are creating new fields
         if(new)then
c           find the AID number to write
            id=id+1
            if(id.ge.idlim)
     &       call fatal2('Too many students in file ',fin(1:finlt))
            id0=idorg(id)
c           write the output record
            if(colnot.gt.0)then
               bufstr(bufnot+1:bufnot+3)=idstr(id0)(2:4)
               call inttos(id0+1,cint,cintl)
               bufstr(bufnot+8-cintl:bufnot+7)=cint(1:cintl)
            endif
            if(coladd.eq.1)then
               if(doquot.ne.0)call savbuf(1,quot)
               if(dosort)then
                  call inttos(id0+1,cint,cintl)
                  call savbuf(cintl,cint)
               else
                  call savbuf(cidlt,cid)
                  call savbuf(3,idstr(id0)(2:4))
               endif
               if(doquot.ne.0)call savbuf(1,quot)
               call savbuf(1,sepchr)
               call savbuf(bufmax,bufchr)
            else
               call savbuf(bufadd,bufchr)
               call savbuf(1,sepchr)
               if(doquot.ne.0)call savbuf(1,quot)
               if(dosort)then
                  call inttos(id0+1,cint,cintl)
                  call savbuf(cintl,cint)
               else
                  call savbuf(cidlt,cid)
                  call savbuf(3,idstr(id0)(2:4))
               endif
               if(doquot.ne.0)call savbuf(1,quot)
               if(kill)then
                  call savbuf(term,bufchr(bufmax-term+1))
               else
                  call savbuf(bufmax-bufadd,bufchr(bufadd+1))
               endif
            endif
c        case that we are adding missing sort number fields
         else if(dosort)then
c           get data on the AID sort number
            fndlt=-9
            call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,coladd,
     &       fndlt,fnd,fldlt,bufnxt,col)
            if(fldlt.lt.0)call fatal('Program error 2300A')
            call trimlt(fnd,fndlt)
c           clean up the found number if any
            do 2303 i=1,fndlt
               if(fnd(i:i).ne.'0')then
                  if(i.gt.1)fnd(1:i-1)=' '
                  goto 2304
               endif
 2303       continue
            fndlt=0
 2304       call trimlt(fnd,fndlt)
            do 2305 i=fndlt,1,-1
               if(index('.,',fnd(i:i)).gt.0)then
                  fndlt=i-1
                  goto 2306
               else if(fnd(i:i).ne.'0')then
                  goto 2306
               endif
 2305       continue
 2306       continue
c           if the AID sort number field is blank or zero, create a new entry
            if(fndlt.le.0)then
c              find the AID sort number to write
               id=id+1
               if(id.ge.idlim)
     &          call fatal2('Too many students in file ',fin(1:finlt))
               id0=idorg(id)
c              write the output record
               if(colnot.gt.0)then
                  bufstr(bufnot+1:bufnot+3)=idstr(id0)(2:4)
                  call inttos(id0+1,cint,cintl)
                  bufstr(bufnot+8-cintl:bufnot+7)=cint(1:cintl)
               endif
               call savbuf(bufnxt-1-fldlt,bufchr)
               if(doquot.ne.0)call savbuf(1,quot)
               call inttos(id0+1,cint,cintl)
               call savbuf(cintl,cint)
               if(doquot.ne.0)call savbuf(1,quot)
               if(kill)then
                  call savbuf(term,bufchr(bufmax-term+1))
               else
                  call savbuf(bufmax-bufnxt+1,bufchr(bufnxt))
               endif
c           else check the AID sort number and then copy the line as is
            else
               id0=0
               do 2310 i=1,fndlt
                  i1=ichar(fnd(i:i))-48
                  id0=id0*10+i1
                  if(i.gt.3 .or. i1.lt.0 .or. i1.gt.9
     &             .or. (i.eq.1 .and. i1.eq.0) .or. id0.gt.idcnt)
     &             call fatal2('Bad AID sort number "'//
     &             fnd(1:fndlt)//'" in ',fin(1:finlt))
 2310          continue
               id0=id0-1
               if(orgid(id0).ge.idused)
     &          call fatal2('AID sort number "'//fnd(1:fndlt)//
     &          '" should be unused in ',fin(1:finlt))
               if(colnot.gt.0)then
                  bufstr(bufnot+1:bufnot+3)=idstr(id0)(2:4)
                  call inttos(id0+1,cint,cintl)
                  bufstr(bufnot+8-cintl:bufnot+7)=cint(1:cintl)
               endif
               if(kill)then
                  call savbuf(bufnxt-1,bufchr)
                  call savbuf(term,bufchr(bufmax-term+1))
               else
                  call savbuf(bufmax,bufchr)
               endif
            endif
c        case that we are adding missing AID string fields
         else
c           get data on the AID string
            fndlt=-9
            call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,coladd,
     &       fndlt,fnd,fldlt,bufnxt,col)
            if(fldlt.lt.0)call fatal('Program error 2310A')
            call trimlt(fnd,fndlt)
c           if the AID string field is blank, create a new entry
            if(fndlt.le.0)then
c              find the AID string to write
               id=id+1
               if(id.ge.idlim)
     &          call fatal2('Too many students in file ',fin(1:finlt))
               id0=idorg(id)
c              write the output record
               if(colnot.gt.0)then
                  bufstr(bufnot+1:bufnot+3)=idstr(id0)(2:4)
                  call inttos(id0+1,cint,cintl)
                  bufstr(bufnot+8-cintl:bufnot+7)=cint(1:cintl)
               endif
               call savbuf(bufnxt-1-fldlt,bufchr)
               if(doquot.ne.0)call savbuf(1,quot)
               call savbuf(cidlt,cid)
               call savbuf(3,idstr(id0)(2:4))
               if(doquot.ne.0)call savbuf(1,quot)
               if(kill)then
                  call savbuf(term,bufchr(bufmax-term+1))
               else
                  call savbuf(bufmax-bufnxt+1,bufchr(bufnxt))
               endif
c           else check the AID string and then copy the line as is
            else
               if(fndlt.ne.cidlt+3)call fatal2(
     &          'Bad AID "'//fnd(1:fndlt)//'" in ',fin(1:finlt))
               if(cidlt.gt.0)then
                  if(fnd(1:cidlt).ne.cid(1:cidlt))call fatal2(
     &             'Bad AID "'//fnd(1:fndlt)//'" in ',fin(1:finlt))
               endif
               idstr0=' '//fnd(cidlt+1:fndlt)
               do 2320 i=0,idused-1
                  id0=idorg(i)
                  if(idint(id0).eq.idint0)goto 2330
 2320          continue
               call fatal2('AID "'//fnd(1:fndlt)//
     &          '" not found in ',fin(1:finlt))
 2330          if(colnot.gt.0)then
                  bufstr(bufnot+1:bufnot+3)=idstr(id0)(2:4)
                  call inttos(id0+1,cint,cintl)
                  bufstr(bufnot+8-cintl:bufnot+7)=cint(1:cintl)
               endif
               if(kill)then
                  call savbuf(bufnxt-1,bufchr)
                  call savbuf(term,bufchr(bufmax-term+1))
               else
                  call savbuf(bufmax,bufchr)
               endif
            endif
         endif
c        start a log file record
         if(idrecl(id0).gt.0)call fatal2(
     &    'Duplicate AID "'//idstr(id0)(2:4)//'" in ',fin(1:finlt))
c        number of characters in the header
         i1=5+cidlt+3+2
c        header
         idrec(id0)(1:5+cidlt)=' AID='//cid
         idrec(id0)(i1-4:i1)=idstr(id0)(2:4)//': '
c        complete the record in generic csv mode
         if(.not.bb .and. .not.canvas)then
c           add the (start of the) input record
            i=min(i1+bufmax-term,155)
            idrecl(id0)=i
            if(i.gt.i1)idrec(id0)(i1+1:i)=bufstr(1:i-i1)
         endif
c        extract xls file data in blackboard mode
         if(bb)then
c           find the student name
            fndlt=-9
            call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,colnam,
     &       fndlt,fnd,fldlt,bufnxt,col)
            call trimlt(fnd,fndlt)
            if(fndlt.le.0)
     &       call fatal2('Missing student last name in ',fin(1:finlt))
            fndlt2=-9
            call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,colfnm,
     &       fndlt2,fnd2,fldlt,bufnxt,col)
            call trimlt(fnd2,fndlt2)
            if(fndlt2.le.0)
     &       call fatal2('Missing student first name in ',fin(1:finlt))
            i=fndlt+fndlt2+2
            if(i.gt.len(fnd))
     &       call fatal2('Student name too long in ',fin(1:finlt))
            fnd(fndlt+1:fndlt+2)=', '
            fnd(fndlt+3:i)=fnd2(1:fndlt2)
            fndlt=i
c           find the student login ID
            fndlt2=-9
            call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,collog,
     &       fndlt2,fnd2,fldlt,bufnxt,col)
            call trimlt(fnd2,fndlt2)
            if(fndlt2.le.0)
     &       call fatal2('Missing student username in ',fin(1:finlt))
c           set the e-mail domain, including the @
            domlt=dom0lt
            dom(1:domlt)=dom0(1:dom0lt)
         endif
c        extract csv file data in canvas mode
         if(canvas)then
c           find the student name
            fndlt=-9
            call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,colnam,
     &       fndlt,fnd,fldlt,bufnxt,col)
            call trimlt(fnd,fndlt)
            if(fndlt.le.0)
     &       call fatal2('Missing Student Name in ',fin(1:finlt))
c           try to find the student login ID
            fndlt2=0
            if(collog.gt.0)then
               fndlt2=-9
               call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,collog,
     &          fndlt2,fnd2,fldlt,bufnxt,col)
               call trimlt(fnd2,fndlt2)
               if(fndlt2.le.0)then
                  print2,' ** Missing Login ID!'
                  fndlt2=0
               endif
            endif
c           try to find the e-mail domain, including the @
            domlt=dom0lt
            dom(1:domlt)=dom0(1:dom0lt)
            if(colacc.gt.0)then
               domlt=-9
               call csvfnd(bufdim,bufmax,bufchr,term,sepchr,quot,colacc,
     &          domlt,dom,fldlt,bufnxt,col)
               call trimlt(dom,domlt)
               if(domlt.le.0)then
                  print2,' ** Missing Root Account!'
                  domlt=dom0lt
                  dom(1:domlt)=dom0(1:dom0lt)
               else if(dom(1:domlt).eq.'canvas.fsu.edu')then
                  domlt=11
                  dom(1:domlt)='@my.fsu.edu'
               else if(dom(1:domlt).eq.'famu-fsu-eng.instructure.com')
     &             then
                  domlt=9
                  dom(1:domlt)='@famu.edu'
               else
                  domlt=dom0lt
                  dom(1:domlt)=dom0(1:dom0lt)
               endif
            endif
         endif
c        complete the record in blackboard or canvas mode
         if(bb .or. canvas)then
c           truncate the student name as needed
            i=i1+fndlt+2+fndlt2+domlt+1
            if(i.gt.79)then
               if(fndlt-50.ge.i-79)then
c                 if truncating the name to up to 50 does it, do so
                  fndlt=fndlt-(i-79)
                  fnd(fndlt-2:fndlt)='...'
                  i=i1+fndlt+2+fndlt2+domlt+1
               else
c                 still truncate the name to show the start of the email
                  if(fndlt.gt.50)then
                     fndlt=50
                     fnd(fndlt-2:fndlt)='...'
                     i=i1+fndlt+2+fndlt2+domlt+1
                  endif
c                 the next is unlikely since the local part is 64 bytes max
                  if(i.gt.155)then
                     fndlt2=fndlt2-(i-155)
                     fnd2(fndlt2-2:fndlt2)='...'
                     i=i1+fndlt+2+fndlt2+domlt+1
                  endif
               endif
            endif
            if(i.gt.155)call fatal('Program error 2330A')
c           finish the log file record
            idrecl(id0)=i
            i=i1
            idrec(id0)(i+1:i+fndlt)=fnd(1:fndlt)
            i=i+fndlt
            idrec(id0)(i+1:i+2)=' ('
            i=i+2
            if(fndlt2.gt.0)idrec(id0)(i+1:i+fndlt2)=fnd2(1:fndlt2)
            i=i+fndlt2
            idrec(id0)(i+1:i+domlt)=dom(1:domlt)
            i=i+domlt
            idrec(id0)(i+1:i+1)=')'
            i=i+1
            if(i.ne.idrecl(id0))call fatal('Program error 2330B')
         endif
c        kill off control characters
         do 2390 i2=1,i
            ichr=ichar(idrec(id0)(i2:i2))
            if(ichr.lt.32 .or. ichr.eq.127)idrec(id0)(i2:i2)='?'
 2390    continue
c        show the record
         print10,idrec(id0)(1:min(i,79))
         write(3,10,err=9003)idrec(id0)(1:min(i,79))
         write(7,10,err=9007)idrec(id0)(1:i)

c     read the plain text input file and write to the output files
      else
c        read the input file
         call readfu(fin,finlt,1,bufdim,bufmax,bufchr,term)
         if(term.eq.0 .and. bufmax.le.0)goto 8000
         outcnt=outcnt+1
c        case that we are creating new fields
         if(new)then
c           find the AID string to write
            id=id+1
            if(id.ge.idlim)
     &       call fatal2('Too many students in file ',fin(1:finlt))
            id0=idorg(id)
c           case that the AID is at the start of the line
            if(coladd.eq.-1)then
c              write the output record
               call savbuf(cidlt,cid)
               call savbuf(3,idstr(id0)(2:4))
               call savbuf(seplt,sep)
               call savbuf(bufmax,bufchr)
c           case that the AID is at the end of the line
            else
c              write the output record
               call savbuf(bufmax-term,bufchr)
               call savbuf(seplt,sep)
               call savbuf(cidlt,cid)
               call savbuf(3,idstr(id0)(2:4))
               call savbuf(term,bufchr(bufmax-term+1))
            endif
c        case that we are adding missing fields
         else
c           case that the AID is at the start of the line
            if(coladd.eq.-1)then
c              see whether there is already an AID
               if(bufmax-term.lt.cidlt+3+seplt)goto 2520
               if(cidlt.ne.0)then
                  if(bufstr(1:cidlt).ne.cid(1:cidlt))goto 2520
               endif
               if(seplt.ne.0)then
                  i=cidlt+3
                  if(bufstr(i+1:i+seplt).ne.sep(1:seplt))goto 2520
               endif
               idstr0=' '//bufstr(cidlt+1:cidlt+3)
               do 2510 i=0,idused-1
                  id0=idorg(i)
                  if(idint(id0).eq.idint0)goto 2550
 2510          continue
c              add a new AID
 2520          continue
c              find the AID string to write
               id=id+1
               if(id.ge.idlim)
     &          call fatal2('Too many students in file ',fin(1:finlt))
               id0=idorg(id)
c              write the output record
               call savbuf(cidlt,cid)
               call savbuf(3,idstr(id0)(2:4))
               call savbuf(seplt,sep)
               call savbuf(bufmax,bufchr)
c           case that the AID is at the end of the line
            else
c              see whether there is already an AID
               if(bufmax-term.lt.seplt+cidlt+3)goto 2540
               if(seplt.ne.0)then
                  i=bufmax-term-3-cidlt-seplt
                  if(bufstr(i+1:i+seplt).ne.sep(1:seplt))goto 2540
               endif
               if(cidlt.ne.0)then
                  i=bufmax-term-3-cidlt
                  if(bufstr(i+1:i+cidlt).ne.cid(1:cidlt))goto 2540
               endif
               idstr0=' '//bufstr(bufmax-term-2:bufmax-term)
               do 2530 i=0,idused-1
                  id0=idorg(i)
                  if(idint(id0).eq.idint0)goto 2550
 2530          continue
c              add a new AID
 2540          continue
c              find the AID string to write
               id=id+1
               if(id.ge.idlim)
     &          call fatal2('Too many students in file ',fin(1:finlt))
               id0=idorg(id)
c              write the output record
               call savbuf(bufmax-term,bufchr)
               call savbuf(seplt,sep)
               call savbuf(cidlt,cid)
               call savbuf(3,idstr(id0)(2:4))
               call savbuf(term,bufchr(bufmax-term+1))
            endif
            goto 2560
c           copy the line as is
 2550       call savbuf(bufmax,bufchr)
 2560       continue
         endif
c        start a log file record
         if(idrecl(id0).gt.0)call fatal2(
     &    'Duplicate AID "'//idstr(id0)(2:4)//'" in ',fin(1:finlt))
c        number of characters in the header
         i1=5+cidlt+3+2
c        header
         idrec(id0)(1:5+cidlt)=' AID='//cid
         idrec(id0)(i1-4:i1)=idstr(id0)(2:4)//': '
c        add the (start of) the input record
         i=min(i1+bufmax-term,155)
         idrecl(id0)=i
         if(i.gt.i1)idrec(id0)(i1+1:i)=bufstr(1:i-i1)
         do 2590 i2=i1+1,i
            ichr=ichar(idrec(id0)(i2:i2))
            if(ichr.lt.32)idrec(id0)(i2:i2)='?'
 2590    continue
c        show the record
         print10,idrec(id0)(1:min(i,79))
         write(3,10,err=9003)idrec(id0)(1:min(i,79))
         write(7,10,err=9007)idrec(id0)(1:i)

c     the record is processed for all file types
      endif

c     dump the stored data to file
      call writfu(fout,foutlt,2,savmax,savchr)
      savmax=0

c     go process the next line in the input file if any
      if(term.eq.0)goto 8000
      goto 2000

c final processing

c     jump point
 8000 continue

c     close the output file
      call writfu(fout,foutlt,2,-1,bufchr)

c     added AID strings
      idnew=id+1-idused

c     print the output records sorted by AID to screen and files
      print*
      write(3,*,err=9003)
      do 8100 id0=0,idcnt-1
         i=idrecl(id0)
         if(i.gt.0)then
            print10,idrec(id0)(1:min(79,i))
            write(3,10,err=9003)idrec(id0)(1:min(79,i))
            write(8,20,err=9008)
     &       idrec(id0)(1:9+cidlt),id0+1,idrec(id0)(10+cidlt:i)
         endif
 8100 continue
      print*
      write(3,*,err=9003)

c     close the AID data files
      close(7,err=9007)
      close(8,err=9008)

c     print the numbered sorted AID strings out as an html page
      call openw('alpaid.htm',10)
      write(10,2,err=9010)
     & '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">'
      write(10,2,err=9010)
     & '<html>',
     & '<head><title>Find Your AID String '
     & //'from its Sort Number</title></head>',
     & '<body>',
     & '<h1 align="center">Find Your AID String '
     & //'from its Sort Number</h1>',
     & '<table width="100%" border=0 cellpadding=0 cellspacing=0>',
     & '<tbody align=center>'
      rowcnt=(idcnt+9)/10
      do 8250 row=0,rowcnt-1
         if(row.ne.0)
     &    write(10,2,err=9010)'<tr><td colspan=10><hr></td></tr>'
         do 8240 col=0,9
            outst1(col)=' '
            outst2(col)=' '
            id0=row*10+col
            if(id0.ge.idcnt)goto 8240
            write(outst1(col),'(i3)')id0+1
            outst2(col)='_______'
            if(idrecl(id0).le.0)goto 8240
            if(cidlt.gt.0)outst2(col)(1:cidlt)=cid(1:cidlt)
            outst2(col)(cidlt+1:cidlt+3)=idstr(id0)(2:4)
 8240    continue
         write(10,8260,err=9010)(outst1(col),col=0,9)
         write(10,8260,err=9010)(outst2(col)(1:cidlt+3),col=0,9)
 8250 continue
 8260 format('<tr>',10('<td>',a,'</td>'),'</tr>')
      write(10,2,err=9010)
     & '</tbody>',
     & '</table>',
     & '</body>',
     & '</html>'
      close(10,err=9010)

c     print the numbered sorted AID strings out as a text file
      call openw('alpaid.srt.txt',11)
      write(11,2,err=9011)
     & 'Please first find your "AIDsort" number under your grades.',
     & 'From that find your actual AID string in the table below.',
     & 'Write that AID string *instead of your name* on all your',
     & 'assignments and homeworks.   Keep it secret.',
     & ' ',
     & '                           Table of AID Strings:'
      rowcnt=(idcnt+9)/10
      word=' '
      if(cidlt.gt.0)word(5-cidlt:4)=cid(1:cidlt)
      do 8280 row=0,rowcnt
         write(11,10,err=9011)
     &    '-------------------------------------',
     &    '-------------------------------------'
         if(row.eq.rowcnt)goto 8280
         i=max(row*10+9-idcnt+1,0)
         write(11,8290,err=9011)(row*10+col+1,col=0,9-i)
         do 8270 col=0,9-i
            outst2(col)=' '
            id0=row*10+col
            if(idrecl(id0).le.0)goto 8270
            outst2(col)=word//idstr(id0)(2:4)
 8270    continue
         write(11,8291,err=9011)(outst2(col),col=0,9-i)
 8280 continue
 8290 format('Num:',10i7)
 8291 format('AID:',10a7)
      write(11,*,err=9011)
      close(11,err=9011)

c     list the next few unused AID strings
      print10,
     & 'First few still unused AID strings preceded by sort numbers:'
      write(3,10,err=9003)
     & 'First few still unused AID strings preceded by sort numbers:'
      rowcnt=min((idcnt-1-id+9)/10,2)
      word=' '
      if(cidlt.gt.0)word(5-cidlt:4)=cid(1:cidlt)
      do 8300 row=0,rowcnt-1
         write(3,10,err=9003)
     &    ' ---------------------------------------',
     &    '---------------------------------------'
         i=max(id+1+row*10+9-idcnt+1,0)
         print8310,
     &    (idorg(id+1+row*10+col)+1,col=0,9-i)
         print8320,
     &    (word//idstr(idorg(id+1+row*10+col))(2:4),col=0,9-i)
         write(3,8310,err=9003)
     &    (idorg(id+1+row*10+col)+1,col=0,9-i)
         write(3,8320,err=9003)
     &    (word//idstr(idorg(id+1+row*10+col))(2:4),col=0,9-i)
 8300 continue
 8310 format('Num:',10i7)
 8320 format('AID:',10a7)
      write(3,10,err=9003)
     & ' ---------------------------------------',
     & '---------------------------------------'
      print*
      write(3,*,err=9003)

c     check the output record count
      if(outcnt.le.0)then
         print10,
     &    ' ** WARNING: Input file is empty: ',fin(1:finlt)
         write(3,10,err=9003)
     &    ' ** WARNING: Input file is empty: ',fin(1:finlt)
         print*
         write(3,*,err=9003)
      else if(outcnt.le.headct)then
         print10,
     &    ' ** WARNING: Input file has a header only: ',fin(1:finlt)
         write(3,10,err=9003)
     &    ' ** WARNING: Input file has a header only: ',fin(1:finlt)
         print*
         write(3,*,err=9003)
      endif

c     ignore the header lines from now on
      outcnt=outcnt-headct

c     write statistics
      if(new)then
         print20,'Number of AID strings assigned:',idnew
         if(idnew.le.0)print10,
     &    ' ** WARNING: no new AID strings assigned!'
         write(3,20,err=9003)'Number of AID strings assigned:',idnew
         if(idnew.le.0)write(3,10,err=9003)
     &    ' ** WARNING: no new AID strings assigned!'
      else
         print20,'Previous AID strings assigned:',idused
         print20,'     New AID strings assigned:',idnew
         print20,'Total student records written:',outcnt
         if(outcnt-idnew.le.0 .and. idused.gt.0)print10,
     &    ' ** WARNING: no previous AID strings found!'
         if(idnew.le.0)print10,
     &    ' ** WARNING: no new AID strings assigned!'
         write(3,20,err=9003)'Previous AID strings assigned:',idused
         write(3,20,err=9003)'     New AID strings assigned:',idnew
         write(3,20,err=9003)'Total student records written:',outcnt
         if(outcnt-idnew.le.0 .and. idused.gt.0)write(3,10,err=9003)
     &    ' ** WARNING: no previous AID strings found!'
         if(idnew.le.0)write(3,10,err=9003)
     &    ' ** WARNING: no new AID strings assigned!'
      endif
      print*
      write(3,*,err=9003)

c     write the id limit, new used count, and seed to alpaid.lcs
      if(havlcs)then
         open(9,file='alpaid.lcs',status='old',access='append',err=9009)
      else
         open(9,file='alpaid.lcs',status='new',err=9009)
      endif
      call inttos(idlim,cint,cintl)
      write(9,50,err=9009)
     & dattim(1:15)//' Limit, Used, and Seed: '//cint(1:cintl)//', '
      call inttos(idused+idnew,cint,cintl)
      write(9,50,err=9009)cint(1:cintl)//','
      write(9,*,err=9009)seed
      close(9,err=9009)

c     all OK
      call exit(0)

c     errors
 9000 call fatal('Disk error looking for files')
 9001 call fatal('Error reading Class ID file alpaid.cid')
 9002 call fatal('Error writing to Class ID file alpaid.cid')
 9003 call fatal('Error writing to log file alpaid.log')
 9004 call fatal('Error reading limit, count, and seed file alpaid.lcs')
 9007 call fatal('Error writing to names list file alpaid.nam.txt')
 9008 call fatal('Error writing to AID list file alpaid.aid.txt')
 9009 call fatal('Error writing to last count and seed file alpaid.lcs')
 9010 call fatal('Error writing to web page alpaid.htm')
 9011 call fatal('Error writing to text file alpaid.srt.txt')
 9012 call fatal('Error reading e-mail domain file email.txt')

      end
