/*
 *	Command execution
 *	Copyright
 *		(C) 1992 Joseph H. Allen
 *
 *	This file is part of JOE (Joe's Own Editor)
 */
#include "config.h"
#include "types.h"

#include <string.h>

#include "b.h"
#include "bw.h"
#include "cmd.h"
#include "hash.h"
#include "help.h"
#include "macro.h"
#include "main.h"
#include "menu.h"
#include "path.h"
#include "poshist.h"
#include "pw.h"
#include "rc.h"
#include "tty.h"
#include "tw.h"
#include "ublock.h"
#include "uedit.h"
#include "uerror.h"
#include "ufile.h"
#include "uformat.h"
#include "uisrch.h"
#include "umath.h"
#include "undo.h"
#include "usearch.h"
#include "ushell.h"
#include "utag.h"
#include "utils.h"
#include "va.h"
#include "vs.h"
#include "w.h"

extern int smode;
int beep = 0;
int uexecmd(BW *bw);

/* Command table */

CMD cmds[] = {
	{"abort", TYPETW + TYPEPW + TYPEMENU + TYPEQW, uabort, NULL, 0, NULL},
	{"abortbuf", TYPETW, uabortbuf, NULL, 0, NULL},
	{"arg", TYPETW + TYPEPW + TYPEMENU + TYPEQW, uarg, NULL, 0, NULL},
	{"ask", TYPETW + TYPEPW, uask, NULL, 0, NULL},
	{"uarg", TYPETW + TYPEPW + TYPEMENU + TYPEQW, uuarg, NULL, 0, NULL},
	{"backs", TYPETW + TYPEPW + ECHKXCOL + EFIXXCOL + EMINOR + EKILL + EMOD, ubacks, NULL, 1, "delch"},
	{"backsmenu", TYPEMENU, umbacks, NULL, 1, NULL},
	{"backw", TYPETW + TYPEPW + ECHKXCOL + EFIXXCOL + EKILL + EMOD, ubackw, NULL, 1, "delw"},
	{"bknd", TYPETW + TYPEPW, ubknd, NULL, 0, NULL},
	{"bkwdc", TYPETW + TYPEPW, ubkwdc, NULL, 1, "fwrdc"},
	{"blkcpy", TYPETW + TYPEPW + EFIXXCOL + EMOD, ublkcpy, NULL, 1, NULL},
	{"blkdel", TYPETW + TYPEPW + EFIXXCOL + EKILL + EMOD, ublkdel, NULL, 0, NULL},
	{"blkmove", TYPETW + TYPEPW + EFIXXCOL + EMOD, ublkmove, NULL, 0, NULL},
	{"blksave", TYPETW + TYPEPW, ublksave, NULL, 0, NULL},
	{"bof", TYPETW + TYPEPW + EMOVE + EFIXXCOL, u_goto_bof, NULL, 0, NULL},
	{"bofmenu", TYPEMENU, umbof, NULL, 0, NULL},
	{"bol", TYPETW + TYPEPW + EFIXXCOL, u_goto_bol, NULL, 0, NULL},
	{"bolmenu", TYPEMENU, umbol, NULL, 0, NULL},
	{"bop", TYPETW + TYPEPW + EFIXXCOL, ubop, NULL, 1, "eop"},
	{"bos", TYPETW + TYPEPW + EMOVE, ubos, NULL, 0, NULL},
	{"bufed", TYPETW, ubufed, NULL, 0, NULL},
	{"byte", TYPETW + TYPEPW, ubyte, NULL, 0, NULL},
	{"center", TYPETW + TYPEPW + EFIXXCOL + EMOD, ucenter, NULL, 1, NULL},
	{"ctrl", TYPETW + TYPEPW + EMOD, uctrl, NULL, 0, NULL},
	{"col", TYPETW + TYPEPW, ucol, NULL, 0, NULL},
	{"complete", TYPETW + TYPEPW + EMINOR + EMOD, ucmplt, NULL, 0, NULL},
	{"copy", TYPETW + TYPEPW, ucopy, NULL, 0, NULL},
	{"crawll", TYPETW + TYPEPW, ucrawll, NULL, 1, "crawlr"},
	{"crawlr", TYPETW + TYPEPW, ucrawlr, NULL, 1, "crawll"},
	{"delbol", TYPETW + TYPEPW + EFIXXCOL + EKILL + EMOD, udelbl, NULL, 1, "deleol"},
	{"delch", TYPETW + TYPEPW + ECHKXCOL + EFIXXCOL + EMINOR + EKILL + EMOD, udelch, NULL, 1, "backs"},
	{"deleol", TYPETW + TYPEPW + EKILL + EMOD, udelel, NULL, 1, "delbol"}, 
	{"dellin", TYPETW + TYPEPW + EFIXXCOL + EKILL + EMOD, udelln, NULL, 1, NULL}, 
	{"delw", TYPETW + TYPEPW + EFIXXCOL + ECHKXCOL + EKILL + EMOD, u_word_delete, NULL, 1, "backw"},
	{"dnarw", TYPETW + TYPEPW + EMOVE, udnarw, NULL, 1, "uparw"},
	{"dnarwmenu", TYPEMENU, umdnarw, NULL, 1, "uparwmenu"}, 
	{"dnslide", TYPETW + TYPEPW + TYPEMENU + TYPEQW + EMOVE, udnslide, NULL, 1, "upslide"},
	{"drop", TYPETW + TYPEPW, udrop, NULL, 0, NULL},
	{"dupw", TYPETW, uduptw, NULL, 0, NULL},
	{"edit", TYPETW + TYPEPW, uedit, NULL, 0, NULL},
	{"eof", TYPETW + TYPEPW + EFIXXCOL + EMOVE, u_goto_eof, NULL, 0, NULL},
	{"eofmenu", TYPEMENU, umeof, NULL, 0, NULL},
	{"eol", TYPETW + TYPEPW + EFIXXCOL, u_goto_eol, NULL, 0, NULL},
	{"eolmenu", TYPEMENU, umeol, NULL, 0, NULL},
	{"eop", TYPETW + TYPEPW + EFIXXCOL, ueop, NULL, 1, "bop"},
	{"execmd", TYPETW + TYPEPW, uexecmd, NULL, 0, NULL},
	{"explode", TYPETW + TYPEPW + TYPEMENU + TYPEQW, uexpld, NULL, 0, NULL},
	{"exsave", TYPETW + TYPEPW, uexsve, NULL, 0, NULL},
	{"ffirst", TYPETW + TYPEPW, pffirst, NULL, 0, NULL},
	{"filt", TYPETW + TYPEPW + EMOD, ufilt, NULL, 0, NULL},
	{"fnext", TYPETW + TYPEPW, pfnext, NULL, 1, NULL},
	{"format", TYPETW + TYPEPW + EFIXXCOL + EMOD, uformat, NULL, 1, NULL},
	{"fmtblk", TYPETW + EMOD + EFIXXCOL, ufmtblk, NULL, 1, NULL},
	{"fwrdc", TYPETW + TYPEPW, ufwrdc, NULL, 1, "bkwdc"},
	{"gomark", TYPETW + TYPEPW + EMOVE, ugomark, NULL, 0, NULL},
	{"groww", TYPETW, ugroww, NULL, 1, "shrinkw"},
	{"isrch", TYPETW + TYPEPW, uisrch, NULL, 0, NULL},
	{"killproc", TYPETW + TYPEPW, ukillpid, NULL, 0, NULL},
	{"help", TYPETW + TYPEPW + TYPEQW, u_help, NULL, 0, NULL},
	{"hnext", TYPETW + TYPEPW + TYPEQW, u_help_next, NULL, 0, NULL},
	{"hprev", TYPETW + TYPEPW + TYPEQW, u_help_prev, NULL, 0, NULL},
	{"insc", TYPETW + TYPEPW + EFIXXCOL + EMOD, uinsc, NULL, 1, "delch"},
	{"insf", TYPETW + TYPEPW + EMOD, uinsf, NULL, 0, NULL}, 
	{"lindent", TYPETW + TYPEPW + EFIXXCOL + EMOD, ulindent, NULL, 1, "rindent"},
	{"line", TYPETW + TYPEPW, uline, NULL, 0, NULL},
	{"lose", TYPETW + TYPEPW, ulose, NULL, 0, NULL}, 
	{"ltarw", TYPETW + TYPEPW + EFIXXCOL + ECHKXCOL, u_goto_left, NULL, 1, "rtarw"},
	{"ltarwmenu", TYPEMENU, umltarw, NULL, 1, "rtarwmenu"},
	{"macros", TYPETW + EFIXXCOL, umacros, NULL, 0, NULL},
	{"markb", TYPETW + TYPEPW, umarkb, NULL, 0, NULL},
	{"markk", TYPETW + TYPEPW, umarkk, NULL, 0, NULL},
	{"markl", TYPETW + TYPEPW, umarkl, NULL, 0, NULL},
	{"math", TYPETW + TYPEPW, umath, NULL, 0, NULL},
	{"mode", TYPETW + TYPEPW + TYPEQW, umode, NULL, 0, NULL},
	{"msg", TYPETW + TYPEPW + TYPEQW + TYPEMENU, umsg, NULL, 0, NULL},
	{"nbuf", TYPETW, unbuf, NULL, 1, "pbuf"},
	{"nedge", TYPETW + TYPEPW + EFIXXCOL, unedge, NULL, 1, "pedge"}, 
	{"nextpos", TYPETW + TYPEPW + EFIXXCOL + EMID + EPOS, unextpos, NULL, 1, "prevpos"}, 
	{"nextw", TYPETW + TYPEPW + TYPEMENU + TYPEQW, unextw, NULL, 1, "prevw"},
	{"nextword", TYPETW + TYPEPW + EFIXXCOL, u_goto_next, NULL, 1, "prevword"},
	{"nmark", TYPETW + TYPEPW, unmark, NULL, 0, NULL},
	{"notmod", TYPETW, unotmod, NULL, 0, NULL},
	{"nxterr", TYPETW, unxterr, NULL, 1, "prverr"},
	{"open", TYPETW + TYPEPW + EFIXXCOL + EMOD, uopen, NULL, 1, "deleol"},
	{"parserr", TYPETW, uparserr, NULL, 0, NULL},
	{"pbuf", TYPETW, upbuf, NULL, 1, "nbuf"},
	{"pedge", TYPETW + TYPEPW + EFIXXCOL, upedge, NULL, 1, "nedge"}, 
	{"pgdn", TYPETW + TYPEPW + TYPEMENU + TYPEQW + EMOVE, upgdn, NULL, 1, "pgup"},
	{"pgup", TYPETW + TYPEPW + TYPEMENU + TYPEQW + EMOVE, upgup, NULL, 1, "pgdn"},
	{"picokill", TYPETW + TYPEPW + EFIXXCOL + EKILL + EMOD, upicokill, NULL, 1, NULL},
	{"play", TYPETW + TYPEPW + TYPEMENU + TYPEQW, uplay, NULL, 1, NULL},	/* EFIXX? */ 
	{"prevpos", TYPETW + TYPEPW + EPOS + EMID + EFIXXCOL, uprevpos, NULL, 1, "nextpos"}, 
	{"prevw", TYPETW + TYPEPW + TYPEMENU + TYPEQW, uprevw, NULL, 1, "nextw"}, 
	{"prevword", TYPETW + TYPEPW + EFIXXCOL + ECHKXCOL, u_goto_prev, NULL, 1, "nextword"},
	{"prverr", TYPETW, uprverr, NULL, 1, "nxterr"},
	{"psh", TYPETW + TYPEPW + TYPEMENU + TYPEQW, upsh, NULL, 0, NULL},
	{"pop", TYPETW + TYPEPW + TYPEMENU + TYPEQW, upop, NULL, 0, NULL},
	{"qrepl", TYPETW + TYPEPW + EMOD, pqrepl, NULL, 0, NULL},
	{"query", TYPETW + TYPEPW + TYPEMENU + TYPEQW, uquery, NULL, 0, NULL},
	{"quote", TYPETW + TYPEPW + EMOD, uquote, NULL, 0, NULL},
	{"quote8", TYPETW + TYPEPW + EMOD, uquote8, NULL, 0, NULL},
	{"record", TYPETW + TYPEPW + TYPEMENU + TYPEQW, urecord, NULL, 0, NULL},
	{"redo", TYPETW + TYPEPW + EFIXXCOL, uredo, NULL, 1, "undo"},
	{"retype", TYPETW + TYPEPW + TYPEMENU + TYPEQW, uretyp, NULL, 0, NULL},
	{"rfirst", TYPETW + TYPEPW, prfirst, NULL, 0, NULL}, 
	{"rindent", TYPETW + TYPEPW + EFIXXCOL + EMOD, urindent, NULL, 1, "lindent"},
	{"run", TYPETW + TYPEPW, urun, NULL, 0, NULL},
	{"rsrch", TYPETW + TYPEPW, ursrch, NULL, 0, NULL},
	{"rtarw", TYPETW + TYPEPW + EFIXXCOL, u_goto_right, NULL, 1, "ltarw"},
	{"rtarwmenu", TYPEMENU, umrtarw, NULL, 1, "ltarwmenu"},
	{"rtn", TYPETW + TYPEPW + TYPEMENU + TYPEQW + EMOD, urtn, NULL, 1, NULL},
	{"save", TYPETW + TYPEPW, usave, NULL, 0, NULL},
	{"setmark", TYPETW + TYPEPW, usetmark, NULL, 0, NULL},
	{"shell", TYPETW + TYPEPW + TYPEMENU + TYPEQW, ushell, NULL, 0, NULL},
	{"shrinkw", TYPETW, ushrnk, NULL, 1, "groww"},
	{"splitw", TYPETW, usplitw, NULL, 0, NULL},
	{"stat", TYPETW + TYPEPW, ustat, NULL, 0, NULL},
	{"stop", TYPETW + TYPEPW + TYPEMENU + TYPEQW, ustop, NULL, 0, NULL},
	{"swap", TYPETW + TYPEPW + EFIXXCOL, uswap, NULL, 0, NULL},
	{"tag", TYPETW + TYPEPW, utag, NULL, 0, NULL},
	{"tomarkb", TYPETW + TYPEPW + EFIXXCOL, utomarkb, NULL, 0, NULL},
	{"tomarkbk", TYPETW + TYPEPW + EFIXXCOL, utomarkbk, NULL, 0, NULL},
	{"tomarkk", TYPETW + TYPEPW + EFIXXCOL, utomarkk, NULL, 0, NULL},
	{"tomatch", TYPETW + TYPEPW + EFIXXCOL, utomatch, NULL, 0, NULL},
	{"tos", TYPETW + TYPEPW + EMOVE, utos, NULL, 0, NULL},
	{"tw0", TYPETW + TYPEPW + TYPEQW + TYPEMENU, utw0, NULL, 0, NULL},
	{"tw1", TYPETW + TYPEPW + TYPEQW + TYPEMENU, utw1, NULL, 0, NULL},
	{"txt", TYPETW + TYPEPW, utxt, NULL, 0, NULL}, 
	{"type", TYPETW + TYPEPW + TYPEQW + TYPEMENU + EMINOR + EMOD, utype, NULL, 1, "backs"},
	{"undo", TYPETW + TYPEPW + EFIXXCOL, uundo, NULL, 1, "redo"},
	{"uparw", TYPETW + TYPEPW + EMOVE, uuparw, NULL, 1, "dnarw"},
	{"uparwmenu", TYPEMENU, umuparw, NULL, 1, "dnarwmenu"}, 
	{"upslide", TYPETW + TYPEPW + TYPEMENU + TYPEQW + EMOVE, uupslide, NULL, 1, "dnslide"},
	{"yank", TYPETW + TYPEPW + EFIXXCOL + EMOD, uyank, NULL, 1, NULL},
	{"yapp", TYPETW + TYPEPW + EKILL, uyapp, NULL, 0, NULL},
	{"yankpop", TYPETW + TYPEPW + EFIXXCOL + EMOD, uyankpop, NULL, 1, NULL}
};

