#include <exec/memory.h>
#include <dos/dosextens.h>
#include <clib/macros.h>
#include "classbase.h"

struct FFRHandle {
  APTR                   fh_SysBase;
  struct StandardPacket  fh_Pkt;
  struct MsgPort         fh_ReplyPort;
  BYTE                   fh_Pending,fh_Mode;
  struct FileHandle     *fh_Handle;
  UBYTE                 *fh_Buffer,
                        *fh_Pre,
                        *fh_Ptr;
  ULONG                  fh_Left,
                         fh_BufferSize;
  UBYTE                 *fh_Buf1,*fh_Buf2;
};

#define FIRST_BLOCK   512L
#define BUFFER_SIZE 32768L

#define mySetIoErr(fh,e) ((struct Process *)fh->fh_ReplyPort.mp_SigTask)->pr_Result2=(e)

#define NEWLIST(l) ((l)->lh_Head = (struct Node *)&(l)->lh_Tail, \
                    /*(l)->lh_Tail = NULL,*/ \
                    (l)->lh_TailPred = (struct Node *)&(l)->lh_Head)

ASM(STATIC VOID) BeginAction(REG(a1,struct FFRHandle *fh),REG(d0,LONG bytes))
{ LREG(a6,APTR SysBase) = fh->fh_SysBase;

  fh->fh_Pending = -1;

  fh->fh_Pkt.sp_Pkt.dp_Port = &fh->fh_ReplyPort;
  fh->fh_Pkt.sp_Pkt.dp_Arg2 = (LONG)fh->fh_Pre;
  fh->fh_Pkt.sp_Pkt.dp_Arg3 = bytes;

  PutMsg(fh->fh_Handle->fh_Type,&fh->fh_Pkt.sp_Msg);
}

STATIC LONG EndAction(struct FFRHandle *fh,LONG cont)
{ LREG(a6,APTR SysBase) = fh->fh_SysBase;
  LONG size,res1 = fh->fh_BufferSize;

  if (!fh->fh_ReplyPort.mp_Node.ln_Pri) {
    if (fh->fh_Pending) {
      fh->fh_Pending = 0;
      fh->fh_ReplyPort.mp_Flags = PA_SIGNAL;
      Remove((struct Node *)WaitPort(&fh->fh_ReplyPort));
      fh->fh_ReplyPort.mp_Flags = PA_IGNORE;
      if ((res1=fh->fh_Pkt.sp_Pkt.dp_Res1) < 0) {
        fh->fh_ReplyPort.mp_Node.ln_Pri = -1; mySetIoErr(fh,fh->fh_Pkt.sp_Pkt.dp_Res2); return FALSE;
      }
    }
  } else return FALSE;

  fh->fh_Ptr    = fh->fh_Pre;
  fh->fh_Pre    = fh->fh_Buffer;
  fh->fh_Buffer = fh->fh_Ptr;

  if (cont) {
    if (fh->fh_Mode != FFR_MODE_READ || res1 == fh->fh_Pkt.sp_Pkt.dp_Arg3)
      if ((size=fh->fh_BufferSize-fh->fh_Left))
        BeginAction(fh,size);
    fh->fh_Left = res1;
  }

  return TRUE;
}

LONG FFRGetC(struct FFRHandle *fh)
{
  if (fh->fh_Left || (fh->fh_Pending && (mySetIoErr(fh,0),EndAction(fh,TRUE)) && fh->fh_Left)) {
    fh->fh_Left--; return *fh->fh_Ptr++;
  }

  return -1L;
}

