/*
        This file is part of the cFos project,
        Copyright (c) 1993 by Martin Winkler, Christoph Lders

        This file contains the Hayes modem command emulator.


        You are allowed to use, copy, distribute, publish, redistribute
        and/or modify this program under the terms of the 'cFos public
        licence and warranty'.

        This program is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

        See COPYING.CF for the 'cFos public license and warranty'.
*/


#define MAXLEN 70

#include <string.h>
#include <stdarg.h>

#include "vars.h"


extern char banner1[], banner2[];

word sreg[MAXPORTS][MAXREGS];
char dialnumber[MAXPORTS][MAXDIGITS+1];
byte diallen[MAXPORTS];
MODEM_SETTINGS hayes[MAXPORTS];
char profile[PATHLEN];

static byte nbcount[MAXPORTS];
static char number[MAXPORTS][MAXDIGITS+1];
static char cmdline[MAXPORTS][MAXLEN+1];
static short cmdcount[MAXPORTS];

static char *verbal_result[] = { "OK",
                                 "CONNECT",
                                 "RING",
                               "\nNO CARRIER",
                                 "ERROR",
                                 NULL,
                                 "NO DIALTONE",
                                 "BUSY",
                                 "NO ANSWER",
                                 NULL,
                                 NULL,
                                 "RINGING",
                                 "VOICE",
                                 NULL };                /* "CONNECT 9600" */

static char *dreason_text[] = { "None",
                                "Passive",
                                "Active",
                                "Disconnect B3 timeout",
                                "Disconnect D timeout",
                                "CAPI reset",
                                "Connect timeout",
                                "Inactivity timeout" };


static char *service_text[] = { NULL,
                                "VOICE",
                                NULL,
                                NULL, // "X21",
                                NULL, // "FAX_G4",
                                NULL, // "BTX64",
                                NULL,
                                "",
                                NULL, // "X25",
                                NULL,
                                NULL,
                                NULL,
                                NULL,
                                NULL,
                                NULL,
                                NULL, // "BTX"
                              };


static word bauds[] = { 1200,
                        1275,
                        7512,
                        2400,
                        4800,
                        9600,
                        14400,
                        19200,
                        48000,
                        56000 };



word sreg_bounds [MAXREGS][2] = { {     0, 0xffff},     /* S0 */
                                  {     0, 0xffff},     /* S1 */
                                  {     0,   0xff},     /* S2 */
                                  {     0, 0xffff},     /* S3 */
                                  {     0, 0xffff},     /* S4 */
                                  {     0, 0xffff},     /* S5 */
                                  {     0, 0xffff},     /* S6 */
                                  {     0, 0xffff},     /* S7 */
                                  {     0, 0xffff},     /* S8 */
                                  {     0, 0xffff},     /* S9 */
                                  {     0, 0xffff},     /* S10 */
                                  {     0, 0xffff},     /* S11 */
                                  {     0, 0xffff},     /* S12 */
                                  {     0, 0xffff},     /* S13 */
                                  {     0, 0xffff},     /* S14 */
                                  {     0,   0xff},     /* S15 */
                                  {     0,   0xff},     /* S16 */
                                  {     0,   0xff},     /* S17 */
                                  {   '0',    '9'},     /* S18 */
                                  {     0, 0xffff},     /* S19 */
                                  {     0,   0xff},     /* S20 */
                                  {     0,   0xff},     /* S21 */
                                  {     1,   2048},     /* S22 */
                                  {     0, 0xffff},     /* S23 */
                                  {     0, 0xffff},     /* S24 */
                                  {     0, 0xffff},     /* S25 */
                                  {     1,      7},     /* S26 */
                                  {     0,   0xff},     /* S27 */
                                  {     1,   2048},     /* S28 */
                                  {     0,      3},     /* S29 */
                                  {     0, 0xffff},     /* S30 */
                                  {     0, 0xffff},     /* S31 */
                                  {     0, 0xffff},     /* S32 */
                                  {     0, 0xffff},     /* S33 */
                                  {     0, 0xffff},     /* S34 */
                                  {     0, 0xffff},     /* S35 */
                                  {     0, 0xffff},     /* S36 */
                                  {     0, 0xffff},     /* S37 */
                                  {     0, 0xffff},     /* S38 */
                                  {     0, 0xffff},     /* S39 */
                                  {     0, 0xffff},     /* S40 */
                                  {     0, 0xffff},     /* S41 */
                                  {     0, 0xffff},     /* S42 */
                                  {     0, 0xffff},     /* S43 */
                                  {     0, 0xffff},     /* S44 */
                                  {     0, 0xffff},     /* S45 */
                                  {     0, 0xffff},     /* S46 */
                                  {     0, 0xffff},     /* S47 */
                                  {     0, 0xffff},     /* S48 */
                                  {     0, 0xffff},     /* S49 */
                                  {     0, 0xffff},     /* S50 */
                                  {     0, 0xffff},     /* S51 */
                                  {     0, 0xffff},     /* S52 */
                                  {     0, 0xffff},     /* S53 */
                                  {     0, 0xffff},     /* S54 */
                                  {     0, 0xffff},     /* S55 */
                                  {     0, 0xffff},     /* S56 */
                                  {     0, 0xffff},     /* S57 */
                                  {     0, 0xffff},     /* S58 */
                                  {     0, 0xffff},     /* S59 */
                                };

