/*  File support routines to complement ARP.
 *  Author: Mark R. Rinfret
 *          Usenet:     mrr@amanpt1.Newport.RI.US
 *          BIX:        markr
 *          CIS:        72017, 136 (good luck!)
 *
 *          348 Indian Avenue
 *          Portsmouth, RI 02871
 *          401-846-7639 (home)
 *          401-849-9390 (work)       
 *
 *  This package was written primarily because of _one_ missing element
 *  in ARP: FGets. ARGH! ARPFFFT! It extends ARP by adding buffering to
 *  the basic file type (FileHandle) and defining a new type, named
 *  ARPFileHandle (hope this is OK with the ARP guys). Also, I've used the
 *  convention of embedding ARP (vs. Arp in MicroSmith's stuff) in all type
 *  and function names to (hopefully) avoid naming collisions.
 *
 *  This package (as far as I am concerned) is public domain. Use it any
 *  way you like. Some comments on the "MR" prefix: it isn't short for
 *  "Mister", it comprises the initials of my first and last names.
 *  I use it primarily to avoid name collisions with stuff by other
 *  authors. If any other authors whose initials are "MR" start doing
 *  this, we'll probably have to appeal to Electronic Arts to dole out
 *  "author codes" along the lines of those issued for IFF :-). I hereby
 *  stake a claim to 'MRR_'.
 *
 *  A word of warning:
 *
 *  DO NOT INTERMIX STANDARD AMIGADOS FILE SUPPORT CALLS WITH CALLS TO
 *  THIS PACKAGE ON FILES USING THIS FILE METHOD!
 *
 *  Obviously, the system doesn't know about the buffering added here
 *  and will cause unpredictable results if you mix calls to this package
 *  with calls to Read/Write/Seek, etc. (unless, of course, you take care 
 * to maintain the ARPFileHandle information).
 */


#if __STDC__
#include <stdlib.h>
#endif

#include "MRARPFile.h"
#include <exec/memory.h>
#include <string.h>
#include <functions.h>

#ifndef min
#define min(a,b) ((a) <= (b) ? (a) : (b))
#define max(a,b) ((a) >= (b) ? (a) : (b))
#endif

/* struct DefaultTracker *StoreTracker __PARMS( (void) ); */

static LONG FillARPFileBuffer __PARMS( (ARPFileHandle *file) );
static LONG FlushARPFileBuffer __PARMS( (ARPFileHandle *file) );

/*  FUNCTION
 *      FGetsARP - get string from a buffered ARP file.
 *
 *  SYNOPSIS
 *      #include <MRARPFile.h>
 *
 *      char *FGetsARP(s, length, file)
 *                     UBYTE         *s;
 *                     LONG           length;
 *                     ARPFileHandle *file;
 *
 *  DESCRIPTION
 *      FGetsARP models the behavior of the "standard" fgets, except that
 *      it is used with a buffered ARP file. The data is read from <file>
 *      and transfered to string <s>. Up to length-1 characters will be
 *      read. Reading is also terminated upon receipt of a newline
 *      or detection of end-of-file. 
 *
 *      If the <file> was opened without a buffer, one of MaxInputBuf
 *      bytes will be allocated.
 *
 *      If end of file or an error is detected, the return value will be
 *      NULL. Otherwise, the return value will be <s>. <s> will be 
 *      terminated with a null byte.
 */
char *
FGetsARP(s, length, file)
    char            *s;
    LONG            length;
    ARPFileHandle   *file;
{
    LONG    bytesNeeded = length - 1;
    LONG    bytesRead = 0;
    char    c;
    char    *s1 = s;

    /* Set string initially empty to protect user from failure to check
     * return value.
     */
    *s1 = '\0';    
    if (file->mode != MODE_OLDFILE)
        file->lastError = ERROR_READ_PROTECTED;
         
    if (file->lastError) {
dangit:
        return NULL;
    }
    if (! file->buf ) {                 /* Ohmigosh! No buffer? */
        file->buf = ArpAlloc(MaxInputBuf);
        if (! file->buf ) {
            file->lastError = ERROR_NO_FREE_STORE;
            goto dangit;
        }
        file->bufSize = MaxInputBuf;    /* bufLength, bufPos are zero. */
    }
    while (bytesNeeded) {
        if (file->bufPos >= file->bufLength) {
            if (FillARPFileBuffer(file) < 0) goto dangit;
            if (file->bufLength == 0) break;
        }
        c = file->buf[file->bufPos++];
        ++bytesRead;
        if (c == '\n') break;
        *s1++ = c;
        --bytesNeeded; 
    }
    *s1 = '\0';
    return (bytesRead ? s : NULL);
}

