/* Mat -- string and file matching filter  87:8:15 */
/* Copyright (C) 1987 Peter Goodeve */


#include <stdio.h>
#include "exec/types.h"
#include "exec/memory.h"
#include "libraries/DOS.h"
#include "libraries/DOSextens.h"

#include "splicer.h"

/* CmplPat() returns this bit set if pattern is "sliced",
 * and therefore template is needed: */
#define CODE_SLICE 128

/* This structure is used to keep track of subdirectory and file names
 * and patterns as they are scanned for matches: */
struct FileRef {
    struct FileInfoBlock fileblock; /* declared first so it will be LW-A */
    ULONG   currlock,       /* lock for PARENT directory to this segment */
            flags;
    char    *pathname,      /* remainder of path name from this point */
            chunkpat[64],   /* pattern specified for this file or dir */
            chunkaux[64];   /* auxiliary vector for pattern match */
    struct FileRef *nextref, *prevref;  /* list pointers */
};

#define HAVELOCK 1
#define NEWLOCK 2
#define DONETOP 4
#define ISDEVICE 16
#define ISPATTERN 256

#define MATCH_NODIRS 256
#define MATCH_NEEDPAT 128
#define MATCH_NEEDTMPLT 64
#define MATCH_TEXT (0 | MATCH_NODIRS |MATCH_NEEDPAT)
#define MATCH_NAMES (1 | MATCH_NEEDTMPLT)
#define MATCH_IMMED (2 | MATCH_NEEDPAT)
#define JOIN_FILES (3 | MATCH_NODIRS)


int casesens = TRUE, newlines = TRUE, firstonly = FALSE;
int matchmode = MATCH_TEXT;
int success = FALSE;


ULONG maindir;

/* current file references (for sliceset): */
char LocalName[64], FileDir[120];

UBYTE filecuts[33]; /* only need one of these */

/* Pointers to ends of File reference list
 * (full Kernel list structure wasn't worth it here...)
 */
struct FileRef *firstref, *endref;

int builderr = 0; /* set if bad directory reference pattern */

/*
 * buildrefs -- procedure to allocate FileRefs for the segments
 *              of a file path pattern.
 *              Calls itself recursively until the complete path
 *              specification has been processed.
 */
struct FileRef *buildrefs(pathname, prev)
 char *pathname; struct FileRef *prev;
{
    char *pp, *cp;
    struct FileRef *tref;
    int res;
    tref = AllocMem(sizeof(struct FileRef),MEMF_PUBLIC | MEMF_CLEAR);
    if (!tref) {
        freerefs(firstref);
        exit(55);
    }
    endref = tref;
    if (!(tref->prevref = prev)) {
        *filecuts = builderr = 0;
        firstref = tref;
    }
    pp = tref->pathname = pathname;
    cp = tref->chunkpat;
    while (*pp && *pp != '/' && *pp != ':')
        *cp++ = (*pp >= 'a' && *pp <= 'z') ? *pp++ + ('A' - 'a') : *pp++;
    if (*pp == ':') {
        tref->flags |= ISDEVICE;
        *cp++ = ':';
        if (tref->prevref /* not first segment!*/) {
            builderr = 2;
            return tref;
        }
    }
    else if (pp == pathname)
        *cp++ = '/';
    *cp = '\0';
    res = CmplPat(tref->chunkpat, tref->chunkaux);
    if (res > 1) tref->flags |= ISPATTERN;
    if (!res || ((res & CODE_SLICE) && tref->nextref)) {
        builderr = 1;
        return tref;
    }
    if (*pp)
        tref->nextref = buildrefs(++pp, tref);
    return tref;
}

/* freerefs --  procedure to return the memory used by the
 *              chain of FileRefs to the free list.
 *              Calls itself recursively until all items
 *              in the chain have been freed.
 */
freerefs(ref) struct FileRef *ref;
{
    if (!ref) return;
    firstref = endref = NULL;
    if (ref->flags & NEWLOCK) UnLock(ref->currlock);
    freerefs(ref->nextref);
    FreeMem(ref,sizeof(struct FileRef));
}

/*
 * nextlock --  procedure to get a lock for the next higher level;
 *              on the first call, it calls itself recursively until
 *              the top level of the File Reference chain is reached,
 *              at which point it obtains a lock for the current parent,
 *              then returns a lock either for the specific name or
 *              for the first match it finds to the specified pattern;
 *              each level then repeats the process until a final
 *              filename is reached.
 *              Subsequent calls will return a new match if there is
 *              one, otherwise will make recursive calls as far as
 *              necessary to get a new branch of the pattern tree.
 *              [Note that the bottom level match MUST be a file
 *               -- it will not return a directory name.]
 */
