/*
** most of the DLG extensions are in this file
**
** most others are in file.c, fileio.c, ansi.c, xenospell.c, xenorc.c
**
 **********************************************************************/

// Want to move the cookie stuff outta here eventually.

#include "Include.h"

#include <link/io.h>

#include <private/Version.h>
#define  ObjRev "1"
const UBYTE version[]="\0$VER: DLG ScreenEdit " BUILDVER "." ObjRev " ©" COPYRIGHT " by Digerati Dreams "__AMIGADATE__;


#define OBUFSIZE  1024L
#define LAST_HTYPE NEWS_MSG
#define AHEAD_NUM 2500
#define NUM_COOKIE_TOPICS 20
#define MAX_OUTFORTUNE 1200


APTR  oldWindowPtr;

BOOL  oldwindow_set;

char  out_buf[OBUFSIZE+1];    // output character buffer
char *dlg_initials;
char *hack_quote;             // name of quote file if hack&slash hack
char *cfg_fname;              // set during startup to EmacDLG.cfg path
char  color_ok;
char  id_ok;
char  ok_broadcast = TRUE;    // allows broadcast and sleepy timer messages
char  uploading;              // disallow broadcast and time updates during uploads
char  xl_no[11];
char  xl_yes[11];
char  xl_NO[11];
char  xl_YES[11];
char *Zhello = "In a message dated %d, %F wrote:%N";
char *Zabort = "Abort message? (yes/NO) ";
char *Zbye = "Save message and exit? (YES/no) ";
char *Zmore = "More (YES/no) ";
char *Ztop = "Press ESC ? for help      ^Z save & exit     ESC ESC abort     ESC Q quote";
char *Zqh = "RETURN quote line, ^XQ quote all, ESC Q back to message, ^C close window";
char *Zto =   "  To: ";
char *Zsub =  "Subj: ";
char *Znewto =  "[RETURN=keep] To: ";
char *Znewsub = "[RETURN=keep] Subj: ";
char *Zdir = "RAM:";          // file requester default directory
char *Zfquery = "Append fortune to your message";
char *Xmsgcolor = "CYAN";
char *Xquotecolor = "RED";
char *Xhelpcolor = "YELLOW";
char *Xhbcolor = "LGREEN";
char *Xwbcolor = "BLUE";
char *Xmlcolor = "WHITE";
char *Zwhoports;              // default is in code to prevent hacking
char *dlg_uname;
char ahead[AHEAD_NUM];
char bc_displayed = FALSE;

int init_read_result = FALSE;
int quote_style = 0;          // 0 for initials, 1 for first name

short out_ptr = 0;            // index to next char to put in buffer
short dlg_hdr_style;
short a_cnt = 0;
short a_next = 0;

long a_delay = 5;

struct IntuitionBase   *IntuitionBase;
struct ReqToolsBase    *ReqToolsBase;
struct Library         *DLGBase;
struct Process         *myproc;
struct File_Header      dlg_file_hdr;
struct Msg_Header      *dlg_quote;

static struct rtFileRequester *fr;

UBYTE hdr_changed = 0;

unsigned char hack_ignore;             // number of lines to ignore in hack&slash quote
unsigned char xl_access = 0;
unsigned char xl_acc_read = XL_SYSOP;
unsigned char xl_acc_write = XL_SYSOP;
unsigned char Zfask;                   // ask for fortunes?  0=never 1=ask 2=always
unsigned char Zftopic = 2;             // fortune cookie topic: 0=never 1=ask 2+=topic
unsigned char Zchuck;                  // chuck buck word wrap: 0=always, 1=not on "other" files

/// Message Header Types from dlg/msg.h (char *htype[])
char *htype[] = {
    "",         /* -1 */
    "Private Message",  /* 0 */
    "Public Message",   /* 1 */
    "Private Netmail",
    "Public Netmail",
    "Echomail",
    "UUCP",
    "Carbon Copy",
    "File Description",
    "Signature",
    "Bulletin",
    "Text",
    "Usenet",
    "News"
};
//-

/// Customizable language strings; to be moved to .lang file.
char *TEXT8  =  "Aborted";
char *TEXT9  =  "Mark set";
char *TEXT19 =  "Unknown command";
char *TEXT70 =  "Region copied";
char *TEXT76 =  "No mark set";
char *TEXT78 =  "Search";
char *TEXT79 =  "Not found";
char *TEXT81 =  "Reverse search";
char *TEXT84 =  "Replace";
char *TEXT86 =  "with";
char *TEXT109 =  "Key illegal in this window";
char *TEXT148 =  "Saving...";

// These are defined once here
char *TEXT94 = "No Mem";
char *TEXT16 = "No such function";
char *TEXT52 = "No such variable as '%s'";
char *TEXT130 = "Macro not defined";
char *TEXT152 = "No such file";
//-

/// Fortune cookie command structure
struct fortcmd
{
    char *cmd;
    char *desc;
} fc[NUM_COOKIE_TOPICS];
//-

/// Monsters for the idle blanker
char *monster[] =
{
"Mark Montminy", // monster zero will be rare
"Rock Lizard",
"Dark Troll",
"Broo",
"Dragonnewt",
"Jack O'Bear",
"Medhi Ali",
"Vampire",
"Sea Serpent",
"Klingon Warrior",
"Zombie",
"Cute Little Puppy",
"Barry Herbert",
"Gorp",
"Dream Dragon",
"Trollkin",
"Scorpion Man",
"Harpy",
"Guru",
};
//-

/// Intros for the idle blanker
char *intro[] = {
"You hear a noise up ahead...",
"Suddenly, from out of the darkness, a monster attacks!",
"Something foul is in this chamber...",
"There's something lurking here...",
"Suddenly, you forget which program you're running..."
};
//-

/// Treasures for the idle blanker
char *treasure[] = {
"Ring of Spell Checking",
"Scroll of Free Netmail",
"Jar of Fortune Cookies",
"Potion of Homebrew",
"Bag of Mouse Parts",
"Book of Fidonet Policy",
"Modem of Speed",
"CD of X-Rated GIFs",
"Staff of Programmers",
"Chip of 16 Million Colors",
"Guru Meditation Numbers",
"Book of Vogon Poetry",
"Bank of On-line Time",
};
//-

/// Stolen items in the idle blanker
char *stolen[] = {
"files",
"echos",
"lint",
"mail",
"savings bonds",
"floppies",
"windows",
"comic books",
"house plants",
"bad habits",
"money",
"chocolate",
"farm animals",
"kryptonite",
"credit cards",
"grandchildren",
"passwords",
"cable channels",
"jokes"
};
//-