/* Execute a command n with key k */

int execmd(CMD *cmd, int k)
{
	BW *bw = (BW *) maint->curwin->object;
	int ret = -1;

	if (cmd->m)
		return exmacro(cmd->m, 0);

	/* We don't execute if we have to fix the column position first
	 * (i.e., left arrow when cursor is in middle of nowhere) */
	if ((cmd->flag & ECHKXCOL)
	    && bw->cursor->xcol != piscol(bw->cursor))
		goto skip;

	/* Don't execute command if we're in wrong type of window */
	if (!(cmd->flag & maint->curwin->watom->what))
		goto skip;

	if ((maint->curwin->watom->what & TYPETW) && bw->b->rdonly && (cmd->flag & EMOD)) {
		msgnw(bw->parent, "Read only");
		if (beep)
			ttputc(7);
		goto skip;
	}

	/* Execute command */
	ret = cmd->func(maint->curwin->object, k);

	if (smode)
		--smode;

	/* Don't update anything if we're going to leave */
	if (leave)
		return 0;

	bw = (BW *) maint->curwin->object;

	/* Maintain position history */
	/* If command was not a positioning command */
	if (!(cmd->flag & EPOS)
	    && (maint->curwin->watom->what & (TYPETW | TYPEPW)))
		afterpos();

	/* If command was not a movement */
	if (!(cmd->flag & (EMOVE | EPOS)) && (maint->curwin->watom->what & (TYPETW | TYPEPW)))
		aftermove(maint->curwin, bw->cursor);

	if (cmd->flag & EKILL)
		justkilled = 1;
	else
		justkilled = 0;

      skip:

	/* Make dislayed cursor column equal the actual cursor column
	 * for commands which arn't simple vertical movements */
	if (cmd->flag & EFIXXCOL)
		bw->cursor->xcol = piscol(bw->cursor);

	/* Recenter cursor to middle of screen */
	if (cmd->flag & EMID) {
		int omid = mid;

		mid = 1;
		dofollows();
		mid = omid;
	}

	if (beep && ret)
		ttputc(7);
	return ret;
}

