/*
 * zle_params.c - ZLE special parameters
 *
 * This file is part of zsh, the Z shell.
 *
 * Copyright (c) 1992-1997 Paul Falstad
 * All rights reserved.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and to distribute modified versions of this software for any
 * purpose, provided that the above copyright notice and the following
 * two paragraphs appear in all copies of this software.
 *
 * In no event shall Paul Falstad or the Zsh Development Group be liable
 * to any party for direct, indirect, special, incidental, or consequential
 * damages arising out of the use of this software and its documentation,
 * even if Paul Falstad and the Zsh Development Group have been advised of
 * the possibility of such damage.
 *
 * Paul Falstad and the Zsh Development Group specifically disclaim any
 * warranties, including, but not limited to, the implied warranties of
 * merchantability and fitness for a particular purpose.  The software
 * provided hereunder is on an "as is" basis, and Paul Falstad and the
 * Zsh Development Group have no obligation to provide maintenance,
 * support, updates, enhancements, or modifications.
 *
 */

#include "zle.mdh"

#include "zle_params.pro"

/*
 * ZLE SPECIAL PARAMETERS:
 *
 * These special parameters are created, with a local scope, when
 * running user-defined widget functions.  Reading and writing them
 * reads and writes bits of ZLE state.  The parameters are:
 *
 * BUFFER   (scalar)   entire buffer contents
 * CURSOR   (integer)  cursor position; 0 <= $CURSOR <= $#BUFFER
 * LBUFFER  (scalar)   portion of buffer to the left of the cursor
 * RBUFFER  (scalar)   portion of buffer to the right of the cursor
 */

static const struct gsu_scalar buffer_gsu =
{ get_buffer, set_buffer, zleunsetfn };
static const struct gsu_scalar context_gsu =
{ get_context, nullstrsetfn, zleunsetfn };
static const struct gsu_scalar cutbuffer_gsu =
{ get_cutbuffer, set_cutbuffer, unset_cutbuffer };
static const struct gsu_scalar keymap_gsu =
{ get_keymap, nullstrsetfn, zleunsetfn };
static const struct gsu_scalar keys_gsu =
{ get_keys, nullstrsetfn, zleunsetfn };
static const struct gsu_scalar lastabortedsearch_gsu =
{ get_lasearch, nullstrsetfn, zleunsetfn };
static const struct gsu_scalar lastsearch_gsu =
{ get_lsearch, nullstrsetfn, zleunsetfn };
static const struct gsu_scalar lastwidget_gsu =
{ get_lwidget, nullstrsetfn, zleunsetfn };
static const struct gsu_scalar lbuffer_gsu =
{ get_lbuffer, set_lbuffer, zleunsetfn };
static const struct gsu_scalar postdisplay_gsu =
{ get_postdisplay, set_postdisplay, zleunsetfn };
static const struct gsu_scalar prebuffer_gsu =
{ get_prebuffer, nullstrsetfn, zleunsetfn };
static const struct gsu_scalar predisplay_gsu =
{ get_predisplay, set_predisplay, zleunsetfn };
static const struct gsu_scalar rbuffer_gsu =
{ get_rbuffer, set_rbuffer, zleunsetfn };
static const struct gsu_scalar widget_gsu =
{ get_widget, nullstrsetfn, zleunsetfn };
static const struct gsu_scalar widgetfunc_gsu =
{ get_widgetfunc, nullstrsetfn, zleunsetfn };
static const struct gsu_scalar widgetstyle_gsu =
{ get_widgetstyle, nullstrsetfn, zleunsetfn };
static const struct gsu_scalar zle_state_gsu =
{ get_zle_state, nullstrsetfn, zleunsetfn };

static const struct gsu_integer bufferlines_gsu =
{ get_bufferlines, NULL, zleunsetfn };
static const struct gsu_integer cursor_gsu =
{ get_cursor, set_cursor, zleunsetfn };
static const struct gsu_integer histno_gsu =
{ get_histno, set_histno, zleunsetfn };
static const struct gsu_integer mark_gsu =
{ get_mark, set_mark, zleunsetfn };
static const struct gsu_integer numeric_gsu =
{ get_numeric, set_numeric, unset_numeric };
static const struct gsu_integer pending_gsu =
{ get_pending, NULL, zleunsetfn };
static const struct gsu_integer region_active_gsu =
{ get_region_active, set_region_active, zleunsetfn };
static const struct gsu_integer undo_change_no_gsu =
{ get_undo_current_change, NULL, zleunsetfn };

