/****************************************************\
*                                                    *
* ROBBS - Rexx Object Building Block System          *
*                                                    *
* sermod.c - the first building block in the series. *
*                                                    *
\****************************************************/

#include <libraries/dos.h>
#include <devices/serial.h>
#include <fcntl.h>
#include <functions.h>
#include <exec/types.h>
#include <stdio.h>
#include <exec/memory.h>
#include <rexx/storage.h>
#include <rexx/rxslib.h>
#include <intuition/intuition.h>
#include "Options.h"
#include "SerialIO.h"
#include "defs.h"

BOOL  DoPrefs  = TRUE;
BOOL  KeepGoing;

char    portname[32]    = "NONE";
char    macroport[32]   = "ROBBS_ctrl";
char    command[10]     = "TEXT";
char    translate       = '\x7f';
char    status[100];
char    sendstring[255];
STRPTR  Token;
STRPTR  CreateArgstring();

int  sendoffset;
int  connected;
int  outstanding_rexx_commands = 0;

unsigned  char  pattern[10][99];
unsigned  char  table[10][100];

long  listening = 1,
      talking   = 1,
      sending   = 0,
      scan     = 0;

struct  RexxLib  *RexxSysBase;
struct  MsgPort  RexxPort;
struct  RexxMsg  *rmptr, *OutMsg, *CreateRexxMsg();

void FreePort();

struct IntuitionBase *IntuitionBase = NULL;


/* Defaults if no "Init.key" file. */
static struct options Options =
  {
  /* Baud      */  300,
  /* Xon       */  FALSE,
  } ;

struct rx_mes
{
struct rx_mes *next;
struct RexxMsg *mes;
BYTE flag;
};

struct rx_mes *mslist;

static char *validargs[] =
  {
  "RXD",    /* 0  */
  "TXD",    /* 1  */
  "SEND",    /* 2  */
  "LSEND",    /* 3  */
  "MATCH",    /* 4  */
  "SCAN",    /* 5  */
  "STATUS",  /* 6  */
  "CONNECT",  /* 7  */
  "CTRL",    /* 8 */
  "COMM",    /* 9 */
  "ABORT",    /* 10 */
  "DIE",    /* 11 */
  NULL
  };

static  int    numcmds = 12;
static  int    SerialIO;
static  long    PrefBuf;
static  struct  Preferences *Preferences;
static  struct  IOExtSer *rd = NULL, *wr = NULL;

extern int Mode;

/*--------------------------------------------------------------*/
/*      cleanup: all done so close up and prepare to exit       */
/*--------------------------------------------------------------*/

static void cleanup()
  {
  if (RexxSysBase != NULL)
    {
    RemPort(&RexxPort);                   /* unlink it                     */
    FreePort(&RexxPort);                  /* release the port resources    */
    CloseLibrary(RexxSysBase);
    }
  if ( SerialIO != NULL )
    CloseSerialIO();
  if ( IntuitionBase != NULL )
    CloseLibrary(IntuitionBase);
  }

/*--------------------------------------------------------------*/
/*      setdefaults: set defaults occording to current options. */
/*--------------------------------------------------------------*/

static void setdefaults()
  {

  /* set defaults */

  if (DoPrefs)
    {
    switch ( Preferences->BaudRate )
      {
      case 1:
        Options.Baud = 300;
        break;
      case 2:
        Options.Baud = 1200;
        break;
      case 3:
        Options.Baud = 2400;
        break;
      case 4:
        Options.Baud = 4800;
        break;
      case 5:
        Options.Baud = 9600;
        break;
      case 6:
        Options.Baud = 19200;
        break;
      case 7:
        Options.Baud = MIDI;
        break;
      default:
        Options.Baud = 300;
        break;
      }
    }

    SetSerBaud( (int)Options.Baud);
    SetXonMode( (BOOL)Options.Xon );
    DoPrefs = FALSE;

  }

