/* Asynchronous file input/output routines (c) Stone, The SST
   Public domain. email: c9107253@cs.newcastle.edu.au

   See example programs for usage
*/

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <st/st_proto.h>

/*
NOTE:   Becareful  on implicit GetMsg's & Waits when calling ARead, AWrite,
AStatus  or  AResultWait.   Always keep all GetMsg's in a single loop!  (so
resort to AStatusQuick and AGetReply)
*/


/*********************************************************/
long			ASigBit(struct AFileHandle *afh)
/*********************************************************
 Call this to find the bit to wait on when waiting for
           replies from the associated file
   Note that several files may share the same reply port
so all files sharing the port must be checked with AStatus
 *********************************************************/
{ return(ASIGBIT(afh)); }


/*********************************************************/
long			AResult(struct AFileHandle *afh)
/*********************************************************/
{ return(ARESULT1(afh)); }


/*********************************************************/
long			AStatusQuick(struct AFileHandle *afh)
/*********************************************************/
{ return(afh->Status); }


/*********************************************************/
struct AFileHandle		*AGetReply(struct APort *aport)
/*********************************************************
       Attempt GetMsg a single message from the port
 *********************************************************/
{	struct AFileHandle *afh;
	if(afh = (struct AFileHandle *)GetMsg(aport->Port))
								afh->Status = ASTATUS_IDLE;
	return(afh);
}


/*********************************************************/
long					AStatus(struct AFileHandle *afh)
/*********************************************************
              Return the status if not busy
If busy remove messages until no more or we found our reply
   WARNING: this possibly removes all pending messages
        so be VERY careful with this and a 'Wait'
       (best to use this with only a single file)
 *********************************************************/
{
struct	AFileHandle *afh_reply;
	if(afh->Status != ASTATUS_BUSY) return(afh->Status);
	while(afh_reply = AGetReply(afh->APort)) if(afh_reply == afh) break;
	return(afh->Status);
}


/*********************************************************/
long				AResultWait(struct AFileHandle *afh)
/*********************************************************
        Wait until status is NOT ASTATUS_BUSY
   Be careful with this routine also (see AStatus)
 *********************************************************/
{
	while(AStatus(afh) == ASTATUS_BUSY) Wait(1<<ASIGBIT(afh));
	return(ARESULT1(afh));
}


/*********************************************************/
void			AClose(struct AFileHandle *afh)
/*********************************************************
                       Close a file
Note: IF A PACKET IS PENDING THIS WILL WAIT AND ALL MSG'S
          UP TO THIS PACKET WILL BE AGetReply'd
     (so AStatus all other files if this is the case)
 *********************************************************/
{
	if(!afh) return;

/** WAIT FOR THE PENDING PACKET IF NEED BE */
	AResultWait(afh);

/** UNLINK US FROM THE PORT LIST OF OPEN FILES */
	if(afh->Prev)
		afh->Prev->Next = afh->Next;
	else afh->APort->First = afh->Next;
	if(afh->Next)
		afh->Next->Prev = afh->Prev;

/** CLOSE THE FILE & FREE THE MEMORY */
	if(afh->FileHandle && (afh->Flags&AFLAG_CLOSEONCLOSE))
							Close(((long)afh->FileHandle)>>2);
	FreeMem(afh,sizeof(struct AFileHandle));
}


/*********************************************************/
void			ASafeClose(struct AFileHandle **afh)
/*********************************************************/
{ if(*afh) { AClose(*afh); *afh = 0; } }


/*********************************************************/
struct AFileHandle
		*AMakeFD2AFD(struct APort *aport, long fd)
/*********************************************************
     Build an AFileDescriptor from the fd passed in
             Default to no auto file close
               (so Input() can be copied)
 *********************************************************/
{
struct AFileHandle *afh;
	if(!aport || !fd ||
		!(afh = AllocMem(sizeof(struct AFileHandle),MEMF_PUBLIC | MEMF_CLEAR)))
			return(0);
	afh->FileHandle	= (struct FileHandle *)(fd<<2);
	afh->Prev		= 0;
	if(afh->Next	= aport->First) afh->Next->Prev = afh;
	afh->APort		= aport;
	afh->Status		= ASTATUS_FIRSTTIME;
	afh->Flags		= AFLAG_NOCLOSE;
	aport->First	= afh;
	return(afh);
}