/*  FUNCTION
 *      FillARPFileBuffer - read data into ARPFile buffer.
 *
 *  SYNOPSIS
 *      #include <MRARPFile.h>
 *
 *      static LONG FillARPFileBuffer(file)
 *                                    ARPFileHandle *file;
 *
 *  DESCRIPTION
 *      Attempt to fill the buffer associated with <file> by reading
 *      data from the associated external file. The return value will
 *      be one of the following:
 *          >0  => number of bytes read
 *           0  => end of file
 *          -1  => a Bad Thing happened (error code in file->lastError) 
 *
 *      Note: this is a local routine and is thus declared as "static".
 *      Please inform the author if this is a hardship.
 */
static LONG
FillARPFileBuffer(file)
    ARPFileHandle *file;
{
    /* Remember where we were. The user might want to try error
     * recovery. Of course, this probably isn't enough info, but
     * it's a start.
     */
    file->lastPosition = Seek(file->fh, 0L, OFFSET_CURRENT);
    file->bufPos = 0;
    file->bufLength = Read(file->fh, file->buf, file->bufSize);
    if (file->bufLength == -1) {    /* We got trubble! */
        file->lastError = IoErr();
    }
    else if (file->bufLength == 0) {
        file->endOfFile = TRUE;
    }
    return file->bufLength;
} 

/*  FUNCTION
 *      FlushARPFileBuffer - write file buffer contents to disk.
 *
 *  SYNOPSIS
 *      #include <MRARPFile.h>
 *
 *      static LONG FlushARPFileBuffer(ARPFileHandle *file);
 *
 *  DESCRIPTION
 *      FlushARPFileBuffer writes the contents of <file>'s buffer
 *      (if any) to disk and resets the buffer information. The
 *      return value may be any of:
 *
 *          >0 =>   number of bytes written
 *           0 =>   nothing in buffer
 *          <0 =>   negated error code
 *
 *      Note: it is assumed that this function will only be used locally
 *      and therefore need not be public. If you disagree, please contact
 *      the author.
 */

static LONG
FlushARPFileBuffer(file)
    ARPFileHandle *file;
{
    LONG    bytesWritten = 0;

    /* This operation is only allowed for output files. */

    if (file->mode != MODE_NEWFILE) {
        file->lastError = ERROR_WRITE_PROTECTED;
badstuff:
        return -file->lastError;
    }

    if (file->lastError) goto badstuff; /* Residual error? */

    if (file->bufLength) {
        file->lastPosition = Seek(file->fh, 0L, OFFSET_CURRENT);
        bytesWritten = Write(file->fh, (const char *) file->buf, file->bufLength);
        if (bytesWritten != file->bufLength) {
            file->lastError = IoErr();
            goto badstuff;
        }
        else {
            file->bufLength = 0;
            file->bufPos = 0;
        }
    }
    return bytesWritten;
}

/*  FUNCTION
 *      FPutsARP - write a string to a buffered ARP file.
 *
 *  SYNOPSIS
 *      #include <MRARPFile.h>
 *
 *      LONG FPutsARP(s, file)
 *                    char          *s;
 *                    ARPFileHandle *file;
 *
 *  DESCRIPTION
 *      FPutsARP writes the contents of string <s> to the specified <file>.
 *      If successful, it returns 0. On failure, it returns the negated
 *      system error code. 
 */
LONG
FPutsARP(s, file)
    char          *s;
    ARPFileHandle *file;
{
    LONG    bytesLeft, bytesUsed;
    char    *s1 = s;

    if (file->mode != MODE_NEWFILE) 
        file->lastError = ERROR_WRITE_PROTECTED;

    if (file->lastError) {
shucks:
        return -file->lastError;
    }

    bytesLeft = strlen(s);

    /* Attempt to be smart about this transfer. Copy the string to the
     * buffer in chunks. There is a possibility that the string is bigger
     * than the size of the buffer.
     */
    while (bytesLeft) {
        if (file->bufPos >= file->bufSize) {
            if (FlushARPFileBuffer(file) <= 0) goto shucks;
        }
        bytesUsed = min(file->bufSize - file->bufPos, bytesLeft);
        CopyMem(s1, &file->buf[file->bufPos], bytesUsed);
        s1 += bytesUsed;
        file->bufLength = (file->bufPos += bytesUsed);
        bytesLeft -= bytesUsed;
    }
    return 0;
}