char *itoa (int, char*, int);


static void stuff_char2 (int c, unsigned short cport)
{
   if (c == 10)
      stuff_char (13, cport);
   stuff_char ((byte) c, cport);
}


static int mycprintf (unsigned short cport, char *msg, ...)
{
   int result;
   va_list argptr;

   va_start (argptr, msg);
   result = _output ((int (*)(int, int)) stuff_char2, cport, NULL, msg, argptr);
   va_end (argptr);

   return result;
}


static void printout (int cport, const char *string)
{
   while (*string)
      stuff_char2 (*string++, cport);
}


static void hayes_init_port (short i)
{
   short j;

   if (carrier_detect[i])
      disconnect_wait (i);

   cmdcount[i] = 0;
   nbcount[i] = 0;
   offhook[i] = 0;

   for (j=0; j<MAXREGS; j++)
      sreg[i][j] = 0;

   sreg[i][2] = '+';            /* escape char */
   sreg[i][7] = 60;             /* number of sec modem waits for a carrier */
   sreg[i][SREG_X1] = 0x7fff;   /* enable all codes & comments */
   sreg[i][15] = 0x83;          /* b-channel ID */
   sreg[i][16] = 7;             /* SI */
   sreg[i][18] = '0';           /* source eaz */
   sreg[i][20] = 1;             /* b2 prot, def = X75 */
   sreg[i][21] = 4;             /* b3 prot, def = transp. */
   sreg[i][22] = capi_framelen; /* b2 framelen (!) */
   sreg[i][23] = 3;             /* b2 linkadr a */
   sreg[i][24] = 1;             /* b2 linkadr b */
   sreg[i][25] = 8;             /* b2 modulo size */
   sreg[i][26] = 2;             /* b2 window size, capi def == 7 (!) */
   sreg[i][27] = 64;            /* b2 v110 user rate */
   sreg[i][28] = min (2048, capi_framelen);
   sreg[i][36] = 8;             /* b3 modulo mode */
   sreg[i][43] = 0x0840;        /* lo = def v110 user rate, hi = v110 */

   for (j=50; j<60; j++)
      sreg[i][j] = 0x800;       /* low = inactive, hi = v110 */

   for (j=0; j<MAXPORTS; j++) {
      sreg[j][13] = 1023;          /* serviced eaz mask */
      sreg[j][14] = 0x80;          /* serviced si mask */
      sreg[j][40] = 0;             /* controller */
      sreg[j][41] = 63;            /* infomask low */
      sreg[j][42] = 0;             /* infomask hi */
   }

   hayes[i].x = 9;
   hayes[i].spv = 0;
   hayes[i].echo = 1;
   hayes[i].hangup = 0;
   hayes[i].quiet = 0;
   hayes[i].verbal = 1;
   hayes[i].debug = 0;
   hayes[i].connecting = 0;
   hayes[i].eaz = '0';
}


