// main routines

/* make global definitions not external */
#define maindef

#include "Include.h"
#include "ebind.h"

#include <Link/io.h>

#define GOOD   0
#define NUM_ARGS 6            // argc of required args

extern long __stack = 20000;

int DNEAR numfunc = NFUNCS;   /* number of bindable functions */
int DNEAR reptc = CTRL | 'U'; /* current universal repeat char*/
int DNEAR prefix;             /* currently pending prefix bits */
int DNEAR prenum;             /*     "       "     numeric arg */
int DNEAR predef = TRUE;      /*     "       "     default flag */
int DNEAR eexitval;           /* and the exit return value  */

BUFFER *blistp;               /* Buffer for C-X C-B      */

char *dlg_body;
char *dlg_header;
char *dlg_reply;
char *dlg_port;
char *default_dir = "DLGconfig:misc/";
char  dlg_local;

struct USER_DATA        user_data;
struct Ram_File         ram_data;
struct Msg_Header       dlg_msg_hdr;

// internally used functions

SCREEN  *STD init_screen(char *, BUFFER *);
         REG edinit(char *);
         REG editloop(void);
         REG execute(int, int, int);
         REG initchars(void);
         REG mlferase(void);
int dlgparse(int, char **);
void dlgusage(void);


/// main()
main(int argc, char *argv[])
{
   register int status = 0;

   // Parse arguments from command line.

   if (dlgparse(argc, argv) == FALSE)
   {
       dlgusage();
   }

   /* Initialize the editor */
   eexitflag = FALSE;

   vtinit();      /* Terminal */

   if (eexitflag)
   {
      TTclose();
      TTkclose();
      xeno_exit(status);
   }

   edinit(mainbuf);   /* Buffers, windows, screens */

   varinit();     /* user variables */
   initchars();      /* character set definitions */

   /* Process the command line and let the user edit */

   xeno_init();
   startup("");

   // check if fini set
   if (fini == FALSE) {
      meexit(-1);
      // continues to edit loop and forces exit
   }
   else {
      /* display screen before reading in quoted message */
      update(TRUE);
      // can't do this unless RC file succeeded
      xeno_readquote();
   }

   status = editloop();

   TTclose();
   TTkclose();
   xeno_exit(status);
}
//-

/// quickexit()
/*
** Fancy quit command, as implemented by Norm. If the any buffer
** has changed do a write on that buffer and exit emacs, otherwise simply
** exit.
*/
STD quickexit(int f, int n)
{
   register BUFFER *bp; /* scanning pointer to buffers */
   register BUFFER *oldcb; /* original current buffer */
   register int status;

   oldcb = curbp;          /* save in case we fail */

   bp = bheadp;

   while (bp != NULL)
   {
      if ((bp->b_flag&BFCHG) != 0      /* Changed.    */
          && (bp->b_flag&BFINVS) == 0) /* Real.    */
      {
         curbp = bp;    /* make that buffer cur */
         mlwrite(TEXT103,bp->b_fname);
/*          "[Saving %s]" */
         mlwrite("\n");

         if ((status = filesave(f, n)) != TRUE)
         {
            curbp = oldcb; /* restore curbp */
            return(status);
         }
      }

      bp = bp->b_bufp;        /* on to the next buffer */
   }

   quit(f, n);          /* conditionally quit   */
   return(TRUE);
}
//-

/// meexit()
/*
** int status;    return status of emacs
 ***********************************************************************/
REG meexit(int status)
{
   eexitflag = TRUE; /* flag a program exit */
   eexitval = status;

   /* and now.. we leave and let the main loop kill us */
   return(TRUE);
}
//-

/// ctlxlp()
/*
** Begin a keyboard macro.
** Error if not at the top level in keyboard processing. Set up variables and
** return.
 ******************************************************************************/
STD ctlxlp(int f, int n)
{
   if (kbdmode != STOP) {
      mlwrite(TEXT105);
/*       "%%Macro already active" */
      return(FALSE);
   }
   mlwrite(TEXT106);
/*    "[Start macro]" */
   kbdptr = &kbdm[0];
   kbdend = kbdptr;
   kbdmode = RECORD;
   return(TRUE);
}
//-

/// ctlxrp()
/*
** End keyboard macro. Check for the same limit conditions as the above
** routine. Set up the variables and return to the caller.
 ************************************************************************/
