/*	-*- mode: c; mode: fold -*-	*/
# include	"config.h"
# ifdef		SCRIPT_SLANG
# ifndef	FLOAT_TYPE
# define	FLOAT_TYPE
# endif		/* FLOAT_TYPE */
# include	<stdio.h>
# include	<stdlib.h>
# include	<unistd.h>
# include	<string.h>
# include	<slang.h>
# include	"pager.h"
# include	"script.h"

# define	STARTUP		"Startup.sl"

/*{{{	string class */
static char	*ifmt = NULL;
static char	*ffmt = NULL;

static inline char *
make_scratch (unsigned char typ, VOID_STAR p)
{
	char	buf[128];

	buf[0] = '\0';
	switch (typ) {
	case INT_TYPE:
		sprintf (buf, (ifmt ? ifmt : "%d"), *(int *) p);
		break;
	case FLOAT_TYPE:
		sprintf (buf, (ffmt ? ffmt : "%f"), (double) (*(float64 *) p));
		break;
	}
	return strdup (buf);
}

static int
binop_string (int op, unsigned char atyp, unsigned char btyp,
	      VOID_STAR ap, VOID_STAR bp)
{
	char	*scratch;
	char	*a, *b;
	int	la, lb;
	int	len;
	int	n;
	char	*ptr;
	char	*res;

	scratch = NULL;
	if ((atyp == STRING_TYPE) && (btyp == STRING_TYPE)) {
		a = (char *) ap;
		b = (char *) bp;
	} else if (atyp == STRING_TYPE) {
		a = (char *) ap;
		scratch = make_scratch (btyp, bp);
		b = scratch;
	} else {
		b = (char *) ap;
		scratch = make_scratch (atyp, ap);
		a = scratch;
	}
	if (! (a && b))
		return 0;
	res = NULL;
	switch (op) {
	case SLANG_PLUS:
		la = strlen (a);
		lb = strlen (b);
		if (res = SLMALLOC (la + lb + 1)) {
			strcpy (res, a);
			strcat (res, b);
		}
		break;
	case SLANG_MINUS:
		la = strlen (a);
		lb = strlen (b);
		if (ptr = strstr (a, b)) {
			if (res = SLMALLOC (la - lb + 1)) {
				len = (int) ((unsigned long) ptr - (unsigned long) a);
				strncpy (res, a, len);
				res[len] = '\0';
				strcat (res, a + len + lb);
			}
		} else if (res = SLMALLOC (la + 1))
			strcpy (res, a);
		break;
	case SLANG_TIMES:
		la = strlen (a);
		if ((n = atoi (b)) > 0) {
			if (res = SLMALLOC (la * n + 1)) {
				ptr = res;
				while (n-- > 0) {
					strcpy (ptr, a);
					while (*ptr)
						++ptr;
				}
			}
		} else if (res = SLMALLOC (2))
			res[0] = '\0';
		break;
	case SLANG_DIVIDE:
		for (ptr = a; *ptr; ++ptr)
			if (! strchr (b, *ptr))
				break;
		if (res = SLMALLOC (strlen (ptr) + 1)) {
			strcpy (res, ptr);
			if (res[0]) {
				for (ptr = res; *ptr; ++ptr)
					;
				--ptr;
				while (ptr != res)
					if (strchr (b, *ptr))
						*ptr-- = '\0';
					else
						break;
			}
		}
		break;
	case SLANG_EQ:
		SLang_push_integer (strcmp (a, b) == 0 ? 1 : 0);
		break;
	case SLANG_NE:
		SLang_push_integer (strcmp (a, b) ? 1 : 0);
		break;
	case SLANG_GT:
		SLang_push_integer (strcmp (a, b) > 0 ? 1 : 0);
		break;
	case SLANG_GE:
		SLang_push_integer (strcmp (a, b) >= 0 ? 1 : 0);
		break;
	case SLANG_LT:
		SLang_push_integer (strcmp (a, b) < 0 ? 1 : 0);
		break;
	case SLANG_LE:
		SLang_push_integer (strcmp (a, b) <= 0 ? 1 : 0);
		break;
	default:
		return 0;
	}
	if (scratch)
		free (scratch);
	if (res)
		SLang_push_malloced_string (res);
	return 1;
}