static const struct gsu_array killring_gsu =
{ get_killring, set_killring, unset_killring };
/* implementation is in zle_refresh.c */
static const struct gsu_array region_highlight_gsu =
{ get_region_highlight, set_region_highlight, unset_region_highlight };

#define GSU(X) ( (GsuScalar)(void*)(&(X)) )
static struct zleparam {
    char *name;
    int type;
    GsuScalar gsu;
    void *data;
} zleparams[] = {
    { "BUFFER",  PM_SCALAR,  GSU(buffer_gsu), NULL },
    { "BUFFERLINES", PM_INTEGER | PM_READONLY, GSU(bufferlines_gsu),
        NULL },
    { "CONTEXT", PM_SCALAR | PM_READONLY, GSU(context_gsu),
	NULL },
    { "CURSOR",  PM_INTEGER, GSU(cursor_gsu),
	NULL },
    { "CUTBUFFER", PM_SCALAR, GSU(cutbuffer_gsu), NULL },
    { "HISTNO", PM_INTEGER, GSU(histno_gsu), NULL },
    { "KEYMAP", PM_SCALAR | PM_READONLY, GSU(keymap_gsu), NULL },
    { "KEYS", PM_SCALAR | PM_READONLY, GSU(keys_gsu), NULL },
    { "killring", PM_ARRAY, GSU(killring_gsu), NULL },
    { "LASTABORTEDSEARCH", PM_SCALAR | PM_READONLY, GSU(lastabortedsearch_gsu),
      NULL },
    { "LASTSEARCH", PM_SCALAR | PM_READONLY, GSU(lastsearch_gsu), NULL },
    { "LASTWIDGET", PM_SCALAR | PM_READONLY, GSU(lastwidget_gsu), NULL },
    { "LBUFFER", PM_SCALAR,  GSU(lbuffer_gsu), NULL },
    { "MARK",  PM_INTEGER, GSU(mark_gsu), NULL },
    { "NUMERIC", PM_INTEGER | PM_UNSET, GSU(numeric_gsu), NULL },
    { "PENDING", PM_INTEGER | PM_READONLY, GSU(pending_gsu), NULL },
    { "POSTDISPLAY", PM_SCALAR, GSU(postdisplay_gsu), NULL },
    { "PREBUFFER",  PM_SCALAR | PM_READONLY,  GSU(prebuffer_gsu), NULL },
    { "PREDISPLAY", PM_SCALAR, GSU(predisplay_gsu), NULL },
    { "RBUFFER", PM_SCALAR,  GSU(rbuffer_gsu), NULL },
    { "REGION_ACTIVE", PM_INTEGER, GSU(region_active_gsu), NULL},
    { "region_highlight", PM_ARRAY, GSU(region_highlight_gsu), NULL },
    { "UNDO_CHANGE_NO", PM_INTEGER | PM_READONLY, GSU(undo_change_no_gsu),
      NULL },
    { "WIDGET", PM_SCALAR | PM_READONLY, GSU(widget_gsu), NULL },
    { "WIDGETFUNC", PM_SCALAR | PM_READONLY, GSU(widgetfunc_gsu), NULL },
    { "WIDGETSTYLE", PM_SCALAR | PM_READONLY, GSU(widgetstyle_gsu), NULL },
    { "ZLE_STATE", PM_SCALAR | PM_READONLY, GSU(zle_state_gsu), NULL },
    { NULL, 0, NULL, NULL }
};

/* ro means parameters are readonly, used from completion */

/**/
mod_export void
makezleparams(int ro)
{
    struct zleparam *zp;

    for(zp = zleparams; zp->name; zp++) {
	Param pm = createparam(zp->name, (zp->type |PM_SPECIAL|PM_REMOVABLE|
					  PM_LOCAL|(ro ? PM_READONLY : 0)));
	if (!pm)
	    pm = (Param) paramtab->getnode(paramtab, zp->name);
	DPUTS(!pm, "param not set in makezleparams");

	pm->level = locallevel + 1;
	pm->u.data = zp->data;
	switch(PM_TYPE(zp->type)) {
	    case PM_SCALAR:
		pm->gsu.s = zp->gsu;
		break;
	    case PM_ARRAY:
		pm->gsu.a = (GsuArray)zp->gsu;
		break;
	    case PM_INTEGER:
		pm->gsu.i = (GsuInteger)zp->gsu;
		pm->base = 10;
		break;
	}
	if ((zp->type & PM_UNSET) && (zmod.flags & MOD_MULT))
	    pm->node.flags &= ~PM_UNSET;
    }
}

