#include <stdio.h>
#include <exec/types.h>
#include <libraries/dos.h>
#include <functions.h>
#include <arpfunctions.h>

extern unsigned verbose;

extern char *index(), *rindex();
extern char *malloc();

static char *GetPathString();
static char *NextDirName();
static char *ScanDir();

/*  Define the maximum length of the search path variable or file. */

#define MAXPATHLENGTH 4096

/*  Hacker's note: the following string constants are declared as static
 *  strings with preset values.  The reason for doing this is to allow
 *  the end user to edit these variables (using FileZap, et al) to
 *  customize the operation of MRMan.
 */

static char PATCHHEADER[] = "# Patch Area ";
static char ENVDESC[] = "# Environment device name (32) ->";
/*  ENVironment device name. */
static char ENVNAME[33] = "ENV:";

static char VARDESC[] = "# Search path variable name (32) ->";
/*  ENVironment variable name which contains the Man search path. */
static char MANPATHVAR[33] = "ManPath";

/*  If ENV:ManPath is undefined, the following name defines the default
 *  Man directory.
 */
static char MANDIRDESC[] = "# Man directory logical name (32) ->";
static char MANDIR[33] = "MAN:";

static char MANPATHDESC[] = "# Man search path file name (32) ->";

/*  If ENV:ManPath is undefined, this file contains the Man search path. */
static char MANPATHFILE[33] =  "ManPath";

static char MANVIEWERDESC[] = "# Default viewer program name (32) ->";
/*  The name of our viewing program: */
static char MANVIEWER[33] = "More";

#define SUFFIXES    3

static char SUFFIX1DESC[] = "# Suffix 1 (8) ->";
static char suffix1[9] = "man";
static char SUFFIX2DESC[] = "# Suffix 2 (8) ->";
static char suffix2[9] = "doc";
static char SUFFIX3DESC[] = "# Suffix 3 (8) ->";
static char suffix3[9] = "text";

static char *suffixes[SUFFIXES] = { suffix1, suffix2, suffix3 };

static char     dirName[33];
static char     fullPath[256];
static char     manPathFile[256];
static unsigned checkEnvironment = 1;
static char     *manDir = MANDIR;
static char     searchPath;         /* from ENV:ManPath or DOC:ManPath */
static char     testName[33];
static char     *theSubject;

/*  FUNCTION
        BuildPathName - concatenate name to directory or device spec.

    SYNOPSIS
        static char *BuildPathName(prefix, suffix, result)
                        char *prefix, *suffix, *result;

    DESCRIPTION
        BuildPathName concatenates a simple file or directory name to a 
        (possibly compound) pathname.  Its primary function is to insert 
        a slash character between <prefix> and <suffix> if <suffix> does 
        not end in a colon (:).

        BuildPathName returns <result>.
*/
static char *
BuildPathName(prefix, suffix, result)
    char *prefix, *suffix, *result;
{
    register char    prefixEndChar;
    register int     prefixEndPos = strlen(prefix) - 1;

    /* The <prefix> string may be empty. */
    if (prefixEndPos >= 0) {
        strcpy(result, prefix);
        prefixEndChar = prefix[prefixEndPos];
        if ((prefixEndChar != ':') && (prefixEndChar != '/'))
            strcat(result,"/");
    }
    else
        *result = '\0';

    strcat(result, suffix);
    return result;
}

/*  FUNCTION
        GetManPath - get the Man search path.

    SYNOPSIS
        static char *GetManPath(dirName);
                        char *dirName;

    DESCRIPTION
        GetManPath first looks for an ENVironment variable containing
        the Man search path. If not found, GetManPath next attempts to
        locate a file named by MANPATHFILE and stored in the directory 
        whose name is pointed to by <dirName>.  

        If successful, GetManPath returns a pointer to a dynamically
        allocated buffer containing the search path.  Otherwise,
        NULL is returned.
*/

