/* keyboard macros for MicroGnuEmacs 3a */

#include	"no_macro.h"

#ifndef NO_MACRO
#include "fkeys.h"
#include "def.h"
#include "macro.h"
#include "key.h"

#ifdef	ANSI
#include <stdlib.h>
#include <string.h>
#endif

/* KBD macro grows by MGROWSIZE macros at a time */
#define	MGROWSIZE	50

int             macrodef;
struct macro   *inmacro = NULL;
struct macro    kbdmacro;
static struct macro savemac;	/* Place to put macro being defined */

static int      growmacro
PROTO((struct macro *));
static struct macro *makemacro
PROTO((char *, KCHAR *, int));
static int      finsert
PROTO((KCHAR));

/* ARGSUSED */
definemacro(f, n)
	int             f, n;
{
	if (macrodef) {
		ewprintf("Already defining macro!");
		return macrodef = FALSE;
	}
	savemac = kbdmacro;
	kbdmacro.m_max = kbdmacro.m_count = 0;
	kbdmacro.m_text = NULL;
	ewprintf("Defining Keyboard Macro...");
	return macrodef = TRUE;
}

/* ARGSUSED */
finishmacro(f, n)
	int             f, n;
{
	if (inmacro)
		return TRUE;
	if (!macrodef) {
		ewprintf("Not defining kbd macro.");
		return FALSE;
	}
	ewprintf("Keyboard macro defined");
	macrodef = FALSE;
	if (savemac.m_text)
		free((char *) savemac.m_text);
	if ((f & FFARG) == 0)
		kbdmacro.m_count -= key.k_count;
	else if (n != 1)
		return runmacro(&kbdmacro, f, n - 1, FALSE);
	return TRUE;
}

VOID
restore_macro()
{

	if (kbdmacro.m_max > 0)
		free((char *) kbdmacro.m_text);
	kbdmacro = savemac;
}

/* ARGSUSED */
executemacro(f, n)
	int             f, n;
{
	if (macrodef) {
		ewprintf("Can't execute anonymous macro while defining one");
		return FALSE;
	}
	if (kbdmacro.m_count == 0) {
		ewprintf("No kbd macro has been defined");
		return FALSE;
	}
	return runmacro(&kbdmacro, f, n, FALSE);
}

runmacro(run, f, count, onecommand)
	struct macro   *run;
	int             f, count, onecommand;
{
	int             status = TRUE;
	struct macro   *save;

	if (run->m_count == 0)
		return TRUE;
	run->m_max = (f & FFARG) ? count : 1;
	save = inmacro;
	inmacro = run;
	while (run->m_max != 0) {
		run->m_cur = run->m_text;
		while (run->m_cur - run->m_text < run->m_count) {
			if ((status = doin()) != TRUE || onecommand) {
				/* Error or abort */
				run->m_max = 0;
				break;
			}
			lastflag = thisflag;
			thisflag = 0;
		}
		if (run->m_max > 0)
			run->m_max -= 1;
	}
	inmacro = save;
	return status != TRUE ? status
		: (run->m_cur - run->m_text < run->m_count
		   ? FALSE
		   : TRUE);
}

VOID
add_key_to_macro(c, mac)
	KCHAR           c;
	struct macro   *mac;
{

	if (mac->m_count >= mac->m_max && growmacro(mac) != TRUE)
		return;
	mac->m_text[mac->m_count++] = c;
}

VOID
add_string_to_macro(string, mac)
	char           *string;
	struct macro   *mac;
{
	int             length;

	if (!string)
		return;
	length = strlen(string);
	while (length + mac->m_count >= mac->m_max)
		if (growmacro(mac) != TRUE)
			return;
	while (*string)
		mac->m_text[mac->m_count++] = *string++;
}

static int
growmacro(mac)
	struct macro   *mac;
{
	KCHAR          *oldtext;
	int             size;

	oldtext = mac->m_text;
	size = mac->m_max + MGROWSIZE;
	if ((mac->m_text = (KCHAR *) malloc(size * sizeof(KCHAR))) == NULL) {
		ewprintf("Can't get %d bytes", size * sizeof(KCHAR));
		mac->m_text = oldtext;
		return FALSE;
	}
	bcopy((char *) oldtext, (char *) mac->m_text,
	      mac->m_count * sizeof(KCHAR));
	free((char *) oldtext);
	mac->m_max = size;
	return TRUE;
}

insertmacro(f, n)
	int             f, n;
{
	register KCHAR *k;
	register int    i;
	char            macname[NMACN];
	struct macro   *out;

	if (eread("Insert kbd macro (name): ", macname, NMACN, EFNEW | EFMACRO) != TRUE)
		return FALSE;
	if ((out = find_macro(macname)) == NULL) {	/* shouldn't happen */
		ewprintf("Macro %s not found!", macname);
		return FALSE;
	}
	if (sinsert("fset ") == FALSE || sinsert(macname) == FALSE
	    || sinsert(" \"") == FALSE)
		return FALSE;
	/* Insert all chars but the C-x ) used to end it */
	for (k = out->m_text, i = out->m_count; i > 0; i -= 1, k += 1)
#ifdef	FKEYS
		if (*k >= KFIRST && *k <= KLAST) {
			if (sinsert("\\f") == FALSE)
				return FALSE ;
			if (finsert((KCHAR) (*k - KFIRST)) == FALSE)
				return FALSE ;
		} else
#endif	
		if (*k < ' ') {
			if (sinsert("\\^") == FALSE || linsert(1, CCHR(*k)) == FALSE)
				return FALSE;
		} else if (*k == '"') {
			if (sinsert("\\\"") == FALSE)
				return FALSE;
		} else if (*k == '\\') {
			if (sinsert("\\\\") == FALSE)
				return FALSE;
		} else if (*k > CCHR('?')) {
			if (linsert(1, '\\') == FALSE || finsert(*k) == FALSE)
				return FALSE;
		} else if (linsert(1, *k) == FALSE)
			return FALSE;

	if (sinsert("\"\n") == FALSE)
		return FALSE;
	if (f & FFARG)
		sinsert("; Need to put binding code here\n");
	return TRUE;
}

