/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

/*
 * Copyright  (c) 1985 AT&T
 *	All Rights Reserved
 */
#ident	"@(#)if_help.c	1.6	93/09/30 SMI"       /* SVr4.0 1.42 */

#include	<stdio.h>
#include	<string.h>
#include        <curses.h>
#include	<sys/types.h>		/* EFT abs k16 */
#include	"wish.h"
#include	"vtdefs.h"
#include	"ctl.h"
#include	"token.h"
#include	"winp.h"
#include	"form.h"
#include	"slk.h"
#include	"actrec.h"
#include	"typetab.h"
#include	"fm_mn_par.h"
#include	"objhelp.h"
#include	"var_arrays.h"
#include	"terror.h"
#include	"moremacros.h"
#include	"interrupt.h"
#include 	"vt.h"		/* abs for headers */
#include 	"sizes.h"
#include 	"message.h"

#define HL_INTR		PAR_INTR  
#define HL_ONINTR	PAR_ONINTR
#define HL_DONE		PAR_DONE
#define HL_TITLE	3 
#define HL_TEXT		4 
#define HL_WRAP		5 
#define HL_EDIT		6 
#define HL_INIT		7 
#define HL_LIFE		8 
#define HL_ROWS		9 
#define HL_COLUMNS	10
#define HL_BEGROW	11
#define HL_BEGCOL	12
#define HL_HELP		13
#define HL_REREAD	14
#define HL_CLOSE	15
#define HL_ALTSLKS	16
#define HL_FRMMSG	17
#define HL_HEADER       18	/* abs */

/* defined above
#define HL_INTR		PAR_INTR
#define HL_ONINTR	PAR_ONINTR
*/
#define HL_ACTI		PAR_ACTION
#define HL_NAME		PAR_NAME
#define HL_BUTT		4
#define HL_SHOW		5

#define HL_KEYS 19
static struct attribute Hl_tab[HL_KEYS] = {
	{ "interrupt",	RET_STR|EVAL_ALWAYS,    NULL, NULL, 0 },
	{ "oninterrupt",RET_STR|EVAL_ALWAYS,	NULL, NULL, 0 },
	{ "done",	RET_ARGS|EVAL_ALWAYS,	"", NULL, 0 }, /* abs */
	{ "title",	RET_STR|EVAL_ONCE,	"Text", NULL, 0 },
	{ "text",	RET_STR|EVAL_ONCE,	NULL, NULL, 0 },
	{ "wrap",	RET_BOOL|EVAL_ONCE,	"", NULL, 0 },
	{ "edit",	RET_BOOL|EVAL_ONCE,	NULL, NULL, 0 },
	{ "init",	RET_BOOL|EVAL_ALWAYS,	"", NULL, 0 },
	{ "lifetime",	RET_STR|EVAL_ALWAYS,	"longterm", NULL, 0 },
	{ "rows",	RET_INT|EVAL_ONCE,	"10", NULL, 0 },
	{ "columns",	RET_INT|EVAL_ONCE,	"30", NULL, 0 },
	{ "begrow",	RET_STR|EVAL_ONCE,	"any", NULL, 0 },
	{ "begcol",	RET_STR|EVAL_ONCE,	"any", NULL, 0 },
	{ "help",	RET_ARGS|EVAL_ALWAYS,	NULL, NULL, 0 },
	{ "reread",	RET_BOOL|EVAL_ALWAYS,	NULL, NULL, 0 },
	{ "close",	RET_BOOL|EVAL_ONCE,	NULL, NULL, 0 },
	{ "altslks",	RET_BOOL|EVAL_ONCE,	NULL, NULL, 0 },
	{ "framemsg",	RET_STR|EVAL_ONCE,	"",   NULL, 0 },
        { "header",     RET_STR|EVAL_ONCE,      NULL, NULL, 0 } /* abs */
};