static char *
GetManPath(dirName)
    char *dirName;
{
    char    *sp = NULL;
    char    tempName[256];

    if (checkEnvironment) {
        BuildPathName(ENVNAME, MANPATHVAR, tempName);
        sp = GetPathString(tempName);
    }
    if (sp == NULL) {
        BuildPathName(dirName,MANPATHFILE,tempName);
        sp = GetPathString(tempName);
    }    
    return sp;
}

/*  FUNCTION
        GetPathString - get the MRMan search path string.

    SYNOPSIS
        static char *GetPathString(pathName)
                        char *pathName;

    DESCRIPTION
        GetPathString attempts to read the contents of a file into a
        dynamically allocated buffer. The <pathName> argement may be
        the name of a disk file or environment variable.

    CREDITS
        This code was pretty much lifted from Matt Dillon's GetDEnv
        routine.  Thanks, Matt.
*/

static char *
GetPathString(pathName)
    char *pathName;
{
    char *res = NULL;           /* result string */
    long fh;                    /* file handle */
    long len;

    if (fh = (long) Open(pathName, 1005L)) {
        len = (Seek(fh, 0L, 1L), Seek(fh, 0L, 0L));
        if (len > MAXPATHLENGTH) {
            Printf("MRMan: search path file '%s' is too big (%ld)!\n",
                    pathName, len);
        }
        else if (len >= 0 && (res = malloc((int) (len+1) ) ) ) {
            Seek(fh, 0L, -1L);
            if (Read(fh, res, len) != len)
                len = 0;
            res[len] = 0;
        }
        Close(fh);
    }
    return res;
}

/*  FUNCTION
        GetViewerName - get the name of the preferred text viewer.

    SYNOPSIS
        char *GetViewerName();

    DESCRIPTION
        GetViewerName first checks the environment variable MANVIEWER for
        the name of the program to be used for viewing man files.  If it
        is undefined, the name in MANVIEWER is used.
*/
char *
GetViewerName()
{
    char    *theViewer;
    char    varName[33];

    BuildPathName(ENVNAME, "MANVIEWER", varName);
    theViewer = GetPathString(varName);
    if (! theViewer)
        theViewer = MANVIEWER;
    return theViewer;
}

/*  FUNCTION
        ScanDir - search directory for document file.

    SYNOPSIS
        static char *ScanDir(dirName, lock, scanSubDir)
                             char           *dirName;
                             struct Lock    *lock;
                             unsigned       scanSubDir

    DESCRIPTION
        ScanDir performs a search for a document, described by the package
        variable <theSubject>, beginning with directory <dirName>.  The
        directory is also described by <lock>, a filesystem lock on the
        directory.  If <scanSubDir> is TRUE, ScanDir will also perform a
        recursive scan of subdirectories. Otherwise, the search is limited
        to the current directory.

        Each filename encountered is subjected to a set of pattern matches
        to determine if it meets the rules for eligibility. ScanDir returns
        a pointer to a full pathname if a file is found, NULL otherwise.
        The pointer returned points to the package variable <fullPath> and
        thus should not be freed.  If the pathname is not used immediately,
        it should be copied, since subsequent calls to ScanDir or uses of
        fullPath will render it invalid.
 */