/*--------------------------------------------------------------*/
/*      init: initialize Sermod - set defaults                  */
/*--------------------------------------------------------------*/

static void init()
  {

  IntuitionBase = (struct IntuitionBase *)
    OpenLibrary("intuition.library", INTUITION_REV);
  if( IntuitionBase == NULL )
    {
    puts("can't open intuition\n");
    goto badopen;
    }

  Preferences = GetPrefs (&PrefBuf, (long)sizeof(PrefBuf));

  if(( SerialIO = InitSerialIO(&rd, &wr) ) == NULL )
    {
    puts("Can't open the Serial Port.\n");
    goto badopen;
    }

  setdefaults();

  RexxSysBase = (struct RexxLib *)OpenLibrary("rexxsyslib.library",0L);
  if (RexxSysBase == NULL)
    {
    printf("Can't open rexxsyslib.library.\n");
    goto badopen;
    }
  InitPort(&RexxPort,"ROBBS_ser");    /* Initialize our message port   */
  AddPort(&RexxPort);          /* Make the port public          */

  clearmatch();

  return;

  badopen:
  cleanup();
  exit( 4 );
  }

/*--------------------------------------------------------------*/
/*      clearmatch: clear all pattern strings and table         */
/*--------------------------------------------------------------*/

clearmatch()
{
  int i, j;
  for (j=0; j<NUM_MATCH; j++)  {
    *pattern[j] = 0;
    for (i=0; i<MAX_MATCH; i++)
      table[j][i] = '\0';
    }
}

/*--------------------------------------------------------------*/
/*      buildstatus: build status string from variables         */
/*--------------------------------------------------------------*/

void buildstatus()
{
char serioports[20];

  sprintf(status, "%d ",(int)Options.Baud);

  if (Mode)
    strcat(status, "NOECHO ");
  else
    strcat(status, "NOECHO ");
  if ( (BOOL)Options.Xon )
    strcat(status, "XENABLED ");
  else
    strcat(status, "XDISABLED ");
  if (translate == '\x7f')
    strcat(status, "TRANSLATE ");
  else
    strcat(status, "NOTRANSLATE ");
  if (talking)
    strcat(status, "TALKON ");
  else
    strcat(status, "TALKOFF ");
  if (listening)
    strcat(status, "LISTENON ");
  else
    strcat(status, "LISTENOFF ");
  if (scan)
    strcat(status, "SCANON ");
  else
    strcat(status, "SCANOFF ");
  strcat(status, portname);
  strcat(status, " ");
  strcat(status, macroport);
  sprintf(serioports, " %lx %lx", rd, wr);
  strcat(status, serioports);
}

/*-----------------------------------------------------------------*/
/*  checkmatch(stringpointer): check for match in serial stream   */
/*-----------------------------------------------------------------*/

checkmatch(s)
char *s;
{
int  to, from,
    matchflag,
    killedflag,
    offset,
    patnum;

  while (*s) {
    for (patnum=0; patnum<NUM_MATCH; patnum++)
      {
      matchflag = 0;  killedflag = 0;
      if (*s == *pattern[patnum]) {
        matchflag = 1;
        if (*pattern[patnum]+1 == '\0') {
          sendmatch( patnum );
          matchflag = 0;
          }
        }
      for(offset=0; offset<MAX_MATCH && table[patnum][offset]; offset++)
        {
        if (*s == pattern[patnum][ (table[patnum][offset]) ] )
          {
          table[patnum][offset]++;
          if (pattern[patnum][ (table[patnum][offset]) ] == '\0')
            sendmatch(patnum);
          }
        else
          {
          table[patnum][offset] = '\0';
          killedflag = 1;
          }
        }

      if (matchflag)
        table[patnum][offset] = '\1';

      if (killedflag)
        {
        to = 0;
        while (table[patnum][to])
          to++;
      
        for (from=to; from<MAX_MATCH; from++)
          if (table[patnum][from] != 0)
            {
            table[patnum][to] = table[patnum][from];
            to++;
            table[patnum][from] = '\0';
            }
        }
      }
    s++;
    }
}