STD ctlxrp(f, n)
{
   if (kbdmode == STOP) {
      mlwrite(TEXT107);
/*       "%%Macro not active" */
      return(FALSE);
   }
   if (kbdmode == RECORD) {
      mlwrite(TEXT108);
/*       "[End macro]" */
      kbdmode = STOP;
   }
   return(TRUE);
}
//-

/// ctlxe()
/*
** Execute a macro.
** The command argument is the number of times to loop. Quit as soon as a
** command gets an error. Return TRUE if all ok, else FALSE.
 **************************************************************************/
STD ctlxe(int f, int n)
{
   if (kbdmode != STOP) {
      mlwrite(TEXT105);
/*       "%%Macro already active" */
      return(FALSE);
   }
   if (n <= 0)
      return(TRUE);
   kbdrep = n;    /* remember how many times to execute */
   kbdmode = PLAY;   /* start us in play mode */
   kbdptr = &kbdm[0];   /*    at the beginning */
   return(TRUE);
}
//-

/// rdonly()
/* tell the user that this command is illegal while we are in
** VIEW (read-only) mode            */
REG rdonly(void)
{
   TTbeep();
   mlwrite(TEXT109);
/*    "[Key illegal in VIEW mode]" */
   return(FALSE);
}
//-

/// resterr()
REG resterr(void)
{
   TTbeep();
   mlwrite(TEXT110);
/*    "[That command is RESTRICTED]" */
   return(FALSE);
}
//-

/// nullproc()
/* user function that does NOTHING */
STD nullproc(int f, int n)
{return(0);
}
//-

/// meta()
/* set META prefixing pending
 ***********************************************************************/
STD meta(int f, int n)
{
   prefix |= META;
   prenum = n;
   predef = f;
   return(TRUE);
}
//-

/// cex()
/* set ^X prefixing pending */
STD cex(int f, int n)
{
   prefix |= CTLX;
   prenum = n;
   predef = f;
   return(TRUE);
}
//-

/// dummy1()
/*
** used to substitute for those functions that return TRUE
** but do nothing -- a great multitude in the DLG version.
**
 ***********************************************************************/
STD dummy1(int f, int n)
{
   return(TRUE);
}
//-

/// quit()
/*
** Quit command. If an argument, always quit. Otherwise confirm if a buffer
** has been changed and not written out. Normally bound to "C-X C-C".
 ***********************************************************************/
STD quit(int f, int n)
{
   register int status; /* return status */

   if (f != FALSE    /* Argument forces it.  */
   || anycb() == FALSE  /* All buffers clean or user says it's OK. */
   || (status = mlyesno(TEXT104)) == TRUE) {
/*            "Modified buffers exist. Leave anyway" */
      if (f)
         status = meexit(n);
      else
         status = meexit(GOOD);
   }
   mlerase();
   return(status);
}
//-

// internally used functions

/// init_screen()
/* this function initializes a new screen....
**    char *scr_name;      screen name
**    BUFFER *scr_buf;     buffer to place in first window of screen
************************/

SCREEN *STD init_screen(char *scr_name, BUFFER *scr_buf)
{
   int cmark;     /* current mark to initialize */
   SCREEN *sp;    /* pointer to allocated screen */
   SCREEN *last_sp;  /* pointer to last screen */
   WINDOW *wp;    /* ptr to first window of new screen */

   /* ONLY ONE SCREEN */
   static SCREEN zz;
   sp = &zz;

   /* set up this new screens fields! */
   sp->s_next_screen = (SCREEN *)NULL;
   sp->s_screen_name = strdup(scr_name);
   sp->s_roworg = term.t_roworg;
   sp->s_colorg = term.t_colorg;
   sp->s_nrow = term.t_nrow;
   sp->s_ncol = term.t_ncol;

   /* allocate its first window */
   wp = (WINDOW *)malloc(sizeof(WINDOW));
   if (wp == (WINDOW *)NULL) {
      free((char *)sp);
      return((SCREEN *)NULL);
   }
   sp->s_first_window = sp->s_cur_window = wp;

   /* and setup the windows info */
   wp->w_wndp = NULL;
   wp->w_bufp = scr_buf;
   scr_buf->b_nwnd += 1;
   wp->w_linep = scr_buf->b_linep;

   /* position us at the buffers dot */
   wp->w_dotp  = scr_buf->b_dotp;
   wp->w_doto  = scr_buf->b_doto;

   /* set all the marks to UNSET */
   for (cmark = 0; cmark < NMARKS; cmark++) {
      wp->w_markp[cmark] = NULL;
      wp->w_marko[cmark] = 0;
   }
   wp->w_toprow = 0;
   /* initalize colors to global defaults */
   wp->w_fcolor = gfcolor;
   wp->w_bcolor = gbcolor;
   wp->w_fcol = 0;
   wp->w_ntrows = term.t_nrow-1;    /* "-1" for mode line.  */
   wp->w_force = 0;
   wp->w_flag  = WFMODE|WFHARD;     /* Full.    */

   /* first screen? */
   if (first_screen == (SCREEN *)NULL) {
      first_screen = sp;
      return(sp);
   }

   /* insert it at the tail of the screen list */
   last_sp = first_screen;
   while (last_sp->s_next_screen != (SCREEN *)NULL)
      last_sp = last_sp->s_next_screen;
   last_sp->s_next_screen = sp;

   /* and return the new screen pointer */
   return(sp);
}
//-

