/*
*   Extended (M-X) commands.
*/
#include    "def.h"

extern char MSG_not_now[];
extern char MSG_func[];
extern char MSG_unk_func[];
extern char MSG_cmd_t_ex[];
extern char MSG_unk_ext[];
extern char MSG_d_b[];
extern char MSG_unbd[];
extern char MSG_bnd_to[];
extern char MSG_ins_self[];
extern char MSG_bnd_file[];
extern char MSG_bld_wall[];
extern char MSG_wall_head[];
extern char MSG_beavrc[];
extern char MSG_null[];
extern char MSG_extended_command[];
extern char MSG_unk_rc[];


#ifdef CUSTOMIZE

char *flook ();

static char *bindnm =
{
    0
};				/* file name for customized key bindings */
#endif

/*
* This function modifies the keyboard
* binding table, by adjusting the entries in the
* big "bindings" array. Most of the grief deals with the
* prompting for additional arguments. This code does not
* work right if there is a keyboard macro floating around.
* Should be fixed.
*/
bool
bindtokey ()
{

    register int s;
    register SYMBOL *sp;
    register int c;
    char xname[NXNAME];
#ifdef CUSTOMIZE
    char xname2[NXNAME];
#endif

    if (kbdmip != NULL || kbdmop != NULL)
    {
	writ_echo (MSG_not_now);
	return (FALSE);
    }

    if ((s = eread (MSG_func, xname, NXNAME, EFAUTO, NULL)) != TRUE)
	return (s);
    if ((sp = symlookup (xname)) == NULL)
    {
	writ_echo (MSG_unk_func);
	return (FALSE);
    }

#ifdef CUSTOMIZE
    strcpy (xname2, xname);
#endif
    eputc (' ');
    eputc ('K');
    eputc ('e');
    eputc ('y');
    eputc (':');
    eputc (' ');
    ttflush ();
    c = getkey ();		/* Read key.        */
    keyname (xname, c);		/* Display keyname. */
    eputs (xname);
    ttflush ();
    if (binding[c] != NULL)	/* Unbind old, and  */
	--binding[c]->s_nkey;
    binding[c] = sp;		/* rebind new.      */
    ++sp->s_nkey;
    sp->s_modify |= SBOUND;	/* flag as altered key binding */

    return (TRUE);
}

/*
* Extended command. Call the message line
* routine to read in the command name and apply autocompletion
* to it. When it comes back, look the name up in the symbol table
* and run the command if it is found and has the right type.
* Print an error if there is anything wrong.
*/
char
extend (f, n, k)
    int f, n, k;
{

    register SYMBOL *sp;
    register char s;
    char xname[NXNAME];

    if ((s = eread (MSG_cmd_t_ex, xname, NXNAME, EFNEW | EFAUTO, NULL)) != TRUE)
	return (s);
    if ((sp = symlookup (xname)) != NULL)
	return ((*sp->s_funcp) (f, n, KRANDOM));
    writ_echo (MSG_unk_ext);
    return (ABORT);
}

/*
* Read a key from the keyboard, and look it
* up in the binding table. Display the name of the function
* currently bound to the key. Say that the key is not bound
* if it is indeed not bound, or if the type is not a
* "builtin". This is a bit of overkill, because this is the
* only kind of function there is.
*/
bool
help ()
{
    register SYMBOL *sp;
    register int c;
    char b[20];
    char buf[80];

    writ_echo (MSG_d_b);

    c = getkey ();
    keyname (b, c);
    if ((sp = binding[c]) == NULL)
    {
	sprintf (buf, MSG_unbd, b);
	writ_echo (buf);
    }
    else
    {
	sprintf (buf, MSG_bnd_to, b, sp->s_name);
	writ_echo (buf);
    }
    return (TRUE);
}

/*
*   Sort the lines in the buffer.
*/
void
sort_buf (b_ptr, cnt)
    BUFFER *b_ptr;
    int cnt;
{
    LINE *lp1, *lp2;
    bool no_swap;
    int loop1, loop2;

    for (loop1 = cnt; loop1 > 0; loop1--)
    {
	no_swap = TRUE;
	lp1 = b_ptr->b_linep->l_fp;	/* point to first line */
	lp2 = lp1->l_fp;	/* point to next line */
	for (loop2 = 0; loop2 <= loop1; loop2++)
	{
	    /* compare strings and swap if necessary */
	    if (0 < strcmp (&lp1->l_text[HFUNCCOL], &lp2->l_text[HFUNCCOL]))
	    {
		lp1->l_bp->l_fp = lp2;	/* get pointer to first string */
		lp2->l_fp->l_bp = lp1;	/* make it point to second string */

		lp1->l_fp = lp2->l_fp;
		lp2->l_bp = lp1->l_bp;

		lp1->l_bp = lp2;
		lp2->l_fp = lp1;

		lp2->l_file_offset = lp1->l_file_offset;
		lp1->l_file_offset = lp2->l_file_offset + lp2->l_used;

		no_swap = FALSE;
	    }
	    else
	    {
		/* if no swap then advance both pointers */
		lp1 = lp2;
	    }
	    lp2 = lp1->l_fp;
	}
	/* quick exit if sort is finished sooner than expected */
	if (no_swap)
	{
	    return;
	}
    }
}