/* Return command table index for given command name */

HASH *cmdhash = NULL;

static void izcmds(void)
{
	int x;

	cmdhash = htmk(256);
	for (x = 0; x != sizeof(cmds) / sizeof(CMD); ++x)
		htadd(cmdhash, cmds[x].name, cmds + x);
}

CMD *findcmd(char *s)
{
	if (!cmdhash)
		izcmds();
	return (CMD *) htfind(cmdhash, s);
}

void addcmd(unsigned char *s, MACRO *m)
{
	CMD *cmd = (CMD *) joe_malloc(sizeof(CMD));

	if (!cmdhash)
		izcmds();
	cmd->name = strdup(s);
	cmd->flag = 0;
	cmd->func = NULL;
	cmd->m = m;
	cmd->arg = 1;
	cmd->negarg = NULL;
	htadd(cmdhash, cmd->name, cmd);
}

static char **getcmds(void)
{
	char **s = vaensure(NULL, sizeof(cmds) / sizeof(CMD));
	int x;
	HENTRY *e;

	for (x = 0; x != cmdhash->len; ++x)
		for (e = cmdhash->tab[x]; e; e = e->next)
			s = vaadd(s, vsncpy(NULL, 0, sz(e->name)));
	vasort(s, aLen(s));
	return s;
}

