/****************************************************************
 * Support routines for file transfer protocol implementations. *
 * The routines in this file provide easy access to the serial  *
 * device with true timeouts, and simplified window management. *
 * Kenneth Österberg 02-May-1989                                *
 ****************************************************************/

/* Compilation with Aztec C:
 * cc +B -o protsupp.o protsupp.c
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <devices/serial.h>
#include <devices/timer.h>
#include <graphics/rastport.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>

#define GWID 48        /* Gadget width */
#define GHGT 12        /* Gadget height */

#define WC_NONE 0      /* Window cleanup (Wcleanup()) error codes. Call */
#define WC_WINDOW 1    /* with WC_NONE when you want to get rid of the */
#define WC_INTUI 2     /* window */

#define SC_RDPORT 5     /* Error codes for Scleanup(). Call the routine */
#define SC_RDDEV 4      /* with SC_NONE to deallocate all serial port   */
#define SC_WRREQ 3      /* resources. */
#define SC_WRPORT 2
#define SC_WRDEV 1
#define SC_NONE 0

#define TC_PORT 2       /* Error codes for Tcleanup(). Call with TC_NONE */
#define TC_DEV 1        /* to deallocate timer.device resources */
#define TC_NONE 0

#define GS_TIMEOUT 1    /* Error codes returned by getserdata() and */
#define GS_NONE 0       /* getserchar(). GS_NONE means everything's OK */

/* Amiga library routines used by this file */

extern APTR AllocMem();
extern ULONG Wait();
extern struct MsgPort *CreatePort();
extern int OpenDevice(), DoIO();
extern void FreeMem(), DeletePort(), CloseDevice(), DeletePort();
extern ULONG AbortIO(), CheckIO();
extern void CloseLibrary(), CloseWindow();
extern struct Window *OpenWindow();
extern APTR OpenLibrary();
extern void SetAPen(), Move(), Text(), ClearEOL();
extern struct IntuiMessage *GetMsg();

/* Global variables */

struct IOExtSer *RR, *WR;
struct timerequest *TR;
struct GfxBase *GfxBase = NULL;
struct IntuitionBase *IntuitionBase = NULL;
struct Window *pwin = NULL;

/* The structure that SetupWindow() takes as argument */

struct windef {
  UWORD Width, Height;
  UWORD GXpos, GYpos;
  char *Title;
  char **Text;
};

/* Structures to create the Abort gadget */

struct IntuiText AText = { 1, 0, JAM2, 5, 2 ,NULL, (UBYTE *)"Abort", NULL };
SHORT AVectors[] = { -1,-1, GWID,-1, GWID,GHGT, -1,GHGT, -1,-1 };
struct Border ABorder = { 0, 0, 1, 0, JAM2, 5, AVectors, NULL };
struct Gadget AbortGadget = { NULL, 0,0, GWID,GHGT, GADGHCOMP, RELVERIFY,
  BOOLGADGET, (APTR)&ABorder, NULL, &AText, 0L, NULL, 0, NULL };

/* Local routines */

int OpenUpSerial(), OpenUpTimer(), getserdata(), getserchar();
int sendserdata(), sendserchar();
void Scleanup(), Tcleanup();
int SetupWindow(), CheckAbort();
void Wcleanup(), PText();

/********************
 * The code section *
 ********************/

int sendserchar(ch)
/* Sends a character to the serial device. Returns the error from DoIO() */
register char *ch;
{
  return(sendserdata(ch,1L));
}

int sendserdata(ch,len)
/* Sends a data buffer to the serial device. Returns the error from DoIO()
 * Uses external variable struct IOExtSer *WR. */
register UBYTE *ch;
register ULONG len;
{
  register struct IOExtSer *rWR = WR; /* Cache pointer in a register */

  rWR->IOSer.io_Command = CMD_WRITE;
  rWR->IOSer.io_Length = len;
  rWR->IOSer.io_Data = (APTR) ch;
  return(DoIO(rWR));
}

int getserchar(timeout,chrptr)
/* Get one character from the serial port */
register ULONG timeout;
register UBYTE *chrptr;
{
  return(getserdata(timeout,chrptr,1L));
}