static char *
ScanDir(dirName, lock, scanSubDir)
    char *dirName; struct Lock *lock; unsigned scanSubDir;
{
    static char     testName[33];
    struct FileInfoBlock *fib;
    unsigned        i, ok;
    char            *newDirName;
    struct Lock     *newLock = NULL;
    char            *theFile = NULL;
    char            *tp = NULL;

    if (verbose)
        Printf("Scanning directory '%s'.\n", dirName);

    fib = AllocMem((long) sizeof(*fib), 0l);
    if (! fib) {
no_mem:
        Printf("MRMan: out of memory!\n");
        goto done;
    }
    Examine(lock, fib);
    while (ExNext(lock, fib)) {
        /* Directory entries require special processing. */
        if (fib->fib_DirEntryType > 0) {  /* Subdirectory? */
            /* Is it OK to scan subdirectories? */
            if (! scanSubDir) continue;
            newDirName = malloc(strlen(dirName) + 
                                strlen(fib->fib_FileName) + 2);
            if (! newDirName) goto no_mem;

            BuildPathName(dirName, fib->fib_FileName, newDirName);
            newLock = (struct Lock *) Lock(newDirName, SHARED_LOCK);
            if (! newLock) {
                Printf("MRMan: failed to lock subdirectory '%s'.\n",
                       newDirName);
                goto done;
            }
            /* Note: we ALWAYS scan sub-subdirectories. */
            tp = ScanDir(newDirName, newLock, 1);
            UnLock(newLock);
            free(newDirName);
            if (tp) {
                theFile = tp;
                goto done;
            }
            else 
                continue;
        }

        /* When attempting to match <theSubject> to the current filename,
         * we elminate any trailing suffix matter (following an embedded
         * period) from the comparison.  Thus, the match will work for
         * files of the form "subject.man", "subject.doc", "subject.doc.Z",
         * etc.  I realize that this is a pretty "hardwired" approach, but
         * it should work in most situations.
         */
 
        strcpy(testName, fib->fib_FileName);

        /* If the file has a suffix, only allow one of the predefined
         * suffixes, above.  Since the file may be compressed, we first
         * dispose of the trailing ".Z" suffix, if one exists.
         */ 
        if (tp = rindex(testName, '.')) {
            *tp = '\0';
            /* This might be a ".Z" suffix for a compressed file.  If it
             * is, ignore it and look backward to the next suffix.
             */
            if ( ! Strcmp(tp+1,"Z") )
                tp = rindex(testName,'.');

            if (tp) {
                *tp = '\0';
                ++tp;
                for (i = 0, ok = 0; i < SUFFIXES; ++i)
                    if (! Strcmp(tp, suffixes[i])) {
                        ok = 1;
                        break;
                    }
                if (! ok ) continue;
            }
        }
        /* Now, drop ANY suffix material from the filename and compare it
         * to the subject string.
         */

        if (tp = index(testName, '.'))
            *tp = '\0';         /* Truncate the string. */
        if (!Strcmp(testName, theSubject)) {
            theFile = BuildPathName(dirName, fib->fib_FileName, fullPath);
            goto done;
        }
    }
done:
    if (fib) FreeMem(fib, (long) sizeof(*fib));
    return theFile;
}

/*  FUNCTION
        ManSearch - top-level document search routine

    SYNOPSIS
        char *ManSearch(startDir, subject)
                        char *startDir, subject;

    DESCRIPTION
        ManSearch performs a search for a document file which describes the
        <subject>.  If <startDir> is not NULL or empty, it is expected to
        contain the pathname of the directory where the search is to begin.
        In this case, the environment variable ManPath is not referenced.
        Otherwise, ManSearch performs its search according to the search
        rules described in the MRMan document.

        If a document file is found, a pointer to its name is returned.
        Otherwise, ManSearch returns NULL.
 */