/*---------------------------------------------------------------*/
/*  send_rexx_msg: send a message to a port                      */
/*                 RexxPort is reply port. look for reply there  */
/*---------------------------------------------------------------*/

send_rexx_msg(port, cmd, s)
char *port;
char *cmd;
char *s;

{
  struct MsgPort *localport;

  if ((OutMsg = CreateRexxMsg(&RexxPort, "robbs", "ROBBS" )) != NULL)
    {
    OutMsg->rm_Args[0] = CreateArgstring( cmd, (long) strlen( cmd ));
    OutMsg->rm_Args[1] = CreateArgstring( s, (long) strlen( s ));
    OutMsg->rm_Action = RXFUNC | 1;

    if( addmsglink( OutMsg ) ) {
      Forbid();
      if ((localport = FindPort(port)) != NULL)
        PutMsg(localport, OutMsg);
      else
        freemsglink( OutMsg );
      Permit();
      }
    else {
     free_rexx_command(OutMsg);
     }
    }
  return;
}

/*-----------------------------------------------------------------*/
/*  free_rexx_command: free the message we sent and decrement count */
/*-----------------------------------------------------------------*/

free_rexx_command(rexxmsg)
register struct RexxMsg *rexxmsg;
{
  /* delete the argument that we originally sent */

  DeleteArgstring(rexxmsg->rm_Args[0]);
  DeleteArgstring(rexxmsg->rm_Args[1]);

  /* delete the extended message */

  DeleteRexxMsg( rexxmsg );
}

/*-----------------------------------------------------------------*/
/*  addmsglink: keep track of all outstanding Arexx messages sent  */
/*-----------------------------------------------------------------*/

addmsglink( outmsg )
struct RexxMsg *outmsg;
{
struct rx_mes *rxptr;
register struct rx_mes *new;

 if( new = AllocMem((long)sizeof(struct rx_mes), MEMF_CLEAR) ) {
  outstanding_rexx_commands++;
  new->mes = outmsg;
  if( !mslist ) {
   mslist = new;
  }
  else {
   rxptr = mslist;
   while( rxptr->next )
    rxptr = rxptr->next;
   rxptr->next = new;
  }
  return TRUE;
 }
 return FALSE;
}

/*-----------------------------------------------------------------*/
/*  freemsglink: delete message from list of outstanding messages  */
/*-----------------------------------------------------------------*/

freemsglink( outmsg )
struct RexxMsg *outmsg;
{
register struct rx_mes *ptr, *next;

 ptr = mslist;                                 /* start at top of linked list    */
 if( ptr->mes == outmsg ) {                    /* first see if it's the one      */
  mslist = ptr->next;                          /* yes, reset top of list to next */
  free_rexx_command( outmsg );                 /* free the rexx_message          */
  FreeMem( ptr, (long)sizeof(struct rx_mes) ); /* free the link                  */
  outstanding_rexx_commands--;                 /* adjust the (un-needed?) count  */
  return;
 }
 while( ptr ) {                                /* else, start walking the list   */
  next = ptr->next;                            /* get pointer to 'next' in list  */
  if( next->mes == outmsg ) {                  /* is it the one ?                */
   ptr->next = next->next;                     /* yes, get its 'next' pointer... */
   free_rexx_command( outmsg );                /* ...before freeing it           */
   FreeMem( next, (long)sizeof(struct rx_mes) );
   outstanding_rexx_commands--;
   return;
  }
  ptr = next;    /* nope, move on */
 }
}

/*-----------------------------------------------------------------*/
/*  sendtext(portname, string): send text to connected port         */
/*-----------------------------------------------------------------*/

sendtext(s)
char *s;
{
  send_rexx_msg(portname, command, s);
}