/// Types of attacks for the idle blanker
char *blast[] = {
"hit",
"smack",
"slap",
"nuke",
"jab",
"hit",
"poke",
"stab",
"whack",
"hit",
"strike",
"pummel",
"kick",
"blast",
};
//-

#define NUM_MONSTERS (sizeof(monster)/sizeof(char*))
#define NUM_INTRO (sizeof(intro)/sizeof(char*))
#define NUM_TREASURES (sizeof(treasure)/sizeof(char*))
#define NUM_STOLEN (sizeof(stolen)/sizeof(char*))
#define NUM_HITS (sizeof(blast)/sizeof(char*))


// Local functions

int STD addline(BUFFER *, char *);
void broadcast(void);
int pick(int);
void str2cat(char *, char *, char *);
void str3cat(char *, char *, char *, char *);
int dlg_named_file(char *);
char *dest_address(void);
static void dlg_defaults(void);
static int get_a_char(void);
void eat_typeahead(void);
void opts_arg(char *, char *, int);
static int get_fortune_topics(void);
int ask_cookie_topic(int);
void gen_cookie(int, char *, int);
void h_instr(char *);
void h_insert(char);


/// dlg_get_filename()
char *dlg_get_filename(void)
{
    if (dlg_hdr_style == STYLE_FILEDESCR) {
   return dlg_file_hdr.Filename;
    } else {
   return dlg_body;
    }
}
//-

/// dlg_hdrstyle()
short dlg_hdrstyle(void)
{
    // 0=message, 1=file description, 2=other, 3=hacknslash
    return dlg_hdr_style;
}
//-

/// dlg_edit_type()
short dlg_edit_type(void)
{
    if (dlg_msg_hdr.TimesRead > LAST_HTYPE) return -1;
    return dlg_msg_hdr.TimesRead;
}
//-

/// dlg_hdr_tyoe
char *dlg_hdr_type(void)
{
    return htype[dlg_edit_type()+1];
}
//-

/// dlg_to
char *dlg_to(void)
{
    return dlg_msg_hdr.To;
}
//-

/// dlg_from()
char *dlg_from(void)
{
    return dlg_msg_hdr.From;
}
//-

/// dlg_subject()
char *dlg_subject(void)
{
    return (char *) dlg_msg_hdr.Title;
}
//-

/// dest_address()
char *dest_address(void)
{
    static char buf[20];
    short t;
    
    if (*buf) return buf;
    
    t = dlg_edit_type();
    if (t == PRINET_MSG || t == PUBNET_MSG) {

   strcpy(buf, "[");
   strcat(buf, int_asc(dlg_msg_hdr.DestNet));
   strcat(buf, "/");
   strcat(buf, int_asc(dlg_msg_hdr.DestNode));
   strcat(buf, "]");
    }
    else {
   *buf = 0;
    }
    return buf;
}
//-

/// newsubject()
void REG newsubject(char *ns)
{
    if (dlg_hdr_style == STYLE_MESSAGE) {
   bytecopy(dlg_msg_hdr.Title, ns, 71);
   hdr_changed = 1;
    }
}
//-

/// new recipient()
void REG newrecipient(char *ns)
{
    int mt;
    
    if (dlg_hdr_style == STYLE_MESSAGE) {
   // dlg.library
   Capitalize(ns);

   // if sending a local message, make sure user exists
   // Allow All in local public message areas

   mt = dlg_edit_type();
   if (mt == PRI_MSG ||
       (mt == PUB_MSG && strcmp(ns, "All") != 0)) {

       if (CheckUser(ns) == 0) {

      char msg[150];
      strcpy(msg, "User or group [");
      strcat(msg, ns);
      strcat(msg, "] does not exist");
      mlforce(msg);
      return;
       }
   }

   bytecopy(dlg_msg_hdr.To, ns, 35);
   hdr_changed = 1;
    }
}
//-

/// newzno
void REG newzno(char *ns)
{
   bytecopy(xl_no, ns, 10);
   mklower(xl_no);
   bytecopy(xl_NO, ns, 10);
   mkupper(xl_NO);
}
//-

/// newzyes()
void REG newzyes(char *ns)
{
   bytecopy(xl_yes, ns, 10);
   mklower(xl_yes);
   bytecopy(xl_YES, ns, 10);
   mkupper(xl_YES);
}
//-

/// newpromptstring()
void REG newpromptstring(char **prompt, char *ns)
{
    // memory leak but who cares?
    *prompt = strdup(ns);
}
//-

void _CXBRK(int n) { game_over(); }

/// ttopen()
/*
** This function is called once to set up the terminal device streams.
 ***********************************************************************/
STD ttopen(void)
{
   rawcon(1);
   return(1);
}
//-