char *
ManSearch(startDir, subject)
    char *startDir, *subject;
{
    char        dirName[256];
    struct FileInfoBlock *fib;
    struct Lock *lock;
    char        *manPath, *mp;
    char        *manPathSub, *mpSub;
    unsigned    scanSubDir;
    char        subDirName[256];
    char        *theFile = NULL;

    theSubject = subject;        /* Copy subject to "global". */
    fib = AllocMem((long) sizeof(*fib), 0L);
    if (fib == NULL) {
no_mem:
        Printf("MRMan: out of memory!\n");
        goto done;
    }
    /* The search for "subject" can use one of 3 different starting points.
     * If the parameter <startDir> is non-null (!= NULL && contains text)
     * it is used.  If the environment variable ManPath is defined, its
     * contents are used. Otherwise, an attempt is made to use the contents
     * of a file named "Man:ManPath".
     */

    if (startDir && *startDir) {    /* Valid starting directory? */
        manDir = startDir;
        checkEnvironment = 0;       /* Don't look at ENV:ManPath. */
    }
    manPath = GetManPath(manDir);
    if (manPath == NULL) {
        /* As a last resort, create a path of one directory, the
         * default or user-specified MAN directory.
         */
        manPath = malloc(strlen(manDir)+2);
        if (! manPath) goto no_mem;
        strcpy(manPath, manDir);
    }

    if (verbose)
        Printf("MRMan: search path is '%s'.\n", manPath);

    checkEnvironment = 0;       /* We're done with ENV:ManPath. */
    mp = manPath;
    while (mp) {
        mp = NextDirName(mp, dirName);
        if (! *dirName) break;

        /* Each top-level directory can, in turn, have its own ManPath
         * file which specifies the search order of its subdirectories.
         * Actually, the ManPath file can specify ANY directory.
         */
        manPathSub = GetManPath(dirName);
        if (manPathSub == NULL) {   /* No ManPath? */
            scanSubDir = 1;         /* Allow subdirectory scanning. */
            manPathSub = malloc(strlen(dirName)+1);
            if (manPathSub == NULL) goto no_mem;
            strcpy(manPathSub,dirName);
        }
        else {
            scanSubDir = 0;         /* Order is specified. */
            if (verbose)
                Printf("Search path for '%s' is '%s'.\n",dirName, manPathSub);
        }
        mpSub = manPathSub;
        while (mpSub) {
            mpSub = NextDirName(mpSub, dirName);
            if (! *dirName) break;

            /* The directory name may be relative to where the ManPath
             * file is stored.  If it is (no ':'), create an absolute
             * path.
             */

            if (!index(dirName, ':')) {
                BuildPathName(manDir, dirName, subDirName);
            }
            else
                strcpy(subDirName, dirName);
            lock = (struct Lock *) Lock(subDirName, SHARED_LOCK);
            if (lock == NULL) {
                Printf("MRMan: could not lock '%s', error %ld\n", subDirName, 
                        IoErr());
                continue;
            }
            if (! Examine(lock, fib)) {
                Printf("MRMan: could not examine '%s', error %ld\n", 
                       subDirName, IoErr());
                UnLock(lock);
                continue;
            }
            if (fib->fib_DirEntryType <= 0) {
                Printf("MRMan: '%s' is not a directory!\n", subDirName);
                goto done;
            }
            theFile = ScanDir(subDirName, lock, scanSubDir);
            UnLock(lock);
            if (theFile) goto done;
        }                       /* while (mpSub) */
        free(manPathSub);
        manPathSub = NULL;
    }           
done:
    if (fib) FreeMem(fib, (long) sizeof(*fib));
    if (manPath) free(manPath);
    if (manPathSub) free(manPathSub);
    return theFile;
}

/*  FUNCTION
        NextDirName - collect next directory name from search path

    SYNOPSIS
        static char *NextDirName(path, dirName)
                        char *path, *dirName;

    DESCRIPTION
        NextDirName collects the next directory name token from the
        search path variable, <path>, ignoring _leading_ white space
        and newlines.  Semicolons separate name tokens.

        NextDirName stores its result in <dirName> and returns an
        updated pointer to the <path>.  If <dirName[0]> is zero, no
        token was collected.
*/

static char *
NextDirName(path, dirName)
    char *path, *dirName;
{
    char        c;
    short       length = 0;

    *dirName = '\0';

    if (path && *path) {
        while (c = *path++) {
            if (index(" \t\n;", c)) continue;
            break;
        }
        if (! c)
            path = NULL;
        else {
            while (c && (c != ';') && (c != '\n')) {
                if (length == 255) {
                    Printf("MRMan: path element is too long!\n'%s'\n",
                           dirName);
                    dirName[0] = '\0';
                    path = NULL;
                }
                dirName[length++] = c;
                c = *path++;
            }
            dirName[length] = '\0';
            if (! c) path = NULL;
        }
    }
    else {                       /* Search path variable is empty. */
        path =  NULL;
    }
    return path;
}