/*-----------------------------------------------------------------*/
/*  sendmatch(number): inform of match in serial stream            */
/*-----------------------------------------------------------------*/

sendmatch( num )
int num;
{
char message[9];

 sprintf( message, "%d", num );
 send_rexx_msg(macroport, "MATCH", message);
}

/************* develop a string, then send it all at once **************
 *
 *  This function is designed to be used with Manx's format() function
 *  Usage: format(PutFormat,format_string...);
 *  Your calls must have one added character at the end
 *  of the format_string-- 255 (-1). Use \xff.
 *  This tells PutFormat to go print the string it has been building.
 *  Make sure your format_string ends with '\xff', otherwise
 *  the string won't print properly.
 *
 ***********************************************************************/

void PutFormat(character)
register UBYTE character;
{
static int count;
static UBYTE buffer[255];

  if ( (character == SQUIRTCHAR) || (count >= OUTSIZE - 3) ) {
    buffer[count] = '\0';
    if (connected)
      sendtext( buffer );
    if (scan) {
      checkmatch(buffer);
      }
    count = 0;
    }
  else
    buffer[count++] = character;
}

/*-----------------------------------------------------------------*
 *  nexttoken(): search for next space or NULL in token string     *
 *-----------------------------------------------------------------*/

nexttoken()
{
  for ( ;(*Token != ' ' && *Token != '\0'); Token++ );
  if (*Token == ' ')
    Token++;
  return;
}

/******************************************************
 *             Main Program                           *
 ******************************************************/