/// xeno_init()
REG xeno_init()
{
    BPTR rh;
   
    // is this the right place for this?
    dlg_defaults();
   
    // default to editing a plain file
    dlg_hdr_style = STYLE_OTHER;

    if (dlg_named_file(dlg_header))
    {
      // check size of header file to determine
      // if we are editing a message, description, or other file
      // changed 5/3/97 by jg: use DLG functions for size

      ULONG sz;

      if (0 == FileSize(dlg_header,&sz))
      {
         if (sz == sizeof(dlg_msg_hdr))
         {
            // editing a message
            dlg_hdr_style = STYLE_MESSAGE;
            rh = Open(dlg_header, MODE_OLDFILE);

            if (rh)
            {
               Read(rh, &dlg_msg_hdr, sizeof(dlg_msg_hdr));
               Close(rh);

               // adjust for hokey stuff
               Capitalize(dlg_msg_hdr.From);
               Capitalize(dlg_msg_hdr.To);
            }
         }
         else
         {
            // editing a file description (probably)
            dlg_hdr_style = STYLE_FILEDESCR;
            rh = Open(dlg_header, MODE_OLDFILE);

            if (rh)
            {
               Read(rh, &dlg_file_hdr, sizeof(dlg_file_hdr));
               Close(rh);
            }
         }
      }
   }
   else if (DOSBase->dl_lib.lib_Version >= 36)
   {
      // MUST BE 2.04 OR LATER
      // no header passed.  check for hack&slash hack.
      char hackbuf[120];
      strcpy(hackbuf, dlg_body);
      strcat(hackbuf, ".hack");
      rh = Open(hackbuf, MODE_OLDFILE);
      if (rh)
      {
         while (FGets(rh, hackbuf, 120) != NULL)
         {
            if (*hackbuf == '\n') continue;
            // t=to (this message)
            // f=from (this message)
            // Q=name of file containing quoted message
            // these must appear after the Q= attribute
            // S=subject
            // F=From (quoted message)
            // T=To (quoted message)
            // d=date and time (quoted message)
            // o=num (number of initial lines of quoted messasge to skip)

            if (*(hackbuf+1) != '=') continue;
            hackbuf[strlen(hackbuf)-1] = 0; /* wipe out newline */

            switch (*hackbuf)
            {

               case 'f':
                  strncpy(dlg_msg_hdr.From, hackbuf+2, 35);
                  break;

               case 't':
                  strncpy(dlg_msg_hdr.To, hackbuf+2, 35);
                  break;

               case 'F':
                  if (dlg_quote) strncpy(dlg_quote->From, hackbuf+2, 35);
                  break;

               case 'T':
                  if (dlg_quote) strncpy(dlg_quote->To, hackbuf+2, 35);
                  break;

               case 'S':
                  if (dlg_quote) strncpy(dlg_quote->Title, hackbuf+2, 71);
                  strncpy(dlg_msg_hdr.Title, hackbuf+2, 71);
                  break;

               case 'd':
                  if (dlg_quote) strncpy(dlg_quote->Date, hackbuf+2, 19);
                  break;

               case 'Q':
                  dlg_quote = malloc(sizeof(*dlg_quote));
                  memset(dlg_quote, 0, sizeof(*dlg_quote));
                  hack_quote = malloc(strlen(hackbuf)-2+1);
                  strcpy(hack_quote, hackbuf+2);
                  break;

               case 'i':
                  hack_ignore = asc_int(hackbuf+2);
                  break;
            }
         }

         Close(rh);

         // adjust for hokey stuff
         Capitalize(dlg_msg_hdr.From);
         Capitalize(dlg_msg_hdr.To);
         dlg_hdr_style = STYLE_HACKNSLASH;
      }
   }
    
    /* read the reply header */
   if (dlg_named_file(dlg_reply))
   {
      rh = Open(dlg_reply, MODE_OLDFILE);

      if (rh)
      {
         dlg_quote = malloc(sizeof(*dlg_quote));

         if (dlg_quote)
         {
            Read(rh, dlg_quote, sizeof(*dlg_quote));
         }

         Close(rh);
      }
   }

   return(1);
}
//-

/// xeno_readquote()
/*
** Switch to quote buffer, append message, switch back (and other initialization)
 ***********************************************************************/
void REG xeno_readquote(void)
{
    char *p;

    /* Turn off system requestors for this process. */
    myproc = (struct Process *)FindTask(0);
    oldWindowPtr = myproc->pr_WindowPtr;
    myproc->pr_WindowPtr = (APTR)(-1L);
    oldwindow_set = 1;

    /* Read in the initial message file, if any. */
    if (dlg_named_file(dlg_body)) {
   // need to do word-wrapping on DLG sigs and file descriptions
   // same for correcting an existing message
   init_read_result = wrapreadin(dlg_body, TRUE);
    }

    /* Message buffer will have the current time at the far right */
    curbp->b_mode |= MDTIME;

    /* Turn off word wrapping if editing a plain file */
    p = gtusr("chuck");
    if (p && *p == '1') Zchuck = 1;

    if (dlg_hdrstyle() == STYLE_OTHER) {
   /* but only if it's chuck buck */
   if (Zchuck) {
       curbp->b_mode &= ~MDWRAP;
   }
    }
}
//-

/// xeno_exit()
/*
** All exits should come here.
 ***********************************************************************/
void REG xeno_exit(int status)
{
    rawcon(0);
   
    /* Restore system requestors for this process. */
    if (oldwindow_set) myproc->pr_WindowPtr = oldWindowPtr;

    if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase);
    if (ReqToolsBase) CloseLibrary((struct Library *)ReqToolsBase);
    if (fr) rtFreeRequest(fr);
    if (DLGBase) CloseLibrary(DLGBase);
    
    exit(status);
}
//-

/// ttclose()
/*
** This function gets called just before we go back home to the command
** interpreter.
 ***********************************************************************/
STD ttclose(void)
{
    /* make sure there is no pending output */
    ttflush();
    return(1);
}
//-

/// ttputc()
/*
** Write a character to the display. On VeeMS, terminal output is buffered, and
** we just put the characters in the big array, after checking for overflow.
** On CPM terminal I/O unbuffered, so we just write the byte out. Ditto on
** MS-DOS (use the very very raw console output routine).
 ***********************************************************************/
STD ttputc(int c)
{
   /* compensate for xenolink escape bug on local display */
   if (c == 27 && out_ptr >= OBUFSIZE/2) {
      ttflush();
   }

   /* add the character to the output buffer */
        out_buf[out_ptr++] = c;

        /* send the buffer out if we are at the limit */
        if (out_ptr >= OBUFSIZE)
                ttflush();

   return(1);
}
//-

/// ttflush()
/*
** Flush terminal buffer. Does real work where the terminal output is buffered
** up. A no-operation on systems where byte at a time terminal I/O is done.
 ***********************************************************************/
STD ttflush(void)
{
   /* if there are any characters waiting to display... */
        if (out_ptr) {
         out_buf[out_ptr] = 0;   /* terminate the buffer string */
      Write(Output(), out_buf, out_ptr);
           out_ptr = 0;    /* and reset the buffer */
   }

   return(1);
}
//-

/// ttgetc()
/*
** Read a character from the terminal.
 ***********************************************************************/
STD ttgetc(void)
{
    int ret;
    if (a_cnt > 0) {
   /* once buffer starts filling, lengthen the delay */
   if (a_cnt > 250) {
       a_delay = 2 * 1000000;
       uploading = TRUE;
   }
   /* get character from typeahead buffer */
   ret = ahead[a_next++];
   if (a_next >= a_cnt) {
       // buffer has been emptied, look for more typeahead
       a_next = 0;
       if (WaitForChar(Input(), a_delay) == FALSE) {
      /* no pending characters */
      a_cnt = 0;
       } else {
      /* more typeahead waiting, grab it */
      a_cnt = Read(Input(), ahead, AHEAD_NUM);
       }
   }
// if (ret) return ret;
// update(TRUE);
   return ret;
    }
    /* no typeahead */
    /* make sure there is no pending output */
    ttflush();
    return get_a_char();
}
//-

