static char rcsid[] = "$Id: mmaux.c,v 1.2 1992/11/08 23:29:54 mike Exp $";

/* $Log: mmaux.c,v $
 * Revision 1.2  1992/11/08  23:29:54  mike
 * - Added view mode.
 *
 * Revision 1.1  1992/09/05  01:13:32  mike
 * Initial revision
 *
 */

#ifdef __hpux
#pragma OPT_LEVEL 1	/* !!! Stupid HP-UX s300 8.0 cc doesn't like -O */
#endif

/*
 * mmaux.c : Support for external Mutt (ME2) functions.
 *  Craig Durland 6/87
 */

/* Copyright 1990, 1991, 1992 Craig Durland
 *   Distributed under the terms of the GNU General Public License.
 *   Distributed "as is", without warranties of any kind, but comments,
 *     suggestions and bug reports are welcome.
 */

#include <stdio.h>
#include "me2.h"
#include "mm.h"
#include "bind.h"

extern Binding bindings[];
extern Buffer *id_to_buffer();
extern char
  result[],				/* in mm.c */
  *strcpy(), *strcat(), *getenv(),
  *ext(), *savestr();
extern int MMask_pgm;			/* in mm.c */
extern uint8 *MMglobal_vars;		/* in mm.c */
extern void  *MMglobal_object_table;	/* in mm.c   Really a *Object[] */
extern MMDatum RV, TV;			/* in mm.c */

int
  arg_flag = FALSE, arg_prefix = 1,	/* for ME commands */
  pgm_flag = FALSE, pgm_prefix = 1;	/* for pgms */

/* ******************************************************************** */
/* **************** tables ******************************************** */
/* ******************************************************************** */
	/* holes:  */
MuttCmd mutcmds[] =	/* last number = 583 */
{
  "EoB",			505,
  "OS-filter",			536,
  "append-to-bag",		569,
  "arg-flag",			521,
  "arg-prefix",			516,
  "argc",			544,
  "argv",			545,
  "attached-buffer",		560,
  "bag-stats",			518,
  "bag-to-file",		532,
  "bag-to-string",		582,
  "bit-and",			527,
  "bit-or",			528,
  "bit-xor",			529,
  "buffer-flags",		566,
  "buffer-modified",		507,
  "buffer-name",		508,
  "buffer-stats",		554,
  "buffer-var",			578,
  "buffers",			509,
  "case-bag",			580,
  "clear-bag",			570,
  "clear-buffer",		561,
  "compare-marks",		540,
  "complete",			551,
  "create-bag",			537,
  "create-buffer",		541,
  "create-buffer-var",		577,
  "create-mark",		526,
  "create-process",		581,
  "current-buffer",		513,
  "current-column",		511,
  "current-directory",		565,
  "current-window",		558,
  "do-undo",			579,
  "erase-rectangle",		523,
  "exe-key",			503,
  "file-exists",		530,
  "file-name",			510,
  "file-to-bag",		535,
  "forward-line",		519,
  "free-bag",			538,
  "free-buffer",		543,
  "free-mark",			553,
  "free-window",		556,
  "get-key",			502,
  "get-matched",		549,
  "getchar",			522,
  "getenv",			568,
  "goto-line",			576,
  "goto-mark",			547,
  "insert-bag",			571,
  "insert-text",		524,
  "is-space",			525,
  "key-bound-to",		562,
  "key-pressed",		514,
  "key-waiting",		504,
  "looking-at",			548,
  "modify-syntax-entry",	546,
  "move-cursor",		557,
  "nth-buffer",			542,
  "pgm-exists",			515,
  "prefix-key",			567,
  "puts",			531,
  "re-search-forward",		572,
  "re-search-replace",		575,
  "re-search-reverse",		573,
  "re-string",			506,
  "region-stats",		517,
  "search-forward",		552,
  "search-replace",		574,
  "search-reverse",		563,
  "set-mark",			550,
  "swap-marks",			583,
  "sysvar",			501,
  "to-col",			533,
  "update",			534,
  "window-height",		520,
  "window-ledge",		564,
  "window-row",			555,
  "windows",			559,
  "yesno",			512,
};
int msize = NITEMS(mutcmds);

MuttCmd sysvars[] =	/* last number = 13, holes: 10 */
{
  "HELP",		 1,
  "beeper",		11,
  "case-fold-search",	 5,
  "complete-key",	 3,
  "horizontal-scroll",	13,
  "modeline-color",	 9,
  "overstrike",		 2,
  "screen-length",	 4,
  "screen-width",	12,
  "tab-stops",		 6,
  "text-color",		 8,
  "word-wrap",		 7,
};
int svsize = NITEMS(sysvars);