/* Special unset function for ZLE special parameters: act like the standard *
 * unset function if this is a user-initiated unset, but nothing is done if *
 * the parameter is merely going out of scope (which it will do).           */

/**/
static void
zleunsetfn(Param pm, int exp)
{
    if(exp)
	stdunsetfn(pm, exp);
}

/**/
static void
set_buffer(UNUSED(Param pm), char *x)
{
    if(x) {
	setline(x, 0);
	zsfree(x);
    } else
	zlecs = zlell = 0;
    fixsuffix();
    menucmp = 0;
}

/**/
static char *
get_buffer(UNUSED(Param pm))
{
    if (zlemetaline != 0)
	return dupstring(zlemetaline);
    return zlelineasstring(zleline, zlell, 0, NULL, NULL, 1);
}

/**/
static void
set_cursor(UNUSED(Param pm), zlong x)
{
    if(x < 0)
	zlecs = 0;
    else if(x > zlell)
	zlecs = zlell;
    else
	zlecs = x;
    fixsuffix();
    menucmp = 0;
}

/**/
static zlong
get_cursor(UNUSED(Param pm))
{
    if (zlemetaline != NULL) {
	/* A lot of work for one number, but still... */
	ZLE_STRING_T tmpline;
	int tmpcs, tmpll, tmpsz;
	char *tmpmetaline = ztrdup(zlemetaline);
	tmpline = stringaszleline(tmpmetaline, zlemetacs,
				  &tmpll, &tmpsz, &tmpcs);
	free(tmpmetaline);
	free(tmpline);
	return tmpcs;
    }
    return zlecs;
}

/**/
static void
set_mark(UNUSED(Param pm), zlong x)
{
    if (x < 0)
	mark = 0;
    else if (x > zlell)
	mark = zlell;
    else
	mark = x;
}

/**/
static zlong
get_mark(UNUSED(Param pm))
{
    return mark;
}

/**/
static void
set_region_active(UNUSED(Param pm), zlong x)
{
    region_active = (int)!!x;
}

/**/
static zlong
get_region_active(UNUSED(Param pm))
{
    return region_active;
}

/**/
static void
set_lbuffer(UNUSED(Param pm), char *x)
{
    ZLE_STRING_T y;
    int len;

    if (x && *x != ZWC('\0'))
	y = stringaszleline(x, 0, &len, NULL, NULL);
    else
	y = ZWS(""), len = 0;
    sizeline(zlell - zlecs + len);
    ZS_memmove(zleline + len, zleline + zlecs, zlell - zlecs);
    ZS_memcpy(zleline, y, len);
    zlell = zlell - zlecs + len;
    zlecs = len;
    zsfree(x);
    if (len)
	free(y);
    fixsuffix();
    menucmp = 0;
}

/**/
static char *
get_lbuffer(UNUSED(Param pm))
{
    if (zlemetaline != NULL)
	return dupstrpfx(zlemetaline, zlemetacs);
    return zlelineasstring(zleline, zlecs, 0, NULL, NULL, 1);
}

/**/
static void
set_rbuffer(UNUSED(Param pm), char *x)
{
    ZLE_STRING_T y;
    int len;

    if (x && *x != ZWC('\0'))
	y = stringaszleline(x, 0, &len, NULL, NULL);
    else
	y = ZWS(""), len = 0;
    sizeline(zlell = zlecs + len);
    ZS_memcpy(zleline + zlecs, y, len);
    zsfree(x);
    if (len)
	free(y);
    fixsuffix();
    menucmp = 0;
}

/**/
static char *
get_rbuffer(UNUSED(Param pm))
{
    if (zlemetaline != NULL)
	return dupstrpfx((char *)zlemetaline + zlemetacs,
			 zlemetall - zlemetacs);
    return zlelineasstring(zleline + zlecs, zlell - zlecs, 0, NULL, NULL, 1);
}

/**/
static char *
get_prebuffer(UNUSED(Param pm))
{
    /*
     * Use the editing current history line, not necessarily the
     * history line that's currently in the history mechanism
     * since our line may have been stacked.
     */
    if (zle_chline) {
	/* zle_chline was NULL terminated when pushed onto the stack */
	return dupstring(zle_chline);
    }
    if (chline) {
	/* hptr is valid */
	return dupstrpfx(chline, hptr - chline);
    }
    return dupstring("");
}

/**/
static char *
get_widget(UNUSED(Param pm))
{
    return bindk ? bindk->nam : "";
}