/// typahead()
/*
** typahead:   Check to see if any characters are already in the
** keyboard buffer
 ***********************************************************************/
STD typahead(void)
{
    return a_cnt;
}
//-

/// execprg()
/*
** Run an external program with arguments. When it returns, wait for a single
** character to be typed, then mark the screen as garbage so a full repaint is
** done. Bound to "C-X $".
 ***********************************************************************/
STD execprg(int f, int n)
{
    register int    s;
        char            line[NLINE];

   /* don't allow this command if restricted */
   if (restflag)
      return(resterr());

        if ((s=mlreply("!", line, NLINE)) != TRUE)
                return (s);
   // assume command is directed to nil:
    system(line);
        return(TRUE);
}
//-

/// exec_user_opts
void exec_user_opts(void)
{
    char ufile[NSTRING];
    str3cat(ufile, "user:", dlg_uname, "/ScreenEdit.opt");
    dofile(ufile);
    // ignore result, ok if not found
}
//-

/// opts()
STD opts(int f, int n)
{
    char cmd[1024];
    char *p;
    
    // Convert double-quotes in Zhello to something else.
    // Will be re-read when EmacsOpts finished, so ok to destroy Zhello.
    p = Zhello;
    while (*p) {
   if (*p == '"') *p = 1;
   ++p;
    }
//    strcpy(cmd, pathname[2]);
    strcpy(cmd, "EditOpts");
    opts_arg(cmd, dlg_uname, 0);
    opts_arg(cmd, Zhello, 1);
    opts_arg(cmd, int_asc(quote_style), 0);
    opts_arg(cmd, Xmsgcolor, 0);
    opts_arg(cmd, Xquotecolor, 0);
    opts_arg(cmd, Xhelpcolor, 0);
    opts_arg(cmd, Xhbcolor, 0);
    opts_arg(cmd, Xwbcolor, 0);
    opts_arg(cmd, Xmlcolor, 0);
    opts_arg(cmd, int_asc(kool), 0);
    opts_arg(cmd, int_asc(Zfask), 0);
    opts_arg(cmd, int_asc(Zchuck), 0);
    opts_arg(cmd, int_asc(Zftopic), 0);
    opts_arg(cmd, cfg_fname, 1);

    system(cmd);

    // reread preferences
    exec_user_opts();

    // cause them to take effect
    mode_color = -1;
    ml_color = -1;

    // update colors in all active windows (macro in xenorc.c)
    docmd("upc");
    
    // update all the mode lines
    upmode();

    sgarbf = TRUE;
    return(TRUE);
}
//-

/// do_fortune
/*
** fortune cookie - main controlling function
 ***********************************************************************/
void do_fortune(void)
{
   int numfort = 0;

   // dlg.library
   Capitalize(dlg_msg_hdr.To);

   if(strcmp(dlg_msg_hdr.To, "Areafix") == 0 ||
      strcmp(dlg_msg_hdr.To, "Areamgr") == 0 ||
      strcmp(dlg_msg_hdr.To, "Filemgr") == 0 ||
      strcmp(dlg_msg_hdr.To, "Raid") == 0)
   {
      return;
   }
    
   // are any fortune cookie topics available?
   numfort = get_fortune_topics();

    // append fortune cookie
   if (numfort                     &&
       Zfask                       &&
       (dlg_hdrstyle() == 0)       &&
       (init_read_result != TRUE)  &&
       Zfquery                     &&
      *Zfquery)
   {
   
      char tempfile[80];
      int cookie = TRUE;
      int topic;
   
      str3cat(tempfile, "t:", dlg_port, ".emacs");

      // Zfask:   0=never, 1=ask, 2=do not ask
      // Zftopic: 0=never, 1=ask, 2-n=topic(do not ask)
   
      if ((Zfask == 1 || Zftopic == 1))
      {
         // need to ask for cookie or possibly topic, so set up the screen area.
         BUFFER *save_bp = curbp;
       
         // Entry condition.  Current buffer is the message buffer.
         // Only it and the top window are displayed.
       
         // Delete the message window so only the top window remains.
         // Kill the contents of the top window so it is empty.
         // Then split it to create the fortune window.

         delwind(FALSE, 1);
         strcpy(curwp->w_bufp->b_bname, "Fortune Cookie!");
         gotobob(1, 1);
         setmark(FALSE, 1);
         gotoeob(1, 1);
         killregion(FALSE, 1);
         splitwind(FALSE, 1);
         resize(TRUE, 2);  // start with max possible cookie window
         nextwind(FALSE, 1);
         makename("fc", "fc");     /* New buffer name.  */
       
         for (;;)
         {

            // Ask for topic if necessary
            if (Zftopic == 1)
            {
               topic = ask_cookie_topic(numfort);
            }
            else
            {
               topic = Zftopic-2;
            }

            if (topic < 0)
            {
               cookie = FALSE;
               break;
            }
      
            // Generate a random cookie
            gen_cookie(topic, tempfile, numfort);

      // Ask if they want to use it
      if (Zfask != 1) {
          // They do not want us to ask, use it
          break;
      }

      // Insert cookie into temporary buffer
      gotobob(1, 1);
      setmark(FALSE, 1);
      gotoeob(1, 1);
      killregion(FALSE, 1);
      ifile(tempfile);
    
      // show cookie to the user and ask if it is acceptable
      curwp->w_fcolor = lookup_color(Xquotecolor);
      strcpy(curbp->b_bname, ":-)");

      // Go to end of fortune to see how many lines it is.
      // Resize the window to match, and display it from the top.
      gotoeob(1, 1);
      resize(TRUE, getlinenum(curbp, curwp->w_dotp));
      gotobob(1, 1);
      
      upmode();
      update(FALSE);
      
      eat_typeahead();
      
      // returns TRUE, FALSE, ABORT for Yes, No, Retry
      cookie = xenoyesno(Zfquery, *xl_yes, 'r', "retry");

      if (cookie == ABORT) {
          // Retry, choose another fortune.
          continue;
      }
      break;
       }
       swbuffer(save_bp);

   } else {
       // user does not want to ask, so use default topic
       topic = Zftopic - 2;
       gen_cookie(topic, tempfile, numfort);
       cookie = TRUE;
   }

   if (cookie == TRUE) {
       // goto last line in file, delete blank lines, add one, then fortune
       gotoeob(1, 1);
       deblank(1, 1);
       lnewline();
       ifile(tempfile);
   }
   unlink(tempfile);
    }
}
//-

