/*******************************************************************************
 *
 * (c) Copyright 1993 Commodore-Amiga, Inc.  All rights reserved.
 *
 * This software is provided as-is and is subject to change; no warranties
 * are made.  All use is at your own risk.  No liability or responsibility
 * is assumed.
 *
 * asyncio.c - See AmigaMail Vol.2 Section 2 P77 for more details. Note that
 * this has a few modifications of my own to reduce time spent copying memory
 * from the Asyncio routine's buffers to mine.
 *
 ******************************************************************************/
/*
SC5 -L -b0 -ms -v -rr -cfistqmc -d1 asyncio.c
quit
*/
#include <exec/types.h>
#include <exec/exec.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <stdio.h>

#include <clib/exec_protos.h>
#include <clib/dos_protos.h>

#include <pragmas/exec_pragmas.h>
#include <pragmas/dos_pragmas.h>

#include "asyncio.h"


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


extern struct Library *DOSBase;
extern struct Library *SysBase;


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


/* this macro lets us long-align structures on the stack */
#define D_S(type,name) char a_##name[sizeof(type)+3]; \
		       type *name = (type *)((LONG)(a_##name+3) & ~3);


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


static VOID SendAsync(struct AsyncFile *file, APTR arg2)
{
    /* send out an async packet to the file system. */

    file->af_Packet.sp_Pkt.dp_Port = &file->af_PacketPort;
    file->af_Packet.sp_Pkt.dp_Arg2 = (LONG)arg2;
    PutMsg(file->af_Handler, &file->af_Packet.sp_Msg);
    file->af_PacketPending = TRUE;
}


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


static VOID WaitPacket(struct AsyncFile *file)
{
    /* This enables signalling when a packet comes back to the port */
    file->af_PacketPort.mp_Flags = PA_SIGNAL;

    /* Wait for the packet to come back, and remove it from the message
     * list. Since we know no other packets can come in to the port, we can
     * safely use Remove() instead of GetMsg(). If other packets could come in,
     * we would have to use GetMsg(), which correctly arbitrates access in such
     * a case
     */
    Remove((struct Node *)WaitPort(&file->af_PacketPort));

    /* set the port type back to PA_IGNORE so we won't be bothered with
     * spurious signals
     */
    file->af_PacketPort.mp_Flags = PA_IGNORE;

    /* packet is no longer pending, we got it */
    file->af_PacketPending = FALSE;
}


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


static VOID FakePacket(struct AsyncFile *file)
{
    /* this function puts the packet back on the message list of our
     * message port.
     */

    AddHead(&file->af_PacketPort.mp_MsgList,&file->af_Packet.sp_Msg.mn_Node);
    file->af_PacketPending = TRUE;
}


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


struct AsyncFile *OpenAsync(const STRPTR fileName, UBYTE accessMode, LONG bufferSize)
{
struct AsyncFile  *file;
struct FileHandle *fh;
BPTR               handle;
BPTR               lock;
LONG               blockSize;
D_S(struct InfoData,infoData);

    handle = NULL;
    file   = NULL;
    lock   = NULL;

    if (accessMode == MODE_READ)
    {
        if (handle = Open(fileName,MODE_OLDFILE))
            lock = DupLockFromFH(handle);
    }
    else
    {
        if (accessMode == MODE_WRITE)
        {
            handle = Open(fileName,MODE_NEWFILE);
        }
        else if (accessMode == MODE_APPEND)
        {
            /* in append mode, we open for writing, and then seek to the
             * end of the file. That way, the initial write will happen at
             * the end of the file, thus extending it
             */

            if (handle = Open(fileName,MODE_READWRITE))
            {
                if (Seek(handle,0,OFFSET_END) < 0)
                {
                    Close(handle);
                    handle = NULL;
                }
            }
        }

        /* we want a lock on the same device as where the file is. We can't
         * use DupLockFromFH() for a write-mode file though. So we get sneaky
         * and get a lock on the parent of the file
         */
        if (handle)
            lock = ParentOfFH(handle);
    }

    if (handle)
    {
        /* if it was possible to obtain a lock on the same device as the
         * file we're working on, get the block size of that device and
         * round up our buffer size to be a multiple of the block size.
         * This maximizes DMA efficiency.
         */

        blockSize = 512;
        if (lock)
        {
            if (Info(lock,infoData))
            {
                blockSize = infoData->id_BytesPerBlock;
                bufferSize = (((bufferSize + blockSize - 1) / blockSize) * blockSize) * 2;
            }
            UnLock(lock);
        }

        /* now allocate the ASyncFile structure, as well as the read buffers.
         * Add 15 bytes to the total size in order to allow for later
         * quad-longword alignement of the buffers
         */

        if (file = AllocVec(sizeof(struct AsyncFile) + bufferSize + 15,MEMF_ANY|MEMF_CLEAR))
        {
            file->af_File      = handle;
            file->af_ReadMode  = (accessMode == MODE_READ);
            file->af_BlockSize = blockSize;

            /* initialize the ASyncFile structure. We do as much as we can here,
             * in order to avoid doing it in more critical sections
             *
             * Note how the two buffers used are quad-longword aligned. This
	     * helps performance on 68040 systems with copyback cache. Aligning
             * the data avoids a nasty side-effect of the 040 caches on DMA.
             * Not aligning the data causes the device driver to have to do
             * some magic to avoid the cache problem. This magic will generally
             * involve flushing the CPU caches. This is very costly on an 040.
             * Aligning things avoids the need for magic, at the cost of at
             * most 15 bytes of ram.
             */

            fh                  = BADDR(file->af_File);
            file->af_Handler    = fh->fh_Type;
            file->af_BufferSize = bufferSize / 2;
            file->af_Buffers[0] = (APTR)(((ULONG)file + sizeof(struct AsyncFile) + 15) & 0xfffffff0);
            file->af_Buffers[1] = (APTR)((ULONG)file->af_Buffers[0] + file->af_BufferSize);
            file->af_Offset     = file->af_Buffers[0];

            /* this is the port used to get the packets we send out back.
             * It is initialized to PA_IGNORE, which means that no signal is
             * generated when a message comes in to the port. The signal bit
             * number is initialized to SIGB_SINGLE, which is the special bit
             * that can be used for one-shot signalling. The signal will never
             * be set, since the port is of type PA_IGNORE. We'll change the
             * type of the port later on to PA_SIGNAL whenever we need to wait
             * for a message to come in.
             *
             * The trick used here avoids the need to allocate an extra signal
             * bit for the port. It is quite efficient.
             */

            file->af_PacketPort.mp_MsgList.lh_Head     = (struct Node *)&file->af_PacketPort.mp_MsgList.lh_Tail;
            file->af_PacketPort.mp_MsgList.lh_TailPred = (struct Node *)&file->af_PacketPort.mp_MsgList.lh_Head;
            file->af_PacketPort.mp_Node.ln_Type        = NT_MSGPORT;
            file->af_PacketPort.mp_Flags               = PA_IGNORE;
            file->af_PacketPort.mp_SigBit              = SIGB_SINGLE;
            file->af_PacketPort.mp_SigTask             = FindTask(NULL);

            file->af_Packet.sp_Pkt.dp_Link          = &file->af_Packet.sp_Msg;
            file->af_Packet.sp_Pkt.dp_Arg1          = fh->fh_Arg1;
            file->af_Packet.sp_Pkt.dp_Arg3          = file->af_BufferSize;
            file->af_Packet.sp_Msg.mn_Node.ln_Name  = (STRPTR)&file->af_Packet.sp_Pkt;
            file->af_Packet.sp_Msg.mn_Node.ln_Type  = NT_MESSAGE;
            file->af_Packet.sp_Msg.mn_Length        = sizeof(struct StandardPacket);

            if (accessMode == MODE_READ)
            {
                /* if we are in read mode, send out the first read packet to
                 * the file system. While the application is getting ready to
                 * read data, the file system will happily fill in this buffer
                 * with DMA transfers, so that by the time the application
                 * needs the data, it will be in the buffer waiting
                 */

                file->af_Packet.sp_Pkt.dp_Type = ACTION_READ;
                if (file->af_Handler)
                    SendAsync(file,file->af_Buffers[0]);
            }
            else
            {
                file->af_Packet.sp_Pkt.dp_Type = ACTION_WRITE;
                file->af_BytesLeft             = file->af_BufferSize;
            }
        }
        else
        {
            Close(handle);
        }
    }

    return(file);
}


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


LONG CloseAsync(struct AsyncFile *file)
{
LONG result;
LONG result2;

    result = 0;
    if (file)
    {
        if (file->af_PacketPending)
            WaitPacket(file);

        result  = file->af_Packet.sp_Pkt.dp_Res1;
        result2 = file->af_Packet.sp_Pkt.dp_Res2;
        if (result >= 0)
        {
            if (!file->af_ReadMode)
            {
                /* this will flush out any pending data in the write buffer */
                result  = Write(file->af_File,file->af_Buffers[file->af_CurrentBuf],file->af_BufferSize - file->af_BytesLeft);
                result2 = IoErr();
            }
        }

        Close(file->af_File);
        FreeVec(file);

        SetIoErr(result2);
    }

    return(result);
}


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


extern UBYTE *rbuff;

LONG ReadAsync(struct AsyncFile *file, APTR buffer, LONG numBytes)
{
LONG totalBytes;
LONG bytesArrived;
BOOL partcopy = FALSE;

    totalBytes = 0;
    rbuff = buffer;

    /* if we need more bytes than there are in the current buffer, enter the
     * read loop
     */

    if (numBytes > file->af_BytesLeft)
    {
        /* this takes care of NIL: */
        if (!file->af_Handler)
            return(0);

        /* copy bytes that are in buffer */
        if (file->af_BytesLeft)
        {
	    partcopy = TRUE;
            CopyMem(file->af_Offset,buffer,file->af_BytesLeft);

            numBytes   -= file->af_BytesLeft;
            buffer      = (APTR)((ULONG)buffer + file->af_BytesLeft);
            totalBytes += file->af_BytesLeft;
        }

        /* if there's no pending packet, then we hit EOF last time around */
        if (!file->af_PacketPending)
            return(totalBytes);

        while (TRUE)
        {
            WaitPacket(file);

            bytesArrived = file->af_Packet.sp_Pkt.dp_Res1;
            if (bytesArrived <= 0)
            {
                if (bytesArrived <= 0)
                {
                    /* error, get out of here */
                    SetIoErr(file->af_Packet.sp_Pkt.dp_Res2);

                    if (bytesArrived == 0)
                        return(totalBytes);

                    return(-1);
                }
            }

            /* ask that the buffer be filled */
            SendAsync(file,file->af_Buffers[1-file->af_CurrentBuf]);

            if (file->af_SeekOffset > bytesArrived)
                file->af_SeekOffset = bytesArrived;

            file->af_Offset     = (APTR)((ULONG)file->af_Buffers[file->af_CurrentBuf] + file->af_SeekOffset);
            file->af_CurrentBuf = 1 - file->af_CurrentBuf;
            file->af_BytesLeft  = bytesArrived - file->af_SeekOffset;
            file->af_SeekOffset = 0;

            if (numBytes <= file->af_BytesLeft)
                break;

	    partcopy = TRUE;
            CopyMem(file->af_Offset,buffer,file->af_BytesLeft);

            numBytes   -= file->af_BytesLeft;
            buffer      = (APTR)((ULONG)buffer + file->af_BytesLeft);
            totalBytes += file->af_BytesLeft;
        }
    }

    if (partcopy)
    {
        CopyMem(file->af_Offset,buffer,numBytes);
    }
    else
    {
        rbuff = file->af_Offset;
    }

    file->af_BytesLeft -= numBytes;
    file->af_Offset     = (APTR)((ULONG)file->af_Offset + numBytes);

    return (totalBytes + numBytes);
}


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


LONG ReadCharAsync(struct AsyncFile *file)
{
char ch;

    if (file->af_BytesLeft)
    {
        /* if there is at least a byte left in the current buffer, get it
         * directly. Also update all counters
         */

        ch = *(char *)file->af_Offset;
        file->af_BytesLeft--;
        file->af_Offset = (APTR)((ULONG)file->af_Offset + 1);

        return((LONG)ch);
    }

    /* there were no characters in the current buffer, so call the main read
     * routine. This has the effect of sending a request to the file system to
     * have the current buffer refilled. After that request is done, the
     * character is extracted for the alternate buffer, which at that point
     * becomes the "current" buffer
     */

    if (ReadAsync(file,&ch,1) > 0)
        return((LONG)ch);

    /* We couldn't read above, so fail */

    return(-1);
}


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


LONG WriteAsync(struct AsyncFile *file, APTR buffer, LONG numBytes)
{
LONG totalBytes;

    totalBytes = 0;

    while (numBytes > file->af_BytesLeft)
    {
        /* this takes care of NIL: */
        if (!file->af_Handler)
        {
            file->af_Offset    = file->af_Buffers[0];
            file->af_BytesLeft = file->af_BufferSize;
            return(numBytes + totalBytes);
        }

        if (file->af_BytesLeft)
        {
            CopyMem(buffer,file->af_Offset,file->af_BytesLeft);

            numBytes   -= file->af_BytesLeft;
            buffer      = (APTR)((ULONG)buffer + file->af_BytesLeft);
            totalBytes += file->af_BytesLeft;
        }

        if (file->af_PacketPending)
        {
            WaitPacket(file);

            if (file->af_Packet.sp_Pkt.dp_Res1 <= 0)
            {
                /* an error occurred, leave */
                SetIoErr(file->af_Packet.sp_Pkt.dp_Res2);
                return(-1);
            }
        }

        /* send the current buffer out to disk */
        SendAsync(file,file->af_Buffers[file->af_CurrentBuf]);

        file->af_CurrentBuf   = 1 - file->af_CurrentBuf;
        file->af_Offset       = file->af_Buffers[file->af_CurrentBuf];
        file->af_BytesLeft    = file->af_BufferSize;
    }

    if (numBytes)
    {
        CopyMem(buffer,file->af_Offset,numBytes);
        file->af_BytesLeft -= numBytes;
        file->af_Offset     = (APTR)((ULONG)file->af_Offset + numBytes);
    }

    return (totalBytes + numBytes);
}


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


LONG WriteCharAsync(struct AsyncFile *file, char ch)
{
    if (file->af_BytesLeft)
    {
        /* if there's any room left in the current buffer, directly write
         * the byte into it, updating counters and stuff.
         */

        *(char *)file->af_Offset = ch;
        file->af_BytesLeft--;
        file->af_Offset = (APTR)((ULONG)file->af_Offset + 1);

        /* one byte written */
        return(1);
    }

    /* there was no room in the current buffer, so call the main write
     * routine. This will effectively send the current buffer out to disk,
     * wait for the other buffer to come back, and then put the byte into
     * it.
     */

    return(WriteAsync(file,&ch,1));
}


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


LONG SeekAsync(struct AsyncFile *file, LONG position, BYTE mode)
{
LONG  current, target;
LONG  minBuf, maxBuf;
LONG  bytesArrived;
LONG  diff;
LONG  filePos;
LONG  roundTarget;
D_S(struct FileInfoBlock,fib);

    if (file->af_PacketPending)
        WaitPacket(file);

    bytesArrived = file->af_Packet.sp_Pkt.dp_Res1;
    if (bytesArrived < 0)
    {
        /* error, get out of here */
        SetIoErr(file->af_Packet.sp_Pkt.dp_Res2);
        return(-1);
    }

    if (file->af_ReadMode)
    {
        /* figure out what the actual file position is */
        filePos = Seek(file->af_File,OFFSET_CURRENT,0);
        if (filePos < 0)
            return(-1);

        /* figure out what the caller's file position is */
        if (file->af_ReadMode)
            current = filePos - (file->af_BytesLeft+bytesArrived);
        else
            current = filePos + (file->af_BufferSize - file->af_BytesLeft);

        /* figure out the absolute offset within the file where we must seek to */
        if (mode == MODE_CURRENT)
        {
            target = current + position;
        }
        else if (mode == MODE_START)
        {
            target = position;
        }
        else /* if (mode == MODE_END) */
        {
            if (!ExamineFH(file->af_File,fib))
                return(-1);

            target = fib->fib_Size + position;
        }

        /* figure out what range of the file is currently in our buffers */
        minBuf = current - (LONG)((ULONG)file->af_Offset - (ULONG)file->af_Buffers[file->af_CurrentBuf]);
        maxBuf = current + file->af_BytesLeft + bytesArrived;  /* WARNING: this is one too big */

        diff = target - current;

        if ((target < minBuf) || (target >= maxBuf))
        {
            /* the target seek location isn't currently in our buffers, so
             * move the actual file pointer to the desired location, and then
             * restart the async read thing...
             */

            roundTarget = (target / file->af_BlockSize) * file->af_BlockSize;

            if (Seek(file->af_File,roundTarget-filePos,OFFSET_CURRENT) < 0)
                return(-1);

            SendAsync(file,file->af_Buffers[0]);

            file->af_SeekOffset = target-roundTarget;
            file->af_BytesLeft  = 0;
            file->af_CurrentBuf = 0;
        }
        else if ((target < current) || (diff <= file->af_BytesLeft))
        {
            /* one of the two following things is true:
             *
             * 1. The target seek location is within the current read buffer,
             * but before the current location within the buffer. Move back
             * within the buffer and pretend we never got the pending packet,
             * just to make life easier, and faster, in the read routine.
             *
             * 2. The target seek location is ahead within the current
             * read buffer. Advance to that location. As above, pretend to
             * have never received the pending packet.
             */

            FakePacket(file);

            file->af_BytesLeft -= diff;        /* diff is negative */
            file->af_Offset     = (APTR)((ULONG)file->af_Offset + diff);
        }
        else
        {
            /* at this point, we know the target seek location is within
             * the buffer filled in by the packet that we just received
             * at the start of this function. Throw away all the bytes in the
             * current buffer, send a packet out to get the async thing going
             * again, readjust buffer pointers to the seek location, and return
             * with a grin on your face... :-)
             */

            diff -= file->af_BytesLeft;

            SendAsync(file,file->af_Buffers[1-file->af_CurrentBuf]);

            file->af_Offset     = (APTR)((ULONG)file->af_Buffers[file->af_CurrentBuf] + diff);
            file->af_CurrentBuf = 1 - file->af_CurrentBuf;
            file->af_BytesLeft  = bytesArrived - diff;
        }
    }
    else
    {
        if (Write(file->af_File,file->af_Buffers[file->af_CurrentBuf],file->af_BufferSize - file->af_BytesLeft) < 0)
            return(-1);

        current = Seek(file->af_File,position,mode);

        file->af_BytesLeft  = file->af_BufferSize;
        file->af_CurrentBuf = 0;
    }

    return(current);
}


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

#if 0
#if 0

/* test which uses the above routines */

VOID main(VOID)
{
struct AsyncFile *in;
char              buf[3];
LONG              num;
struct AsyncFile *out;
ULONG             i;
char              ch;
BPTR file;

/*
    if (in = OpenAsync("s:Startup-Sequence",MODE_READ,8192))
    {
        if (out = OpenAsync("ram:test",MODE_WRITE,8192))
        {
            while ((num = ReadCharAsync(in)) >= 0)
            {
                WriteCharAsync(out,num);
            }

            CloseAsync(out);
        }

        CloseAsync(in);
    }
*/


    if (in = OpenAsync("ram:AAA",MODE_READ,32))
    {
        ReadAsync(in,&out,4);
        SeekAsync(in,2,MODE_START);
        printf("current pos = %ld\n",SeekAsync(in,0,MODE_CURRENT));
        printf("ch = %lc\n",ReadCharAsync(in));

        SeekAsync(in,32,MODE_CURRENT);
        printf("current pos = %ld\n",SeekAsync(in,0,MODE_CURRENT));
        printf("ch = %lc\n",ReadCharAsync(in));

        CloseAsync(in);
    }
}

#endif


/*
        /* enable this section of code if you want special processing for
         * reads bigger than the buffer size
         */
#ifdef OPTIMIZE_BIG_READS
        if (numBytes > file->af_BytesLeft + bytesArrived + file->af_BufferSize)
        {
            if (file->af_BytesLeft)
            {
                CopyMem(file->af_Offset,buf,file->af_BytesLeft);

                numBytes   -= file->af_BytesLeft;
                buf         = (APTR)((ULONG)buf + file->af_BytesLeft);
                totalBytes += file->af_BytesLeft;
                file->af_BytesLeft = 0;
            }

            if (bytesArrived)
            {
                CopyMem(file->af_Buffers[file->af_CurrentBuf],buf,bytesArrived);

                numBytes   -= bytesArrived;
                buf         = (APTR)((ULONG)buf + bytesArrived);
                totalBytes += bytesArrived;
            }

            bytesArrived = Read(file->af_File,buf,numBytes);

            if (bytesArrived <= 0)
                return(-1);

            SendAsync(file,file->af_Buffers[0]);
            file->af_CurrentBuf = 0;
            file->af_BytesLeft  = 0;

            return(totalBytes + bytesArrived);
        }
#endif
*/
#endif