macroset(f, n)
	int             f, n;
{
	register int    c, count;
	struct macro   *out;
	char            macname[NMACN];
	KCHAR           mactext[NXNAME];

	if (!inmacro)
		return FALSE;	/* Only useable from eval-expression */

	if (eread("", macname, NMACN, EFNEW) != TRUE)
		return FALSE;
	count = 0;
	while ((c = getkey(SEESCR)) != SOFTCR) {
		if (count >= NXNAME)
			return FALSE;
		mactext[count++] = c;
	}
	if ((out = makemacro(macname, mactext, count)) == NULL)
		return FALSE;
	return TRUE;
}

macroquery(f, n)
	int             f, n;
{
	struct macro   *save;

	if (macrodef)
		return TRUE;
	if (!inmacro) {
		ewprintf("Not defining or executing kbd macro");
		return TRUE;
	}
	save = inmacro;
	inmacro = NULL;
	update();
	for (;;) {
		ewprintf("Proceed with macro? (Space, DEL or C-d)");
		switch (getkey(FALSE)) {
		case CCHR('G'):
			return ABORT;
		case CCHR('D'):/* Terminate all reps */
			save->m_max = 1;
			/* no more reps, so fall through to */
		case CCHR('?'):/* Terminate this repition */
			save->m_cur = &(save->m_text[save->m_count + 1]);
		case ' ':	/* Keep going as as is */
			eerase();
			inmacro = save;
			return TRUE;
		}
	}
}

namemacro(f, n)
	int             f, n;
{
	struct macro   *out;
	char            macname[NMACN];

	/* Get a valid name */
	if (kbdmacro.m_count == 0) {
		ewprintf("No keyboard macro defined");
		return FALSE;
	}
	if (eread("Name for last kbd macro: ", macname, NMACN, EFNEW) != TRUE)
		return FALSE;

	if ((out = makemacro(macname, kbdmacro.m_text, kbdmacro.m_count)) == NULL)
		return FALSE;
	return TRUE;
}

static struct macro *
makemacro(name, text, count)
	char           *name;
	KCHAR          *text;
	int             count;
{
	register struct macro *out;
	char           *ntmp;
	KCHAR          *ctmp;

	/* Space for strings */
	if ((ntmp = (char *) malloc(strlen(name) + 1)) == NULL) {
		ewprintf("Can't get %d bytes", strlen(name) + 1);
		return NULL;
	}
	if ((ctmp = (KCHAR *) malloc(count * sizeof(KCHAR))) == NULL) {
		ewprintf("Can't get %d bytes", count * sizeof(KCHAR));
		free(ntmp);
		return NULL;
	}
	if (name_function(name) != NULL) {
		ewprintf("Function %s is already defined and not a keyboard macro.",
			 name);
		return NULL;
	}
	/* Get a macro to (re)use */
	if ((out = find_macro(name)) != NULL) {
		free(out->m_name);
		free(out->m_text);
	} else if ((out = (struct macro *) malloc(sizeof(struct macro))) == NULL) {
		ewprintf("Can't get %d bytes", sizeof(struct macro));
		free((char *) (ctmp));
		free(ntmp);
		return NULL;
	} else {		/* Add the new macro to the chain */
		out->m_macp = kbdmacro.m_macp;
		kbdmacro.m_macp = out;
	}

	/* Now, fill the silly bugger */
	bcopy((char *) text, (char *) ctmp, count * sizeof(KCHAR));
	strcpy(ntmp, name);
	out->m_text = ctmp;
	out->m_cur = NULL;
	out->m_max = 0;
	out->m_count = count;
	out->m_name = ntmp;
	return out;
}

struct macro   *
find_macro(name)
	char           *name;
{
	register struct macro *out;

	if (!name)
		return NULL;
	out = kbdmacro.m_macp;
	while (out) {
		if (strcmp(out->m_name, name) == 0)
			return out;
		out = out->m_macp;
	}
	return NULL;
}

/*
 * Insert a string into the current buffer. For insertmacro. 
 */
sinsert(s)
	register char  *s;
{

	while (*s) {
		if (((*s == '\n') ? lnewline() : linsert(1, *s)) != TRUE)
			return FALSE;
		s += 1;
	}
	return TRUE;
}

/*
 * insert a number into the current buffer. For insertmacro. 
 */
static
finsert(k)
	register KCHAR  k;
{

	if (k > 7 && finsert((KCHAR) (k >> 3)) == FALSE)
		return FALSE;
	return linsert(1, (k & 7) + '0');
}

#else
#include "nullfile.h"
#endif