/// dlgsave()
REG dlgsave(char *MessageText)
{
    BPTR wp;
    int n;
    
    // first, update the message header if it changed

    if (hdr_changed && dlg_named_file(dlg_header)) {
   wp = Open(dlg_header, MODE_NEWFILE);
   if (!wp) {
       // "Cannot open file for writing"
       mlwrite(TEXT155);
       return FALSE;
   }
   if (Write(wp, &dlg_msg_hdr, sizeof(dlg_msg_hdr)) != sizeof(dlg_msg_hdr)) {
       // "Write I/O error"
       mlwrite(TEXT157);
       Close(wp);
       return FALSE;
   }
   Close(wp);
    }
    
    // now, write the message body

    if (dlg_named_file(dlg_body)) {
   wp = Open(dlg_body, MODE_NEWFILE);
    } else {
   wp = 0;
    }
    if (!wp) {
   // "Cannot open file for writing"
   mlwrite(TEXT155);
   return FALSE;
    }
    n = strlen(MessageText);
    if (Write(wp, MessageText, n) != n) {
   // "Write I/O error"
   mlwrite(TEXT157);
   Close(wp);
   return FALSE;
    }

    Close(wp);

    return TRUE;
}
//-

/// msg_area()
STD msg_area(int f, int n)
{
   char buf[200];
   char tbuf[40];

   memset(buf, ' ', 80);
   buf[80] = 0;

   if (mlreply("?", tbuf, 40) != TRUE)
      return FALSE;
        strcpy(buf+76-strlen(tbuf), tbuf);

   linstr(buf);
   lnewline();
   return TRUE;
}
//-

/// isaquote()
/*
** Quoted line is indicated by optional whitespace, a string of alphabetic
** characters, and a right angle bracket.
 ***********************************************************************/
int REG isaquote(char *lp)
{
   while (*lp && (*lp == ' ' || *lp == '\t')) ++lp;

   while ((*lp >= 'A' && *lp <= 'Z') || (*lp >= 'a' && *lp <= 'z')) ++lp;

   if (*lp == '>')
      return TRUE;
   else
      return FALSE;
}
//-

/// not_implemented()
STD not_implemented(int f, int n)
{
    mlwrite("Sorry, that command has not yet been implemented.");
    return(0);
}
//-

/// greet()
STD greet(int f, int n)
{
    char fmtbuf[512];
    register char *fmt = fmtbuf;
    int sv_fillcol;
    int s;
    char initials[37];
    char *p, *q;
    
    if (!dlg_quote) return TRUE;

    if ((s=mlreply("?", fmt, 512)) != TRUE)
    return TRUE;

    /* Get quoted initials */
    p = initials;
    q = dlg_quote->From;

    // quote style:  AB> 0     Alan> 1    ab> 2    > 3   Alan_B> 4
    switch (quote_style) {
    case 1:
    case 4:
   // use first name for quote attribution
   if (strncmp(q, "The ", 4) == 0) {
       q += 4;
   }
   while (*q && *q != ' ') {
       *p++ = *q++;
   }
   if (quote_style == 4) {
       // use first name and next initial
       while (*q && (*q == ' ')) ++q;
       if (isletter(*q)) {
      *p++ = '_';
      *p++ = *q;
       }
   }
   break;
    case 0:
    case 2:
   // use initials for quote attribution
   while (isletter(*q)) {
       *p = *q++;
       if (quote_style == 2) {
      lowercase(p);
       }
       ++p;
       while (*q && (*q != ' ')) ++q;
       while (*q && (*q == ' ')) ++q;
   }
   break;
    default:
   /* else 3 leave initials as zero-length string */
   break;
    }
    
    *p = 0;
    dlg_initials = malloc(strlen(initials)+1);
    if (dlg_initials) {
   strcpy(dlg_initials, initials);
    }

    // allow quote header to extend to end of line
    sv_fillcol = fillcol;
    fillcol = 80;

    while (*fmt) {
   if (*fmt == '%') {
       switch (*(++fmt)) {
       case 'F':
      h_instr(dlg_quote->From);
      break;
       case 'f':
      // first name
      {
          char *p = dlg_quote->From;
          if (strncmp(p, "The ", 4) == 0) {
         p += 4;
          }
          while (*p && *p != ' ') {
         h_insert(*p++);
          }
      }
      break;
       case 'T':
      h_instr(dlg_quote->To);
      break;
       case 'S':
      h_instr(dlg_quote->Title);
      break;
       case 'q':
      if (dlg_initials)
      h_instr(dlg_initials);
      break;
       case 'd':
      h_instr(dlg_quote->Date);
      break;
       case 'N':
      lnewline();
      break;
       default:
      h_insert(*fmt);
      break;
       }
   }
   else {
       h_insert(*fmt);
   }
   if (!*fmt) break;
   ++fmt;
    }
    
    fillcol = sv_fillcol;
    
    return TRUE;
}
//-

/// xenorequest()
int REG xenorequest(char *buf, char *prompt, int wrt)
{
   char title[60];
   char filename[109];
   struct Window *oldwinptr;

   /* local nodes only! */
   if (!dlg_local) return FALSE;

   if (!IntuitionBase) {
       IntuitionBase  = (struct IntuitionBase *) OpenLibrary("intuition.library", 33);
       if (!IntuitionBase) return FALSE;
   }
   
   if (!ReqToolsBase) {
      ReqToolsBase = (struct ReqToolsBase *)OpenLibrary(REQTOOLSNAME, 37);
      if (!ReqToolsBase) return FALSE;
   }

   if (!fr) {
      fr = rtAllocRequestA(RT_FILEREQ, NULL);
      if (!fr) return FALSE;
      rtChangeReqAttr(fr, RTFI_Dir, Zdir, TAG_END);
   }

   
   filename[0] = 0;

   str3cat(title, PROGNAME, ": ", prompt);

   if (wrt)
      fr->Flags = FREQF_SAVE;
   else
      fr->Flags = 0;

   fr->Flags |= FREQF_PATGAD;
   
   // open on active window at time of request
   oldwinptr = myproc->pr_WindowPtr;
   myproc->pr_WindowPtr = IntuitionBase->ActiveWindow;

   if (rtFileRequest(fr, filename, title, TAG_END)) {
      strmfp(buf, fr->Dir, filename);
      myproc->pr_WindowPtr = oldwinptr;
      return TRUE;
   }
   else {
      myproc->pr_WindowPtr = oldwinptr;
      return ABORT;  /* cancel */
   } 
}
//-