/// edinit()
/*
** Initialize all of the buffers, windows and screens. The buffer name is
** passed down as an argument, because the main routine may have been told
** to read in a file by default, and we want the buffer name to be right.
 ***********************************************************************/
REG edinit(char bname[])
{
   register BUFFER *bp;

   /* initialize some important globals */

   readhook.k_ptr.fp = nullproc; /* set internal hooks to OFF */
   readhook.k_type = BINDFNC;
   wraphook.k_ptr.fp = wrapword;
   wraphook.k_type = BINDFNC;
   cmdhook.k_ptr.fp = nullproc;
   cmdhook.k_type = BINDFNC;
   writehook.k_ptr.fp = nullproc;
   writehook.k_type = BINDFNC;
   bufhook.k_ptr.fp = nullproc;
   bufhook.k_type = BINDFNC;
   exbhook.k_ptr.fp = nullproc;
   exbhook.k_type = BINDFNC;

   /* allocate the first buffer */
   bp = bfind(bname, TRUE, 0);      /* First buffer   */
   blistp = bfind("[List]", TRUE, BFINVS); /* Buffer list buffer  */
   if (bp==NULL || blistp==NULL)
      meexit(1);

   /* and allocate the default screen */
   first_screen = (SCREEN *)NULL;
   init_screen("MAIN", bp);
   if (first_screen == (SCREEN *)NULL)
      meexit(1);

   /* set the current default screen/buffer/window */
   curbp = bp;
   curwp = wheadp = first_screen->s_cur_window = first_screen->s_first_window;

   return(1);
}
//-

/// editloop()
/*
** This is called to let the user edit something.  Note that if you
** arrange to be able to call this from a macro, you will have
** invented the "recursive-edit" function.
 ***********************************************************************/