int getserdata(timeout,chrptr,len)
/* Wait for a specific number of characters to arrive at the serial port.
 * The routine doesn't return until all characters have arrived, or an
 * timeout occurs. Uses the external variable struct IOExtSer *RR and
 * struct timerequest *TR.
 * The function returns an error code (see #defines with prefixes 'GS_') */

register ULONG timeout;
UBYTE *chrptr;
ULONG len;
{
  ULONG mask;
  register ULONG ReadMask, TimeMask = 0L;
  register struct timerequest *rTR = TR; /* TR into register for speed */
  register struct IOExtSer *rRR = RR;    /* RR into register for speed */

/* Send the request for the characters we need. Try to use quick IO */

  rRR->IOSer.io_Command = CMD_READ;
  rRR->IOSer.io_Data = (APTR)chrptr;
  rRR->IOSer.io_Length = len;
  rRR->IOSer.io_Flags |= IOF_QUICK;
  BeginIO(rRR);

/* Check if quick IO succeeded. If so, we don't have to mess with all that
 * message passing, and the timer doesn't even have to get involved. */

  if (rRR->IOSer.io_Flags & IOF_QUICK) {
    return(GS_NONE);
  }

/* If the timeout value <> 0 then send the request to the timer device.
 * If you don't need any timeout limits then simply give a timeout value
 * NULL to this routine, and a timeout will never occur. */

  if (timeout) {
    rTR->tr_time.tv_secs = timeout / 50;
    rTR->tr_time.tv_micro = (timeout % 50L) * 20000L;
    rTR->tr_node.io_Command = TR_ADDREQUEST;
    SendIO(rTR);
    TimeMask = 1L << rTR->tr_node.io_Message.mn_ReplyPort->mp_SigBit;
  }
  ReadMask = 1L << rRR->IOSer.io_Message.mn_ReplyPort->mp_SigBit;

/* Wait until the characters have been received or a timeout occurs */

  do {
    mask = Wait(ReadMask|TimeMask);

/* Check if the serial device request has completed */

    if ((mask & ReadMask) && (CheckIO(rRR))) {
      if (timeout)
        if (!CheckIO(rTR))
          AbortIO(rTR);
        GetMsg(rTR->tr_node.io_Message.mn_ReplyPort);
      return(GS_NONE);  /* Return with no error */
    }

/* Check if the timer device request has completed */

    if ((mask & TimeMask) && (CheckIO(rTR))) { 
      GetMsg(rTR->tr_node.io_Message.mn_ReplyPort);
      AbortIO(rRR);                              /* Abort the read request */
      GetMsg(rRR->IOSer.io_Message.mn_ReplyPort);
      return(GS_TIMEOUT);
    }

/* Repeat the waiting loop until one of the requests has completed.
 * I'd be extremely happy if someone could explain why we get all those
 * dummy signals which do not correspond to any completed requests. */

  } while (1);
}

int OpenUpTimer(TReq)
/* Opens up the timer.device. If an error occured the routine releases
 * all resources it has allocated and returns FALSE (==0) */
struct timerequest **TReq;
{
  register struct timerequest *TR;

  TR = (struct timerequest *)AllocMem((ULONG)sizeof(*TR),MEMF_PUBLIC|MEMF_CLEAR);
  if (TR == NULL) {
    return(FALSE);
  }
  else
    *TReq = TR;

  if ((TR->tr_node.io_Message.mn_ReplyPort = CreatePort("SerTimer",0L))==NULL) {
    Tcleanup(TC_PORT);
    return(FALSE);
  }
  if (OpenDevice(TIMERNAME, UNIT_VBLANK, TR, 0L)) {
    Tcleanup(TC_DEV);
    return(FALSE);
  }
  return(TRUE);
}

void Tcleanup(num)
/* Perform timer.device resource deallocation. Call this routine with
 * Tcleanup(TC_NONE) to free all resources after your'e done with the
 * timer. */