/// get_a_char()
static int get_a_char(void)
{
    int phase;
    int mon;
    int mon_hp;
    int you_hp;
    int sleepy_time;
    int hit;
    int next_swing = 1;
    int last_mon = -1;
    char killed[60];
    char *swing;
    int ret;
    int not_gaming;
    static long update_time; /* update time once a minute */
    long now;
    
    phase = 0;
    sleepy_time = 10;
    not_gaming = 18; /* initial delay is sleepy_time*not_gaming */
    
    while (WaitForChar(Input(), sleepy_time*1000000) == FALSE) {

   uploading = FALSE;
   chkabort();
   
   // Do not screw up input prompts with system messages, game,
   // or time updates.
   if (!ok_broadcast) continue;
   
   now = AmigaTime();
   if (now >= update_time) {
       upmode();
       update(FALSE);
       update_time = now+60;
   }

   if (not_gaming) {
       // not ready to play the game yet, do system messages instead
       --not_gaming;
       broadcast();
       continue;
   }
   
   switch (phase) {
   case 0:
       mlwrite(intro[pick(NUM_INTRO)]);
       phase = 1;
       sleepy_time = 4;
       /* choose a random monster */
       do {
      mon = pick(NUM_MONSTERS);
      if (mon == 0) {
          // have to get two MMs in a row
          mon = pick(NUM_MONSTERS);
      }
       } while (mon == last_mon);
       last_mon = mon;

       break;
   case 1:
       mon_hp = pick(50) + 50; /* 50-100 */
       you_hp = pick(50) + 50;
       if (next_swing > 0) {
      swing = "It gets";
      phase = 3;
       } else {
      swing = "You get";
      phase = 4;
       }
       next_swing = -next_swing;
       mlwrite("It's a %s!  %s the first swing!", monster[mon], swing);
       sleepy_time = 3;
       break;
   case 3:
       hit = pick(40) + 20;
       you_hp -= hit;
       if (you_hp <= 0) {
      str3cat(killed, "  The ", monster[mon], " killed you!");
      phase = 10;
      sleepy_time = 4;
       } else {
      *killed = 0;
      phase = 4;
      sleepy_time = 2;
       }
       mlwrite("It %ss you for %d points.%s", blast[pick(NUM_HITS)], hit, killed);
       break;
   case 4:
       hit = pick(40) + 20;
       mon_hp -= hit;
       if (mon_hp <= 0) {
      str3cat(killed, "  You killed the ", monster[mon], "!");
      phase = 11;
      sleepy_time = 4;
       } else {
      *killed = 0;
      phase = 3;
      sleepy_time = 2;
       }
       mlwrite("You %s it for %d points.%s", blast[pick(NUM_HITS)], hit, killed);
       break;
       // phases >= 10 are ending conditions, for teleport away
   case 10:
       mlwrite("It gets all your %s!", stolen[pick(NUM_STOLEN)]);
       phase = 99;
       sleepy_time = 5;
       break;
   case 11:
       mlwrite("You get its %s!", treasure[pick(NUM_TREASURES)]);
       phase = 99;
       sleepy_time = 5;
       break;
   case 99:
       mlerase();
       phase = 0;
       sleepy_time = 10;
       broadcast();  // do broadcasts only between battles
       break;
   }
   update(FALSE); // reposition cursor
   
    }

    if (phase != 0) {
   if (mon_hp > 0 && phase < 10) {
       mlwrite("The %s teleports away from the battle.", monster[mon]);
       mpresf = FALSE;
   } else {
       mlerase();
   }
    }
    
    // Character arrived, read it
    
    if (!uploading) {
   broadcast();
    
   now = AmigaTime();
   if (now >= update_time) {
       upmode();
       update_time = now+60;
   }
    }
    
    if (!dlg_local) {
   // begin 10/2 attempt at fixing typeahead
   a_cnt = Read(Input(), ahead, AHEAD_NUM);
   a_delay = 5;
   ret = ahead[0] & 0xff;
   if (a_cnt == 1) {
       // only one read, no typeahead
       a_cnt = 0;
   } else {
       // just used first character
       a_next = 1;
   }
    } else {
   // do it the non-typeahead way for local port
   char rbuf;
   Read(Input(), &rbuf, 1);
   
   chkabort();
   
   ret = rbuf & 0xff;
    }
    return ret;
}
//-

/// getdlgstring()
int getdlgstring(char *prompt, char *buf, char *dflt)
{
    int c;
    int cpos;
    int clen;
    int max_input;
    int i;
    
    max_input =     term.t_ncol - strlen(prompt) - 1;
    if (max_input <= 0) return ABORT;
    
    /* prompt the user for the input string */
    mlwrite(prompt);
    
    strcpy(buf, dflt);
    buf[max_input] = 0;
    clen = strlen(buf);
    cpos = clen;
    if (clen > 0) {
   outstring(buf);
    }

    for (;;) {
   /* get a character from the user */
   c = tgetc();
   switch (c) {
   case '\r':
       mlerase();
       buf[clen] = 0;
       if (buf[0] == 0) return FALSE;
       return TRUE;
   case 7:
       mlerase();
       return ABORT;
   case 24:
       mlwrite(prompt);
       cpos = 0;
       clen = 0;
       break;
   case 11:
       ttputs("\033[K");
       clen = cpos;
       break;
   case 8:
       if (cpos == 0) break;
       mlout('\b');
       --cpos;
       --ttcol;
       /* fall thru */
   case 127:
       if (cpos == clen) break;
       if (cpos == clen-1) {
      outstring(" \b");
      --clen;
      break;
       }
       i = cpos;
       while (i < clen-1) {
      if (!id_ok) mlout(buf[i+1]);
      buf[i] = buf[i+1];
      ++i;
       }
       if (id_ok) {
      ttputs("\033[P");
       } else {
      mlout(' ');
      i = cpos;
      while (i < clen) {
          mlout('\b');
          ++i;
      }
       }
       --clen;
       break;
   case 27:
       if (tgetc() != '[') {
      mlerase();
      return ABORT;
       }
       /* fall thru */
   case 0x9b:
       switch (tgetc()) {
       case 'C': /* right */
      if (cpos < clen) {
          mlout(buf[cpos]);
          ++cpos;
          ++ttcol;
      }
      break;
       case 'D': /* left */
      if (cpos > 0) {
          mlout('\b');
          --cpos;
          --ttcol;
      }
      break;
       case ' ':
      // CSI sp @ is shift-right
      // CSI sp A is shift-left
      switch (tgetc()) {
      case '@':
          while (cpos < clen) {
         mlout(buf[cpos]);
         ++cpos;
         ++ttcol;
          }
          break;
      case 'A':
          while (cpos) {
         mlout('\b');
         --cpos;
         --ttcol;
          }
          break;
      }
       }
       break;
   case 1:
       while (cpos) {
      mlout('\b');
      --cpos;
      --ttcol;
       }
       break;
   case 5:
       while (cpos < clen) {
      mlout(buf[cpos]);
      ++cpos;
      ++ttcol;
       }
       break;
   case 6: /* right */
       if (cpos < clen) {
      mlout(buf[cpos]);
      ++cpos;
      ++ttcol;
       }
       break;
   case 2: /* left */
       if (cpos > 0) {
      mlout('\b');
      --cpos;
      --ttcol;
       }
       break;
       
   default:
       if (c >= ' ' /* && c < 0x7f */ && clen < max_input) {
      if (clen == cpos) {
          mlout(c);
          buf[cpos++] = c;
          ++clen;
          ++ttcol;
      } else {
          for (i=clen; i>=cpos; --i) {
         buf[i] = buf[i-1];
          }
          buf[cpos] = c;
          if (clen < max_input) {
         // this relies on buf having extra space at end
         ++clen;
          }
          if (id_ok) {
         ttputs("\033[@");
         mlout(c);
          } else {
         for (i=cpos; i<clen; ++i) {
             mlout(buf[i]);
         }
          }
          ++ttcol;
          ++cpos;
          if (!id_ok) {
         for (i=cpos; i<clen; ++i) {
             mlout('\b');
         }
          }
      }
       }
       break;
   }
    }
}
//-