#define HL_FLD_KEYS 6
static struct attribute Hl_fld_tab[HL_FLD_KEYS] = {
	{ "interrupt",	RET_STR|EVAL_ALWAYS,    NULL, NULL, 0 },
	{ "oninterrupt",RET_STR|EVAL_ALWAYS,	NULL, NULL, 0 },
	{ "action",	RET_ARGS|EVAL_ALWAYS	,NULL, NULL, 0 },
	{ "name",	RET_STR|EVAL_ONCE,	NULL, NULL, 0 },
	{ "button",	RET_INT|EVAL_ONCE,	"0", NULL, 0 },
	{ "show",	RET_BOOL|EVAL_SOMETIMES,"", NULL, 0 }
};

#define CURhelp() (&(((helpinfo *) Cur_rec->odptr)->fm_mn))
#define CURhelpinfo() ((helpinfo *) Cur_rec->odptr)
#define ARGS() (((helpinfo *) Cur_rec->odptr)->args)
#define PTRS() (((helpinfo *) Cur_rec->odptr)->holdptrs)

extern int    Vflag;		/* abs k15 */
extern char  *strnsave();
extern char  *shrink_str();
static struct actrec *Cur_rec;
struct fm_mn parse_help();

static token bighelp_stream();

/*
** Returns a token so that the help object can be brought up.
*/
static token 
objhelp_help(a) 
struct actrec *a; 
{ 
    return(setaction(sing_eval(CURhelp(), HL_HELP)));
}

/*
** Frees up the structures and calls the close function.
*/
static int 
objhelp_close(a) 
struct actrec *a; 
{ 
    register int i, lcv;
    char *p, *strchr();

    Cur_rec = a;
    copyAltenv(ARGS());		/* in case HL_CLOSE references $ARGs abs k14*/
    form_close(a->id);		/* free the form FIRST */
    sing_eval(CURhelp(), HL_CLOSE);
    objhelp_noncur(a, FALSE);       /* remove ARGs from Altenv */

    /*
     * free information IN the helpinfo structure
     */
    freeitup(CURhelp());	/* the text parse table */ 
    if (PTRS())			/* holdptrs array */
	free(PTRS());
    lcv = array_len(ARGS());	/* the object specific variable */
    for (i = 0; i < lcv; i++) {	/* (e.g., $TEXT) */
	char namebuf[BUFSIZ];

	if (p = strchr(ARGS()[i], '='))
	    *p = '\0';
	strncpy(namebuf, ARGS()[i], BUFSIZ);
	namebuf[BUFSIZ-1] = '\0';
	if (p)
	    *p = '=';
	delaltenv(&ARGS(), namebuf);
	delAltenv(namebuf);
    }
    array_destroy(ARGS());	/* the object variable array */

    /*
     * Free information in the activation record structure
     */
    free(a->odptr);		/* the helpinfo structure itself */
    free(a->slks);		/* the object specific SLKS */
    free(a->path);		/* the definition file path */

    return(SUCCESS);
}

/*
** Checks to see whether to reread and if so, calls reread.
*/
static int
objhelp_reinit(a)
struct actrec *a;
{
    Cur_rec = a;
    if (sing_eval(CURhelp(), HL_REREAD))
	return(objhelp_reread(a));
    return(SUCCESS);
}

/*
** Front-end to parser(), which sets up defaults.
*/
static struct fm_mn
parse_help(flags, info_or_file, fp)
int flags;
char *info_or_file;
FILE *fp;
{
    struct fm_mn fm_mn;

    fm_mn.single.attrs = NULL;
    fm_mn.multi = NULL;
    filldef(&fm_mn.single, Hl_tab, HL_KEYS);
    parser(flags, info_or_file, Hl_tab, HL_KEYS, &fm_mn.single,
	   Hl_fld_tab, HL_FLD_KEYS, &fm_mn.multi, fp);
    return(fm_mn);
}

