/*  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 (unless, of course, you
 *  take care to maintain the ARPFileHandle information).
 */

/*  Olsen: In order to adapt the code to ANSI some declarations and
 *         function calls were rearranged. I also fixed two parts of
 *         code which appeared not to work correctly.
 */

	/* Skip prototypes. */

#define _MRARPFILE_PRIVATE 1

#include "MRARPFile.h"

/* StoreTracker is my attempt to fix an apparent bug in ARP 1.3. I have
 * found that the LastTracker kludge (via IoErr()) doesn't work reliably.
 * StoreTracker simply stuffs A1 into D0 and returns . It *MUST* be
 * called immediately after any ARP call which allocates a tracker to
 * assure that the value is correct.
 */

struct DefaultTracker *	StoreTracker(VOID);

char *			FGetsARP(char *s,LONG length,ARPFileHandle *file);
static LONG		FillARPFileBuffer(ARPFileHandle *file);
static LONG		FlushARPFileBuffer(ARPFileHandle *file);
LONG			FPutsARP(char *s,ARPFileHandle *file);
LONG			ReadARPFile(ARPFileHandle *file,UBYTE *buffer,LONG length);
LONG			CloseARPFile(ARPFileHandle *file);
ARPFileHandle *		OpenARPFile(char *name,LONG accessMode,LONG bytes);
LONG			SeekARPFile(ARPFileHandle *file,LONG position,LONG mode);
LONG			WriteARPFile(ARPFileHandle *file,UBYTE *buffer,LONG length);

/*  FUNCTION
 *      FGetsARP - get string from a buffered ARP file.
 *
 *  SYNOPSIS
 *      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(char *s,LONG length,ARPFileHandle *file)
{
	struct DefaultTracker	*tracker;
	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);
	}

		/* Ohmigosh! No buffer? */

	if(!file -> buf)
	{
		file -> buf = ArpAllocMem(MaxInputBuf, MEMF_CLEAR | MEMF_PUBLIC);

		tracker = StoreTracker();

		if(!file -> buf)
		{
			file -> lastError = ERROR_NO_FREE_STORE;
			goto dangit;
		}

		file -> bufSize		= MaxInputBuf;	/* bufLength, bufPos are zero. */
		file -> bufTracker	= tracker;
	}

	while(bytesNeeded)
	{
		if(file -> bufPos >= file -> bufLength)
		{
			if(FillARPFileBuffer(file) < 0)
				goto dangit;

			if(!file -> bufLength)
				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
 *      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(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);

		/* We got trubble! */

	if(file -> bufLength == -1)
		file -> lastError = IoErr();
	else
	{
		if(!file -> bufLength)
			file -> endOfFile = TRUE;
	}

	return(file -> bufLength);
} 

/*  FUNCTION
 *      FlushARPFileBuffer - write file buffer contents to disk.
 *
 *  SYNOPSIS
 *      static LONG FlushARPFileBuffer(file)
 *                                     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(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, 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
 *      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(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
 *      LONG ReadARPFile(file, buffer, length)
 *                       ARPFile *file;
 *                       UBYTE   *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(ARPFileHandle *file,UBYTE *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;

		/* Have residual error? */

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

		/* No buffer? */

	if(!file -> buf )
	{
		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)
					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
 *      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(ARPFileHandle *file)
{
	LONG result = 0;

		/* Just in case... */

	if(file)
	{
		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, file -> buf, file -> bufLength) != file -> bufLength)
					result = IoErr();
			}

			FreeTrackedItem(file -> fileTracker);
		}

		if(file -> bufTracker)
			FreeTrackedItem(file -> bufTracker);

		FreeTrackedItem(file -> myTracker);
	}

	return(result);
}

/*  FUNCTION
 *      OpenARPFile - open a buffered ARP file
 *
 *  SYNOPSIS
 *      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(char *name,LONG accessMode,LONG bytes)
{
	struct DefaultTracker	*lastTracker;
	ARPFileHandle		*theFile = NULL;
	BPTR			 fh;

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

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

	/* 
	 * Note: I'm using ArpAllocMem vs. ArpAlloc here because it appears
	 * that ArpAlloc gives me a bad tracker pointer (?).
	 */

	theFile = ArpAllocMem((LONG) sizeof(ARPFileHandle), MEMF_PUBLIC | MEMF_CLEAR);

	lastTracker = StoreTracker();

	if(theFile)
	{
		theFile -> myTracker = lastTracker;
		theFile -> mode = accessMode;

		fh = ArpOpen(name, accessMode);

		lastTracker = StoreTracker();

		if(!fh)
		{
fungu:			CloseARPFile(theFile);	/* Don't worry - it's "smart". */
			theFile = NULL;

			return(NULL);		/* This one was missing -Olsen */
		}

		theFile -> fileTracker	= lastTracker;
		theFile -> fh		= fh;

			/* Does user want a buffer? */

		if(bytes)
		{
			theFile -> buf = ArpAllocMem(bytes, MEMF_PUBLIC | MEMF_CLEAR);

			lastTracker = StoreTracker();

			if(!theFile -> buf)
				goto fungu;

			theFile -> bufSize	= bytes;
			theFile -> bufTracker	= lastTracker;
		}
	}

	return(theFile);
}

/*  FUNCTION
 *      SeekARPFile - move to new logical position in file.
 *
 *  SYNOPSIS
 *      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(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. Seek(file,...) fixed
		 * to use Seek(file -> fh,...) -Olsen.
		 */

	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
 *      LONG WriteARPFile(file, buffer, length)
 *                        ARPFileHandle *file;
 *                        UBYTE         *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(ARPFileHandle *file,UBYTE *buffer,LONG length)
{
	LONG	bufferBytes;
	LONG	bytesLeft = length;
	LONG	pos = 0;
	LONG	usedBytes = 0;

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

		/* Catches mode and residual errors. */

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

		/* No buffer? */

	if(!file -> buf)
	{
		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);
}
