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

struct FFRHandle {
  struct StandardPacket  fh_Pkt;
  struct FileHandle     *fh_Handle;
  struct MsgPort         fh_ReplyPort;
  BYTE                   fh_Pending,pad;
  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)

STATIC VOID BeginRead(struct FFRHandle *fh,LONG bytes)
{
  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 EndRead(struct FFRHandle *fh)
{
  LONG res1;

  (VOID) WaitPort(&fh->fh_ReplyPort);
  (VOID) GetMsg(&fh->fh_ReplyPort);

  fh->fh_Pending = FALSE;

  if ((res1=fh->fh_Pkt.sp_Pkt.dp_Res1) < 0) {
    mySetIoErr(fh,fh->fh_Pkt.sp_Pkt.dp_Res2); return FALSE;
  }

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

  if (res1 == fh->fh_Pkt.sp_Pkt.dp_Arg3)
    BeginRead(fh,fh->fh_BufferSize);

  return TRUE;
}

LONG FFRGetC(struct FFRHandle *fh)
{
  mySetIoErr(fh,0);

  if (fh->fh_Left || (fh->fh_Pending && EndRead(fh) && fh->fh_Left)) {
    fh->fh_Left--; return *fh->fh_Ptr++;
  }

  return -1L;
}

LONG FFRRead(struct FFRHandle *fh,APTR buffer,LONG bytes)
{
  UBYTE *ptr;
  LONG left,size;

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

  left = bytes; ptr = buffer;

  if (left > 0)
    do {
      if ((size=MIN(fh->fh_Left,left)) != 0) {
        CopyMem(fh->fh_Ptr,ptr,size);
        fh->fh_Ptr += size; fh->fh_Left -= size;
        ptr += size; left -= size;
      }

      if (left)
        if (!fh->fh_Pending)
          break;
      else if (!EndRead(fh))
             return -1L;
    } while(left > 0);

  return bytes-left;
}

VOID CloseFFR(struct FFRHandle *fh)
{
  if (fh->fh_Pending != FALSE) {
    (VOID) WaitPort(&fh->fh_ReplyPort); (VOID) GetMsg(&fh->fh_ReplyPort);
  }

  FreeVec(fh->fh_Buf2);
  FreeVec(fh->fh_Buf1);
  FreeSignal(fh->fh_ReplyPort.mp_SigBit);
  FreeVec(fh);
}

struct FFRHandle *OpenFFR(BPTR handle)
{
  struct FFRHandle *fh;
  struct InfoData *infoData;
  LONG bufferSize,firstBlock,newSize;

  if (handle != NULL) {

    if ((fh=AllocVec(sizeof(struct FFRHandle),MEMF_PUBLIC|MEMF_CLEAR)) != NULL) {

      fh->fh_Handle = (struct FileHandle *)BADDR(handle);

      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 = ACTION_READ;
      fh->fh_Pkt.sp_Pkt.dp_Arg1 = fh->fh_Handle->fh_Arg1;

      fh->fh_ReplyPort.mp_Node.ln_Type = NT_MSGPORT;

      if ((BYTE)(fh->fh_ReplyPort.mp_SigBit=AllocSignal(-1)) >= 0) {

        fh->fh_ReplyPort.mp_SigTask = FindTask(NULL);
        NEWLIST(&fh->fh_ReplyPort.mp_MsgList);

        bufferSize = BUFFER_SIZE; firstBlock = FIRST_BLOCK;

        if ((infoData=AllocVec(sizeof(struct 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;
            }
          }
          FreeVec(infoData);
        }

        fh->fh_BufferSize = bufferSize;

        if ((fh->fh_Buf1=AllocVec(bufferSize+15,MEMF_PUBLIC)) != NULL) {
          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);
            BeginRead(fh,firstBlock-MOD(Seek(handle,0,OFFSET_CURRENT),firstBlock)); return fh;
          }
          FreeVec(fh->fh_Buf1);
        }
        FreeSignal(fh->fh_ReplyPort.mp_SigBit);
      }
      FreeVec(fh);
    }
  }
  return NULL;
}