register int num;
{
  switch(num) {
  case TC_NONE:
    CloseDevice(TR);
  case TC_DEV:
    DeletePort(TR->tr_node.io_Message.mn_ReplyPort);    
  case TC_PORT:
    FreeMem(TR,(ULONG)sizeof(*TR));
  default:
    TR = NULL;
    break;
  }
}

int OpenUpSerial(RReq,WReq,bufsize,baud,unit,name)
/* Opens up the named serial device for read and write. If an error
 * occurs the routine releases all resources it has taken and returns
 * FALSE, so that the caller may do it's own deallocation if serial isn't
 * available. */

struct IOExtSer **RReq, **WReq;
ULONG bufsize, baud, unit;
char *name;
{
  register struct IOExtSer *RR, *WR;

/* Allocate the read request (IOExtSer) structure, initialize it and open
 * the serial device for reading */

  RR = (struct IOExtSer *)AllocMem((ULONG)sizeof(*RR),MEMF_PUBLIC|MEMF_CLEAR);
  if (RR == NULL)
    return(FALSE);
  else
    *RReq = RR;
  RR->io_SerFlags = SERF_SHARED|SERF_XDISABLED;  /* Disable XON/XOFF */
  if ((RR->IOSer.io_Message.mn_ReplyPort = CreatePort("SerRead",0L)) == NULL) {
    Scleanup(SC_RDPORT);
    return(FALSE);
  }
  if (OpenDevice(name,unit,RR,NULL)) {
    Scleanup(SC_RDDEV);
    return(FALSE);
  }

/* Allocate the write request (IOExtSer) structure, initialize it and open
 * the serial device for writing */

  WR = (struct IOExtSer *)AllocMem((ULONG)sizeof(*WR),MEMF_PUBLIC|MEMF_CLEAR);
  if (WR == NULL) {
    Scleanup(SC_WRREQ);
    return(FALSE);
  }
  else
     *WReq = WR;

  WR->io_SerFlags = SERF_SHARED|SERF_XDISABLED;
  if ((WR->IOSer.io_Message.mn_ReplyPort = CreatePort("SerWrite",0L)) == NULL) {
    Scleanup(SC_WRPORT);
    return(FALSE);
  }
  if (OpenDevice(name,unit,WR,NULL)) {
    Scleanup(SC_WRDEV);
    return(FALSE);
  }

/* Set up needed parameters with SDCMD_SETPARAMS */

  RR->io_SerFlags = SERF_SHARED|SERF_XDISABLED;
  RR->io_Baud = baud;
  RR->io_ReadLen = 8;
  RR->io_WriteLen = 8;
  RR->io_StopBits = 1;
  RR->io_CtlChar = 1L;
  RR->io_RBufLen = bufsize;
  RR->io_BrkTime = 500000L;
  RR->IOSer.io_Command = SDCMD_SETPARAMS;
  if (DoIO(RR)) {
    Scleanup(SC_NONE);
    return(FALSE);
  }
  else
    return(TRUE);
}

void Scleanup(num)
/* This routine performs deallocation of serial device related resources.
 * The variable 'num' specifies how much has been allocated before calling
 * the routine. Call with Scleanup(SC_NONE) to free all resource.
 * Uses external variables struct IOExtSer *RR, *WR. */
register int num;
{
  switch(num) {
  case SC_NONE:
    CloseDevice(WR);
  case SC_WRDEV:
    DeletePort(WR->IOSer.io_Message.mn_ReplyPort);
  case SC_WRPORT:
    FreeMem(WR,(ULONG)sizeof(*WR));
  case SC_WRREQ:
    CloseDevice(RR);
  case SC_RDDEV:
    DeletePort(RR->IOSer.io_Message.mn_ReplyPort);
  case SC_RDPORT:
    FreeMem(RR,(ULONG)sizeof(*RR));
  default:
    RR = WR = NULL;
    break;
  }
}

/***************************
 * Window related routines *
 ***************************/

int SetupWindow(wd)
/* Open up the protocol window according to the windef structure
 * provided. Returns TRUE if window opened without error. */