LONG FFRRead(struct FFRHandle *fh,UBYTE *buffer,LONG bytes)
{ LREG(a6,APTR SysBase) = fh->fh_SysBase;
  LONG left,size;

  if (bytes <= fh->fh_Left) {
    CopyMem(fh->fh_Ptr,buffer,bytes);
    fh->fh_Ptr += bytes; fh->fh_Left -= bytes; return bytes;
  }

  if ((left=bytes) > 0)
    do {
      if ((size=MIN(fh->fh_Left,left))) {
        CopyMem(fh->fh_Ptr,buffer,size);
        fh->fh_Ptr += size; buffer += size; fh->fh_Left -= size;
      }
      if ((left-=size))
        if (!fh->fh_Pending)
          break;
      else if (!EndAction(fh,TRUE))
             return -1L;
    } while (left > 0);

  return bytes-left;
}
#if 0
LONG FFRWrite(struct FFRHandle *fh,UBYTE *buffer,LONG bytes)
{ LREG(a6,APTR SysBase) = fh->fh_SysBase;
  LONG left,size;

  if (bytes <= fh->fh_Left) {
    CopyMem(buffer,fh->fh_Ptr,bytes);
    fh->fh_Ptr += bytes; fh->fh_Left -= bytes; return bytes;
  }

  if ((left=bytes) > 0)
    do {
      if ((size=MIN(fh->fh_Left,left))) {
        CopyMem(buffer,fh->fh_Ptr,size);
        fh->fh_Ptr += size; buffer += size; fh->fh_Left-=size; left -= size;
      }
      if (!fh->fh_Left && !EndAction(fh,TRUE))
        return -1L;
    } while (left > 0);

  return bytes;
}
#endif
LONG CloseFFR(struct FFRHandle *fh)
{ LREG(a6,APTR SysBase) = fh->fh_SysBase;
  LONG result;

  if (fh->fh_Mode != FFR_MODE_READ)
    EndAction(fh,TRUE);
  result = EndAction(fh,FALSE);
  FreeVec(fh->fh_Buf2);
  FreeVec(fh->fh_Buf1);

  return result;
}

struct FFRHandle *OpenFFR(struct ClassBase *cb,APTR pool,LONG mode,BPTR handle)
{ LONG bufferSize,firstBlock,newSize;
  LREG(a6,APTR SysBase),DOSBase;
  struct InfoData *infoData;
  struct FFRHandle *fh;

  if (handle && (DOSBase=cb->DOSBase,SysBase=cb->SysBase,(fh=AllocPooled(pool,sizeof(*fh))))) {

    fh->fh_SysBase = SysBase;

    fh->fh_Pkt.sp_Msg.mn_Node.ln_Name = (char *)&fh->fh_Pkt.sp_Pkt;

    fh->fh_Pkt.sp_Pkt.dp_Link = &fh->fh_Pkt.sp_Msg;
    fh->fh_Pkt.sp_Pkt.dp_Type = (mode != FFR_MODE_READ ? ACTION_WRITE : ACTION_READ);
    fh->fh_Pkt.sp_Pkt.dp_Arg1 = (fh->fh_Handle=BADDR(handle))->fh_Arg1;

    fh->fh_ReplyPort.mp_Node.ln_Type = NT_MSGPORT;

    fh->fh_ReplyPort.mp_Flags   = PA_IGNORE;
    fh->fh_ReplyPort.mp_SigBit  = SIGB_SINGLE;
    fh->fh_ReplyPort.mp_SigTask = FindTask(NULL);
    NEWLIST(&fh->fh_ReplyPort.mp_MsgList);

    fh->fh_Mode = mode;

    bufferSize = BUFFER_SIZE; firstBlock = FIRST_BLOCK;

    if ((infoData=AllocVec(sizeof(*infoData),MEMF_PUBLIC)) != NULL) {
      if (DoPkt1(fh->fh_Handle->fh_Type,ACTION_DISK_INFO,MKBADDR(infoData)) != DOSFALSE) {
        if ((newSize=infoData->id_BytesPerBlock) > 0) {
          firstBlock = newSize;
          for (; newSize<bufferSize; newSize<<=1);
          bufferSize = newSize;
        }
      }
      SysBase = fh->fh_SysBase; FreeVec(infoData);
    }

    fh->fh_BufferSize = bufferSize;

    if ((fh->fh_Buf1=AllocVec(bufferSize+15,MEMF_PUBLIC)) != NULL) {
      fh->fh_Ptr = fh->fh_Buffer = (UBYTE *)(((ULONG)(fh->fh_Buf1+15))&~15);
      if ((fh->fh_Buf2=AllocVec(bufferSize+15,MEMF_PUBLIC)) != NULL) {
        fh->fh_Pre = (UBYTE *)(((ULONG)(fh->fh_Buf2+15))&~15);
        if (fh->fh_Mode == FFR_MODE_READ) {
          bufferSize = 0; BeginAction(fh,firstBlock-Seek(MKBADDR(fh->fh_Handle),0,OFFSET_CURRENT)%firstBlock);
        }
        fh->fh_Left = bufferSize; return fh;
      }
      FreeVec(fh->fh_Buf1);
    }
    /*FreePooled(pool,fh,sizeof(*fh));*/
  }
  return NULL;
}
