/***************************************************************************
 * This program is Copyright (C) 1986, 1987, 1988 by Jonathan Payne.  JOVE *
 * is provided to you without charge, and with no warranty.  You may give  *
 * away copies of JOVE, including sources, provided that this notice is    *
 * included in all the files.                                              *
 ***************************************************************************/

#include "jove.h"
#include "ctype.h"
#include "fp.h"
#include "chars.h"
#include "disp.h"
#include "ask.h"
#include "commands.h"
#include "macros.h"
#include "extend.h"
#include "fmt.h"

private void
	pop_macro_stack proto((void));

#define SAVE		01	/* this macro needs saving to a file */

struct macro	*macros = NULL;		/* macros */
bool	InMacDefine = NO;

private void
add_mac(new)
struct macro	*new;
{
	register struct macro	*mp,
				*prev = NULL;

	for (mp = macros; mp != NULL; prev = mp, mp = mp->m_nextm)
		if (mp == new)
			return;

	if (prev)
		prev->m_nextm = new;
	else
		macros = new;
	new->m_nextm = NULL;
	new->Type = MACRO;
}

private void
del_mac(mac)
struct macro	*mac;
{
	register struct macro	*m;

	for (m = macros; m != NULL; m = m->m_nextm)
		if (m->m_nextm == mac) {
			m->m_nextm = mac->m_nextm;
			break;
		}
	free((UnivPtr) mac->Name);
	free((UnivPtr) mac->m_body);
	free((UnivPtr) mac);
}

private struct macro	KeyMacro;	/* Macro used for defining */

/* To execute a macro, we have a "stack" of running macros.  Whenever
   we execute a macro, we push it on the stack, run it, then pop it
   from the stack.  */
struct m_thread {
	struct m_thread	*mt_prev;
	struct macro	*mt_mp;
	int	mt_offset,
		mt_count;
};

private struct m_thread	*mac_stack = NULL;

private struct m_thread *
alloc_mthread()
{
	return (struct m_thread *) emalloc(sizeof (struct m_thread));
}

private void
free_mthread(t)
struct m_thread	*t;
{
	free((UnivPtr) t);
}

void
unwind_macro_stack()
{
	while (mac_stack != NULL)
		pop_macro_stack();
}

private void
pop_macro_stack()
{
	register struct m_thread	*m;

	if ((m = mac_stack) == NULL)
		return;
	mac_stack = m->mt_prev;
	free_mthread(m);
}

private void
push_macro_stack(m, count)
register struct macro	*m;
int	count;
{
	register struct m_thread	*t;

	for (t = mac_stack; t != NULL; t = t->mt_prev)
		if (t->mt_mp == m)
			complain("[Cannot execute macro recusively]");
	if (count <= 0)
		complain("[Cannot execute macro a negative number of times]");
	t = alloc_mthread();
	t->mt_prev = mac_stack;
	mac_stack = t;
	t->mt_offset = 0;
	t->mt_mp = m;
	t->mt_count = count;
}

void
do_macro(mac)
struct macro	*mac;
{
	push_macro_stack(mac, arg_value());
}

private struct macro *
mac_exists(name)
char	*name;
{
	register struct macro	*mp;

	for (mp = macros; mp; mp = mp->m_nextm)
		if (strcmp(mp->Name, name) == 0)
			return mp;
	return NULL;
}

void
mac_init()
{
	add_mac(&KeyMacro);
	KeyMacro.Name = "keyboard-macro";
	KeyMacro.m_len = 0;
	KeyMacro.m_buflen = 16;
	KeyMacro.m_body = emalloc((size_t) KeyMacro.m_buflen);
}

void
mac_putc(c)
int	c;
{
	if (KeyMacro.m_len >= KeyMacro.m_buflen) {
		KeyMacro.m_buflen += 16;
		KeyMacro.m_body = realloc((UnivPtr) KeyMacro.m_body, (size_t) KeyMacro.m_buflen);
		if (KeyMacro.m_body == NULL) {
			KeyMacro.m_buflen = KeyMacro.m_len = 0;
			complain("[Can't allocate storage for keyboard macro]");
		}
	}
	KeyMacro.m_body[KeyMacro.m_len++] = c;
}

bool
in_macro()
{
	return (mac_stack != NULL);
}

int
mac_getc()
{
	struct m_thread	*mthread;
	struct macro	*m;

	if ((mthread = mac_stack) == NULL)
		return EOF;
	m = mthread->mt_mp;
	if (mthread->mt_offset == m->m_len) {
		mthread->mt_offset = 0;
		if (--mthread->mt_count == 0)
			pop_macro_stack();
		return mac_getc();
	}
	return m->m_body[mthread->mt_offset++];
}