REG editloop(void )
{
   register int c;      /* command character */
   register int f;   /* default flag */
   register int n;   /* numeric repeat count */
   register int mflag;  /* negative flag on repeat */
   register int basec;  /* c stripped of meta character */
   register int oldflag;   /* old last flag value */

   /* setup to process commands */
   lastflag = 0;     /* Fake last flags.  */

loop:
   /* if a macro error is pending, wait for a character */
   if (exec_error) {
      mlforce(TEXT227);
/*       "\n--- Press any key to Continue ---" */
      tgetc();
      sgarbf = TRUE;
      update(FALSE);
      mlferase();
      exec_error = FALSE;
   }

   /* if we were called as a subroutine and want to leave, do so */
   if (eexitflag)
      return(eexitval);

   /* execute the "command" macro...normally null */
   oldflag = lastflag;  /* preserve lastflag through this */
   execkey(&cmdhook, FALSE, 1);
   lastflag = oldflag;

   // DLG broadcast messages

   /* Fix up the screen */
   update(FALSE);

   // DLG broadcast messages and other interruptions are allowed
   // while we are sitting at the main input routine.  They are
   // forbidden while getting input from a prompt or macro.
   ok_broadcast = TRUE;

   /* get the next command from the keyboard */
   discmd = TRUE;
   disinp = TRUE;
   c = getkey();

   // Starting a command, so do not allow broadcast messages
   // until we get back to the main input loop.
   ok_broadcast = FALSE;

   /* if there is something on the command line, clear it */
   if (mpresf != FALSE) {
      mlerase();
      update(FALSE);
   }

   /* override the arguments if prefixed */
   if (prefix) {
      if (islower(c & 255))
         c = (c & ~255) | upperc(c & 255);
      c |= prefix;
      f = predef;
      n = prenum;
      prefix = 0;
   } else {
      f = FALSE;
      n = 1;
   }

   /* do META-# processing if needed */

   basec = c & ~META;      /* strip meta char off if there */
   if ((c & META) && ((basec >= '0' && basec <= '9') || basec == '-') &&
       (getbind(c) == NULL)) {
      f = TRUE;      /* there is a # arg */
      n = 0;         /* start with a zero default */
      mflag = 1;     /* current minus flag */
      c = basec;     /* strip the META */
      while ((c >= '0' && c <= '9') || (c == '-')) {
         if (c == '-') {
            /* already hit a minus or digit? */
            if ((mflag == -1) || (n != 0))
               break;
            mflag = -1;
         } else {
            n = n * 10 + (c - '0');
         }
         if ((n == 0) && (mflag == -1))   /* lonely - */
            mlwrite("Arg:");
         else
            mlwrite("Arg: %d",n * mflag);

         c = getkey();  /* get the next key */
      }
      n = n * mflag; /* figure in the sign */
   }

   /* do ^U repeat argument processing */

   if (c == reptc) {       /* ^U, start argument   */
      f = TRUE;
      n = 4;            /* with argument of 4 */
      mflag = 0;        /* that can be discarded. */
      mlwrite("Arg: 4");
      while ((c=getkey()) >='0' && c<='9' || c==reptc || c=='-') {
         if (c == reptc)
            if ((n > 0) == ((n*4) > 0))
               n = n*4;
            else
               n = 1;
         /*
          * If dash, and start of argument string, set arg.
          * to -1.  Otherwise, insert it.
          */
         else if (c == '-') {
            if (mflag)
               break;
            n = 0;
            mflag = -1;
         }
         /*
          * If first digit entered, replace previous argument
          * with digit and set sign.  Otherwise, append to arg.
          */
         else {
            if (!mflag) {
               n = 0;
               mflag = 1;
            }
            n = 10*n + c - '0';
         }
         mlwrite("Arg: %d", (mflag >=0) ? n : (n ? -n : -1));
      }
      /*
       * Make arguments preceded by a minus sign negative and change
       * the special argument "^U -" to an effective "^U -1".
       */
      if (mflag == -1) {
         if (n == 0)
            n++;
         n = -n;
      }
   }

   /* and execute the command */
   execute(c, f, n);
   goto loop;
}
//-

/// execute()
/*
** This is the general command execution routine. It handles the fake binding
** of all the keys to "self-insert". It also clears out the "thisflag" word,
** and arranges to move it to the "lastflag", so that the next command can
** look at it. Return the status of command.
**
** int c;         key to execute
** int f;         prefix argument flag
** int n;         prefix value
**
 ***********************************************************************/