static int
unop_string (int op, unsigned char typ, VOID_STAR p)
{
	char	*s;
	char	*r;
	int	len;
	
	s = (char *) p;
	switch (op) {
	case SLANG_ABS:
		SLang_push_integer (strlen (s));
		break;
	case SLANG_SIGN:
		SLang_push_integer (*s ? 1 : 0);
		break;
	case SLANG_SQR:
		break;
	case SLANG_MUL2:
		len = strlen (s);
		if (r = SLMALLOC (len * 2 + 1)) {
			strcpy (r, s);
			strcat (r, s);
			SLang_push_malloced_string (r);
		}
		break;
	case SLANG_CHS:
		SLang_push_string (s);
		break;
	default:
		return 0;
	}
	return 1;
}

static void
get_format (char **var)
{
	char	*fmt;
	int	fre;

	if (*var) {
		free (*var);
		*var = NULL;
	}
	if (SLang_pop_string (& fmt, & fre))
		return;
	*var = strdup (fmt);
	if (fre)
		SLFREE (fmt);
}

static void
str_iformat (void)
{
	get_format (& ifmt);
}

static void
str_fformat (void)
{
	get_format (& ffmt);
}

static SLang_Name_Type	avail_string[] = {
	/* void		str_iformat (string format);			*/
	MAKE_INTRINSIC (".str_iformat", str_iformat, VOID_TYPE, 0),
	/* void		str_fformat (string format);			*/
	MAKE_INTRINSIC (".str_fformat", str_fformat, VOID_TYPE, 0),
/*
	MAKE_INTRINSIC (".", , _TYPE, 0),
	MAKE_INTRINSIC (".function_name", c_function, TYPE, 0),
	MAKE_VARIABLE (".", & , TYPE, ),
	MAKE_VARIABLE (".var", &c_variable, TYPE, flag),
*/
        SLANG_END_TABLE
};

static int
string_class (void)
{
	if ((! SLang_register_class (STRING_TYPE, NULL, NULL)) ||
	    (! SLang_add_binary_op (STRING_TYPE, STRING_TYPE,
				    (VOID_STAR) binop_string)) ||
	    (! SLang_add_binary_op (STRING_TYPE, INT_TYPE,
				    (VOID_STAR) binop_string)) ||
	    (! SLang_add_binary_op (STRING_TYPE, FLOAT_TYPE,
				    (VOID_STAR) binop_string)) ||
	    (! SLang_add_unary_op (STRING_TYPE,
				   (VOID_STAR) unop_string)) ||
	    (! SLang_add_table (avail_string, "string")))
		return 0;
	return 1;
}
/*}}}*/
/*{{{	statics */
static Bool		isinit = False;
static script		*sls = NULL;
static char		*slline = NULL;
static int		slsiz = 0;
static SLang_Name_Type	*slcb = NULL;
/*}}}*/
/*{{{	callable functions and variables */
static void
sllogger (void)
{
	char	*str;
	int	fre;
	int	typ;
	
	if (SLang_pop_string (& str, & fre) ||
	    SLang_pop_integer (& typ))
		return;
	if (sls && sls -> logger) {
		if (! typ)
			typ = LG_INF;
		(*sls -> logger) ((char) typ, "%s\n", str);
	}
	if (fre)
		SLFREE (str);
}

static void
slcallback (void *sp, string_t *s, char_t sep, void *data)
{
	int	len;
	char	*str;

	if (str = sextract (s)) {
		len = strlen (str);
		if (len + 2 >= slsiz) {
			slsiz = len + 64;
			if (! (slline = Realloc (slline, slsiz + 4)))
				slsiz = 0;
		}
		if (slline) {
			sprintf (slline, "%s%c", str, (char) sep);
			if (slcb) {
				SLang_push_string (slline);
				SLexecute_function (slcb);
			}
		}
		free (str);
	}
}