void
NameMac()
{
	char	*name;
	struct macro	*m;

	if (KeyMacro.m_len == 0)
		complain("[No keyboard macro to name!]");
	if (in_macro() || InMacDefine)
		complain("[Can't name while defining/executing]");
	if ((m = mac_exists(name = ask((char *) NULL, ProcFmt))) == NULL)
		m = (struct macro *) emalloc(sizeof *m);
	else {
		if (strcmp(name, KeyMacro.Name) == 0)
			complain("[Can't name it that!]");
		free((UnivPtr) m->Name);
		free((UnivPtr) m->m_body);
	}
	name = copystr(name);
	m->Type = KeyMacro.Type;
	m->m_len = KeyMacro.m_len;
	m->m_buflen = KeyMacro.m_buflen;
	m->m_body = emalloc((size_t) m->m_buflen);
	byte_copy(KeyMacro.m_body, m->m_body, (size_t) m->m_len);
	m->m_flags = SAVE;
	m->Name = name;
	add_mac(m);
}

void
RunMacro()
{
	struct macro	*m;

	if ((m = (struct macro *) findmac(ProcFmt)) != NULL)
		do_macro(m);
}

private void
pr_putc(c, fp)
int	c;
File	*fp;
{
	if (c == '\\' || c == '^') {
		jputc('\\', fp);
	} else if (jiscntrl(c)) {
		jputc('^', fp);
		c = (c == RUBOUT) ? '?' : (c + '@');
	}
	jputc(c, fp);
}

void
WriteMacs()
{
	struct macro	*m;
	char	*file,
		filebuf[FILESIZE];
	File	*fp;
	int	i;

	file = ask_file((char *)NULL, (char *)NULL, filebuf);
	fp = open_file(file, iobuff, F_WRITE, YES, YES);

	/* Don't write the keyboard macro which is always the first */
	for (m = macros->m_nextm; m != NULL; m = m->m_nextm) {
		fwritef(fp, "define-macro %s ", m->Name);
		for (i = 0; i < m->m_len; i++)
			pr_putc(m->m_body[i], fp);
		jputc('\n', fp);
		m->m_flags &= ~SAVE;
	}
	close_file(fp);
}

void
DefKBDMac()
{
	char	*macro_name,
		*macro_body,
		nextc,
		c,
		macro_buffer[LBSIZE];
	int	i;
	struct macro	*m;

	macro_name = do_ask(" \r\n", (bool (*) ptrproto((int))) NULL, (char *) NULL,
		ProcFmt);
	if (macro_name == NULL)
		complain("[No default]");
	macro_name = copystr(macro_name);
	if ((m = mac_exists(macro_name)) != NULL)
		del_mac(m);
	macro_body = ask((char *)NULL, ": %f %s enter body: ", macro_name);
	i = 0;
	while ((c = *macro_body++) != '\0') {
		if (c == '\\') {
			if ((nextc = *macro_body++) == LF)
				complain("[Premature end of line]");
			c = nextc;
		} else if (c == '^') {
			if ((nextc = *macro_body++) == '?')
				c = RUBOUT;
			else if (jisalpha(nextc) || strchr("@[\\]^_", nextc))
				c = CTL(nextc);
			else
				complain("Bad control-character: '%c'", nextc);
		}
		macro_buffer[i++] = c;
	}
	m = (struct macro *) emalloc(sizeof (*m));
	m->Name = macro_name;
	m->m_len = m->m_buflen = i;
	m->m_body = emalloc((size_t) i);
	m->m_flags = InJoverc ? 0 : SAVE;
	byte_copy(macro_buffer, m->m_body, (size_t) i);
	add_mac(m);
}

void
Remember()
{
	/* We're already executing the macro; ignore any attempts
	   to define the keyboard macro while we are executing. */
	if (in_macro())
		return;
	if (InMacDefine)
		message("[Already defining ... continue with definition]");
	else {
		UpdModLine = YES;
		InMacDefine = YES;
		KeyMacro.m_len = 0;
		message("Defining...");
	}
}

void
Forget()
{
	char	*cp;
	struct macro	*m = &KeyMacro;

	UpdModLine = YES;
	if (InMacDefine) {
		message("Keyboard macro defined.");
		InMacDefine = NO;

		/* try and strip off the key sequence that invoked us */
		cp = &m->m_body[m->m_len - 2];
		if (PrefChar(*cp))
			m->m_len -= 2;
		else if (commands[cp[1]].c_proc == Forget)
			m->m_len -= 1;
	} else
		complain("[end-kbd-macro: not currently defining macro!]");
}

void
ExecMacro()
{
	do_macro(&KeyMacro);
}

void
MacInter()
{
	if (Asking)
		Interactive = YES;
}

bool
ModMacs()
{
	register struct macro	*m;

	for (m = macros->m_nextm; m != NULL; m = m->m_nextm)
		if (m->m_flags & SAVE)
			return YES;
	return NO;
}

data_obj *
findmac(prompt)
const char	*prompt;
{
	char	*strings[100];
	register char	**strs = strings;
	register int	com;
	register struct macro	*m = macros;

	for (; m != NULL; m = m->m_nextm)
		*strs++ = m->Name;
	*strs = NULL;

	if ((com = complete(strings, prompt, NOTHING)) < 0)
		return NULL;
	m = macros;
	while (--com >= 0)
		m = m->m_nextm;
	return (data_obj *) m;
}

void
DelMacro()
{
	struct macro	*m;

	if ((m = (struct macro *) findmac(ProcFmt)) == NULL)
		return;
	if (m == &KeyMacro)
		complain("[It's illegal to delete the keyboard-macro!]");
	del_mac(m);
}