ULONG nextlock(ref) struct FileRef *ref;
{
    struct FileInfoBlock *fbp;
    char matchstr[108], *pp, *cp;
    ULONG lk, succ, thislock;
    fbp = &ref->fileblock;
    do {
      if (ref->flags & HAVELOCK) {
       CurrentDir(ref->currlock);
       if (!(ref->flags & ISPATTERN)) {
           thislock = Lock(ref->chunkpat, ACCESS_READ);
           ref->flags ^= HAVELOCK; /* this way only once..*/
           if (thislock && Examine(thislock,fbp)) {
               return thislock;
           }
       }
       else {
           lk = ref->currlock;
           while (succ = ExNext(lk, fbp)) {
             if( ref->nextref /* not bottom level*/ ?
                       fbp->fib_EntryType < 0 /* ignore files */
                  : ((matchmode & MATCH_NODIRS) &&
                       fbp->fib_EntryType > 0 /* ignore dirs */))
                      continue;
             pp = fbp->fib_FileName;
             cp = matchstr;
             while (*cp++ = (*pp >= 'a' && *pp <= 'z') ?
                           *pp++ + ('A' - 'a') : *pp++) /* loop */;
             if (SMatch(ref->chunkpat, ref->chunkaux,
                         matchstr, filecuts) )
                break;
            }    ; /* loop until found or exhausted */
           if (succ) {
               return Lock(fbp->fib_FileName, ACCESS_READ);
           }
           else {
           ref->flags ^= HAVELOCK; /* lock no longer valid ..*/
           }
       }
      } /**[END if (..HAVELOCK) ]**/

      /* continues if no current valid lock */
      if (ref->prevref) {
          if (ref->flags & NEWLOCK) UnLock(ref->currlock);
          ref->flags &= ~(HAVELOCK | NEWLOCK);
          if (thislock = nextlock(ref->prevref)) {
              ref->currlock = thislock;
              ref->flags |= HAVELOCK | NEWLOCK;
          }
      }
      else if (!(ref->flags & DONETOP)) {
          ref->currlock = CurrentDir(0);
          CurrentDir(ref->currlock);
          ref->flags |= (HAVELOCK | DONETOP);
      }
      if (ref->flags & HAVELOCK) {
          Examine(ref->currlock, fbp);
      }
    } while (ref->flags & HAVELOCK);
    return NULL;
}

/*
 * findfile --  procedure to scan the File Reference chain
 *              to set a parent directory and get the name of
 *              the next file to be accessed.
 *              Calls nextlock to do most of the work.
 *              Also sets up Directory and Filename strings
 *              for use by the splicing section.
 */
char *findfile(ref) struct FileRef *ref;
{
    ULONG thislock;
    char *thisname = endref->fileblock.fib_FileName;
    char *cp, *pp;
    struct FileRef *rp, *nrp;
    thislock = nextlock(endref); /* go up chain to find a matching file */
    if (!thislock)
        return NULL;

    UnLock(thislock);
    strcpy(LocalName, thisname);
    cp = FileDir;
    for (rp = ref; nrp = rp->nextref; rp = nrp) {
        pp = rp->fileblock.fib_FileName;
        while (*cp = *pp++) cp++;
        *cp++ = (rp->flags & ISDEVICE) ? ':'
                : nrp->nextref ? '/' : '\0';
    }
    *cp = '\0';
    return thisname;
}

/*
 * openfile --  procedure to open the file found by findfile
 */
FILE *openfile(fname) char *fname;
{
    FILE *fp;
    fp = fopen(fname,"r");
    if (!fp) {
        fputs(fname,stderr);
        fputs(" -- Couldn't open file!\n",stderr);
    }
    return fp; /* regardless */
}

/***************************************************************/