static void
slsetcb (void)
{
	char	*func, *sep;
	int	fr1, fr2;
	
	if (SLang_pop_string (& sep, & fr2) ||
	    SLang_pop_string (& func, & fr1))
		return;
	if (func && *func)
		slcb = SLang_get_function (func);
	else
		slcb = NULL;
	if (sls && sls -> sp)
		tty_set_line_callback (sls -> sp, slcallback, sep, NULL);
	if (fr1)
		SLFREE (func);
	if (fr2)
		SLFREE (sep);
}

static void
slclrcb (void)
{
	slcb = NULL;
	if (sls && sls -> sp)
		tty_set_line_callback (sls -> sp, NULL, NULL, NULL);
}

static void
slget_line (void)
{
	SLang_push_string (slline ? slline : "");
}

static void
slhangup (void)
{
	int	msec;
	
	if (SLang_pop_integer (& msec))
		return;
	if (sls && sls -> sp)
		tty_hangup (sls -> sp, msec);
}

static int
slsend (void)
{
	int	ret;
	char	*str;
	int	fre;
	
	if (SLang_pop_string (& str, & fre))
		return 0;
	ret = 0;
	if (sls && sls -> sp)
		if (tty_send_string (sls -> sp, str) != -1)
			ret = 1;
	if (fre)
		SLFREE (str);
	return ret;
}

static int
slcsend (void)
{
	int	ret;
	char	*str;
	int	fre;
	char	*rstr;
	
	if (SLang_pop_string (& str, & fre))
		return 0;
	ret = 0;
	if (sls && sls -> sp)
		if (rstr = scr_convert (sls, str)) {
			if (tty_send_string (sls -> sp, rstr) != -1)
				ret = 1;
			free (rstr);
		}
	if (fre)
		SLFREE (str);
	return ret;
}

static int
slexpect (void)
{
	int	ret;
	int	cnt;
	char	**str;
	int	*fre;
	int	*len;
	int	tout;
	int	n;
	
	if (SLang_pop_integer (& cnt))
		return -1;
	ret = -1;
	if ((str = (char **) malloc ((cnt + 2) * sizeof (char *))) &&
	    (fre = (int *) malloc ((cnt + 2) * sizeof (int))) &&
	    (len = (int *) malloc ((cnt + 2) * sizeof (int)))) {
		for (n = cnt - 1; n >= 0; --n)
			if (SLang_pop_string (& str[n], & fre[n]))
				return -1;
			else
				len[n] = strlen (str[n]);
		str[cnt] = NULL;
		len[cnt] = 0;
		if (SLang_pop_integer (& tout))
			return -1;
		if (sls && sls -> sp)
			ret = tty_expect_list (sls -> sp, tout, str, len);
		for (n = 0; n < cnt; ++n)
			if (fre[n])
				SLFREE (str[n]);
		free (str);
		free (fre);
		free (len);
	}
	return ret;
}

static int
slsend_expect (void)
{
	int	tout;
	char	*str;
	int	fre;
	int	ret;
	
	if (SLang_pop_string (& str, & fre) ||
	    SLang_pop_integer (& tout))
		return 0;
	ret = 0;
	if (sls && sls -> sp)
		if (tty_send_expect (sls -> sp, tout, str, NULL) != -1)
			ret = 1;
	if (fre)
		SLFREE (str);
	return ret;
}

static void
sldrain (void)
{
	int	secs;
	
	if (SLang_pop_integer (& secs))
		return;
	if (sls && sls -> sp)
		tty_drain (sls -> sp, secs);
}

static void
slcvdef (void)
{
	int	src, dst;
	
	if (SLang_pop_integer (& dst) ||
	    SLang_pop_integer (& src))
		return;
	if (sls) {
		if (! sls -> ctab)
			sls -> ctab = cv_new ();
		if (sls -> ctab)
			cv_define (sls -> ctab, (char_t) src, (char_t) dst);
	}
}

static void
slcvundef (void)
{
	int	ch;
	
	if (SLang_pop_integer (& ch))
		return;
	if (sls && sls -> ctab)
		cv_undefine (sls -> ctab, (char_t) ch);
}