main()
{
ULONG class, waitmask;
int c, i, j, temp, code ;
UBYTE character;
struct rx_mes *mslist2;

  init();

  waitmask = (1L << rd->IOSer.io_Message.mn_ReplyPort->mp_SigBit)
    |  (1L << wr->IOSer.io_Message.mn_ReplyPort->mp_SigBit)
    |  (1L << RexxPort.mp_SigBit);

  KeepGoing = TRUE;
  while( KeepGoing )
    {

    if (( rmptr = (struct RexxMsg *) GetMsg(&RexxPort)) == NULL )
      {
      Wait( waitmask );
      rmptr = (struct RexxMsg *) GetMsg(&RexxPort);
      }
    if (rmptr)
      {
      if (rmptr->rm_Node.mn_Node.ln_Type == NT_REPLYMSG )
        freemsglink(rmptr);
      else
        {
        Token = rmptr->rm_Args[0];
        for (i=0; i < numcmds; i++)
          if ( StrcmpN(rmptr->rm_Args[0], validargs[i], 3L) == NULL)
            break;

        if (rmptr->rm_Action & 15L)
          Token = rmptr->rm_Args[1];
        else
          nexttoken();

        /* these are set here, and only changed in case of error */
        rmptr->rm_Result1 = 0;           /* return code      */
        rmptr->rm_Result2 = 0;           /* secondary result */

        switch (i)
          {
          case 0:    /* RXD */
            if (StrcmpN( Token, "ON", 2L) == NULL) {
              listening = 1;
              RestartSerIO();
              }
            else  if (StrcmpN( Token, "OFF", 3L) == NULL) {
              listening = 0;
              FlushSerIO();
              }
            else  rmptr->rm_Result1 = 18;
            break;
          case 1:    /* TXD */
            if (strcmp( Token, "ON", 2L) == NULL) talking = 1;
            else  if (strcmp( Token, "OFF") == NULL) talking = 0;
            else  rmptr->rm_Result1 = 18;
            break;
          case 2:    /* SEND   */
          case 3:    /* LSEND */
            for (j=0; Token[j]; j++)
              if (Token[j] == '\r')
                Token[j] = '\n';
            strcpy(sendstring, Token);
            if (i == 3) {
              strcat(sendstring, "\n");
              }
            sending = 1; sendoffset = 0;
            break;
          case 4:    /* MATCH */
            if (*Token == 'c' || *Token == 'C')
              {
              clearmatch();
              break;
              }
            if (*Token >= '0' && *Token <= '9')
              {
              j = atoi(Token);
              for (i=0; i<MAX_MATCH; i++)
                table[j][i] = '\0';
              nexttoken();
              strcpy(pattern [j], Token);
              }
            else
              rmptr->rm_Result1 = 18;
            break;
          case 5:    /* SCAN */
            if (strcmp( Token, "ON", 2L) == NULL) scan = 1;
            else  if (strcmp( Token, "OFF") == NULL) scan = 0;
            else  rmptr->rm_Result1 = 18;
            break;
          case 6:    /* STATUS */
            buildstatus();
            if (rmptr->rm_Action & (RXFF_RESULT))
              rmptr->rm_Result2 = (long)CreateArgstring(status,
                            (long)strlen(status));
            break;
          case 7:    /* CONNECT */
            if (*Token) {
              for (j = 0; Token[j] != ' '; j++) {
                portname[j] = Token[j];
                }
              portname[j] = '\0';
              nexttoken();
              sprintf(command,"%s", Token);
              connected = 1;
              }
            else {
              portname[0] = '\0';
              connected = 0;
              }
            break;
          case 8:    /* CTRL */
            if (*Token)
              strcpy(macroport, Token);
            break;
          case 9:    /* COMM */
            Options.Baud = atoi(Token);
            nexttoken();
            if (*Token == 'N')  {
              Mode = NOECHO;
              }
            if (*Token == 'E')  {
              Mode = MODEECHO;
              }
            nexttoken();
            if (StrcmpN( Token, "XE", 2L) == NULL)
              Options.Xon = 1;
            if (StrcmpN( Token, "XD", 2L) == NULL)
              Options.Xon = 0;
            nexttoken();
            if (*Token == 'T')
              translate = '\x7f';
            if (*Token == 'N')
              translate = '\xff';
            setdefaults();

          case 10:    /* ABORT */
            sending = 0;
            break;

          case 11:    /* DIE */
            KeepGoing = FALSE;
            break;

          default:
            rmptr->rm_Result1 = 5;           /* return code      */
            break;
          }
        ReplyMsg(rmptr);                 /* send it back     */
        }
      }

    /*-----  Send string out serial port  -------*/

    if (talking) {
      while (sending) {
        if (sendstring[sendoffset] == '\n')
          sendstring[sendoffset] = '\r';
        SerIOPut(sendstring[sendoffset++]);
        if (sendstring[sendoffset] == 0)
          sending = 0;
        }
      }
     /*----- Drain the Serial Port Buffer-------------*/

    if (listening)
      {
      if ( temp = CheckSerIO() )
      {
        do
          {
          c = SerIOGet() & translate;
          PutFormat(c);
          } while( CheckSerIO() );
        PutFormat((UBYTE)'\xff');  /* squirt out all received chars */
        }
      }
    }          /* end while ( keepgoing )    */

  while (outstanding_rexx_commands)
    {
    printf("sermod: Outstanding commands: %d\n",outstanding_rexx_commands );
    if (( rmptr = (struct RexxMsg *) GetMsg(&RexxPort)) == NULL )
      {
      Wait( 1L << RexxPort.mp_SigBit );
      rmptr = (struct RexxMsg *) GetMsg(&RexxPort);
      }
      if (rmptr) {
      if( rmptr->rm_Node.mn_Node.ln_Type == NT_REPLYMSG ) {
        freemsglink(rmptr);
        }
      else {
        if (StrcmpN( rmptr->rm_Args[0], "REALLY_DIE", 10L) == NULL)
          while( mslist ) {
            free_rexx_command( mslist->mes );
            mslist2 = mslist->next;
            FreeMem( mslist, (long)sizeof(struct rx_mes) );
            mslist = mslist2;
            }

          ReplyMsg(rmptr);
          break;
          }
      }
    }
  cleanup();
  exit( 0 );
} /* end of main */