/*********************************************************/
struct AFileHandle
		*AOpen(struct APort *aport,	char *name, long mode)
/*********************************************************
  Attempt to open a file with 'mode' and link into the
          list of open files on the port APort
   return NULL if file could not be opened or no memory
**********************************************************/
{
long fd;
struct AFileHandle *afh;
	if(!(fd = Open(name, mode) )) return(0);
	if(!(afh = AMakeFD2AFD(aport, fd))) {
		Close(fd); return(0);
	}
/* CLOSE ACTUAL FILE ON A DELETE PORT OR EXPLICIT CLOSE */
	afh->Flags = AFLAG_CLOSEONCLOSE;
	return(afh);
}





/*********************************************************/
void 			ADeletePort(struct APort *aport)
/*********************************************************
         Close & Free all opened files on this port
                 Delete the port
**********************************************************/
{
	if(!aport) return;
	while(aport->First) AClose(aport->First);
	DeletePort(aport->Port);
	FreeMem(aport, sizeof(struct APort));
}


/*********************************************************/
struct APort	*ACreatePort(void)
/*********************************************************
        Create a reply port for the dos packets
**********************************************************/
{
struct APort *aport;
	if(!(aport = (struct APort *)
			AllocMem(sizeof(struct APort), MEMF_PUBLIC | MEMF_CLEAR)))
				return(0);
	spf(aport->PortName, APORTNAMESIZE, "AIOReply.%08lx", aport);
	if(!(aport->Port = CreatePort(aport->PortName, 0)))
		{ FreeMem(aport, sizeof(struct APort)); return(0); }
	aport->First = 0;
	return(aport);
}



/*********************************************************/
short	ASendPacket(struct AFileHandle *afh, long action,
			long arg1, long arg2, long arg3, long arg4)
/*********************************************************
       Try and send a packet using the given handle
      return 1 on ok, 0 if packet is already in use
 *********************************************************/
{
struct StandardPacket *sp = &(afh->Pkt);
struct MsgPort *port = afh->FileHandle->fh_Type;
	if(afh->Status == ASTATUS_BUSY) return(0);
	afh->Status = ASTATUS_BUSY;

/** ADDRESS NEGATIVE IF PLAIN FILE?? (I don't believe...) */
	sp->sp_Msg.mn_Node.ln_Name =(char *)&(sp->sp_Pkt);
	sp->sp_Pkt.dp_Link = &(sp->sp_Msg);
	sp->sp_Pkt.dp_Port = afh->APort->Port;
	sp->sp_Pkt.dp_Type = action;
	sp->sp_Pkt.dp_Arg1 = arg1;
	sp->sp_Pkt.dp_Arg2 = arg2; /* DESTINATION */
	sp->sp_Pkt.dp_Arg3 = arg3; /* BUFSIZE */
	sp->sp_Pkt.dp_Arg4 = arg4; /* ANOTHER THING */

	PutMsg(port, &(sp->sp_Msg));
	return(1);
}




/*********************************************************/
long	ARead(struct AFileHandle *afh, void *buf, long n)
/*********************************************************
                 Send a packet to read
    This returns the results of the PREVIOUS packet
                   of THIS filehandle
   NOTE that the new packet is sent ONLY if the previous
                 packet result is OK
**********************************************************/
{
long prev_res;
	if(AStatus(afh) == ASTATUS_FIRSTTIME) prev_res = -1;
	else prev_res = AResultWait(afh);
	if(prev_res) ASendPacket(afh, ACTION_READ,
							afh->FileHandle->fh_Args, (long)buf, n, 0);
	return(prev_res);
}

/*********************************************************/
long	AWrite(struct AFileHandle *afh, void *buf, long n)
/*********************************************************
                 Send a packet to write
    This returns the results of the PREVIOUS packet
                of THIS filehandle
   NOTE that the new packet is sent ONLY if the previous
                 packet result is OK
**********************************************************/
{
long prev_res;
	if(AStatus(afh) == ASTATUS_FIRSTTIME) prev_res = -1;
	else prev_res = AResultWait(afh);
	if(prev_res) ASendPacket(afh, ACTION_WRITE,
							afh->FileHandle->fh_Args, (long)buf, n, 0);
	return(prev_res);
}