/* Command line */

char **scmds = NULL;

static char **regsub(char **z, int len, char *s)
{
	char **lst = NULL;
	int x;

	for (x = 0; x != len; ++x)
		if (rmatch(s, z[x]))
			lst = vaadd(lst, vsncpy(NULL, 0, sz(z[x])));
	return lst;
}

static void inscmd(BW *bw, char *line)
{
	P *p = pdup(bw->cursor);

	p_goto_bol(p);
	p_goto_eol(bw->cursor);
	bdel(p, bw->cursor);
	binsm(bw->cursor, sv(line));
	p_goto_eol(bw->cursor);
	prm(p);
	bw->cursor->xcol = piscol(bw->cursor);
}

static int cmdabrt(BW *bw, int x, char *line)
{
	if (line) {
		inscmd(bw, line);
		vsrm(line);
	}
	return -1;
}

static int cmdrtn(MENU *m, int x, char *line)
{
	inscmd(m->parent->win->object, m->list[x]);
	vsrm(line);
	m->object = NULL;
	wabort(m->parent);
	return 0;
}

static int cmdcmplt(BW *bw)
{
	MENU *m;
	P *p, *q;
	char *line;
	char *line1;
	char **lst;

	if (!scmds)
		scmds = getcmds();
	p = pdup(bw->cursor);
	p_goto_bol(p);
	q = pdup(bw->cursor);
	p_goto_eol(q);
	line = brvs(p, (int) (q->byte - p->byte));	/* Assumes short lines :-) */
	prm(p);
	prm(q);
	m = mkmenu(bw->parent, NULL, cmdrtn, cmdabrt, NULL, 0, line, NULL);
	if (!m)
		return -1;
	line1 = vsncpy(NULL, 0, sv(line));
	line1 = vsadd(line1, '*');
	lst = regsub(scmds, aLEN(scmds), line1);
	vsrm(line1);
	ldmenu(m, lst, 0);
	if (!lst) {
		wabort(m->parent);
		ttputc(7);
		return -1;
	} else {
		if (aLEN(lst) == 1)
			return cmdrtn(m, 0, line);
		else if (smode || isreg(line))
			return 0;
		else {
			char *com = mcomplete(m);

			vsrm(m->object);
			m->object = com;
			wabort(m->parent);
			smode = 2;
			ttputc(7);
			return 0;
		}
	}
}

static int docmd(BW *bw, char *s, void *object, int *notify)
{
	MACRO *mac;
	int ret = -1;
	CMD *cmd = findcmd(s);

	vsrm(s);	/* allocated in pw.c::rtnpw() */
	if (!cmd)
		msgnw(bw->parent, "No such command");
	else {
		mac = mkmacro(MAXINT, 0, 0, cmd);
		ret = exmacro(mac, 1);
		rmmacro(mac);
	}
	if (notify)
		*notify = 1;
	return ret;
}

B *cmdhist = NULL;

int uexecmd(BW *bw)
{
	if (wmkpw(bw->parent, "cmd: ", &cmdhist, docmd, "cmd", NULL, cmdcmplt, NULL, NULL)) {
		return 0;
	} else {
		return -1;
	}
}