/*
** Frees contents of old help, and sets new one.  Note:  odptr
** is set either way since freeitup will not free anything if
** the single array is empty
*/
static int
objhelp_reread(a)
register struct actrec *a;
{
    extern struct slk Defslk[MAX_SLK + 1];
    extern struct slk Textslk[];
    register int i;
    register struct fm_mn *fm_mn;
    register helpinfo *hi;
    char *label, *intr, *onintr, *get_def();
    int   lcv, but;
    FILE *fp = NULL;

    Cur_rec = a;
    fm_mn = CURhelp();
    hi = CURhelpinfo();

    /* make sure file exists and is readable (if there is a file) 
     * The "flags" say if a->path is  the information
     * itself or the file of where the information sits.  abs k15
     */
    if (!(hi->flags & INLINE))
	if ((fp = fopen(a->path, "r")) == NULL)
	{
	    if (a->id >= 0)	/* if frame is already posted */
		warn(NOT_UPDATED, a->path);
	    else
		warn(FRAME_NOPEN, a->path);
	    return(FAIL);
	}
    if (a->id >= 0)
	freeitup(fm_mn);
    hi->fm_mn = parse_help(hi->flags, a->path, fp);	/* abs k14.0 */
    if (fm_mn->single.attrs == NULL) {
	/*
	 * very strange indeed ...
	 *
	 if (a->id < 0)
	 sing_eval(fm_mn, HL_CLOSE);
	 */
	return(FAIL);
    }
    if (PTRS())
	free(PTRS());
    lcv = sing_eval(fm_mn, HL_HEADER) ? 2:1;
    if ((PTRS() = (char **) calloc(lcv, sizeof(char *))) == NULL)
	fatal(NOMEM, nil);
    for (i = 0; i < lcv; i++)
	PTRS()[i] = (char *) NULL;	
    fm_mn->seqno = 1;
    hl_vislist(hi);

    /*
     * If "init=false" then clean-up
     */
    if (!sing_eval(CURhelp(), HL_INIT))
    {
	if (a->id >= 0)		/* form is already posted */
	{
	    if (a->lifetime == AR_INITIAL)
	    {
		mess_temp("can't close this frame");
		mess_lock();
	    }
	    else
	    {
		ar_close(a, FALSE);
		return(FAIL);
	    }
	}
	else
	{
	    sing_eval(CURhelp(),HL_CLOSE);
	    objhelp_noncur(a, TRUE); /* remove ARGs from Altenv */
	    freeitup(CURhelp());
	    return(FAIL);
	}
    }
    /*
     * update the interrupt descriptors in the activation rec
     */
    ar_ctl(Cur_rec, CTSETINTR, get_sing_def(CURhelp(), HL_INTR), NULL, NULL, NULL, NULL, NULL);
    ar_ctl(Cur_rec, CTSETONINTR, get_sing_def(CURhelp(), HL_ONINTR), NULL, NULL, NULL, NULL, NULL);

    /*
     * Set up object's SLK array
     */
    set_top_slks(Textslk);
    memcpy((char *)a->slks, (char *)Defslk, sizeof(Defslk));
    lcv = array_len(hi->slks);
    for (i = 0; i < lcv; i++) {
	but = atoi(multi_eval(fm_mn, hi->slks[i], HL_BUTT)) - 1;
	if (but <  0 || but >= MAX_SLK)	/* abs */
	    continue;
	label = multi_eval(fm_mn, hi->slks[i], HL_NAME);
	intr  = get_def(CURhelp(),hi->slks[i], HL_INTR);
	onintr  = get_def(CURhelp(),hi->slks[i], HL_ONINTR);
	set_obj_slk(&(a->slks[but]), label, TOK_SLK1 + but, intr, onintr);
    }
    if (a->id >= 0)
	form_ctl(a->id, CTSETDIRTY);
    (void) ar_ctl(Cur_rec, CTSETMSG, FALSE, NULL, NULL, NULL, NULL, NULL); /* was AR_cur.  abs k15 */
    return(SUCCESS);
}