/// who()
/*
** List who's on-line
 ***********************************************************************/
int STD who(int f, int nn)
{
    int numwho;
    struct Ram_File ramfile;
    char *portlist;
    char buf[150];
    BUFFER *save_bp = curbp;
    BUFFER *dirbuf;
    int n;

    // Entry condition.  Current buffer is the message buffer.
    // Only it and the top window are displayed.
       
    if (!Zwhoports) {
   Zwhoports = malloc(10);
   Zwhoports[0] = Zwhoports[3] = Zwhoports[6] = 'T';
   Zwhoports[1] = 'L';
   Zwhoports[4] = Zwhoports[7] = 'R';
   Zwhoports[2] = Zwhoports[5] = '0';
   Zwhoports[8] = '1';
   Zwhoports[9] = 0;
    }
    portlist = Zwhoports;
    if (!Zwhoports) return FALSE;

    // Create the who buffer
    dirbuf = bfind("Who's online", TRUE, 0);
    if (dirbuf) {

   // Split the current window to create the who window
   splitwind(FALSE, 1);
   nextwind(FALSE, 1);
   bclear(dirbuf);
   swbuffer(dirbuf);

   numwho = 0;
   while (*portlist) {

       ++numwho;
       buf[0] = *portlist++;
       buf[1] = *portlist++;
       buf[2] = *portlist++;
       buf[3] = 0;
       
       if (ReadRam(&ramfile, buf) == FALSE) {
      strcpy(ramfile.Name, "Idle");
      *ramfile.Action = 0;
       }    
       buf[3] = ' ';
       buf[4] = ' ';
       strcpy(buf+5, ramfile.Name);
       n = strlen(buf);
       while (n < 36) buf[n++] = ' ';
       strcpy(buf+n, ramfile.Action);
       addline(dirbuf, buf);
   }

   //makename("who", "who");
       
   curwp->w_fcolor = lookup_color(Xquotecolor);
   resize(TRUE, numwho > 2 ? numwho : 2);
   gotobob(1, 1);
   upmode();
   update(FALSE);

   // this display stuff is deferred until after broadcast input
   // pretty kludgey
   delwind(FALSE, 1);
   swbuffer(save_bp);
    }
    return TRUE;
}
//-

/// deblank()
/*
** Delete blank lines around dot. What this command does depends if dot is
** sitting on a blank line. If dot is sitting on a blank line, this command
** deletes all the blank lines above and below the current line. If it is
** sitting on a non blank line then it deletes all of the blank lines after
** the line. Normally this command is bound to "C-X C-O". Any argument is
** ignored.
 ****************************************************************************/
STD deblank(int f, int n)
{
        register LINE   *lp1;
        register LINE   *lp2;
        long nld;

   if (curbp->b_mode&MDVIEW)  /* don't allow this command if   */
      return(rdonly()); /* we are in read only mode   */
        lp1 = curwp->w_dotp;
        while (llength(lp1)==0 && (lp2=lback(lp1))!=curbp->b_linep)
                lp1 = lp2;
        lp2 = lp1;
        nld = 0;
        while ((lp2=lforw(lp2))!=curbp->b_linep && llength(lp2)==0)
                ++nld;
        if (nld == 0)
                return(TRUE);
        curwp->w_dotp = lforw(lp1);
        curwp->w_doto = 0;
        return(ldelete(nld, FALSE));
}
//-

// Local functions

/// addline()
/* Add a new line to the end of the indicated buffer.
** return FALSE if we run out of memory
** note that this works on non-displayed buffers as well!
*********************************************************/
int STD addline(BUFFER *bp, char *text)
{
   register LINE  *lp;
   register int   ntext;

   /* allocate the memory to hold the line */
   ntext = strlen(text);
   if ((lp=lalloc(ntext)) == NULL)
      return(FALSE);

   /* copy the text into the new line */
// for (i=0; i<ntext; ++i)
//    lputc(lp, i, text[i]);
   memcpy(lp->l_text, text, ntext);

   /* add the new line to the end of the buffer */
   bp->b_linep->l_bp->l_fp = lp;
   lp->l_bp = bp->b_linep->l_bp;
   bp->b_linep->l_bp = lp;
   lp->l_fp = bp->b_linep;

   /* if the point was at the end of the buffer,
      move it to the beginning of the new line */
   if (bp->b_dotp == bp->b_linep)
      bp->b_dotp = lp;
   return(TRUE);
}
//-

/// broadcast()
/*
** Display one DLG broadcast message
 ***********************************************************************/
