/* AmigaDisplay:  A "smart display" terminal emulator for the Amiga
 *
 * Based on the AmigaTerm program by Michael Mounier, enhanced by Don Woods.
 * This version emulates either a "dumb terminal" or a DataMedia 2500.
 * Mounier's version was (c) 1985 but was freely distributed.  Woods's
 * rewrite is (c) 1986 and is likewise available if due credit is given.
 *
 * The DM2500 supports a special command sequence used at Stanford to display
 * up to 128 different character glyphs, and also supports various special
 * interpretations applied to the ALT, AMIGA, and F# keys (again for use at
 * Stanford).  These features, and the type of display being emulated, should
 * be fairly easy to change (e.g., if someone would rather emulate a VT100).
 *
 * The emulator also provides a variety of bell volumes (just for kicks), and
 * file send/capture, but NOT the so-called "xmodem" features.  The send/
 * capture stuff uses a requester to avoid mucking up the display region.
 */

/****************************************\
* This file is the main program (main.c) *
\****************************************/

#include <exec/types.h>
#include <exec/exec.h>
#include <graphics/gfxbase.h>
#include <graphics/gfx.h>
#include <graphics/text.h>
#include <devices/serial.h>
#include <devices/keymap.h>
#include <stdio.h>
#include <intuition/intuitionbase.h>
#include <intuition/intuition.h>

/*
 * Library definitions; shared with the other modules
 */

#define INTUITION_REV 1
#define GRAPHICS_REV  1
#define FONT_REV      1

struct IntuitionBase *IntuitionBase = NULL;
struct GfxBase       *GfxBase       = NULL;
struct DiskfontBase  *DiskfontBase  = NULL;

/*
 * Functions imported from other modules
 */

/* in intuit.c */
extern InitWindowStuff(struct Window *);
extern CleanUpWindow(struct Window *);
extern FileMenu(struct Window *, int, FILE **, FILE **, int *);

/* in dpy.c */
extern SetUpDisplay(struct Window *, int);
extern char Emit(char);
extern NeedQuote(char);

/* in beep.c */
extern InitAudio(int);
extern SetBeeper(int);
extern Beep();
extern CleanUpBeeper();

/*
 * Globals and externals for using the serial port
 */

extern struct MsgPort *CreatePort();
static struct IOExtSer *inreq = NULL, *outreq = NULL;
static char inbuf[2], outbuf[2];

/*
 * MAIN PROGRAM
 */