/*
** Takes this object's information out of the major altenv.
*/
static int 
objhelp_noncur(a, all) 
struct actrec *a;
bool all;
{
    register int i;
    register char *p;
    int	lcv;

    Cur_rec = a;
    lcv = array_len(ARGS());
    for (i = 0; i < lcv; i++) {
	char namebuf[BUFSIZ];

	if (p = strchr(ARGS()[i], '='))
	    *p = '\0';
	strncpy(namebuf, ARGS()[i], BUFSIZ);
	namebuf[BUFSIZ - 1] = '\0';
	if (p)
	    *p = '=';
	delAltenv(namebuf);
    }
    if (all)
	return(form_noncurrent(a->id));
    else
	return(SUCCESS);
}

/*
** Puts this object's altenv() into the major altenv().
*/
static int 
objhelp_current(a) 
struct actrec *a; 
{
    int ret;

    Cur_rec = a;
    copyAltenv(ARGS());
    ret = form_current(a->id);
    form_ctl(a->id, CTSETPOS, 1, 0, 0); /* `1' is "text=" field  abs*/
    return(ret);
}

/*
** Sets up SLK array, based on show functions.
*/
hl_vislist(hi)
helpinfo *hi;
{
    int i;
    struct fm_mn *ptr;
    int	lcv;
	
    ptr = &(hi->fm_mn);
    if (!hi->slks)
	hi->slks = (int *) array_create(sizeof(int), array_len(ptr->multi));
    else
	array_trunc(hi->slks);

    lcv = array_len(ptr->multi);
    for (i = 0; i < lcv; i++)
	if (multi_eval(ptr, i, HL_SHOW))
	    hi->slks = (int *) array_append(hi->slks, (char *) &i);
}
#define MIN_ROWS_TEXT 4	  /* includes 2 rows of margin along frame border */

/* Size a text header by counting the newlines */
header_size(m)
formfield m;
{
    register char *c;
    register int linefeeds;
    register struct vt *v;
    int  rows, cols;
    
    if (*m.value == 0)		/* null string */
	return(0);

    for (linefeeds = 1, c = m.value; *c != NULL; c++)
	if (*c == '\n')
	    linefeeds++;

    /* leave at least MIN_ROWS_TEXT rows for text */
    v = &VT_array[VT_curid];
    getmaxyx(v->win, rows, cols);
    return(linefeeds > rows - MIN_ROWS_TEXT) ? rows - MIN_ROWS_TEXT : linefeeds;

}


/*
** Gives header and text as only fields, fields that have no names.
*/
static formfield
objhelp_disp(n, hi)
int n;
helpinfo *hi;
{
    struct fm_mn *ptr;
    char *readfile();
    formfield m;
    static int header_rows;

    ptr = &(hi->fm_mn);
    switch (n)
    {
    case 0:			/* non-scrolling header field. abs8/88 */
	m.name = strsave("");
	m.value = sing_eval(ptr, HL_HEADER);
	m.frow = 0;
	m.fcol = 0;
	m.nrow = VT_UNDEFINED;
	m.ncol = VT_UNDEFINED;
	m.rows = header_rows = header_size(m); 
	m.cols = atoi(sing_eval(CURhelp(), HL_COLUMNS));
	m.flags = I_FANCY | I_NOEDIT | I_TEXT;
	m.ptr = PTRS();
	break;
    case 1:			/* text field */
	m.name = strsave("");
	m.value = sing_eval(ptr, HL_TEXT);
	m.frow = header_rows;	/* header has rows 0 -> header_rows - 1 */
	m.fcol = 0;
	m.nrow = VT_UNDEFINED;
	m.ncol = VT_UNDEFINED;
	m.rows = atoi(sing_eval(CURhelp(), HL_ROWS)) - header_rows;
	m.cols = atoi(sing_eval(CURhelp(), HL_COLUMNS));
	m.flags = I_FANCY|I_SCROLL|I_TEXT;
	if (header_rows == 0)	    /* curses optimization.. */
	    m.flags |= I_FULLWIN;   /* ..if no subwindows needed */
	if (!sing_eval(CURhelp(), HL_EDIT))
	    m.flags |= I_NOEDIT;
	if (sing_eval(CURhelp(), HL_WRAP))
	    m.flags |= I_WRAP;
	m.ptr = PTRS() + 1;
	break;
    default:
	m.name = NULL;
    }
    return(m);
}