/*
* This function creates a table, listing all
* of the command keys and their current bindings, and stores
* the table in the standard pop-op buffer (the one used by the
* directory list command, the buffer list command, etc.). This
* lets the editor produce it's own wall chart. The bindings to
* "ins-self" are only displayed if there is an argument.
*/
char
wallchart (f, n, k)
    int f, n, k;
{

    register char s;
    register int key, i, j;
    register SYMBOL *sp;
    register char *cp1;
    register char *cp2;
    char buf[64];
    WINDOW *wp;

    if ((s = bclear (blistp)) != TRUE)	/* Clear it out.    */
	return (s);
    i = 0;
    (void) strcpy (blistp->b_fname, MSG_null);
    blistp->b_flag = BFVIEW;
    blistp->b_type = BTHELP;
    writ_echo (MSG_bld_wall);
    sprintf (buf, MSG_wall_head);
    if (addline (buf) == FALSE)
	return (FALSE);
    for (key = 0; key < NKEYS; ++key)
    {
	/* For all keys.    */
	sp = binding[key];
	if (sp != NULL &&
	    (f != FALSE || strcmp (sp->s_name, MSG_ins_self) != 0))
	{
	    cp1 = &buf[0];
	    while (cp1 < &buf[HFUNCCOL])	/* Goto column 3.  */
		*cp1++ = ' ';
	    if ((sp->s_modify & SBOUND) == 0)	/* comment out default binding */
		buf[0] = '#';
	    cp2 = sp->s_name;	/* Add function name.   */
	    while (*cp1++ = *cp2++)
		;
	    cp1--;
	    while (cp1 < &buf[HKEY])	/* Goto column 32.  */
		*cp1++ = ' ';
	    keyname (&buf[HKEY], key);
	    cp1 = &buf[strlen (buf)];
	    while (cp1 < &buf[HKEYCODE])	/* Goto column 50.  */
		*cp1++ = ' ';
	    sprintf (&buf[HKEYCODE], "%4X", key);
	    if (addline (buf) == FALSE)
		break;		/* lets go with what we have */
	    i++;
	}
    }

    /* list unbound functions lest they get lost */
    for (j = 0; j < NSHASH; j++)
    {
	sp = symbol[j];
	while (sp != NULL)
	{
	    if (sp->s_nkey == 0)
	    {
		cp1 = &buf[0];
		while (cp1 < &buf[HFUNCCOL])	/* Goto column 3.  */
		    *cp1++ = ' ';
		buf[0] = '#';
		cp2 = sp->s_name;	/* Add function name.   */
		while (*cp1++ = *cp2++)
		    ;
		cp1--;
		while (cp1 < &buf[HENDCOL])
		    *cp1++ = ' ';
		*cp1 = 0;
		i++;
		if (addline (buf) == FALSE)
		    break;	/* lets go with what we have */
	    }
	    sp = sp->s_symp;
	}
    }
    sort_buf (blistp, i);	/* sort buffer lines */
    popblist ();
    writ_echo (MSG_null);
    /* make new window the current window */
    wp = wheadp;
    while (wp != NULL)
    {
	if (wp->w_bufp == blistp)
	{
	    curwp = wp;
	    curbp = wp->w_bufp;
	    return (TRUE);
	}
	wp = wp->w_wndp;
    }
    return (TRUE);
}

/* check for RC file and read it in if found
* - also, set local file variable for bindtokey for saving new defs
* (this is some what of a hack as it only handles 'bindtokey' changes at
* this time - also local file io !!!)
*/
void
check_extend (sfname)

    char *sfname;		/* name of startup file (null if default) */