static void
slcvinval (void)
{
	int	ch;
	
	if (SLang_pop_integer (& ch))
		return;
	if (sls) {
		if (! sls -> ctab)
			sls -> ctab = cv_new ();
		if (sls -> ctab)
			cv_invalid (sls -> ctab, (char_t) ch);
	}
}
static void
slconv (void)
{
	char	*str;
	int	fre;
	char	*rstr;
	
	if (SLang_pop_string (& str, & fre))
		return;
	if (sls)
		rstr = scr_convert (sls, str);
	else
		rstr = NULL;
	SLang_push_string (rstr ? rstr : str);
	if (rstr)
		free (rstr);
	if (fre)
		SLFREE (str);
}

static int	no_err = NO_ERR,
		err_fail = ERR_FAIL,
		err_fatal = ERR_FATAL,
		err_abort = ERR_ABORT;
static date_t	sldelay, slexpire;
static int	slrds = 0;
static int	xFalse = (int) False,
		xTrue = (int) True;
static char	xNull_String[2] = "";
/*}}}*/
/*{{{	function/variable table */
static SLang_Name_Type	avail[] = {
	/* void		logger (string str);				*/
	MAKE_INTRINSIC (".logger", sllogger, VOID_TYPE, 0),
	/* void		setcb (string func, string sep);		*/
	MAKE_INTRINSIC (".setcb", slsetcb, VOID_TYPE, 0),
	/* void		clrcb (void);					*/
	MAKE_INTRINSIC (".clrcb", slclrcb, VOID_TYPE, 0),
	/* string	line (void);					*/
	MAKE_INTRINSIC (".line", slget_line, VOID_TYPE, 0),
	/* void		hangup (int msec);				*/
	MAKE_INTRINSIC (".hangup", slhangup, VOID_TYPE, 0),
	/* int		send (string str);				*/
	MAKE_INTRINSIC (".send", slsend, INT_TYPE, 0),
	/* int		csend (string str);				*/
	MAKE_INTRINSIC (".csend", slcsend, INT_TYPE, 0),
	/* int		expect (int tout, string e1, ..., string en,
				int cnt);				*/
	MAKE_INTRINSIC (".expect", slexpect, INT_TYPE, 0),
	/* int		send_expect (int tout, string str);		*/
	MAKE_INTRINSIC (".send_expect", slsend_expect, INT_TYPE, 0),
	/* void		drain (int secs);				*/
	MAKE_INTRINSIC (".drain", sldrain, INT_TYPE, 0),
	/* void		cvdef (int src, int dst);			*/
	MAKE_INTRINSIC (".cvdef", slcvdef, VOID_TYPE, 0),
	/* void		cvundef (int ch);				*/
	MAKE_INTRINSIC (".cvundef", slcvundef, VOID_TYPE, 0),
	/* void		cvinval (int ch);				*/
	MAKE_INTRINSIC (".cvinval", slcvinval, VOID_TYPE, 0),
	/* string	conv (string str);				*/
	MAKE_INTRINSIC (".conv", slconv, VOID_TYPE, 0),

	MAKE_VARIABLE (".NO_ERR", & no_err, INT_TYPE, 1),
	MAKE_VARIABLE (".ERR_FAIL", & err_fail, INT_TYPE, 1),
	MAKE_VARIABLE (".ERR_FATAL", & err_fatal, INT_TYPE, 1),
	MAKE_VARIABLE (".ERR_ABORT", & err_abort, INT_TYPE, 1),
	MAKE_VARIABLE (".delay_day", & sldelay.day, INT_TYPE, 1),
	MAKE_VARIABLE (".delay_mon", & sldelay.mon, INT_TYPE, 1),
	MAKE_VARIABLE (".delay_year", & sldelay.year, INT_TYPE, 1),
	MAKE_VARIABLE (".delay_hour", & sldelay.hour, INT_TYPE, 1),
	MAKE_VARIABLE (".delay_min", & sldelay.min, INT_TYPE, 1),
	MAKE_VARIABLE (".delay_sec", & sldelay.sec, INT_TYPE, 1),
	MAKE_VARIABLE (".expire_day", & slexpire.day, INT_TYPE, 1),
	MAKE_VARIABLE (".expire_mon", & slexpire.mon, INT_TYPE, 1),
	MAKE_VARIABLE (".expire_year", & slexpire.year, INT_TYPE, 1),
	MAKE_VARIABLE (".expire_hour", & slexpire.hour, INT_TYPE, 1),
	MAKE_VARIABLE (".expire_min", & slexpire.min, INT_TYPE, 1),
	MAKE_VARIABLE (".expire_sec", & slexpire.sec, INT_TYPE, 1),
	MAKE_VARIABLE (".rds", & slrds, INT_TYPE, 1),
	MAKE_VARIABLE (".False", & xFalse, INT_TYPE, 1),
	MAKE_VARIABLE (".True", & xTrue, INT_TYPE, 1),
	MAKE_VARIABLE (".Null_String", & xNull_String, STRING_TYPE, 1),
/*
	MAKE_INTRINSIC (".", , _TYPE, 0),
	MAKE_INTRINSIC (".function_name", c_function, TYPE, 0),
	MAKE_VARIABLE (".", & , TYPE, ),
	MAKE_VARIABLE (".var", &c_variable, TYPE, flag),
*/
        SLANG_END_TABLE
};
/*}}}*/
/*{{{	init/deinit */
static int
slang_init (script *s, char *libdir)
{
	char	*fname;
	
	if (! isinit) {
		if ((! init_SLang ()) || (! init_SLmath ()) ||
		    (! init_SLunix ()) || (! init_SLfiles ()) ||
		    (! string_class ()) || (! SLang_add_table (avail, "yaps")))
			return -1;
		if (libdir && (fname = malloc (strlen (libdir) + sizeof (STARTUP) + 4))) {
			sprintf (fname, "%s/%s", libdir, STARTUP);
			if (access (fname, R_OK) != -1)
				if ((! SLang_load_file (fname)) || SLang_Error)
					SLang_restart (1);
			free (fname);
		}
		isinit = True;
	}
	if (slline)
		slline[0] = '\0';
	return NO_ERR;
}