/*
** There are no args, so return FAIL. Otherwise, pass it on.
*/
int
objhelp_ctl(rec, cmd, arg1, arg2, arg3, arg4, arg5, arg6)
struct actrec *rec;
int cmd;
int arg1, arg2, arg3, arg4, arg5, arg6;
{
    if (cmd == CTGETARG)
	return(FAIL);
    else if (cmd == CTSETMSG) {
	if (arg1 == TRUE) {
	    /* 
	     * if arg1 == TRUE then the frame message was
	     * generated "externally" (i.e., via the message
	     * built-it).  Update the "framemsg" descriptor
	     * accordingly.
	     */
	    char *newmsg, *get_mess_frame();

	    newmsg = get_mess_frame();
	    set_single_default(CURhelp(), HL_FRMMSG, newmsg);
	}
	else 
	    mess_frame(sing_eval(CURhelp(), HL_FRMMSG));
	return(SUCCESS);
    }
    if (cmd == CTSETLIFE) {
	char *life;

	life = sing_eval((&(((helpinfo *) rec->odptr)->fm_mn)), HL_LIFE);
	setlifetime(rec, life);
	return(SUCCESS);
    }
    return(form_ctl(rec->id, cmd, arg1, arg2, arg3, arg4, arg5, arg6));
}

/*
** Uses path_to_ar and nextpath_to_ar to see if it is a reopen.  If
** so, make it current.  Otherwise, set up the actrec and call 
** ar_create.
*/
int
IF_helpopen(args)
register char **args;
{
    register int i;
    int type, startrow, startcol;
    char *begrow, *begcol;
    struct actrec a, *first_rec, *ar_create(), *path_to_ar(), *nextpath_to_ar();
    int inline;
    struct fm_mn *fm_mn;
    extern struct slk Defslk[MAX_SLK + 1];
    extern  char *filename();
    helpinfo *hi;
    char *life;
    char buf[BUFSIZ], envbuf[6];
    char *ptr;

    if (strCcmp(args[0], "-i") == 0)
    {
	inline = TRUE;
	Cur_rec = path_to_ar(args[1]);
    }
    else
    {
	inline = FALSE;
	Cur_rec = path_to_ar(args[0]);
    }

    for (first_rec = Cur_rec; Cur_rec; ) {
	char *env, *getaltenv();

	strcpy(envbuf, "ARG1");
	for (i = inline ? 2 : 1; (env = getaltenv(ARGS(), envbuf)) && args[i];
	     envbuf[3]++, i++)
	    if (strcmp(args[i], env))
		break;
	if (!args[i] && !env) {
	    ar_current(Cur_rec, TRUE); /* abs k15 */
	    return(SUCCESS);
	}
	Cur_rec = nextpath_to_ar(Cur_rec);
	if (Cur_rec == first_rec) /* circular list */
	    break;
    }
    hi = (helpinfo *)new(helpinfo);
    hi->flags = inline ? INLINE : 0;
    hi->args = NULL;
    a.id = -1;
    a.odptr = (char *) hi;
    fm_mn = &(hi->fm_mn);
    fm_mn->single.attrs = NULL;
    if (inline)
	a.path = strsave(args[1]);
    else
	a.path = strsave(args[0]);
    if ((a.slks = (struct slk *) malloc(sizeof(Defslk))) == NULL)
	fatal(NOMEM, nil);
    a.fcntbl[AR_CLOSE] = objhelp_close;
    a.fcntbl[AR_REREAD] = objhelp_reread;
    a.fcntbl[AR_REINIT] = objhelp_reinit;
    a.fcntbl[AR_CURRENT] = objhelp_current;
    a.fcntbl[AR_TEMP_CUR] = objhelp_current; /* abs k15. optimize later */
    a.fcntbl[AR_NONCUR] = objhelp_noncur;
    a.fcntbl[AR_ODSH] = (int (*)())bighelp_stream; /* added cast abs */
    a.fcntbl[AR_HELP] = (int (*)())objhelp_help; /* added cast abs */
    a.fcntbl[AR_CTL] = objhelp_ctl;
    Cur_rec = &a;
    setupenv(hi->flags, args, &ARGS());
    if (objhelp_reread(&a) == FAIL)
	return(FAIL);
    ptr = strnsave("TEXT=", strlen(life = sing_eval(fm_mn, HL_TEXT)) + 6);
    strcat(ptr, life);
    putaltenv(&ARGS(), ptr);
    putAltenv(ptr);
    free(ptr);
    begrow = sing_eval(fm_mn, HL_BEGROW);
    begcol = sing_eval(fm_mn, HL_BEGCOL);
    life = sing_eval(fm_mn, HL_LIFE);
    life_and_pos(&a, life, begrow, begcol, &startrow, &startcol, &type);

    if (Vflag)			/* abs k15 */
	strcpy(buf, shrink_str(filename(sing_eval(fm_mn, HL_TITLE)), MAX_TITLE));
    else			/* abs k15 */
	strcpy(buf, shrink_str(sing_eval(fm_mn, HL_TITLE), MAX_TITLE));
    a.id = form_default(buf, type, startrow, startcol,
			objhelp_disp, (char *)hi);
    if (a.id == FAIL)
	return(FAIL);

    if (sing_eval(fm_mn, HL_ALTSLKS))
	a.flags = AR_ALTSLKS;
    else
	a.flags = 0;
    return(ar_current(Cur_rec = ar_create(&a), FALSE));	/* abs k15 */
}