main() {
   char c;
   int buckied; /* see ToAsc for details */
   int proceed = TRUE, fileFlags = 0; 
   FILE *receive = NULL, *send = NULL;
   struct Window *mywindow;
   struct IntuiMessage *message; /* msg struct for GetMsg */

   /* Bits set in fileFlags */
   #define RAWCAPTURE 1  /* include ctrl chars in captured file */
   #define SENDLFASCR 2  /* translate LFs to CRs when sending */

   /* Emulator window structure */
   static struct NewWindow newWindow = {
      0, 0, 640, 200,
      0,
      1,
      RAWKEY | MENUPICK | GADGETUP | REQCLEAR,
      SMART_REFRESH | ACTIVATE | BORDERLESS,
      NULL, NULL, NULL, NULL, NULL,
      100, 35, 640, 200, /* min/max dims, unused since sizegadget omitted */
      WBENCHSCREEN};

   IntuitionBase = (struct IntuitionBase *)
      OpenLibrary("intuition.library", INTUITION_REV);
   GfxBase = (struct GfxBase *)
      OpenLibrary("graphics.library", GRAPHICS_REV);
   DiskfontBase = (struct DiskfontBase *)
      OpenLibrary("diskfont.library", FONT_REV);
   if (IntuitionBase == NULL || GfxBase == NULL || DiskfontBase == NULL)
      GoAway("Can't open libraries", NULL);

   if ((mywindow = (struct Window *)OpenWindow(&newWindow)) == NULL)
      GoAway("Can't open window", NULL);

   /* Open serial device once and copy the initialised ioreq; */
   /* that way we can have exclusive access with two ports    */

   inreq = (struct IOExtSer *)AllocMem(sizeof(*inreq), MEMF_PUBLIC|MEMF_CLEAR);
   inreq->IOSer.io_Message.mn_ReplyPort = CreatePort("SerialRead", 0);
   if (OpenDevice(SERIALNAME, NULL, inreq, NULL))
      GoAway("Can't open Serial port", mywindow);

   inreq->io_SerFlags = SERF_SHARED | SERF_XDISABLED;
   inreq->IOSer.io_Command = SDCMD_SETPARAMS;
   inreq->io_ReadLen = inreq->io_WriteLen = 8;
   DoIO(inreq);

   inreq->IOSer.io_Length = 1;
   outreq = (struct IOExtSer *)AllocMem(sizeof(*outreq), MEMF_PUBLIC);
   movmem((char *)inreq, (char *)outreq, sizeof(*inreq));
   outreq->IOSer.io_Message.mn_ReplyPort = CreatePort("SerialWrite", 0);

   inreq->IOSer.io_Command = CMD_READ;
   inreq->IOSer.io_Data = (APTR)inbuf;
   outreq->IOSer.io_Command = CMD_WRITE;
   outreq->IOSer.io_Data = (APTR)outbuf;

   if (InitWindowStuff(mywindow)) {
      CloseDevice(inreq);
      CloseDevice(outreq);
      GoAway("Not enough memory", mywindow);
      }

   /* Finish initialisation */
   InitAudio(1);
   SetAPen(mywindow->RPort, 1);
   SetUpDisplay(mywindow, 1);
   SetBaud(1);
   BeginIO(inreq);

   while (proceed) {
      /* wait for something to do */
      if (send == NULL)
         Wait((1 << inreq->IOSer.io_Message.mn_ReplyPort->mp_SigBit)
            | (1 << mywindow->UserPort->mp_SigBit));
      if (CheckIO(inreq)) { /* receive a char from the host */
         WaitIO(inreq);
         c = inbuf[0] & 0x7F;
         BeginIO(inreq);
         if (fileFlags & RAWCAPTURE) Emit(c); else c = Emit(c);
         /* Emit returns hex 80 if char is non-printing */
         if (receive != NULL && c < 0x80) /* capture this character */
            if ((c >= ' ' && c <= '~') || c == '\n' || c == '\t')
               putc(c, receive); /* always capture these chars */
            else if (fileFlags & RAWCAPTURE) {
               if (c == 0x7F) fputs("^?", receive);
               else {putc('^', receive); putc(c+0x40, receive);}
               }
         }
      else if (send != NULL) { /* send file (only if host not talking) */
         if ((c=getc(send)) != EOF)
            Ship(c == '\n' && (fileFlags & SENDLFASCR) ? '\r' : c);
         else {
            fclose(send);
            send = NULL;
            FileMenu(mywindow, 99, &receive, &send, &fileFlags);
            }
         }
      while (message = (struct IntuiMessage *)GetMsg(mywindow->UserPort)) {
         ULONG class;
         USHORT code, qual;
         class = message->Class;
         code = message->Code;
         qual = message->Qualifier;
         ReplyMsg(message);
         switch (class) {
            case RAWKEY:  /* user has touched the keyboard */
               if ((buckied = ToAsc(code, qual)) >= 0) {
                  if (buckied & 0x100) Ship(0x80);
                  else if (NeedQuote(c = buckied & 0x7F)) Ship(0);
                  Ship(buckied & 0xFF);
                  }
               else if (buckied != -99) { /* function key */
                  Ship(0); /* send NUL, number, CR */
                  Ship('0'+((-buckied)/10));
                  Ship('0'+((-buckied)%10));
                  Ship('\r');
                  }
               break;
            case MENUPICK:
               if (code != MENUNULL) switch (MENUNUM(code)) {
                  case 0: FileMenu(mywindow, ITEMNUM(code), &receive, &send, &fileFlags); break;
                  case 1: BaudMenu(code); break;
                  case 2: SetUpDisplay(NULL, ITEMNUM(code)); break;
                  case 3:
                     SetBeeper(ITEMNUM(code));
                     if (Beep()) DisplayBeep(mywindow->WScreen);
                     break;
                  case 4: proceed = WindowMenu(mywindow, code); break;
                  }
               break;
            } /* end of switch (class) */
         } /* end of while (message) */
      } /* end of while (proceed) */

   /* It must be time to quit */

   CloseDevice(inreq);
   CloseDevice(outreq);
   CleanUpWindow(mywindow);
   CleanUpBeeper();
   GoAway(NULL, mywindow); /* exit will close send/receive files if open */
   } /* end of main */

/*
 * Function to clean up however much stuff got started
 */

static GoAway(text, w) char *text; struct Window *w; {
   if (w) CloseWindow(w);
   if (inreq) {
      DeletePort(inreq->IOSer.io_Message.mn_ReplyPort);
      FreeMem(inreq, sizeof(*inreq));
      }
   if (outreq) {
      DeletePort(outreq->IOSer.io_Message.mn_ReplyPort);
      FreeMem(outreq, sizeof(*outreq));
      }
   if (IntuitionBase != NULL) CloseLibrary(IntuitionBase);
   if (GfxBase != NULL) CloseLibrary(GfxBase);
   if (DiskfontBase != NULL) CloseLibrary(DiskfontBase);
   if (text) {printf("ERROR: %s\n", text); exit(100);}
   exit(FALSE);
   }