/**/
static char *
get_widgetfunc(UNUSED(Param pm))
{
    Widget widget = bindk->widget;
    int flags = widget->flags;

    if (flags & WIDGET_INT)
	return ".internal";	/* Don't see how this can ever be returned... */
    if (flags & WIDGET_NCOMP)
	return widget->u.comp.func;
    return widget->u.fnnam;
}

/**/
static char *
get_widgetstyle(UNUSED(Param pm))
{
    Widget widget = bindk->widget;
    int flags = widget->flags;

    if (flags & WIDGET_INT)
	return ".internal";	/* Don't see how this can ever be returned... */
    if (flags & WIDGET_NCOMP)
	return widget->u.comp.wid;
    return "";
}

/**/
static char *
get_lwidget(UNUSED(Param pm))
{
    return (lbindk ? lbindk->nam : "");
}

/**/
static char *
get_keymap(UNUSED(Param pm))
{
    return dupstring(curkeymapname);
}

/**/
static char *
get_keys(UNUSED(Param pm))
{
    return keybuf;
}

/**/
static void
set_numeric(UNUSED(Param pm), zlong x)
{
    zmult = x;
    zmod.flags = MOD_MULT;
}

/**/
static zlong
get_numeric(UNUSED(Param pm))
{
    return zmult;
}

/**/
static void
unset_numeric(Param pm, int exp)
{
    if (exp) {
	stdunsetfn(pm, exp);
	zmod.flags = 0;
	zmult = 1;
    }
}

/**/
static void
set_histno(UNUSED(Param pm), zlong x)
{
    Histent he;

    if (!(he = quietgethist((int)x)))
	return;
    zle_setline(he);
}

/**/
static zlong
get_histno(UNUSED(Param pm))
{
    return histline;
}

/**/
static zlong
get_bufferlines(UNUSED(Param pm))
{
    return nlnct;
}

/**/
static zlong
get_pending(UNUSED(Param pm))
{
    return noquery(0);
}

/**/
static char *
get_cutbuffer(UNUSED(Param pm))
{
    if (cutbuf.buf)
	return zlelineasstring(cutbuf.buf, cutbuf.len, 0, NULL, NULL, 1);
    return "";
}


/**/
static void
set_cutbuffer(UNUSED(Param pm), char *x)
{
    if (cutbuf.buf)
	free(cutbuf.buf);
    cutbuf.flags = 0;
    if (x) {
	int n;
	cutbuf.buf = stringaszleline(x, 0, &n, NULL, NULL);
	cutbuf.len = n;
	free(x);
    } else {
	cutbuf.buf = NULL;
	cutbuf.len = 0;
    }
}

/**/
static void
unset_cutbuffer(Param pm, int exp)
{
    if (exp) {
	stdunsetfn(pm, exp);
	if (cutbuf.buf) {
	    free(cutbuf.buf);
	    cutbuf.buf = NULL;
	    cutbuf.len = 0;
	}
    }
}

/**/
static void
set_killring(UNUSED(Param pm), char **x)
{
    int kcnt;
    Cutbuffer kptr;
    char **p;

    if (kring) {
	for (kptr = kring, kcnt = 0; kcnt < kringsize; kcnt++, kptr++)
	    if (kptr->buf)
		free(kptr->buf);
	zfree(kring, kringsize * sizeof(struct cutbuffer));
	kring = NULL;
	kringsize = kringnum = 0;
    }
    if (x) {
	/*
	 * Insert the elements into the kill ring.
	 * Regardless of the old order, we number it with the current
	 * entry first.
	 *
	 * Be careful to add elements by looping backwards; this
	 * fits in with how we cycle the ring.
	 */
	int kpos = 0;
	kringsize = arrlen(x);
	if (kringsize != 0) {
	    kring = (Cutbuffer)zshcalloc(kringsize * sizeof(struct cutbuffer));
	    for (p = x; *p; p++) {
		int n, len = strlen(*p);
		kptr = kring + kpos;

		kptr->buf = stringaszleline(*p, 0, &n, NULL, NULL);
		kptr->len = n;

		zfree(*p, len+1);
		kpos = (kpos + kringsize -1 ) % kringsize;
	    }
	}
	free(x);
    }
}