void hayes_result (word cport, int number, ...)
{
   char *cp1="", *cp2="", *cp, extra[50];
   va_list argptr;
   int type, enable=1, i, bundle, cnt, sync;
   word b2, b3, si, asi, index;
   long bps;

#ifndef NDEBUG
   if (number < 0 || number > 13) {
      myaprintf ("hayes_result: unknown result code %i\n", number);
      return;
   }
#endif

   va_start (argptr, number);

   switch (number) {
      case HAYES_CONNECT:            /* CONNECT */
         cnt=0;
         b2 = sb2prot[cport];
         b3 = sreg[cport][21];
         si = sservice[cport];
         asi = sservadd[cport];
         if (hayes[cport].connecting) {         /* active */
            bundle = (asi == 128 && manufact == MAN_TELES && is_bundle);
         } else {                               /* passive */
            bundle = 0;                 /* may be changed by MANUFACT_IND */
         }

         if (sreg[cport][SREG_X1] & 1) {
            if ((cp = service_text [si]) == NULL)
               cnt += mysprintf (extra+cnt, " ?");
            else
               cnt += mysprintf (extra+cnt, " %s", cp);

            if (si == 7) {                 // Daten
               switch (b2) {
                  case 1:                  // X.75
                     switch (b3) {
                        case 1: cp2 = "T70NL"; break;
                        case 2: cp2 = "ISO8208"; break;
                        case 3: cp2 = "T90"; break;
                        case 4: cp2 = ""; break;
                        default: cp2 = "?"; break;
                     }
                     bps = (sreg[cport][SREG_X1] & 2)? (bps = bundle? 128000L: 64000L): 9600L;
                     mysprintf (statstr[cport]+STAT_PROT, "X75 %-9s", cp2);
                     statstr[cport][STAT_BUNDLE] = bundle? 'B': ' ';
                     cnt += mysprintf (extra+cnt, "%lu", bps);
                     if (sreg[cport][SREG_X1] & 0x10)
                        cnt += mysprintf (extra+cnt, "/X75/%02x%02x%s%s", si, asi, cp2, bundle? "/BUNDLE": "");
                     break;

                  case 8:                  // V.110
                  case 9:
                  case 0xa:
                     sync = 0;
                     if ((asi & 0xc7) == 0x40) {
                        cp = "V110";
                        bps = 38400;
                        index = 0xfffe;
                     } else if ((asi & 0xc0) == 0xc0) {
                        cp = "V110";
                        index = asi & 7;
                     } else if ((asi & 0xf0) == 0xa0) {
                        cp = "V110";
                        sync = 1;
                        index = asi & 15;
                     } else if ((asi & 0xf0) == 0xb0) {
                        cp = "V120";
                        index = asi & 15;
                     } else
                        index = 0;

                     if (index < 16) {
                        if ((asi & 15) == 15) {
                           cnt += mysprintf (extra+cnt, " INBAND");
                           index = 0xffff;
                        } else if ((asi & 15) > 9) {
                           cnt += mysprintf (extra+cnt, " RATE=%02x", asi & 15);
                           index = 0xffff;
                        } else
                           bps = (dword) bauds[index];
                     }

                     if (index != 0xffff)
                        cnt += mysprintf (extra+cnt, "%lu", bps);
                     if (sreg[cport][SREG_X1] & 0x10)
                        cnt += mysprintf (extra+cnt, "/%s%s/%02x%02x", cp, sync? "/SYN": "", si, asi);
                     i = mysprintf (statstr[cport]+STAT_PROT, "%s %-8lu", cp, bps);
                     statstr[cport][STAT_BUNDLE] = sync? 'S': 'A';
                     cp1 = extra;
                     break;

                  default:
                     mysprintf (statstr[cport]+STAT_PROT, "B2=%02x", b2);
                     cnt += mysprintf (extra+cnt, " B2=%02x", b2);
                     break;
               }

            } else {            // SI != 7
               cnt += mysprintf (extra+cnt, "/%02x%02x", si, asi);
            }
         }
         extra[cnt] = 0;
         cp1 = extra;

         memcpy3 (statstr[cport]+STAT_RX-3, " R:     ", 8);
         memcpy3 (statstr[cport]+STAT_TX-3, " T:     ", 8);
         memcpy3 (statstr[cport]+STAT_CHARGE, "C:   ", 5);
         break;

      case HAYES_RINGING:
         if (!(sreg[cport][SREG_X1] & 0x04))
            enable = 0;
         break;

      case HAYES_RING:
         if (sreg[cport][SREG_X1] & 0x08) {
            cp1 = " ";
            extra [mysprintf (extra, "%s/%02x%02x", callerid [cport], sservice[cport], sservadd[cport])] = 0;
            cp2 = extra;
         }
         for (i=STAT_NB; i<80 && callerid[cport][i-STAT_NB]; i++)
            statstr[cport][i] = callerid [cport][i-STAT_NB];
         for (; i<80; i++)
            statstr[cport][i] = ' ';
         break;

      case HAYES_NO_CARRIER:
         type = va_arg (argptr, int);
         switch (type) {
            case 0x3301:        /* Fehler beim Aufbau D-Kanal Ebene 1 */
            case 0x3302:        /* Fehler beim Aufbau D-Kanal Ebene 2 */
            case 0x3305:        /* Abbruch D-Kanal Ebene 1 */
            case 0x3306:        /* Abbruch D-Kanal Ebene 2 */
            case 0x3307:        /* Abbruch D-Kanal Ebene 3 */
            case 0x348a:        /* No channel available */
            case 0x34a0:        /* Outgoing calls barred */
            case 0x34a3:        /* Nonexistent CUG */
            case 0x34d9:        /* Network congestion */
            case 0x34f0:        /* Local procedure error */
               number = HAYES_NO_DIAL_TONE;
               break;
            case 0x34a1:        /* User access busy */
            case 0x34bb:        /* User busy */
            case 0x34bd:        /* Incoming calls barred */
            case 0x34be:        /* Call rejected */
               number = HAYES_BUSY;
               break;
            case 0x34ba:
               number = HAYES_NO_ANSWER;
               break;
         }

         if (type != 0 && (sreg[cport][SREG_X1] & 0x20)) {
            itoa (type, extra, 16);
            cp1 = "/CAUSE=";
            cp2 = extra;
         }
         break;

      case HAYES_ERROR:
         type = va_arg (argptr, int);
         switch (type) {
            case HAYES_B2_ERR:
               cp1 = "/B2";
               break;
            case HAYES_B3_ERR:
               cp1 = "/B3";
               /* FALL-THRU */
            case 0:
               break;
#ifndef NDEBUG
            default:
               myaprintf ("hayes_result: unknown ERROR type\n");
               break;
#endif
         }
         if (!(sreg[cport][SREG_X1] & 0x40)) cp1 = "";
         break;
   }

   if (enable) {
      if (hayes[cport].verbal) {
#ifndef NDEBUG
         if (!verbal_result[number])
            myaprintf ("hayes_result: result code %i not supported\n", number);
#endif
         mycprintf (cport, "%s%s%s\n", verbal_result[number], cp1, cp2);

      } else
         mycprintf (cport, "%i\n", number);
   }

   switch (number) {
      case HAYES_RING:                  /* RING */
         sreg[cport][1]++;              /* ring count */
         break;
      case HAYES_NO_CARRIER:            /* NO CARRIER */
         sreg[cport][1] = 0;
         break;
   }

   disp_stat (cport);
}