main(argc,argv) int argc; char **argv;
{
    char *pat = NULL, aux[256], *splice = NULL, *failspl = NULL,
         *origpat = NULL, upperpat[256];
    int cmplcode = 0;
    char *fname;
    struct FileRef *ref;
    FILE *fp;

/*    extern int DEBmode;
    DEBmode = 0;          */

    maindir = CurrentDir(0);
    CurrentDir(maindir); /* there must be a neater way to do this ? */

    if (argc < 3) {
        fputs( "format is:   MAT <pattern> [<template>] filenames...\n",
              stderr);
        exit(0);
    }

    ++argv; /* skip over program name */
    argc--;
    while (checkarg(&argc, &argv)) /* loop */;
    if (matchmode & MATCH_NEEDPAT) {
        origpat = *argv++;
        argc--;
        if (!(cmplcode = CmplPat(origpat,aux))) {
            fputs("badly formed pattern\n",stderr);
            exit(20);
        }
        uppercase(origpat, upperpat); /* best to do it now -- in case...*/
    }

    if ((cmplcode & CODE_SLICE) || (matchmode & MATCH_NEEDTMPLT)) {
        if (argc-- < 2) {
            fputs( "this format requires a template argument...\n",
                  stderr);
            exit(20);
        }
        splice = *argv++;
        for (failspl = splice; *failspl;)
            if (*failspl++ == '^' && *failspl++ == '|') break;
        if(*failspl) *(failspl-2) = '\0';
        else failspl = NULL;
    }

    for ( ;  argc;  argc--, argv++) {
        while (checkarg(&argc, &argv)); /* loop */
        if (!argc /* might get keyw. at end of string */) break;
        if (casesens || !origpat) pat = origpat;
        else pat = upperpat;
        if (matchmode == MATCH_IMMED) {
            matchnames(*argv,pat,aux,splice,failspl);
            continue; /* go into short loop! */
        }
        ref = buildrefs(*argv, NULL);
        if (builderr)
            fputs("Bad Filename Pattern\n", stderr);
        else while (fname = findfile(ref)) {
            if (matchmode == MATCH_NAMES) {
                if (!splice) {
                    fputs("MUST have a template!\n", stderr);
                    CurrentDir(maindir);
                    exit(20);
                }
                matchnames(fname,pat,aux,splice,failspl);
            }
            else {
                fp = openfile(fname);
                if (matchmode == JOIN_FILES)
                    copyfile(fp);
                else
                    matchfile(fp, pat, aux, splice, failspl);
                fclose(fp);
            }
        }
        freerefs(ref);
        CurrentDir(maindir);
    }
    exit(success ? 0 : 5); /* returns 0 if success, otherwise WARN */
}

/* numeric string used to maintain sequential index number of matches: */
char findxnum[6] = "00000",
     *endfindx = &findxnum[4];


/*
 * matchfile -- procedure to search for matching lines in the currently
 *              open file.  If there is no template, any matching line is
 *              sent to the standard output, otherwise the pieces specified
 *              by the template are spliced together into a new output line.
 */
matchfile(fp, pat, aux, template, ftemplate)
   FILE *fp; char *pat, *aux, *template, *ftemplate;
{
    char inline[257], upperline[256], cuts[33],
         linenum[10], *endnum = &linenum[4], *lnp,
         pieces[256], compline[300],
         *linep, *matchp;
    struct CutsRef sliceset[15], *fsliceset; /* probably should be static */
    char *Splice();
    int p;
    strcpy(linenum, "00000");
    for (p = 0; p < 15; p++) sliceset[p].Mode = 0;
    sliceset[5].ID = 'O';
    sliceset[5].Segment = inline;
    sliceset[6].ID = 'N';
    sliceset[6].Segment = linenum;
    sliceset[7].ID = 'F';
    sliceset[7].Segment = LocalName;
    sliceset[8].ID = 'D';
    sliceset[8].Segment = FileDir;
    sliceset[9].ID = 'I';
    sliceset[9].Segment = findxnum;
    sliceset[10].ID = 'B';
    sliceset[10].Segment = "\n";
    sliceset[11].ID = 'Q';
    sliceset[11].Segment = "\"";
    sliceset[12].ID = 0; /* until we add more stuff.. */
    fsliceset = &sliceset[5]; /* this set may be used if no match */
    while (fgets(matchp = inline, 256, fp)) {
        p = strlen(inline);
        for (lnp = endnum; lnp >= linenum && ++(*lnp) > '9';)
             *lnp-- = '0';
        if (inline[--p] == '\n') inline[p] = '\0'; /* note we move back */
        if (!casesens)
            uppercase(inline, matchp = upperline);
        if (SMatch(pat, aux, matchp, cuts)) {
            success = TRUE;
            for (lnp = endfindx; lnp >= findxnum && ++(*lnp) > '9';)
                 *lnp-- = '0';
            if (template) {
                Slice(inline, cuts, pieces, sliceset);
                linep = Splice(sliceset, template, compline);
            }
            else linep = inline;
            fputs(linep, stdout);
            if (newlines) fputc('\n',stdout);
            if (firstonly) break;
        }
        else
            if (ftemplate) { /* may specify output if match fails, too */
                fputs( Splice(fsliceset, ftemplate, compline), stdout);
                if (newlines) fputc('\n', stdout);
            }
    }
}


/*
 * matchnames -- procedure to match literal strings -- either filenames
 *              or literal arguments in the command line. Action is
 *              similar to matchfile, but slicing information can be
 *              obtained from the filename pattern rather than a
 *              separately specified one and splicing may occur without
 *              any slices (using implicitly defined pieces).
 */