/* ******************************************************************** */
/* ********** Stuff called from mm.c ********************************** */
/* ******************************************************************** */

    /* Run a ME2 built in command */
static void sysfcn(n)
{
  if ((curbp->b_flags & BFVIEW) && bindings[n].prohibited)
  {
    view_mode_violation();
    return;
  }
#if 0
  {
    mlwrite("[Program used command illegal in view mode -- aborted]");
    t_beep();
    MMabort_pgm(1);
  }
#endif
  if ((RV.val.num = (*bindings[n].fcn)(arg_flag,arg_prefix)) == ABORT)
    MMabort_pgm(1);
  RV.type = BOOLEAN;
  arg_flag = FALSE; arg_prefix = 1;
  MMask_pgm = TRUE;
}

    /* Run a aux function by name.
     * Could be a function, command or sys var.
     */
MMaux_fcn(name) char *name;
{
  int i;

  if ((i = lookupmut(name,mutcmds,msize)) != -1) { Mdomutt(i); return TRUE; }
  if (lookupfcn(name,&i)) { sysfcn(i); return TRUE; }	/* system fcn */
  if ((i = lookupmut(name,sysvars,svsize)) != -1)
	{ Msys_var(i,0); return TRUE; }
  return FALSE;		/* ain't nothing I know about */
}

void MMxtoken(token)
{
  if (token < 500) sysfcn(token); else Mdomutt(token);
}

void MMask(prompt,buf) char *prompt, *buf;
{
  mlreply(prompt,buf,RSIZ,0);
}

void MMmsg(str) char *str; { mlputs(str); }

void MMbitch(msg) char *msg; { mlwrite("PGM ABORT: %s",msg); MMabort_pgm(2); }

void MMmoan(msg) char *msg; { mlputs(msg); }

/* ******************************************************************** */
/* **************** Helper routines *********************************** */
/* ******************************************************************** */

	/* Use binary search to find system function name
	 * returns index of token if found
	 * -1 if not found
	 */
int lookupmut(name,table,size)		/* MUTT functions */
  char *name; MuttCmd *table; int size;
{
  register int  j, lower = 0, upper = size-1, x;

  while (lower<=upper)
  {
    j = (lower+upper)/2;
    if ((x = strcmp(name,table[j].name))>0) lower = j +1;
    else if (x<0) upper = j -1; else return table[j].token;
  }
  return -1;
}

	/* complain about name getting a bad arg and die */
void arg_bitch(name) char *name;
  { MMbitch(strcat(strcpy(result,name),": Bad arg")); }

	/* Get the 0th arg and put it into RV */
void get1arg(type,name) char *name;
  { if (!MMpull_nth_arg(&RV,0) || RV.type != type) arg_bitch(name); }

	/* Get the nth arg and put it into RV */
void get_nth_arg(n,type,name) char *name;
  { if (!MMpull_nth_arg(&RV,n) || RV.type != type) arg_bitch(name); }

	/* RV <= arg 0, TV <= arg 1 */
void get2args(t1,t2,name) char *name;
{
  if (!MMpull_nth_arg(&RV,0) || RV.type != t1 ||
      !MMpull_nth_arg(&TV,1) || TV.type != t2) arg_bitch(name);
}

	/* Get the nth arg (if there is one) and put it into TV */
maybearg(n,type,name) char *name;
{
  register int s;

  if ((s = MMpull_nth_arg(&TV,n)) && TV.type != type) arg_bitch(name);
  return s;
}

	/* Get the 0th arg, make sure its a number in [a,b)
	 *   and put it into RV */
get1num(a,b,name) char *name;
{
  get1arg(NUMBER,name);
  if (RV.val.num < a || b <= RV.val.num) arg_bitch(name);
  return (int)RV.val.num;
}

	/* save bunches of code over macros */
void put_int32(blob,x) register unsigned char *blob; register int32 x;
	{ PUT_INT32(blob,x); }
void put_int16(blob,x) register unsigned char *blob; register int x;
	{ PUT_INT16(blob,x); }

extern Window *nthwindow();

Buffer *Mid_to_buffer(buf_id) register int buf_id;
{
  Buffer *bp;

  if (buf_id == -1) return curbp;
  if (!(bp = id_to_buffer(buf_id))) MMbitch("Bad buffer id");
  return bp;
}