static short eval_hayes (unsigned short cport)
{
   short i, j, k, flag;
   word cpu_flags;
   char *cmd=cmdline[cport], *cp;

   cmd[cmdcount[cport]] = 0;
   if (strnicmp (cmd, "AT", 2) != 0)
      return 0;
   if (hayes[cport].echo)
      stuff_char (10, cport);

   cmd += 2;

   while (*cmd)
      switch (toupper (*cmd)) {
         case ' ':
         case '\t':
            cmd++;
            break;

         case 'D':                         /* ATDn, ATDL */
            cmd++;
            if (carrier_detect [cport])
               return -1;
            flag = 1;
            if (toupper (*cmd) == 'S') {
               cmd++;
               switch (*cmd) {
                  case '0':
                     strcpy (number[cport], "02289111041");
                     nbcount[cport] = 11;
                     break;
                  default:
                     return -1;
               }

            } else if (toupper (*cmd) != 'L') {
               hayes[cport].spv = 0;
               nbcount[cport] = 0;
               flag = 0;

               while (*cmd) {
                  switch (toupper (*cmd)) {
                     case 'V':
                        hayes[cport].spv = 1;           /* enable SPV */
                        break;
                     case '0':
                     case '1':
                     case '2':
                     case '3':
                     case '4':
                     case '5':
                     case '6':
                     case '7':
                     case '8':
                     case '9':
                     case 'A':                  /* those chars are valid ;-) */
                     case 'B':
                     case 'C':
                     case 'D':
                     case '*':
                     case '#':
                        if (nbcount[cport] < MAXDIGITS)
                           number[cport][nbcount[cport]++] = *cmd;
                        break;
                  }
                  cmd++;
               }
            }
            number[cport][nbcount[cport]] = 0;

            if (number[cport][0]) {
               if (flag)
                  mycprintf (cport, "Dialing.. %s\n", number[cport]);
               strcpy ((char*) dialnumber[cport], (char*) number[cport]);
               diallen[cport] = nbcount[cport];
               if (connect (cport, hayes[cport].spv) == 0)
                  return -1;
               offhook[cport] = 0;
               for (i=STAT_NB; i<80 && i-STAT_NB<diallen[cport]; i++)
                  statstr[cport][i] = dialnumber [cport][i-STAT_NB];
               for (; i<80; i++)
                  statstr[cport][i] = ' ';
               memcpy3 (statstr[cport]+STAT_RX-3, "                       ", 23);
               hayes[cport].connecting = 1;
               return 0;
            } else
               return -1;

         case 'A':                              /* ATA */
            if (sreg[cport][1])                 /* there was a ring? */
               capi_ans (plci [cport], 0);
            else
               hayes_result (cport, HAYES_NO_CARRIER, 0);
            return 0;

         case 'L':
         case 'M':
         case 'N':
         case 'K':
         case 'F':
            cmd++;
            if (isdigit (*cmd))
         case 'P':
         case 'T':
               cmd++;
            break;

         case 'E':                         /* ATEn */
            switch (*(++cmd)) {
               case '0':
               case '1':
                  hayes[cport].echo = *cmd - '0';
                  break;
               default:
                  return -1;
            }
            cmd++;
            break;

         case 'B':                         /* ATBn */
            switch (*(++cmd)) {
               case '0':
                  sreg[cport][16] = 7;          /* si = daten 64k */
                  sreg[cport][17] = 0;          /* add si */
                  sreg[cport][20] = 1;          /* b2 = x75 */
                  sreg[cport][21] = 4;          /* b3 = transp. */
                  break;
               case '1':                        /* -- v110 38400 async -- */
                  if (is_v110) {
                     sreg[cport][16] = 7;          /* si = daten 64k */
                     sreg[cport][17] = 64;         /* add si */
                     sreg[cport][20] = 8;          /* b2 = v110 */
                     sreg[cport][21] = 4;          /* b3 = transp. */
                     sreg[cport][27] = 64;         /* v110 user rate = 38400 8n1 */
                     break;
                  } else
                     return -1;
               case '2':                        /* -- v110 19200 async -- */
                  if (is_v110) {
                     sreg[cport][16] = 7;          /* si = daten 64k */
                     sreg[cport][17] = 0xc7;       /* add si */
                     sreg[cport][20] = 8;          /* b2 = v110 */
                     sreg[cport][21] = 4;          /* b3 = transp. */
                     sreg[cport][27] = 0xc7;       /* v110 user rate = 19200 8n1 */
                     break;
                  } else
                     return -1;
               case '3':                        /* -- x75 bundeling -- */
                  if (manufact == MAN_TELES && is_bundle) {
                     sreg[cport][16] = 7;          /* si = daten 64k */
                     sreg[cport][17] = 128;        /* add si */
                     sreg[cport][20] = 1;          /* b2 = x75 */
                     sreg[cport][21] = 4;          /* b3 = transp. */
                     break;
                  } else
                     return -1;
               default:
                  return -1;
            }
            cmd++;
            break;

         case 'V':                         /* ATVn */
            switch (*(++cmd)) {
               case '0':
               case '1':
                  hayes[cport].verbal = *cmd - '0';
                  break;
               default:
                  return -1;
            }
            cmd++;
            break;

         case 'Q':                         /* ATQn */
            switch (*(++cmd)) {
               case '0':
               case '1':
                  hayes[cport].quiet = *cmd - '0';
                  break;
               default:
                  return -1;
            }
            cmd++;
            break;

         case 'H':                         /* ATHn */
            switch (i = *(++cmd)) {
               case '0':
                  if (carrier_detect[cport])
                     disconnect_wait (cport);
                  offhook[cport] = 0;
                  break;
               case '1':
                  if (sreg[cport][1] != 0)
                     disconnect (cport, 0xbe);
                  if (!carrier_detect[cport])
                     offhook[cport] = 1;
                  break;
               default:
                  return -1;
            }
            hayes[cport].hangup = i - '0';
            cmd++;
            break;

         case 'I':                         /* ATIn */
            cmd++;
            if (isdigit (*cmd)) {
               i = *cmd - '0';
               cmd++;
            } else
               i = 0;

            switch (i) {
               case 0:
                  mycprintf (cport, "\n%s"
#ifndef NDEBUG
                  " -- DEBUG VERSION"
#endif
                  "\n%sType ati1, ati2 or ati4 for statistics\n\n", banner1, banner2);
                  break;

               case 1:
                  mycprintf (cport, "%s\n", statstr [cport]+5);
                  break;

               case 2:
                  mycprintf (cport,
                             "\n      Last call:"
                             "\n"
                             "\n   Bytes sent:     %-12luBlocks sent:     %-12lu"
                             "\n   Bytes received: %-12luBlocks received: %-12lu"
                             "\n"
                             "\n      Last inbound call:"
                             "\n"
                             "\n   Service:        %02x          CallerID:    '%s'"
                             "\n   ServiceAdd:     %02x          EAZ:         '%c'"
                             "\n"
                             "\n      Last outbound call:"
                             "\n"
                             "\n   Charge:      %lu"
                             "\n"
                             "\n      Last disconnect: %s"
                             "\n"
                             "\n   Cause:       %04x"
                             "\n   B3-Cause:    %04x"
                             "\n\n",
                             txbytes[cport], txblocks[cport],
                             rxbytes[cport], rxblocks[cport],
                             rservice[cport], callerid[cport],
                             rserviceadd[cport], eaz[cport],
                             charge[cport],
                             dreason_text[dreason[cport]],
                             disconnectinfo[cport], disconnectb3info[cport] );
                  break;

               case 4:
                  mycprintf (cport, "\nMessage dump:\n");
                  for (k=0; k<MAXINFO; k++) {
                     i = (infocnt [cport]+k) % MAXINFO;
                     cp = (char*) infoelement [i] [cport];
                     if (*(word*) cp != 0) {
                        mycprintf (cport, "Record %i:\n", i);
                        for (i=0; i<*(word*) cp; i+=16) {
                           printout (cport, "   ");
                           for (j=i; j<*(word*) cp && j<i+16; j++)
                              mycprintf (cport, "%02x%c", cp[j] & 0xff, (j%16)==7? '-': ' ');
                           for (;j<i+16; j++)
                              printout (cport, "   ");
                           stuff_char (' ', cport);
                           for (j=i; j<*(word*) cp && j<i+16; j++) {
                              stuff_char ((cp[j] & 0xff) < 32 ? '.' : cp[j] & 0xff, cport);
                           }
                           stuff_char2 ('\n', cport);
                        }
                     }
                  }
                  stuff_char2 ('\n', cport);
                  break;

               default:
                  return -1;
            }
            break;

         case 'X':                         /* ATXn */
            switch (hayes[cport].x = *(++cmd) - (byte) '0') {
               case 9:
                  sreg[cport][SREG_X1] = 1022;
                  break;
               case 8:
                  sreg[cport][SREG_X1] = 510;
                  break;
               case 7:
                  sreg[cport][SREG_X1] = 254;
                  break;
               case 6:                                  /* ERROR/<cause> */
                  sreg[cport][SREG_X1] = 126;
                  break;
               case 5:                                  /* NO CARRIER/<cause> */
                  sreg[cport][SREG_X1] = 62;
                  break;
               case 4:                                  /* CONNECT/... */
                  sreg[cport][SREG_X1] = 30;
                  break;
               case 3:                                  /* RING nb */
                  sreg[cport][SREG_X1] = 14;
                  break;
               case 2:                                  /* RINGING */
                  sreg[cport][SREG_X1] = 6;
                  break;
               case 1:                                  /* CONNECT rate */
                  sreg[cport][SREG_X1] = 2;
                  break;
               case 0:                                  /* CONNECT 9600 */
                  sreg[cport][SREG_X1] = 1;
                  break;
               default:
                  return -1;
            }
            cmd++;
            break;

         case 'Z':                         /* ATZ */
            hayes_init_port (cport);
            cpu_flags = mycli ();
            if (numofconnections == 0)
               api_reregister ();
            cmd++;
            if (isdigit (*cmd)) {
               i = *cmd - '0';
               cmd++;
            } else
               i = 0;
            j = -1;
            if (*_indos == 0)
               j = hayes_read_profile (cport, i);
            myseti (cpu_flags);
            if (j != -1)
               capi_listen_req ();
            break;

         case 'O':                         /* ATO */
            if (carrier_detect[cport])
               command_mode[cport] = 0;
            else
               return -1;
            cmd++;
            break;

         case 'S':                         /* ATS.. */
            cmd++;
            i = 0;
            if (!isdigit (*cmd))
               return -1;
            while (isdigit (*cmd))
               i = i*10 + *(cmd++) - '0';
            if (i >= MAXREGS)
               return -1;

            if (*cmd == '.') {
               ++cmd;
               j = 0;
               if (isdigit (*cmd))
                  while (isdigit (*cmd)) {
                     j = j*10 + *cmd - '0';
                     cmd++;
                  }
               else
                  return -1;

            } else if (*cmd == '?') {
               mycprintf (cport, "%05u\n", sreg[cport][i]);
               return 1;

            } else if (toupper (*cmd) == 'H') {
               cmd++;
               j = -2;
            } else if (toupper (*cmd) == 'L') {
               cmd++;
               j = -3;
            } else
               j = -1;

            if (*cmd != '=' || j>15)
               return -1;
            cmd++;

            k = 0;
            while (isdigit (*cmd))
               k = k*10 + *(cmd++) - '0';

            switch (j) {
               case -1:                 // ATSr=n
                  break;
               case -2:                 // ATSrH=n
                  k = ((word) (k & 0xff) << 8) | (sreg[cport][i] & 0xff);
                  break;
               case -3:                 // ATSrL=n
                  k = (k & 0xff) | ((word) sreg[cport][i] & 0xff00);
                  break;
               default:                 // ATSr.b=n
                  k &= 1;
                  k = k? (sreg[cport][i] | 1<<j): (sreg[cport][i] &= ~(1<<j));
            }

            if (i == 29 && k < sreg_bounds[29][1]) {
               sreg_bounds[22][1] = capi_framelen + k;
               sreg_bounds[28][1] = min (2048, capi_framelen)+k;
            }
            if (k<sreg_bounds[i][0] || k>sreg_bounds[i][1])
               return -1;       /* too low, or too high */

            sreg[cport][i] = k;

            if (i == 13 || i == 14 || i == 40 || i == 41 || i == 42) {
               for (j=0; j<MAXPORTS; j++)
                  sreg[j][i] = sreg[cport][i];
               capi_listen_req ();
            }
            break;

         case '?':
            printout (cport,
"\n         cFos help screen...."
"\n"
"\n  A      force Answer                     &V     View profile"
"\n  Bn     Protocol: 0=X75 SLP,             &Wn    Write profile to number n"
"\n         1=V110 38.4, 2=V110 19.2"
"\n  D..    Dial number                      #Rn    Reregister at CAPI; n=1: force"
"\n  En     command Echo"
"\n  Hn     0=terminate, 1=reject            S0     0=manual, 1=auto answer"
"\n  In     Inquiry                          S7     secs to wait for connection"
"\n  O      return On-line                   S11    Status linenumber"
"\n  Qn     Quiet mode: 1=suppress results   S16    Service Indicator"
"\n  Sr=n   Set register r to value n        S17    Add. Service Indicator"
"\n  Sr.b=n Set bit b in register r to n     S19    Inactivity timer in mins"
"\n  Sr?    query register r                 S20    B2 protocol"
"\n  Vn     0=numeric, 1=verbal results      S21    B3 protocol"
"\n  Xn     Result codes: 0=min, 9=max       S22    B2 framelength"
"\n  Zn     Reset                            S26    B2 window size"
"\n                                          S27    V110 user rate"
"\n  &Dn    Debug: 1=status bar on           S28    max B2 Framelen for send"
"\n  &En    Serviced EAZ: '1'-'9', '*'=all   S29    B3 protocol overhead"
"\n  &F     reset to Factory defaults"
"\n\n");
            cmd++;
            break;

         case '&':
            cmd++;
            switch (toupper (*cmd)) {
               case 'D':                         /* AT&Dn */
                  switch (*(++cmd)) {
                     case '0':
                     case '1':
                        hayes[cport].debug = *cmd - '0';
                        break;
                     default:
                        return -1;
                  }
                  cmd++;
                  break;

               case 'L':                        /* at&l */
                  cmd++;
                  if (*cmd == '*') {            // all eaz's
                     i = 0x1023;
                     cmd++;
                  } else {                      // only selected ones
                     i = 0;
                     while (isdigit (*cmd)) {
                        i |= (1 << (*cmd - '0'));
                        cmd++;
                     }
                  }
                  sreg[cport][13] = i;          /* serviced eaz mask */
                  break;

               case 'E':                        /* at&e */
                  cmd++;
                  if (!isdigit (*cmd))
                     return -1;
                  hayes[cport].eaz = *cmd;
                  sreg[cport][18] = i;          /* source eaz */
                  cmd++;
                  break;

               case 'F':
                  cmd++;
                  hayes_init_port (cport);
                  cpu_flags = mycli ();
                  if (numofconnections == 0)
                     api_reregister ();
                  myseti (cpu_flags);
                  break;

               case 'V':                        /* at&v */
                  mycprintf (cport, "\nCurrent profile..\n"
                                    "E%i  H%i  Q%i  V%i  X%i  &D%i  &E%c  &L",
                                    hayes[cport].echo,
                                    hayes[cport].hangup,
                                    hayes[cport].quiet,
                                    hayes[cport].verbal,
                                    hayes[cport].x,
                                    hayes[cport].debug,
                                    hayes[cport].eaz
                                    );
                  for (i=0; i<10; i++)
                     if ((1 << i) & sreg[cport][13])
                        stuff_char (i+'0', cport);
                  stuff_char2 ('\n', cport);
                  stuff_char2 ('\n', cport);

                  for (i=0; i<MAXREGS; i++) {
                     mycprintf (cport, "S%02i=%05u  ", i, sreg[cport][i]);
                     if (i%5 == 4)
                        printout (cport, "\n");
                  }
                  printout (cport, "\n");
                  cmd++;
                  break;

               case 'W':                        /* AT&W */
                  cmd++;
                  if (isdigit (*cmd)) {
                     i = *cmd - '0';
                     cmd++;
                  } else
                     i = 0;
                  if (*_indos == 0) {
                     cpu_flags = mycli ();
                     if ((j = myopen (profile, 1)) != -1) {
                        mylseek (j, 1024*i    , SEEK_SET);
                        mywrite (j, &hayes[cport], sizeof (hayes[0]));
                        mylseek (j, 1024*i+ 50, SEEK_SET);
                        mywrite (j, number[cport], sizeof (number[0]));
                        mylseek (j, 1024*i+100, SEEK_SET);
                        mywrite (j, sreg[cport], sizeof (sreg[0]));
                        myclose (j);
                     }
                     myseti (cpu_flags);
                     if (j == -1)
                        return -1;
                     break;
                  }

               default:
                  return -1;
            }
            break;


         /*
          *     Now follow some debug commands  ------------------------
          */

         case '#':
            cmd++;
            switch (toupper (*cmd)) {
               case 'C':                        /* AT#C */
#ifndef NDEBUG
                  _adisp_char ('\xc');
#endif
                  break;

               case 'P':                        /* AT#P */
#ifndef NDEBUG
                  debug_funcs (0x80);           /* call count */
#endif
                  break;

               case 'R':                        /* AT#Rn api_reregister */
                  if (isdigit (*cmd)) {
                     i = *cmd - '0';
                     cmd++;
                  } else
                     i = 0;
                  cpu_flags = mycli ();
                  if (i != 0 || numofconnections == 0)
                     api_reregister ();
                  myseti (cpu_flags);
                  break;

               case 'T':                        /* AT#T */
                  set_dosdate (dosdate () + timediff);
                  break;

               case 'Z':                        /* AT#Z */
#ifndef NDEBUG
                  debug_funcs (0x81);           /* zero call count */
#endif
                  break;

               default:
                  return -1;

            }
            cmd++;
            break;

         default:
            return -1;
      }

   return 1;
}