matchnames(fname, pat, aux, template, ftemplate)
   char *fname, *pat, *aux, *template, *ftemplate;
{
    char cuts[33], pieces[256], compline[300], *linep, *matchp;
    struct CutsRef sliceset[15], *fsliceset; /* probably should be static */
    char uppername[64];
    char *Splice(), *lnp;
    int p;
    for (p = 0; p < 15; p++) sliceset[p].Mode = 0;
    sliceset[5].ID = 'O';
    sliceset[5].Segment = fname;
    sliceset[6].ID = 'N';
    sliceset[6].Segment = findxnum; /* for convenience (?!) */
    sliceset[7].ID = 'F';
    sliceset[7].Segment = LocalName;
    sliceset[8].ID = 'D';
    sliceset[8].Segment = FileDir;
    sliceset[9].ID = 'I';
    sliceset[9].Segment = findxnum;
    sliceset[10].ID = 'B';
    sliceset[10].Segment = "\n";
    sliceset[11].ID = 'Q';
    sliceset[11].Segment = "\"";
    sliceset[12].ID = 0; /* until we add more stuff.. */
    fsliceset = &sliceset[5];
    *cuts = '\0';
    if (!casesens)
            uppercase(fname, matchp = uppername);
    else matchp = fname;
    if (!pat /* may be missing */
        || SMatch(pat, aux, matchp, cuts)) {
        success = TRUE;
        for (lnp = endfindx; lnp >= findxnum && ++(*lnp) > '9';)
             *lnp-- = '0';
        if (template) {
            Slice(fname, *cuts ? cuts : filecuts, pieces, sliceset);
            linep = Splice(sliceset, template, compline);
        }
        else linep = fname;
        fputs(linep, stdout);
        if (newlines) fputc('\n',stdout);
    }
    else
        if (ftemplate) {
            fputs( Splice(fsliceset, ftemplate, compline), stdout);
            if (newlines) fputc('\n', stdout);
        }
}

/*
 * copyfile --  simply copies current file to the standard output
 */

#define BUFLEN 512

copyfile(fp) FILE *fp;
{
    UBYTE buffer[BUFLEN];
    ULONG fhin, fhout;
    int xfbytes;
    fhin = fileno(fp);
    fhout = fileno(stdout);
    while ((xfbytes = read(fhin, buffer, BUFLEN)) > 0)
        write(fhout, buffer, xfbytes);
    success = TRUE;
}


/*
 * Special Argument Recognition Section
 *
 * checkarg --  matches the argument against argpat, and takes the
 *              appropriate action if there is a match.
 *              Note how it uses the slicing information returned
 *              by SMatch as a convenient way of determining the
 *              actual meaning of the argument.
 */

char argpat[] =
   "NOCASE^|CASE^|(FILES|F)^|STRING^|(JOIN|J)^|FIRST^|ALL^|NOLINES^|LINE^";
#define NOCASE 7
#define CASE 13
#define FILES 24
#define STRING 32
#define JOIN 42
#define FIRST 49
#define ALL 54
#define NOLINES 63
#define LINE 69
UBYTE argaux[72]; /* lengthen as necessary! */

checkarg(pargc,pargv) int *pargc; char **pargv[];
{
    char argcuts[33], upperarg[256], *cutp = argcuts;
    if (!*pargc) return FALSE; /* don't run off the end... */
    CmplPat(argpat, argaux); /* ...pure laziness */
    uppercase(**pargv, upperarg);
    if (SMatch(argpat, argaux, upperarg, argcuts)) {
        while (!cutp[1]) cutp += 2; /* skip phony subpatterns (e.g. "F") */
        switch((int)(*cutp)) {
        case NOCASE: casesens = FALSE;
                     break;
        case CASE:   casesens = TRUE;
                     break;
        case FILES:  matchmode = MATCH_NAMES;
                     break;
        case STRING: matchmode = MATCH_IMMED;
                     break;
        case JOIN:   matchmode = JOIN_FILES;
                     break;
        case FIRST:  firstonly = TRUE;
                     break;
        case ALL:    firstonly = FALSE;
                     break;
        case NOLINES: newlines = FALSE;
                     break;
        case LINE:   newlines = TRUE;
                     fputc('\n', stdout);
                     break;
        }
        (*pargv)++;
        (*pargc)--;
        return TRUE;
    }
    return FALSE;
}

/*
 * uppercase -- translates supplied source string to upper case
 *              (yes, I know there is a library function...)
 */
uppercase(src,dest) char *src, *dest;
{
    while (*dest++ = (*src >= 'a' && *src <= 'z') ?
            *src++ + ('A' - 'a') : *src++) /* loop */;
}