{
    char *fname;		/* resulting file name to execute */
    char rc_name[NFILEN];	/* fixed up name of rc file */
    char *term;
    char *getenv ();
    register SYMBOL *sp;
    char funcname[NXNAME + 1];
    char keybind[NXNAME + 1];
    int keyval;
    FILE *bindf;

    /* look up the startup file */
    if ((sfname != NULL) && (*sfname != 0))
	fname = flook (sfname, TRUE);
    else
    {
#ifdef UNIX
	/* hidden file under unix */
	strcpy (&rc_name[0], ".");
	strcpy (&rc_name[1], MSG_beavrc);

	if ((term = getenv ("TERM")) != 0)
	{
	    strcpy (&rc_name[strlen (rc_name)], ".");
	    strcpy (&rc_name[strlen (rc_name)], term);
	}
	fname = flook (rc_name, TRUE);
	/* if fixed up name is not there then check original */
	if (fname == NULL)
	{
	    /* hidden file under unix */
	    strcpy (&rc_name[0], ".");
	    strcpy (&rc_name[1], MSG_beavrc);
	    fname = flook (rc_name, TRUE);
	}
#else
	strcpy (rc_name, MSG_beavrc);
	fname = flook (rc_name, TRUE);
#ifdef AMIGA
	/* look for .beavrc in the current directory */
	if (!fname)
	{
	    rc_name[0] = '.';
	    strcpy (&rc_name[1], MSG_beavrc);
	    fname = flook (rc_name, TRUE);
	}
	/* look for .beavrc in S: */
	if (!fname)
	{
	    /* Have a look in startup directory */
	    rc_name[0] = 'S';
	    rc_name[1] = ':';
	    rc_name[2] = '.';
	    strcpy (&rc_name[3], MSG_beavrc);
	    fname = flook (rc_name, TRUE);
	}
#endif /* AMIGA */
#endif
    }
    /* if it isn't around, don't sweat it */
    if (fname == NULL)
	return;

    if (bindf = fopen (fname, "r"))
    {
	char buffr[80];
	char *buffp;

	buffp = buffr;
	while (fread (buffp++, sizeof (char), 1, bindf) == 1)
	{
	    /* scanf is unhappy with commas */
	    if (buffp[-1] == ',')
		buffp[-1] = '-';

	    /* did we get a whole line */
	    if (buffp[-1] == '\n')
	    {
		*buffp = 0;	/* terminate line */
		buffp = buffr;
		sscanf (buffr, "%s %s %x", funcname, keybind, &keyval);
		if ((buffr[0] == '#') || (keyval == 0))
		    continue;
		/* check if this is a command to execute */
		if ((strcmp (funcname, MSG_extended_command) == 0) &&
		    (keybind[0] > 'Z'))
		{
		    if ((sp = symlookup (keybind)) != NULL)
			(*sp->s_funcp) (TRUE, keyval, KRANDOM);
		    else
		    {
			char temp_b[40];

			sprintf (temp_b, "%s %s", MSG_unk_rc, keybind);
			writ_echo (temp_b);
		    }
		}
		else
		{
		    if (sp = symlookup (funcname))
		    {
			if (binding[keyval] != NULL)	/* Unbind old, and  */
			    --binding[keyval]->s_nkey;
			binding[keyval] = sp;	/* rebind new.      */
			++sp->s_nkey;
			sp->s_modify |= SBOUND;	/* flag as altered key binding */
		    }
		}
	    }
	}
	fclose (bindf);
    }
}

/*	Look up the existance of a file along the normal or PATH
	environment variable. Look first in the HOME directory if
	asked and possible
*/

char *
flook (fname, hflag)

    char *fname;		/* base file name to search for */
    int hflag;			/* Look in the HOME environment variable first? */

{
    register char *home;	/* path to home directory */
    register char *path;	/* environmental PATH variable */
    register char *sp;		/* pointer into path spec */
    register int i;		/* index */
    static char fspec[NFILEN * 2];	/* full path spec to search */
    char *getenv ();
    FILE *bindf;

    if (hflag)
    {
	home = getenv ("HOME");
	if (home != NULL)
	{
	    /* build home dir file spec */
	    strcpy (fspec, home);
	    if (fspec[strlen (fspec) - 1] != '/')
		strcat (fspec, "/");
	    strcat (fspec, fname);

	    /* and try it out */
	    if (bindf = fopen (fspec, "r"))
	    {
		fclose (bindf);
		return (fspec);
	    }
	}
    }

    /* always try the current directory first */
    if (bindf = fopen (fname, "r"))
    {
	fclose (bindf);
	return (fname);
    }

    /* get the PATH variable */
    path = getenv ("PATH");
    if (path != NULL)
	while (*path)
	{

	    /* build next possible file spec */
	    sp = fspec;
	    while (*path && (*path != PATHCHR))
		*sp++ = *path++;

	    /* add a terminating dir separator if we need it */
	    if (sp[-1] != SEPCHAR)
		*sp++ = SEPCHAR;

	    *sp = 0;
	    strcat (fspec, fname);

	    /* and try it out */
	    if (bindf = fopen (fspec, "r"))
	    {
		fclose (bindf);
		return (fspec);
	    }

	    if (*path == PATHCHR)
		++path;
	}

    return (NULL);		/* no such luck */
}

/* interactive method for loading binding file
* (uses above routine, obviously)
*/
char
load_extend ()
{

#ifdef CUSTOMIZE
    register char s;
    char fname[NFILEN];

    if ((s = ereply (MSG_bnd_file, fname, NFILEN, NULL)) != TRUE)
	return (s);
    check_extend (fname);
    writ_echo (okmsg);
#endif
    return (TRUE);
}

int
find_keyval (name)
    char *name;
{
    SYMBOL *sp;
    int key;

    for (key = 0; key < NKEYS; ++key)
    {
	/* For all keys.    */
	sp = binding[key];
	if (sp != NULL && (strcmp (sp->s_name, name) == 0))
	    return (key);
    }
    return (0);
}
