#include "async.h"

#ifdef ASIO_NOEXTERNALS
AsyncFile*
AS_OpenAsyncFH(
	BPTR handle,
	OpenModes mode,
	LONG bufferSize,
	BOOL closeIt,
	struct ExecBase *SysBase,
	struct DosLibrary *DOSBase )
#else
AsyncFile *
AS_OpenAsyncFH( BPTR handle, OpenModes mode, LONG bufferSize, BOOL closeIt )
#endif
{
	struct FileHandle	*fh;
	AsyncFile		*file = NULL;
	BPTR	lock = NULL;
	LONG	blockSize, blockSize2;
	D_S( struct InfoData, infoData );

	if( mode == MODE_READ )
	{
		if( handle )
		{
			lock = DupLockFromFH( handle );
		}
	}
	else
	{
		if( mode == 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 )
			{
				if( Seek( handle, 0, OFFSET_END ) < 0 )
				{
					if( closeIt )
					{
						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;
		blockSize2 = 1024;

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

			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
		 */

		for( ;; )
		{
			if( file = AllocVec( sizeof( AsyncFile ) + bufferSize + 15, MEMF_PUBLIC | MEMF_ANY ) )
			{
				break;
			}
			else
			{
				if( bufferSize > blockSize2 )
				{
					bufferSize -= blockSize2;
				}
				else
				{
					break;
				}
			}
		}

		if( file )
		{
			file->af_File		= handle;
			file->af_ReadMode	= ( mode == MODE_READ );
			file->af_BlockSize	= blockSize;
			file->af_CloseFH	= closeIt;

			/* 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( AsyncFile ) + 15 ) & 0xfffffff0 );
			file->af_Buffers[ 1 ]	= file->af_Buffers[ 0 ] + file->af_BufferSize;
			file->af_CurrentBuf	= 0;
			file->af_SeekOffset	= 0;
			file->af_PacketPending	= FALSE;
#ifdef ASIO_NOEXTERNALS
			file->af_SysBase	= SysBase;
			file->af_DOSBase	= DOSBase;
#endif

			/* 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_Tail		= NULL;
			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;
			/* Avoid problems with SnoopDos */
			file->af_PacketPort.mp_Node.ln_Name		= NULL;
			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_Pkt.dp_Res1			= 0;
			file->af_Packet.sp_Pkt.dp_Res2			= 0;
			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( mode == 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;
				file->af_BytesLeft		= 0;

				/* MH: We set the offset to the buffer not being filled, in
				 * order to avoid special case code in SeekAsync. ReadAsync
				 * isn't affected by this, since af_BytesLeft == 0.
				 */
				file->af_Offset = file->af_Buffers[ 1 ];

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

	return( file );
}