void trans_to_hayes (unsigned short cport, unsigned char c)
{
   if (!dtr_high [cport] ||             /* no DTR, modem dead */
       disco[cport])                    /* disconnect in progress */
      return;

   if (hayes[cport].connecting) {
      disconnect (cport, 0);
      return;
   }

   if (hayes[cport].echo) {
      if (c != 127 && c != 8)
         stuff_char (c, cport);
   }

   switch (c) {
      case 8:
      case 127:
         if (cmdcount[cport] > 0) {
            cmdcount[cport]--;
            stuff_char (8, cport);
         }
         break;

      case 13:
         switch (eval_hayes (cport)) {
            case -1:
               hayes_result (cport, HAYES_ERROR, 0);
               break;
            case 1:
               hayes_result (cport, HAYES_OK);
            case 0:
               break;
         }
         disp_stat (cport);
         cmdcount[cport] = 0;
         /* FALL THRU */

      case 10:                          /* just ignore */
         break;

      default:
         if (cmdcount[cport] < MAXLEN) {
            cmdline[cport][cmdcount[cport]] = c;
            cmdcount[cport]++;
         }
   }
}


void hayes_init (void)
{
   int i;

   disp_init ();
#ifndef NDEBUG
   if (adisp_init ())
      printf ("   Alternate monitor detected\n");
#endif

   for (i=0; i<MAXPORTS; i++) {
      hayes_init_port (i);
      disp_stat (i);
      hayes_read_profile (i, 0);
   }
}