REG execute(int c, int f, int n)
{
   register int status;
   KEYTAB *key;      /* key entry to execute */

   /* if the keystroke is a bound function...do it */
   key = getbind(c);
   if (key != NULL) {

      /* Don't reset the function type flags on a prefix */
      if ((key->k_type == BINDFNC) &&
          ((key->k_ptr.fp == meta) || (key->k_ptr.fp == cex)))
         status = execkey(key, f, n);
      else {
         thisflag = 0;
         status = execkey(key, f, n);
         lastflag = thisflag;
      }

      return(status);
   }

   /*
    * Wrap if we are past the fill column.  But let spaces accumulate
    * without wrapping.
    */
   if ((curwp->w_bufp->b_mode & MDWRAP) && fillcol > 0 &&
       n >= 0 && getccol(FALSE) > fillcol &&
       (curwp->w_bufp->b_mode & MDVIEW) == FALSE) {

       if (c != ' ') {
      execkey(&wraphook, FALSE, 1);
       }
   }

   /* DO THIS BEFORE INSERTING CHARACTER, NOT AFTER! */
   /* auto-fill this paragraph if we are inserting */
   /* into a line and this line has gotten too long. */

   /* but only if we did not do the wrap hook above */
   else {
       if ((curwp->w_bufp->b_mode & MDWRAP) && fillcol > 0 &&
      n >= 0 && llength(curwp->w_dotp) > fillcol &&
      (curwp->w_bufp->b_mode & MDVIEW) == FALSE) {

      autofillpara(1, 1);
       }
   }

   if ((c>=0x20 && c<=0xFF)) {   /* Self inserting.   */
      if (n <= 0) {        /* Fenceposts.    */
         lastflag = 0;
         return(n<0 ? FALSE : TRUE);
      }
      thisflag = 0;        /* For the future.   */

      /* replace or overwrite mode, not at the end of a string */
      if (curwp->w_bufp->b_mode & (MDREPL | MDOVER) &&
          curwp->w_doto < curwp->w_dotp->l_used) {

         /* if we are in replace mode, or
            (next char is not a tab or we are at a tab stop) */
         if (curwp->w_bufp->b_mode & MDREPL ||
             (lgetc(curwp->w_dotp, curwp->w_doto) != '\t' ||
             getccol(FALSE) % tabsize == (tabsize - 1)))
/*           (curwp->w_doto) % tabsize == (tabsize - 1)))
             ^^^^^^^^^^^^^ these needs to be the column,
         DML        not the offset! */
                  ldelete(1L, FALSE);
      }

      /* do the appropriate insertion */
      {
         status = linsert(n, c);

      }
      lastflag = thisflag;
      return(status);
   }
   TTbeep();
   mlwrite(TEXT19);     /* complain    */
/*    "[Key not bound]" */
   lastflag = 0;           /* Fake last flags.  */
   return(FALSE);
}
//-

/// initchars()
/* initialize the character upper/lower case tables */
REG initchars()
{
   register int index;  /* index into tables */

   /* all of both tables to zero */
   for (index = 0; index < HICHAR; index++) {
      lowcase[index] = 0;
      upcase[index] = 0;
   }

   /* lower to upper, upper to lower */
   for (index = 'a'; index <= 'z'; index++) {
      lowcase[index] = index ^ DIFCASE;
      upcase[index ^ DIFCASE] = index;
   }

   return(1);
}
//-

/// mlferase()
/*
** Erase the message line. This is a special routine because the message line
** is not considered to be part of the virtual screen. It always works
** immediately; the terminal buffer is flushed via a call to the flusher.
 ***********************************************************************/
REG mlferase(void)
{
   register int save_discmd;

   save_discmd = discmd;
   discmd = TRUE;
   mlerase();
   discmd = save_discmd;;
   return(1);
}
//-

/// dlgparse()
int dlgparse(int argc, char *argv[])
{
   short dlg_width;
   short dlg_height;

   DLGBase = OpenLibrary("dlg.library", 4L);
   if (!DLGBase) return FALSE;

   if (argc == NUM_ARGS)
   {
      pathname[2] = default_dir;
   }
   else if (argc == NUM_ARGS+1)
   {
      pathname[2] = argv[NUM_ARGS];
   } else
   {
      return FALSE;
   }

   dlg_body = argv[1];
   dlg_header = argv[2];
   dlg_reply = argv[3];
   dlg_uname = argv[4];
   dlg_port = argv[5];
   /* TLx */
   dlg_local = (dlg_port[1] == 'L' || dlg_port[1] == 'l');

   if (ReadUser(&ram_data, &user_data, dlg_port) == FALSE)
   {
      AFPrintf(NULL,Output(),"no user data\n");
      return FALSE;
   }

   dlg_width = user_data.Screen_Width;
   dlg_height = user_data.Screen_Len;
   xl_access = user_data.User_Level;

   if (dlg_width > 100) dlg_width = 100;  // see display.c

   fillcol = dlg_width - 4;

   term.t_mcol = dlg_width;
   term.t_ncol = dlg_width;
   term.t_mrow = dlg_height - 1;
   term.t_nrow = term.t_mrow;

   id_ok = user_data.Ansi_Flag & ANSI_POS;
   color_ok = user_data.Ansi_Flag & ANSI_COLOR;

   // don't yet know what we're editing
   dlg_msg_hdr.TimesRead = -1;


   // initialize random number generator
   seed = AmigaTime();

   return TRUE;
}
//-

/// dlgusage()
void dlgusage(void)
{
    AFPrintf(NULL,Output(),"Usage: EmacsDLG ~b ~h ~r %UName %Port [cfg-dir]\n");
    exit(5);
}
//-