Window *Mnth_window(n) register int n;
	{ return (n == -1) ? curwp : nthwindow(first_window,n); }


/*************************************************************************
****************** pgm management ****************************************
**************************************************************************/

#define PGMMAX    300		/* Max number of loaded programs */

extern void MMblock_name();

static int lookup_block(), lookuppgm();
static void gcpgms();

    /* Load Mutt code from a file.
     * Notes:
     *   I really don't like this being a ME command, but if it was just
     *     callable from Mutt, it would be a real pain in the butt to if ME
     *     started up not being able to find code - catch 22.
     *   There is a bit of a sleeze here.  I want to (sometimes) load a file
     *     only if it hasn't already been loaded so I (way) overload the
     *     flags.
     */
load_Mutt_file(f,n) int f,n;
{
  char fname[NFILEN];
  int s;

  if ((s = mlreply("Load file: ", fname, NFILEN, CC_FNAME)) != TRUE) return s;

  if (n == 42)
  {
    char buf[NFILEN];

    MMblock_name(buf,fname);
    if (-1 != lookup_block(buf)) return TRUE;
  }

  return MMload(fname,!f);
}

#if 0

ME_load(f,n) int f,n;
{
  char fname[NFILEN];
  int s;

  if ((s = mlreply("Load file: ", fname, NFILEN, CC_FNAME)) != TRUE) return s;

  return MMload(fname,TRUE);
}

this to mmfcn2.c
Mload_Mutt_file(fname, complain, check_first) char *fname;
{
  int s;

  if (check_first)
  {
    char buf[NFILEN];

    MMblock_name(buf,fname);
    if (-1 != lookup_block(buf)) return TRUE;
  }

  return MMload(fname, complain);
}
#endif

/* *********** Code Blocks **************************************** */

void MMblock_name(buf,fname) char *buf,*fname;	/* create the block name */
  { makename(buf,fname); *ext(buf) = '\0'; }

   /* A block is most of the contents of a .mco file.  It contains Mutt
    *   code, the global variable area used by the code, string table and
    *   the names of the programs in the block.  Blocks are created and read
    *   in mm.c.
    */
#define FIRSTBLOCK 1	/* block 0 is a dummy block (the dead block). */
typedef struct
{
  char  *name;		/* Name of the block (munged file name) */
  uint8 *global_vars;	/* Where the global vars are in this block */
  maddr code;		/* The start of the code in this block */
  void *global_object_table;
  int num_global_objects;
} CodeBlock;

static declare_and_init_dTable(blktable,CodeBlock);

static int lookup_block(block_name) char *block_name;
{
  int j;

  for (j = FIRSTBLOCK; j < sizeof_dTable(&blktable); j++)
    if (0 == strcmp(blktable.table[j].name, block_name)) return j;

  return -1;
}

static void free_block(n)
{
  CodeBlock *ptr;

  ptr = &blktable.table[n];
  MMfree_block(ptr->code, ptr->global_object_table, ptr->num_global_objects);
  gcpgms(n);		/* remove pgms attached to this block */
  packkeys();		/* remove keys attached to pgms in this block */
}

	/*  Note: a block contains both the code and the pgm names */
MMadd_block(name,code,global_vars, global_object_table,num_global_objects)
  char *name; maddr code; uint8 *global_vars;
  void *global_object_table; int num_global_objects;
{
  CodeBlock *ptr;
  int j;

  for (j = FIRSTBLOCK; j < sizeof_dTable(&blktable); j++)
  {
    ptr = &blktable.table[j];
    if (0 == strcmp(ptr->name, name))		/* block exists */
    {
      free_block(j);
      goto xxx;
    }
  }

	/* j is max(1, 1+ the last block) */
  if (!xpand_dTable(&blktable,1,10,10) ||
      (blktable.table[j].name = savestr(name)) == NULL)
  {
    MMmoan("Can't add code block");
    return -1;
  }

  sizeof_dTable(&blktable) = j+1;	/* in case skipping first block */
  ptr = &blktable.table[j];

xxx:

  ptr->code		   = code;
  ptr->global_vars	   = global_vars;
  ptr->global_object_table = global_object_table;
  ptr->num_global_objects  = num_global_objects;

  return j;
}

/* ************** Programs ******************************************* */

	/* slime note: I don't use blktable[0] so that the pgms array
	 *   is inited to all dead
	 */