/*  FUNCTION
 *      ReadARPFile - read from a buffered ARP file.
 *
 *  SYNOPSIS
 *      #include <MRARPFile.h>
 *
 *      LONG ReadARPFile(ARPFileHandle *file, char *buffer, LONG length);
 *
 *  DESCRIPTION
 *      ReadARPFile attempts to read <length> bytes from <file>, transferring
 *      the data to <buffer>. 
 *
 *      The return value may be any of the following:
 *
 *          >0      number of bytes transferred
 *          0       end of file
 *         <0       negated error code
 *
 *      Note: if the lastError field of the <file> descriptor contains a
 *            non-zero value, its negated value will be returned and no
 *            attempt will be made to read the file. If you attempt error
 *            recovery, you must clear this field to zero.
 */
LONG
ReadARPFile(file, buffer, length)
    ARPFileHandle   *file;
    char            *buffer;
    LONG            length;
{
    LONG            bytesLeft;
    LONG            bytesRead = 0;
    LONG            needBytes = length;
    LONG            pos = 0;
    LONG            usedBytes = 0;

    /* Prevent read if this file opened for writing. */

    if (file->mode != MODE_OLDFILE)
        file->lastError = ERROR_READ_PROTECTED;

    if (file->lastError) {          /* Have residual error? */
boofar:
        return -file->lastError;
    }

    if ( ! file->buf ) {            /* No buffer? */
        bytesRead = Read(file->fh, buffer, length);
        if (bytesRead == -1) {
            file->lastError = IoErr();
            goto boofar;
        }
    }
    else while (needBytes) {
        if (file->bufLength - file->bufPos <= 0) {
            if (FillARPFileBuffer(file) == -1) goto boofar;
            if (file->bufLength == 0) break;
        }
        bytesLeft = file->bufLength - file->bufPos;
        usedBytes = min(bytesLeft, length);
        CopyMem(&file->buf[file->bufPos], &buffer[pos], usedBytes);
        file->bufPos += usedBytes;
        pos += usedBytes;
        bytesRead += usedBytes;
        needBytes -= usedBytes;
    }
    return bytesRead;   
}

/*  FUNCTION
 *      CloseARPFile - close buffered ARP file.
 *
 *  SYNOPSIS
 *      #include <MRARPFile.h>
 *
 *      LONG CloseARPFile(file)
 *                        ARPFileHandle *file;
 *
 *  DESCRIPTION
 *      CloseARPFile closes the file described by <file> which MUST have
 *      been opened by OpenARPFile. It releases all tracked items
 *      associated with <file>, as well.
 *
 *      The return value is only meaningful for output files. If, upon
 *      flushing the buffer, a write error is detected, a system error
 *      code (ERROR_DISK_FULL, etc.) will be returned.
 */

LONG
CloseARPFile(file)
    ARPFileHandle *file;
{
    LONG        result = 0;

    if (file) {                     /* Just in case... */
        if (file->fileTracker) {
            /* Any left-over stuff in the buffer? If so, we must flush
             * it to disk. However, if an error was detected in the
             * previous operation, punt. 
             */
            if ( ( file->mode == MODE_NEWFILE) && 
                   ! file->lastError &&
                   file->bufLength) {
                if (Write(file->fh, (const char *) file->buf, file->bufLength) !=
                    file->bufLength)
                    result = IoErr();
            }
            FreeTrackedItem(file->fileTracker);
        }

        FreeTrackedItem((struct DefaultTracker *) file->buf);
        FreeTrackedItem((struct DefaultTracker *) file);
    }
    return result;
}

/*  FUNCTION
 *      OpenARPFile - open a buffered ARP file
 *
 *  SYNOPSIS
 *      #include <MRARPFile.h>
 *
 *      struct MRARPFile *OpenARPFile(name, accessMode, bytes)
 *                                    char *name;
 *                                    LONG accessMode, bytes;
 *
 *  DESCRIPTION
 *      OpenARPFile opens the file <name>, with the given <accessMode>
 *      (MODE_OLDFILE, MODE_NEWFILE only!) for buffered access. The
 *      size of the local buffer is specified by <bytes>.
 *
 *      A zero value for <bytes> is OK and is sometimes appropriate, as
 *      when performing file copy operations, etc. However, if a file
 *      opened with a zero length buffer is then passed to the
 *      FGetsARP function, "spontaneous buffer allocation" will occur.
 *      No biggy, really, just something you should know. The ARP constant,
 *      MaxInputBuf will be used for the buffer size, in this case.
 *
 *      If successful, a pointer to the file tracking structure is 
 *      returned. Otherwise, the return value will be NULL.
 *
 *      OpenARPFile uses full resource tracking for all information
 *      associated with the file.
 *
 */