register struct windef *wd;
{
  struct NewWindow nw;
  register struct Screen *fscr;
  register char **strptr;
  register int cnt;

/* Open graphics */

  GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",33L);
  if (GfxBase == NULL)
    return (FALSE);

/* Open intuition library */

  IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",33L);
  if (IntuitionBase == NULL) {  
    Wcleanup(WC_INTUI);
    return(FALSE);
  }

/* Check that the provided window sizes are feasible */

  fscr = IntuitionBase->FirstScreen;    /* The screen we'll use */
  if ((fscr->Width < wd->Width) || (fscr->Height < wd->Height)) {
    Wcleanup(WC_WINDOW);
    return (FALSE);
  }

/* Initialize our NewWindow structure */

  nw.LeftEdge = (fscr->Width - wd->Width) >> 1;
  nw.TopEdge = (fscr->Height - wd->Height) >> 1; 
  nw.Width = wd->Width;
  nw.Height = wd->Height;
  nw.DetailPen = 0;
  nw.BlockPen = 1;
  nw.IDCMPFlags = GADGETUP;
  nw.Flags = ACTIVATE|GIMMEZEROZERO|WINDOWDRAG|WINDOWDEPTH|SMART_REFRESH;
  nw.FirstGadget = &AbortGadget;
  nw.CheckMark = NULL;
  nw.Title = (UBYTE *)wd->Title;
  nw.Screen = fscr;
  nw.BitMap = NULL;
  if (strcmp(fscr->DefaultTitle,"Workbench Screen"))
    nw.Type = CUSTOMSCREEN;
  else
    nw.Type = WBENCHSCREEN;

  AbortGadget.LeftEdge = wd->GXpos;  /* Gadget position within the window */
  AbortGadget.TopEdge = wd->GYpos;

/* Open the window */

  if ((pwin = OpenWindow(&nw)) == NULL) {  
    Wcleanup(WC_WINDOW);
    return(FALSE);
  }

/* Display the window texts */

  SetAPen(pwin->RPort,1L);
  strptr = wd->Text;
  cnt = 0;
  if (strptr)
    while (*strptr) {
      Move(pwin->RPort,4L,(ULONG)++cnt * pwin->RPort->TxHeight);
      Text(pwin->RPort,*strptr,(ULONG)strlen(*strptr));
      strptr++;
    }

  SetAPen(pwin->RPort,3L);
  return(TRUE);
}

void PText(line,col,string,arg1,arg2)
/* Display a formatted string at a defined location in the window. The
 * routine clears the rest of the line to get rid of old messages. Two
 * ----> 32 bit <---- arguments (arg1 and arg2) are passed to sprintf()
 */
register UWORD line,col;
register char *string;
ULONG arg1, arg2;  /* Pleeeze, don't pass integer args to PText sprintf() */
{
  char tstr[80];
  register struct RastPort *rp = pwin->RPort;

  sprintf(tstr,string,arg1,arg2);
  Move(rp,(ULONG)col * rp->TxWidth + 4L, (ULONG)(line+1) * rp->TxHeight);
  Text(rp,tstr,(ULONG)strlen(tstr));
  ClearEOL(rp);
}

int CheckAbort()
/* This routine returns TRUE if the 'Abort' gadget has been pressed by
 * the user. It is assumed that no other custom gadgets exist in the window.
 */
{
  register struct IntuiMessage *imsg;

  if ((imsg = GetMsg(pwin->UserPort)) == NULL)
    return(FALSE);
  else
    if (imsg->Class != GADGETUP) {
      ReplyMsg(imsg);
      return(FALSE);
    }
    else {
      ReplyMsg(imsg);
      return(TRUE);
    }
}

void Wcleanup(num)
/* Perform window cleanup. Call this with WC_NONE to close window and
 * libraries after usage */

register int num;
{
  switch (num) {
  case WC_NONE:
    CloseWindow(pwin);
    pwin = NULL;
  case WC_WINDOW:
    CloseLibrary(IntuitionBase);
    IntuitionBase = NULL;
  case WC_INTUI:
    CloseLibrary(GfxBase);
    GfxBase = NULL;
  }
}