void broadcast(void)
{
    if (ok_broadcast) {

   char msg[2+81];
   static long last_bc = 0;
   static long bc_written = 0;
   long now = AmigaTime();

   if (now > last_bc + 5) {
       if (!BCGet(dlg_port, msg+2)) {
      msg[0] = '*';
      msg[1] = ' ';
      mlwrite(msg);

      bc_written = now;
      update(FALSE);   // reposition cursor
      mpresf = FALSE;  // dont erase it in main loop!
      bc_displayed = TRUE;
       }
       last_bc = now;

       if (bc_displayed == TRUE && now > bc_written + 45) {
      mlerase();
      update(FALSE); // reposition cursor
       }
   }
    }
}
//-

/// pick()
/*
** Random integer from 0 to n-1
 **************************************/
int pick(int n)
{
    return ernd() % n;
}
//-

/// str2cat()
void str2cat(char *buf, char *b1, char *b2)
{
    strcpy(buf, b1);
    strcat(buf, b2);
}
//-

/// str3cat()
void str3cat(char *buf, char *b1, char *b2, char *b3)
{
    str2cat(buf, b1, b2);
    strcat(buf, b3);
}
//-

/// dlg_named_file()
int dlg_named_file(char *fname)
{
    if (fname && *fname && strcmp(fname, "::") != 0)
       return 1;
    else
       return 0;
}
//-

/// dlg_defaults()
static void dlg_defaults(void)
{
    newzno("no");
    newzyes("yes");
}
//-

/// game_over
int game_over(void)
{
    TTclose();
    TTkclose();
    AFPrintf(NULL,Output(),"How rude!\n");
    xeno_exit(5);
    return 1;
}
//-

/// eat_typeahead()
void eat_typeahead(void)
{
    a_cnt = 0;
    a_next = 0;
    a_delay = 5;
    while (ReadChar(a_delay) != 0) {}
}
//-

/// opts_arg()
void opts_arg(char *cmd, char *arg, int quotes)
{
    strcat(cmd, " ");
    if (quotes) {
   strcat(cmd, "\"");
    }
    if (!arg) {
   arg = "::"; /* if no EmacsDLG.cfg, arg will be null */
    }
    strcat(cmd, arg);

    if (quotes) {
   strcat(cmd, "\"");
    }
}
//-

/// get_fortune_topics()
/* Read the fortune cookie topics from the config file.
** Returns the number of available topics.
 *********************************************************/
static int get_fortune_topics(void)
{
    char buf[120];
    int numfort = 0;

    BPTR f;
    f = Open(cfg_fname, MODE_OLDFILE);
    if (f) {
   while (numfort < NUM_COOKIE_TOPICS && FGets(f, buf, 120) != NULL) {
       if (*buf == ';') {
      buf[strlen(buf)-1] = 0; /* destroy newline */
      if (*(buf+1) == '=') {
          fc[numfort].cmd = malloc(strlen(buf+2)+1);
          strcpy(fc[numfort].cmd, buf+2);
      } else if (*(buf+1) == '!') {
          fc[numfort].desc = malloc(strlen(buf+2)+1);
          strcpy(fc[numfort].desc, buf+2);
          ++numfort;
      }
       }
   }
   Close(f);
    }
    return numfort;
}
//-

/// ask_cookie_topic()
/* prompt for the cookie topic.
** returns index into fc array, or -1
 *****************************************/
int ask_cookie_topic(int numfort)
{
    BUFFER *save_bp = curbp;
    BUFFER *dirbuf;
    int i;
    int result;
    char buf[200];

    if (numfort == 1) {
   // only one topic, do not ask
   return 0;
    }

    // Create the buffer
    dirbuf = bfind("Topics", TRUE, 0);
    if (dirbuf) {

   bclear(dirbuf);
   swbuffer(dirbuf);
   addline(dirbuf, "");
   for (i=0; i<numfort; ++i) {
       char buf2[2];
       buf2[1] = 0;

       strcpy(buf, " <");
       if (i < 9) {
      buf2[0] = '1' + i;
       } else {
      buf2[0] = 'a' + i;
       }
       strcat(buf, buf2);
       strcat(buf, "> ");
       strcat(buf, fc[i].desc);
       addline(dirbuf, buf);
   }
   addline(dirbuf, "");
   curwp->w_fcolor = lookup_color(Xquotecolor);
   resize(TRUE, numfort+2);
   gotobob(1, 1);
   upmode();
   update(FALSE);

   for (;;) {
       mlwrite("Select a fortune cookie topic, or 0 for none:", buf, "");
       result = tgetc();
       if (result == 7) {
      swbuffer(save_bp);
      return -1;
       }
       if (result >= '0' && result <= '9') {
      result -= '0';
       } else if (result >= 'a' && result <= 'z') {
      result = result - 'a' + 10;
       }
       if (result >= 0 && result <= numfort) {
      swbuffer(save_bp);
      return result-1;
       }
   }
    }
    return -1;
}
//-

/// gen_cookie()
/*
** generate a cookie of the specified topic
 ************************************************/
void gen_cookie(int topic, char *tempfile, int numfort)
{
    BPTR wp2;
    char outfortune[MAX_OUTFORTUNE+1];

    mlwrite("Fortune cookie");

    // adjust just in case
    if (topic < 0 || topic >= numfort) topic = 0;

    // translate % switches in fortune command
    TranslateBuffer(fc[topic].cmd, outfortune, MAX_OUTFORTUNE,
          &user_data, &ram_data, dlg_port);
    outfortune[MAX_OUTFORTUNE] = 0;


    wp2 = Open(tempfile, MODE_NEWFILE);
    Execute(outfortune, 0, wp2);
    Close(wp2);
}
//-

/// h_instr()
void h_instr(char *str)
{
   while (*str) {
      h_insert(*str);
      ++str;
   }
}
//-

/// h_insert()
/*
** insert lines in hello/greet mode with word wrapping
**
** ----> this function is also used in wrapreadin() in file.c
** ----> and also in ttgetc when ascii send is detected
 ***********************************************************************/
void h_insert(char ch)
{
    static char pch;

    if (fillcol > 0 && getccol(FALSE) > fillcol) {
   execkey(&wraphook, FALSE, 1);
    }
    if (ch == 13) { /* added this for ascii send */
   lnewline();
    }
    else if (ch == 10) {
   if (pch != 13) lnewline(); /* convert CRLF as one newline */
    } else if (ch != 19 && ch != 17) { /* nuke ^S and ^Q for turbo upload */
   linsert(1, ch);
    }
}
//-