ARPFileHandle *
OpenARPFile(name, accessMode, bytes)
    char *name;
    LONG accessMode, bytes;
{
    BPTR                  fh;
    ARPFileHandle         *theFile = NULL;
    struct DefaultTracker *lastTracker;

    /* This package does not support READ/WRITE access! */

    if ( (accessMode != MODE_OLDFILE) && (accessMode != MODE_NEWFILE))
        return NULL;

    theFile = ArpAlloc((LONG) sizeof(ARPFileHandle));
    if (theFile) {
        theFile->mode = accessMode;
        fh = ArpOpen(name, accessMode);
        lastTracker = LastTracker;
        if (!fh) {
fungu:
            CloseARPFile(theFile);  /* Don't worry - it's "smart". */
            theFile = NULL;
        }
        theFile->fileTracker = lastTracker;
        theFile->fh = fh;
        if ( bytes) {               /* Does user want a buffer? */
            theFile->buf = ArpAlloc(bytes);
            if (!theFile->buf) goto fungu;
            theFile->bufSize = bytes;
        }
    }
    return theFile;  
}

/*  FUNCTION
 *      SeekARPFile - move to new logical position in file.
 *
 *  SYNOPSIS
 *      #include <MRARPFile.h>
 *
 *      LONG SeekARPFile(file, position, mode)
 *                       ARPFileHandle *file;
 *                       LONG           position;
 *                       LONG           mode;
 *
 *  DESCRIPTION
 *      SeekARPFile attempts to position the <file> to a new logical
 *      position or report it's current position. The <position>
 *      parameter represets a signed offset. The <mode> parameter may
 *      be one of:
 *
 *          OFFSET_BEGINNING (-1) => from beginning of file
 *          OFFSET_CURRENT (0)    => from current position
 *          OFFSET_END (-1)       => from end of file
 *
 *      On output files, the current buffer contents, if any, are
 *      written to disk.
 *
 *      The return value will either be a positive displacement >=0) or
 *      a negated system error code.
 */
LONG
SeekARPFile(file, position, mode)
            ARPFileHandle *file;
            LONG           position;
            LONG           mode;
{
    LONG    newPosition;

    if (file->mode == MODE_NEWFILE && file->bufLength) {
        if (FlushARPFileBuffer(file) < 0) {
farboo:
            return -file->lastError;
        }
    }
    /* Remember our last position. */
    file->lastPosition = Seek(file->fh, 0L, OFFSET_CURRENT);
    if ((newPosition = Seek(file->fh, position, mode)) == -1) {
        file->lastError = IoErr();
        goto farboo;
    }
    return newPosition;
}  

/*  FUNCTION
 *      WriteARPFile - write data to a buffered ARP file.
 *
 *  SYNOPSIS
 *      #include <MRARPFile.h>
 *
 *      LONG WriteARPFile(ARPFileHandle *file, const char *buffer, LONG length);
 *
 *  DESCRIPTION
 *      WriteARPFile attempts to write <length> bytes from <buffer> to
 *      the buffered ARP file, <file>. The file MUST have been opened
 *      with OpenARPFile for MODE_NEWFILE access.
 *
 *      WriteARPFile will not write to a file if the lastError field is
 *      non-zero, or if the file was opened for input.
 *
 *      If successful, WriteARPFile will return the <length> parameter.
 *      Otherwise, it will return a negated system error code.
 */
LONG
WriteARPFile(file, buffer, length)
    ARPFileHandle   *file;
    const char      *buffer;
    LONG            length;
{
    LONG    bufferBytes;
    LONG    bytesLeft = length;
    LONG    pos = 0;
    LONG    usedBytes = 0;

    if (file->mode != MODE_NEWFILE)
        file->lastError = ERROR_WRITE_PROTECTED;

    if (file->lastError) {           /* Catches mode and residual errors. */
sumbidge:
        return -(file->lastError);
    }

    if ( ! file->buf ) {             /* No buffer? */
        if (Write(file->fh, buffer, length) != length) {
            file->lastError = IoErr();
            goto sumbidge;
        }
    }
    else while (bytesLeft) {
        /* Need to flush the file's buffer? */
        if (file->bufPos >= file->bufSize) {
            if (FlushARPFileBuffer(file) < 0) {
                goto sumbidge;
            }
        }
        bufferBytes = file->bufSize - file->bufPos;
        usedBytes = min(bufferBytes, bytesLeft);
        CopyMem(&buffer[pos], &file->buf[file->bufPos], usedBytes);
        file->bufLength = (file->bufPos += usedBytes);
        pos += usedBytes;
        bytesLeft -= usedBytes;
    }
    return length;
}

/*  Embedded documentation template (cut & paste): */

/*  FUNCTION
 *
 *  SYNOPSIS
 *
 *  DESCRIPTION
 *
 */