extern byte disp_txcnt [MAXPORTS], disp_rxcnt [MAXPORTS];

void disp_stat (int cport)
{
   int i;
   static int in_disp=0;

   if (++in_disp == 1) {
      if (hayes[cport].debug && initialized [cport]) {
         statstr[cport][STAT_LED_A] = sreg[cport][0]? 'A': ' ';
         statstr[cport][STAT_LED_O] = offhook[cport]? 'O': ' ';
         mygotoxy (sreg[cport][11], 0);
         set_atb (0);
         for (i=0; statstr[cport][i]; i++) {
            if (i == STAT_RX+disp_rxcnt[cport] ||
                i == STAT_TX+disp_txcnt[cport]) {
               set_atb (1);
               _disp_char (statstr[cport][i]);
               set_atb (0);
            } else
               _disp_char (statstr[cport][i]);
         }
         disptimer[cport] = 18;
      }
   }
   in_disp--;
}


static int hayes_read_profile (int cport, int i)
{
   int j;

   if ((j = myopen (profile, 0)) != -1) {
      mylseek (j, 1024*i    , SEEK_SET);
      myread (j, &hayes[cport], sizeof (hayes[0]));
      mylseek (j, 1024*i+ 50, SEEK_SET);
      myread (j, number[cport], sizeof (number[0]));
      nbcount[cport] = strlen (number[cport]);
      mylseek (j, 1024*i+100, SEEK_SET);
      myread (j, sreg[cport], sizeof (sreg[0]));
      myclose (j);

      if (sreg[cport][29] < sreg_bounds[29][1]) {
         sreg_bounds[22][1] = capi_framelen + sreg[cport][29];
         sreg_bounds[28][1] = min (2048, capi_framelen) + sreg[cport][29];
      }

      if (sreg[cport][28] == 0)                 /* fixes zero send size  !!! */
         sreg[cport][28] = sreg[cport][22];     /* remove in future versions */

      for (i=0; i<MAXREGS; i++) {
         if (sreg[cport][i]<sreg_bounds[i][0])
            sreg[cport][i] = sreg_bounds[i][0];
         else if (sreg[cport][i]>sreg_bounds[i][1])
            sreg[cport][i] = sreg_bounds[i][1];
      }

      for (i=0; i<MAXPORTS; i++) {
         sreg[i][13] = sreg[cport][13];
         sreg[i][14] = sreg[cport][14];
         sreg[i][40] = sreg[cport][40];
         sreg[i][41] = sreg[cport][41];
         sreg[i][42] = sreg[cport][42];
      }
      return 0;
   }
   return -1;
}



/* --- eof hayes.c */