typedef struct		/* program address table */
{
  maddr addr;		/* address of routine */
  short int block;	/* link to programs code block. 0 => dead */
} Pgm;

static Pgm pgms[PGMMAX];
PgmName pnames[PGMMAX];
int pbsize = 0;			/* number of loaded programs */

maddr MMpgm_addr(n)
{
  CodeBlock *ptr = &blktable.table[pgms[n].block];

  MMglobal_vars		= ptr->global_vars;
  MMglobal_object_table = ptr->global_object_table;

  return pgms[n].addr;
}

#define PGMDEAD(n) (pgms[n].block == 0)
pgmdead(n) { return PGMDEAD(n); }

#define DEL_PGM(n) (pgms[n].block = 0)

static void gcpgms(block)	/* garbage collect programs */
{
  int j,k;

  for (j = 0; j < PGMMAX; j++) if (pgms[j].block == block) DEL_PGM(j);
	/* pack the name table */
  for (j = 0; j < pbsize && !PGMDEAD(pnames[j].index); j++) ;
  for (k = j; j < pbsize; j++)
    if (!PGMDEAD(pnames[j].index)) pnames[k++] = pnames[j];
  pbsize = k;
}

MMadd_pgm(name,block,addr) char *name; int block; maddr addr;
{
  int j,n;
  static int k = 0;

  if (lookuppgm(name,&n)) k = pnames[n].index;	/* pgm exists: overwrite */
  else						/* new name */
  {
    if (pbsize == PGMMAX)	/* Opps - all full up */
    {
      MMmoan("Program table full - load aborted");
      free_block(block);
      return FALSE;
    }
    for (j = pbsize; n < j; j--) pnames[j] = pnames[j-1];	/* open hole */
    pbsize++;
		/* find next available slot */
    while (!PGMDEAD(k)) if (++k == PGMMAX) k = 0;
    pnames[n].index = k;
  }
  pnames[n].name = name;
  pgms[k].block = block; pgms[k].addr = addr;

  return TRUE;
}

	/* Use binary search to find pgm.
	 * Returns:  index into pgm table, -1 if not found.
	 */
MMpgm_lookup(name) char *name;
{
  register int j, lower = 0, upper = pbsize-1, x;

  while (lower <= upper)
  {
    j = (lower+upper)/2;
    if ((x = strcmp(name,pnames[j].name)) > 0) lower = j +1;
    else if (x < 0) upper = j -1; else return pnames[j].index;
  }
  return -1;
}

static int lookuppgm(name,n) char *name; int *n;
{
  register int j, x;

  for (j = pbsize -1; 0 <= j; j--)
  {
    if ((x = strcmp(pnames[j].name,name)) == 0) { *n = j; return TRUE; }
    if (x < 0) break;
  }
  *n = j +1;
  return FALSE;
}


/* ******************* External Objects ********************************** */

    /* Garbage collect all mortal (temporary) objects that may have been
     *   created by Mutt programs and not freed by them.
     * This is typically called (by the Mutt Machine) after a pgm has been
     *   run or aborted to clean up stuff the programmer forgot to or was
     *   unable to (as in the case where the pgm was aborted).
     * Problems with this method of GC:
     *   I won't notice some dependences between Mutt objects and external
     *     objects.  For example, if a global Mutt object creates external
     *     objects, the block GC code will have to be smart enough to free
     *     the external objects.  Which ain't goona happen until I add OOP
     *     support and pass the burden back to the programmer (by having
     *     destructers that can be called from the block GC code).
     *   MMgc_external_objects() can only be called when the root pgm has
     *     been terminated (and control returns from MM to the application)
     *     to ensure objects aren't freed while they are still in use.  This
     *     means I can't GC when low on memory in hopes of getting some
     *     memory back.  This might be a real problem for an application
     *     that just fires off MM and doesn't expect it to return.
     *   Since this might be called quite a lot, it might be expensive to
     *     look though all objects to try and find dead ones (especially if
     *     the Mutt programmer was good and cleaned things up).  On the
     *     other hand, it is probably called from a wait-for-keyboard loop
     *     and thats when we have time to burn.
     * Pros:
     *   This style of GC works well with applications like ME2 - the user
     *     won't see dead buffers and ME2 won't waste time updating dead
     *     marks.
     *   If you have a real GC, you can ignore this call.
     */
static void ME2_gc()
{
  gc_bags();
  gc_buffers();
  gc_marks();
}

void MMgc_external_objects()
{
  call_me_sometime(GC_HOOK, ME2_gc);
}