/*
 * Quickie to ship one char to serial port
 */

static Ship(c) char c; {
   outbuf[0] = c;
   DoIO(outreq);
   }

/*
 * Menu routines (FileMenu is external)
 */

static BaudMenu(code) USHORT code; {
   AbortIO(inreq);
   SetBaud(ITEMNUM(code));
   BeginIO(inreq);
   }

static SetBaud(index) USHORT index; {
   switch (index) {
      case 0: inreq->io_Baud = 300; break;
      case 1: inreq->io_Baud = 1200; break;
      case 2: inreq->io_Baud = 2400; break;
      case 3: inreq->io_Baud = 4800; break;
      case 4: inreq->io_Baud = 9600; break;
      }
   inreq->IOSer.io_Command = SDCMD_SETPARAMS;
   DoIO(inreq);
   inreq->IOSer.io_Command = CMD_READ;
   }

static WindowMenu(w, code) struct Window *w; USHORT code; {
   /* returns TRUE unless window is supposed to close */
   switch (ITEMNUM(code)) {
      case 0: WindowToBack(w); break;
      case 1: WindowToFront(w); break;
      case 2: return(FALSE);
      }
   return(TRUE);
   }

/*
 * Function to convert raw key data into ascii chars
 */

/* The system at Stanford uses 9-bit characters.  The bottom 7 bits are normal
 * ascii, although there are visible characters associated with the ctrl chars.
 * (E.g., ^H prints as lambda, ^U as existential quantifier, ^A as down-arrow.)
 * The 0x80 bit and 0x100 bit are called "bucky bits" (see Hacker's Dictionary)
 * and are typically used to distinguish command chars from regular typein.
 * These bits are set by some extra shift keys on the Stanford keyboards, called
 * CONTROL and META.  When talking to Stanford across an 8-bit serial line,
 * one sends CONTROL in the parity bit and META by a prefixed char (0x80).
 * This emulator interprets ALT as CONTROL, AMIGA as META, and CTRL as the
 * standard-ascii control key.  If you don't need the bucky-bit features, just
 * don't hold down the ALT or AMIGA keys while typing, and you'll be fine.
 * 
 * This function returns the 7-bit ascii code for the given keyboard action,
 * plus bucky-bits in 0x180.  If the keyboard action is a no-op (e.g., key up,
 * or shift key down), it returns -99.  Function keys return -(10+key#), or
 * -key# if shifted.
 */

/* qualifier bits */
#define LSHIFT  (1<<0)
#define RSHIFT  (1<<1)
#define SHLOCK  (1<<2)
#define CTL     (1<<3) /* the real "ctrl" key */
#define LCTRL   (1<<4) /* ALT keys set parity bit */
#define RCTRL   (1<<5)
#define LMETA   (1<<6) /* Amiga keys set 9th bit */
#define RMETA   (1<<7)

static ToAsc(code, qual) USHORT code, qual; {
   char c;
   static char *map = "\
`1234567890-=\\?0qwertyuiop[]?123asdfghjkl;'??456\
?zxcvbnm,./?.789 \b\t\n\r\33\177???-?\13\f";
   if (code >= 0x50 && code <= 0x59) /* function keys */
      return((qual & (LSHIFT|RSHIFT) ? -1 : -11) - (c = code&0xF));
   if (code <= 0x4D) c = map[code];
   else if (code == 0x5F) c = 0; /* HELP key translates into NUL */
   else return(-99);
   if (c == '?') return(-99);
   if (qual & (LSHIFT|RSHIFT)) {
      if (c >= 'a' && c <= 'z') c -= 0x20;
      else if (c >= ',' && c <= '=') {
         code = c - ','; /* sidestep compiler bug re subscript exprs */
         c = "<_>?)!@#$%^&*(::<+"[code];
         }
      else if (c >= '[' && c <= '`') {
         code = c - '['; /* ditto */
         c = "{|}^_~"[code];
         }
      else if (c == '\'') c = '"';
      } /* end shift */
   else if ((qual & SHLOCK) && c >= 'a' && c <= 'z') c -= 0x20;
   if (qual & CTL) c &= 0x1F;
   return(c + (qual & (LCTRL|RCTRL) ? 0x80 : 0)
      + (qual & (LMETA|RMETA) ? 0x100 : 0));
   } /* end of ToAsc */
