#include "async.h"

_ASM LibCall _ARGS LONG
SeekAsync( _REG( a0 ) AsyncFile *file, _REG( d0 ) LONG position, _REG( d1 ) SeekModes mode )
{
#ifdef ASIO_NOEXTERNALS
	struct DosLibrary	*DOSBase = file->af_DOSBase;
#endif
	LONG	current, target;
	LONG	minBuf, maxBuf;
	LONG	bytesArrived;
	LONG	diff;
	LONG	filePos;
	LONG	roundTarget;
	D_S( struct FileInfoBlock, fib );

	bytesArrived = AS_WaitPacket( file );

	if( bytesArrived < 0 )
	{
		return( -1 );
	}

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

		if( filePos < 0 )
		{
			AS_RecordSyncFailure( file );
			return( -1 );
		}

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

		/* We can't clear af_SeekOffset here. If another seek is done directly
		 * after this one, it would mean that we will both return and start
		 * reading from the wrong position.
		 */
		/* file->af_SeekOffset = 0; */

		/* 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 ) )
			{
				AS_RecordSyncFailure( file );
				return( -1 );
			}

			target = fib->fib_Size + position;
		}

		/* MH: Here we must be able to handle two different situations:
		 * 1) A seek directly after having dropped both buffers, and started
		 *    refilling (typical case: File open).
		 * 2) Other seeks (typical case: A seek after some initial reading).
		 *
		 * We need to subtract with "af_Buffers[ 1 - file->af_CurrentBuf ]",
		 * as af_CurrentBuf refers to the *arrived* buffer, not the one we're
		 * currently reading from (and af_Offset points into the buffer we're
		 * reading from)!
		 *
		 * In case 1, there will be only one packet received. af_CurrentBuf
		 * will be zero, and refers to the newly arrived buffer (as it
		 * should). For proper behaviour in the minBuf calculation, we have
		 * set af_Offset to point to af_Buffers[ 1 ], when starting reading
		 * to empty buffers. That way wee need no special case code here.
		 * ReadAsync() can handle this, as af_BytesLeft == 0 in that case.
		 */

		/* figure out what range of the file is currently in our buffers */
		minBuf = current - ( LONG ) ( file->af_Offset - file->af_Buffers[ 1 - 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...
			 */

			/* this is to keep our file reading block-aligned on the device.
			 * block-aligned reads are generally quite a bit faster, so it is
			 * worth the trouble to keep things aligned
			 */
			roundTarget = ( target / file->af_BlockSize ) * file->af_BlockSize;

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

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

			file->af_SeekOffset	= target - roundTarget;
			file->af_BytesLeft	= 0;
			file->af_CurrentBuf	= 0;
			/* MH: We set af_Offset to the buffer not being filled, to be able to
			 * handle a new seek directly after this one (see above; minBuf
			 * calculation). If we start reading after this seek, ReadAsync()
			 * will handle everything correctly, as af_BytesLeft == 0.
			 */
			file->af_Offset		= file->af_Buffers[ 1 ];
		}
		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.
			 */

			AS_RequeuePacket( file );

			file->af_BytesLeft	-= diff;
			file->af_Offset		+= diff;

			/* MH: We don't need to clear the seek offset here, since
			 * if we get here, we must have read some data from the current
			 * buffer, and af_SeekOffset will be zero then (done by
			 * ReadAsync()).
			 */
		}
		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... :-)
			 */

			/* MH: Don't read to the buffer we just got, but the other one! */
			AS_SendPacket( file, file->af_Buffers[ 1 - file->af_CurrentBuf ] );

			/* MH: Account for bytes left in buffer we drop *and* the af_SeekOffset.
			 */
			diff -= file->af_BytesLeft - file->af_SeekOffset;

			/* MH: Set the offset into the current (newly arrived) buffer */
			file->af_Offset = file->af_Buffers[ file->af_CurrentBuf ] + diff;
			file->af_BytesLeft = bytesArrived - diff;

			/* MH: We need to clear the seek offset here, since we can't do it above.
			 */
			file->af_SeekOffset = 0;

			/* MH: This "buffer switching" is important to do. It wasn't done!
			 * This explains the errors one could encounter now and then.
			 * The AS_SendPacket() call above is not the cause, and *is* correct.
			 */
			file->af_CurrentBuf = 1 - file->af_CurrentBuf;
		}
	}
	else
	{
		/* flush the buffers */
		if( file->af_BufferSize > file->af_BytesLeft )
		{
			if( Write(
				file->af_File,
				file->af_Buffers[ file->af_CurrentBuf ],
				file->af_BufferSize - file->af_BytesLeft ) < 0 )
			{
				AS_RecordSyncFailure( file );
				return( -1 );
			}
		}

		/* this will unfortunately generally result in non block-aligned file
		 * access. We could be sneaky and try to resync our file pos at a
		 * later time, but we won't bother. Seeking in write-only files is
		 * relatively rare (except when writing IFF files with unknown chunk
		 * sizes, where the chunk size has to be written after the chunk data)
		 */

		/* MH: Ideas on how to improve the above (not tested, since I don't need
		 * the SeekAsync for writing in any of my programs at the moment! ;):
		 *
		 * Add a new field to the AsyncFile struct. af_WriteOffset or something like
		 * that (af_SeekOffset can probably be used). Like in the read case, we
		 * calculate a roundTarget, but we don't seek to that (but rather to the
		 * "absolute" position), and save the difference in the struct. af_BytesLeft
		 * and af_Offset are adjusted to point into the "middle" of the buffer,
		 * where the write will occur. Writes then needs some minor changes:
		 * Instead of simply writing the buffer from the start, we add the offset
		 * (saved above) to the buffer base, and write the partial buffer. The
		 * offset is then cleared. Voila: The file still block-aligned, at the price
		 * of some non-optimal buffer usage.
		 *
		 * Problem: As it is now, Arg3 in the packet is always set to the buffer size.
		 * With the above fix, this would have to be updated for each SendPacket (i.e.
		 * a new argument would be needed).
		 */

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

		if( current < 0 )
		{
			AS_RecordSyncFailure( file );
			return( -1 );
		}

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

	return( current );
}