/*
** Intercepts SLKs after the editor. Also, TOK_SAVE is an exit.
*/
token
help_stream(tok)
register token tok;
{
    char *buf, *s;
    int *slks;
    int	lcv;
	

    s = NULL;
    if (tok >= TOK_SLK1 && tok <= TOK_SLK16) {
	int num;
	int i;

	slks = CURhelpinfo()->slks;
	num = tok - TOK_SLK1 + 1;
	lcv = array_len(slks);
	for (i = 0; i < lcv; i++)
	    if (atoi(multi_eval(CURhelp(), slks[i], HL_BUTT)) == num) {
		form_ctl(Cur_rec->id, CTGETARG, &s);
		if (sing_eval(CURhelp(), HL_EDIT))
		    set_sing_cur(CURhelp(), HL_TEXT, strsave(s));
		buf = strnsave("TEXT=", strlen(s) + 6);
		strcat(buf, s);
		putaltenv(&ARGS(), buf);
		putAltenv(buf);
		tok = setaction(multi_eval(CURhelp(), slks[i], HL_ACTI));
		free(buf);
		break;
	    }
    }
    if (tok == TOK_SAVE)
	tok = TOK_CLOSE;
    if (tok == TOK_CLOSE) {
	if (!s) {
	    form_ctl(Cur_rec->id, CTGETARG, &s);
	    buf = strnsave("TEXT=", strlen(s) + 6);
	    strcat(buf, s);
	    putaltenv(&ARGS(), buf);
	    putAltenv(buf);
	    free(buf);
	}
	/* tok = sing_eval(CURhelp(), HL_DONE) ? TOK_CLOSE : TOK_BADCHAR; abs */
	tok = make_action(sing_eval(CURhelp(), HL_DONE));
    }
    return(tok);
}

/*
** Sets up stream and calls stream.
*/
static token
bighelp_stream(a, t)
struct actrec *a;
register token t;
{
    token (*func[3])();
    extern int field_stream();
    register int olifetime;

    Cur_rec = a;
    olifetime = Cur_rec->lifetime;
    Cur_rec->lifetime = AR_PERMANENT;
    func[0] = (token (*)())field_stream; /* added cast  abs */
    func[1] = help_stream;
    func[2] = NULL;
    t = stream(t, func);
    Cur_rec->lifetime = olifetime;
    return(t);
}