/**/
static char **
get_killring(UNUSED(Param pm))
{
    /*
     * Return the kill ring with the most recently killed first.
     * Since the kill ring is no longer a fixed length, we return
     * all entries even if empty.
     */
    int kpos, kcnt;
    char **ret, **p;

    /* Supposed to work even if kring is NULL */
    if (!kring) {
	kringsize = KRINGCTDEF;
	kring = (Cutbuffer)zshcalloc(kringsize * sizeof(struct cutbuffer));
    }

    p = ret = (char **)zhalloc((kringsize+1) * sizeof(char *));

    for (kpos = kringnum, kcnt = 0; kcnt < kringsize; kcnt++) {
	Cutbuffer kptr = kring + kpos;
	if (kptr->buf)
	{
	    /* Allocate on heap. */
	    *p++ = zlelineasstring(kptr->buf, kptr->len, 0, NULL, NULL, 1);
	}
	else
	    *p++ = dupstring("");
	kpos = (kpos + kringsize - 1) % kringsize;
    }
    *p = NULL;

    return ret;
}

/**/
static void
unset_killring(Param pm, int exp)
{
    if (exp) {
	set_killring(pm, NULL);
	stdunsetfn(pm, exp);
    }
}

static void
set_prepost(ZLE_STRING_T *textvar, int *lenvar, char *x)
{
    if (*lenvar) {
	free(*textvar);
	*textvar = NULL;
	*lenvar = 0;
    }
    if (x) {
	*textvar = stringaszleline(x, 0, lenvar, NULL, NULL);
	free(x);
    }
}

static char *
get_prepost(ZLE_STRING_T text, int len)
{
    return zlelineasstring(text, len, 0, NULL, NULL, 1);
}

/**/
static void
set_predisplay(UNUSED(Param pm), char *x)
{
    set_prepost(&predisplay, &predisplaylen, x);
}

/**/
static char *
get_predisplay(UNUSED(Param pm))
{
    return get_prepost(predisplay, predisplaylen);
}

/**/
static void
set_postdisplay(UNUSED(Param pm), char *x)
{
    set_prepost(&postdisplay, &postdisplaylen, x);
}

/**/
static char *
get_postdisplay(UNUSED(Param pm))
{
    return get_prepost(postdisplay, postdisplaylen);
}

/**/
void
free_prepostdisplay(void)
{
    if (predisplaylen)
	set_prepost(&predisplay, &predisplaylen, NULL);
    if (postdisplaylen)
	set_prepost(&postdisplay, &postdisplaylen, NULL);
}

/**/
static char *
get_lasearch(UNUSED(Param pm))
{
    if (previous_aborted_search)
	return previous_aborted_search;
    return "";
}

/**/
static char *
get_lsearch(UNUSED(Param pm))
{
    if (previous_search)
	return previous_search;
    return "";
}

/**/
static char *
get_context(UNUSED(Param pm))
{
    switch (zlecontext) {
    case ZLCON_LINE_CONT:
	return "cont";
	break;

    case ZLCON_SELECT:
	return "select";
	break;

    case ZLCON_VARED:
	return "vared";
	break;

    case ZLCON_LINE_START:
    default:
	return "start";
	break;
    }
}

/**/
static char *
get_zle_state(UNUSED(Param pm))
{
    char *zle_state = NULL, *ptr = NULL, **arr = NULL;
    int itp, istate, len = 0;

    /*
     * Substrings are sorted at the end, so the user can
     * easily match against this parameter:
     * if [[ $ZLE_STATE == *bar*foo*zonk* ]]; then ...; fi
     */
    for (itp = 0; itp < 2; itp++) {
	char *str;
	for (istate = 0; istate < 2; istate++) {
	    int slen;
	    switch (istate) {
	    case 0:
		if (insmode) {
		    str = "insert";
		} else {
		    str = "overwrite";
		}
		break;
	    case 1:
		if (hist_skip_flags & HIST_FOREIGN) {
		    str = "localhistory";
		} else {
		    str = "globalhistory";
		}
		break;

	    default:
		str = "";
	    }
	    slen = strlen(str);
	    if (itp == 0) {
		/* Accumulating length */
		if (istate)
		    len++;	/* for space */
		len += slen;
	    } else {
		/* Accumulating string */
		if (istate)
		    *ptr++ = ':';
		memcpy(ptr, str, slen);
		ptr += slen;
	    }
	}
	if (itp == 0) {
	    len++;		/* terminating NULL */
	    ptr = zle_state = (char *)zhalloc(len);
	} else {
	    *ptr = '\0';
	}
    }

    arr = colonsplit(zle_state, 0);
    strmetasort(arr, SORTIT_ANYOLDHOW, NULL);
    zle_state = zjoin(arr, ' ', 1);
    freearray(arr);

    return zle_state;
}
