#include <clib/dos_protos.h>
#include <clib/exec_protos.h>
#include <clib/powerpc_protos.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 copymem(UBYTE *s,UBYTE *d,ULONG len)
{
ULONG l=len&3;
len-=l;
while(l--) *(d++)=*(s++);
while(len)
	{
	*(((long *)d)++)=*(((long *)s)++);
	len-=4;
	}
}

/*
IMPORTANT:
You might wonder why the hell there are these SPrintF calls ?
I don't know why but the lib simply doesn't work without these
dummy calls.
Please DO NOT remove them. I have tested it a thousand times. It simply
locks up when removing the SPrintF calls.
The problem only occurs in write-mode !
If you have any idea, why this happens, give me a hint.
Email to: achimste@gmx.de
*/

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;
SPrintF(0,0);
PutMsg(file->af_Handler,&file->af_Packet.sp_Msg);
SPrintF(0,0);
file->af_PacketPending=TRUE;
}

static long AS_WaitPacket(struct AsyncFile *file)
{
long bytes;
if(file->af_PacketPending)
	{
	while(1)
		{
		file->af_PacketPort.mp_Flags=PA_SIGNAL;
		RemovePPC((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)
{
AddHeadPPC(&file->af_PacketPort.mp_MsgList,&file->af_Packet.sp_Msg.mn_Node);
file->af_PacketPending=TRUE;
}

struct AsyncFile *AS_OpenAsyncFH(BPTR handle,LONG mode,LONG bufferSize,ULONG 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=AllocVecPPC(sizeof(AsyncFile)+bufferSize+15,MEMF_PUBLIC,0)) 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 * __saveds OpenAsyncPPC(struct AsyncIOBase *base,STRPTR fileName,LONG mode,LONG bufferSize)
{
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 * __saveds OpenAsyncFromFHPPC(struct AsyncIOBase *base,BPTR handle,LONG mode,LONG bufferSize)
{
return AS_OpenAsyncFH(handle,mode,bufferSize,FALSE);
}

LONG __saveds CloseAsyncPPC(struct AsyncIOBase *base,struct AsyncFile *file)
{
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);
	FreeVecPPC(file);
	}
else result=-1;
return result;
}

LONG __saveds ReadAsyncPPC(struct AsyncIOBase *base,struct AsyncFile *file,APTR buffer,LONG numBytes)
{
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 __saveds PeekAsyncPPC(struct AsyncIOBase *base,struct AsyncFile *file,APTR buffer,LONG numBytes)
{
if(!file->af_BytesLeft)
	{
	LONG bytes;
	if((bytes=ReadAsyncPPC(base,file,&bytes,1))<=0) return bytes;
	--file->af_Offset;
	++file->af_BytesLeft;
	}
numBytes=MIN(numBytes,file->af_BytesLeft);
copymem(file->af_Offset,buffer,numBytes);
return numBytes;
}

LONG __saveds ReadCharAsyncPPC(struct AsyncIOBase *base,AsyncFile *file)
{
UBYTE	ch;
if(file->af_BytesLeft)
	{
	ch=*file->af_Offset;
	--file->af_BytesLeft;
	++file->af_Offset;
	return ch;
	}
return (ReadAsyncPPC(base,file,&ch,1)>0)?ch:-1;
}

STRPTR __saveds FGetsLenAsyncPPC(struct AsyncIOBase *base,struct AsyncFile *file,STRPTR buf,LONG numBytes,LONG *len)
{
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(ReadAsyncPPC(base,file,p,1)<1) break;
	--numBytes;
	++length;
	if(*p++=='\n') break;
	}
*p='\0';
*len=length;
return (p==(UBYTE *)buf)?0:buf;
}

STRPTR __saveds FGetsAsyncPPC(struct AsyncIOBase *base,struct AsyncFile *file,STRPTR buf,LONG numBytes)
{
LONG len;
return FGetsLenAsyncPPC(base,file,buf,numBytes,&len);
}

LONG __saveds ReadLineAsyncPPC(struct AsyncIOBase *base,AsyncFile *file,APTR buffer,LONG bufSize)
{
LONG	len;
if(FGetsLenAsyncPPC(base,file,buffer,bufSize,&len))
	{
	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(ReadAsyncPPC(base,file,&ch,1)<1) break;
			if(ch=='\n') break;
			}
		if(ch=='\n')
			{
			*end++='\n';
			*end='\0';
			}
		}
	}
return len;
}

static __saveds 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 __saveds SeekAsyncPPC(struct AsyncIOBase *base,struct AsyncFile *file,LONG position,LONG mode)
{
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 __saveds WriteAsyncPPC(struct AsyncIOBase *base,struct AsyncFile *file,APTR buffer,LONG numBytes)
{
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 __saveds WriteCharAsyncPPC(struct AsyncIOBase *base,struct AsyncFile *file,ULONG ch)
{
UBYTE	c;
if(file->af_BytesLeft)
	{
	*file->af_Offset=ch;
	--file->af_BytesLeft;
	++file->af_Offset;
	return 1;
	}
c=ch;
return WriteAsyncPPC(base,file,&c,1);
}

LONG __saveds WriteLineAsyncPPC(struct AsyncIOBase *base,struct AsyncFile *file,STRPTR line)
{
return WriteAsyncPPC(base,file,line,strlen(line));
}
