#include <pragma/dos_lib.h>
#include <pragma/exec_lib.h>
#include <libraries/asyncio.h>
#include <exec/memory.h>
#include <string.h>

#define D_S(type,name) char a_##name[ sizeof( type )+3]; type *name=(type *)((LONG)(a_##name+3)&~3);

#ifndef MIN
#define MIN(a,b) ((a)<(b)?(a):(b))
#endif

static void AS_SendPacket(struct AsyncFile *file,APTR arg2)
{
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 LONG AS_WaitPacket(AsyncFile *file)
{
LONG bytes;
if(file->af_PacketPending)
	{
	while(TRUE)
		{
		file->af_PacketPort.mp_Flags=PA_SIGNAL;
		Remove((struct Node *)WaitPort(&file->af_PacketPort));
		file->af_PacketPort.mp_Flags=PA_IGNORE;
		file->af_PacketPending=FALSE;
		bytes=file->af_Packet.sp_Pkt.dp_Res1;
		if(bytes>=0) return bytes;
		if(ErrorReport(file->af_Packet.sp_Pkt.dp_Res2,REPORT_STREAM,file->af_File,0)) return -1;
		AS_SendPacket(file,file->af_Buffers[file->af_ReadMode?file->af_CurrentBuf:1-file->af_CurrentBuf]);
		}
	}
SetIoErr(file->af_Packet.sp_Pkt.dp_Res2);
return file->af_Packet.sp_Pkt.dp_Res1;
}

static void AS_RecordSyncFailure(struct AsyncFile *file)
{
file->af_LastRes1=file->af_Packet.sp_Pkt.dp_Res1;
file->af_LastBytesLeft=file->af_BytesLeft;
file->af_Packet.sp_Pkt.dp_Res1=-1;
file->af_Packet.sp_Pkt.dp_Res2=IoErr();
file->af_BytesLeft=0;
}

static void AS_RequeuePacket(AsyncFile *file)
{
AddHead(&file->af_PacketPort.mp_MsgList,&file->af_Packet.sp_Msg.mn_Node);
file->af_PacketPending=TRUE;
}

AsyncFile *AS_OpenAsyncFH(BPTR handle,LONG mode,LONG bufferSize,BOOL closeIt)
{
FileHandle *fh;
AsyncFile *file=0;
BPTR	lock=0;
LONG	blockSize,blockSize2;
D_S(struct InfoData,infoData);
if(mode==MODE_READ)
	{
	if(handle) lock=DupLockFromFH(handle);
	}
else
	{
	if(mode==MODE_APPEND)
		{
		if(handle)
			{
			if(Seek(handle,0,OFFSET_END)<0)
				{
				if(closeIt) Close(handle);
				handle=0;
				}
			}
		}
	if(handle) lock=ParentOfFH(handle);
	}
if(handle)
	{
	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);
		}
	for(;;)
		{
		if(file=(AsyncFile *)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;
		fh=(FileHandle *)BADDR(file->af_File);
		file->af_Handler=fh->fh_Type;
		file->af_BufferSize=(ULONG)bufferSize/2;
		file->af_Buffers[0]=(UBYTE *)(((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;
		file->af_SeekPastEOF=FALSE;
		file->af_PacketPort.mp_MsgList.lh_Head=(struct Node *)&file->af_PacketPort.mp_MsgList.lh_Tail;
		file->af_PacketPort.mp_MsgList.lh_Tail=0;
		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_Node.ln_Name=0;
		file->af_PacketPort.mp_Flags=PA_IGNORE;
		file->af_PacketPort.mp_SigBit=SIGB_SINGLE;
		file->af_PacketPort.mp_SigTask=FindTask(0);
		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)
			{
			file->af_Packet.sp_Pkt.dp_Type=ACTION_READ;
			file->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;
}

struct AsyncFile *OpenAsync(register __a0 STRPTR fileName,register __d0 LONG mode,register __d1 LONG bufferSize,register __a6 struct AsyncIOBase *base)
{
static const ULONG PrivateOpenModes[]={MODE_OLDFILE,MODE_NEWFILE,MODE_READWRITE};
BPTR handle;
AsyncFile *file=0;
if(handle=Open(fileName,PrivateOpenModes[mode]))
	{
	if(!(file=AS_OpenAsyncFH(handle,mode,bufferSize,TRUE)))Close(handle);
	}
return file;
}

struct AsyncFile *OpenAsyncFromFH(register __a0 BPTR handle,register __d0 LONG mode,register __d1 LONG bufferSize,register __a6 struct AsyncIOBase *base)
{
return AS_OpenAsyncFH(handle,mode,bufferSize,FALSE);
}

LONG CloseAsync(register __a0 struct AsyncFile *file,register __a6 struct AsyncIOBase *base)
{
LONG	result;
if(file)
	{
	result=AS_WaitPacket(file);
	if(result>=0)
		{
		if(!file->af_ReadMode)
			{
			if(file->af_BufferSize>file->af_BytesLeft)
				{
				result=Write(file->af_File,file->af_Buffers[file->af_CurrentBuf],file->af_BufferSize-file->af_BytesLeft);
				}
			}
		}
	if(file->af_CloseFH) Close(file->af_File);
	FreeVec(file);
	}
else result=-1;
return result;
}

LONG ReadAsync(register __a0 struct AsyncFile *file,register __a1 APTR buffer,register __d0 LONG numBytes,register __a6 struct AsyncIOBase *base)
{
LONG totalBytes=0;
LONG bytesArrived;
while(numBytes>file->af_BytesLeft)
	{
	CopyMem(file->af_Offset,buffer,file->af_BytesLeft);
	numBytes-=file->af_BytesLeft;
	buffer=(APTR)((ULONG)buffer+file->af_BytesLeft);
	totalBytes+=file->af_BytesLeft;
	file->af_BytesLeft=0;
	bytesArrived=AS_WaitPacket(file);
	if(bytesArrived<=0)
		{
		if(bytesArrived==0) return(totalBytes);
		return -1;
		}
	AS_SendPacket(file,file->af_Buffers[1-file->af_CurrentBuf]);
	if(file->af_SeekOffset>bytesArrived) file->af_SeekOffset=bytesArrived;
	file->af_Offset=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;
	}
CopyMem(file->af_Offset,buffer,numBytes);
file->af_BytesLeft-=numBytes;
file->af_Offset+=numBytes;
return totalBytes+numBytes;
}

LONG PeekAsync(register __a0 struct AsyncFile *file,register __a1 APTR buffer,register __d0 LONG numBytes,register __a6 struct AsyncIOBase *base)
{
if(!file->af_BytesLeft)
	{
	LONG bytes;
	if((bytes=ReadAsync(file,&bytes,1,base))<=0) return bytes;
	--file->af_Offset;
	++file->af_BytesLeft;
	}
numBytes=MIN(numBytes,file->af_BytesLeft);
CopyMem(file->af_Offset,buffer,numBytes);
return numBytes;
}

LONG ReadCharAsync(register __a0 AsyncFile *file,register __a6 struct AsyncIOBase *base)
{
UBYTE	ch;
if(file->af_BytesLeft)
	{
	ch=*file->af_Offset;
	--file->af_BytesLeft;
	++file->af_Offset;
	return ch;
	}
return (ReadAsync(file,&ch,1,base)>0)?ch:-1;
}

STRPTR FGetsLenAsync(register __a0 struct AsyncFile *file,register __a1 STRPTR buf,register __d0 LONG numBytes,register __a2 LONG *len,register __a6 struct AsyncIOBase *base)
{
UBYTE *p;
LONG	length=0;
p=(UBYTE *)buf;
if(--numBytes<=0) return 0;
while(TRUE)
	{
	UBYTE *ptr;
	LONG i,count;
	ptr=(UBYTE *)file->af_Offset;
	if(count=file->af_BytesLeft)
		{
		count=MIN(count,numBytes);
		for(i=0;(i<count)&&(*ptr!='\n');++i) *p++=*ptr++;
		length+=i;
		if(i<count)
			{
			*p++='\n';
			++i;
			length+=1;
			}
		file->af_BytesLeft-=i;
		file->af_Offset+=i;
		if((i>=numBytes)||(*(p-1)=='\n')) break;
		numBytes-=i;
		}
	if(ReadAsync(file,p,1,base)<1) break;
	--numBytes;
	++length;
	if(*p++=='\n') break;
	}
*p='\0';
*len=length;
return (p==(UBYTE *)buf)?0:buf;
}

STRPTR FGetsAsync(register __a0 struct AsyncFile *file,register __a1 STRPTR buf,register __d0 LONG numBytes,register __a6 struct AsyncIOBase *base)
{
LONG len;
return FGetsLenAsync(file,buf,numBytes,&len,base);
}

LONG ReadLineAsync(register __a0 AsyncFile *file,register __a1 APTR buffer,register __d0 LONG bufSize,register __a6 struct AsyncIOBase *base)
{
LONG	len;
if(FGetsLenAsync(file,buffer,bufSize,&len,base))
	{
	UBYTE *end;
	end=((UBYTE *)buffer)+len-1;
	if(*end!='\n')
		{
		UBYTE	ch=0;
		while(TRUE)
			{
			UBYTE *ptr;
			LONG i,count;
			ptr=(UBYTE *)file->af_Offset;
			if(count=file->af_BytesLeft)
				{
				for(i=0;(i<count)&& (*ptr!='\n');++i,++ptr)
				if(i<count)
					{
					ch='\n';
					++i;
					}
				file->af_BytesLeft-=i;
				file->af_Offset+=i;
				if(i<count) break;
				}
			if(ReadAsync(file,&ch,1,base)<1) break;
			if(ch=='\n') break;
			}
		if(ch=='\n')
			{
			*end++='\n';
			*end='\0';
			}
		}
	}
return len;
}

static ULONG GetFileSize(AsyncFile *file,LONG *size)
{
D_S(struct FileInfoBlock,fib);
if(!ExamineFH(file->af_File,fib))
	{
	AS_RecordSyncFailure(file);
	return FALSE;
	}
*size=fib->fib_Size;
return TRUE;
}

LONG SeekAsync(register __a0 struct AsyncFile *file,register __d0 LONG position,register __d1 LONG mode,register __a6 struct AsyncIOBase *base)
{
LONG current,target,roundTarget,filePos;
LONG minBuf,maxBuf,bytesArrived,diff;
LONG fileSize;
bytesArrived=AS_WaitPacket(file);
if(bytesArrived<0)
	{
	if(file->af_SeekPastEOF)
		{
		bytesArrived=file->af_LastRes1;
		file->af_BytesLeft=file->af_LastBytesLeft;
		}
	else return -1;
	}
if(file->af_ReadMode)
	{
	filePos=Seek(file->af_File,0,OFFSET_CURRENT);
	if(filePos<0)
		{
		AS_RecordSyncFailure(file);
		return -1;
		}
	current=filePos-(file->af_BytesLeft+bytesArrived)+file->af_SeekOffset;
	if(mode==MODE_CURRENT) target=current+position;
	else if(mode==MODE_START) target=position;
	else 
		{
		if(!GetFileSize(file,&fileSize)) return -1;
		target=fileSize+position;
		}
	minBuf=current-(LONG)(file->af_Offset-file->af_Buffers[1-file->af_CurrentBuf]);
	maxBuf=current+file->af_BytesLeft+bytesArrived;
	diff=target-current;
	if((target<minBuf)||(target>=maxBuf))
		{
		if(target>=maxBuf)
			{
			if(!GetFileSize(file,&fileSize)) return -1;
			if(target>fileSize)
				{
				file->af_SeekPastEOF=TRUE;
				SetIoErr(ERROR_SEEK_ERROR);
				AS_RecordSyncFailure(file);
				return -1;
				}
			}
		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;
		file->af_Offset=file->af_Buffers[1];
		}
	else if((target<current)||(diff<=file->af_BytesLeft))
		{
		AS_RequeuePacket(file);
		file->af_BytesLeft-=diff;
		file->af_Offset+=diff;
		if(file->af_SeekPastEOF) file->af_Packet.sp_Pkt.dp_Res1=file->af_LastRes1;
		}
	else
		{
		AS_SendPacket(file,file->af_Buffers[1-file->af_CurrentBuf]);
		diff-=file->af_BytesLeft-file->af_SeekOffset;
		file->af_Offset=file->af_Buffers[file->af_CurrentBuf]+diff;
		file->af_BytesLeft=bytesArrived-diff;
		file->af_SeekOffset=0;
		file->af_CurrentBuf=1-file->af_CurrentBuf;
		}
	}
else
	{
	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;
			}
		}
	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];
	}
if(file->af_SeekPastEOF) file->af_SeekPastEOF=FALSE;
SetIoErr(0);
return current;
}

LONG WriteAsync(register __a0 struct AsyncFile *file,register __a1 APTR buffer,register __d0 LONG numBytes,register __a6 struct AsyncIOBase *base)
{
LONG totalBytes=0;
if(!file->af_Handler)
	{
	file->af_Offset=file->af_Buffers[0];
	file->af_BytesLeft=file->af_BufferSize;
	return numBytes;
	}
while(numBytes>file->af_BytesLeft)
	{
	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(AS_WaitPacket(file)<0) return -1;
	AS_SendPacket(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;
	}
CopyMem(buffer,file->af_Offset,numBytes);
file->af_BytesLeft-=numBytes;
file->af_Offset+=numBytes;
return totalBytes+numBytes;
}

LONG WriteCharAsync(register __a0 struct AsyncFile *file,register __d0 ULONG ch,register __a6 struct AsyncIOBase *base)
{
UBYTE	c;
if(file->af_BytesLeft)
	{
	*file->af_Offset=ch;
	--file->af_BytesLeft;
	++file->af_Offset;
	return 1;
	}
c=ch;
return WriteAsync(file,&c,1,base);
}

LONG WriteLineAsync(register __a0 struct AsyncFile *file,register __a1 STRPTR line,register __a6 struct AsyncIOBase *base)
{
return WriteAsync(file,line,strlen(line),base);
}