static void
slang_deinit (script *s)
{
	if (slline) {
		free (slline);
		slline = NULL;
	}
	slsiz = 0;
}
/*}}}*/
/*{{{	execute */
static int
slang_execute (script *s, char *label, char *parm)
{
	SLang_Name_Type	*func;
	int		ret;
	
	ret = NO_ERR;
	if (func = SLang_get_function (label)) {
		SLang_push_string (parm ? parm : "");
		sldelay = s -> delay;
		slexpire = s -> expire;
		slrds = s -> rds;
		sls = s;
		SLexecute_function (func);
		if (sls -> sp)
			tty_set_line_callback (sls -> sp, NULL, NULL, NULL);
		sls = NULL;
		if (SLang_Error || SLang_pop_integer (& ret)) {
			ret = ERR_FATAL;
			SLang_restart (1);
		}
	}
	return ret;
}
/*}}}*/
/*{{{	loading */
static int
slang_load_string (script *s, char *scr)
{
	int	err;
	
	err = ERR_FATAL;
	if (SLang_load_string (scr) && (! SLang_Error))
		err = NO_ERR;
	else
		SLang_restart (1);
	return err;
}

static int
slang_load_file (script *s, char *fname)
{
	int	err;

	err = ERR_FATAL;
	if (SLang_load_file (fname) && (! SLang_Error))
		err = NO_ERR;
	else
		SLang_restart (1);
	return err;
}
/*}}}*/
/*{{{	preinit/postdeinit/scriptentry */
static int
slang_preinit (char *libdir)
{
	return slang_init (NULL, libdir);
}

static void
slang_postdeinit (void)
{
	slang_deinit (NULL);
}

funcs	fslang = {
	"SLang",
	slang_init,
	slang_deinit,
	slang_execute,
	slang_load_string,
	slang_load_file,
	slang_preinit,
	slang_postdeinit
};
/*}}}*/
# endif		/* SCRIPT_SLANG */

