/*
 * ksh93.c - support for more ksh93 features
 *
 * This file is part of zsh, the Z shell.
 *
 * Copyright (c) 2022 Barton E. Schaefer
 * 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 Barton E. Schaefer 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 Barton E. Schaefer and the Zsh
 * Development Group have been advised of the possibility of such damage.
 *
 * Barton E. Schaefer 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
 * Barton E. Schaefer and the Zsh Development Group have no
 * obligation to provide maintenance, support, updates, enhancements, or
 * modifications.
 *
 */

#include "ksh93.mdh"
#include "ksh93.pro"

/* Implementing "namespace" requires creating a new keword.  Hrm. */

/*
 * Standard module configuration/linkage
 */

static struct builtin bintab[] = {
    BUILTIN("nameref", BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "gr", "n")
};

#include "zsh.mdh"

static void
edcharsetfn(Param pm, char *x)
{
    /*
     * To make this work like ksh, we must intercept $KEYS before the widget
     * is looked up, so that changing the key sequence causes a different
     * widget to be substituted.  Somewhat similar to "bindkey -s".
     *
     * Ksh93 adds SIGKEYBD to the trap list for this purpose.
     */
    ;
}

static char **
matchgetfn(Param pm)
{
    char **zsh_match = getaparam("match");

    /* For this to work accurately, ksh emulation should always imply
     * that the (#m) and (#b) extendedglob operators are enabled.
     *
     * When we have a 0th element (ksharrays), it is $MATCH.  Elements
     * 1st and larger mirror the $match array.
     */

    if (pm->u.arr)
	freearray(pm->u.arr);
    if (zsh_match && *zsh_match) {
	if (isset(KSHARRAYS)) {
	    char **ap =
		(char **) zalloc(sizeof(char *) * (arrlen(zsh_match)+1));
	    pm->u.arr = ap;
	    *ap++ = ztrdup(getsparam("MATCH"));
	    while (*zsh_match)
		*ap = ztrdup(*zsh_match++);
	} else
	    pm->u.arr = zarrdup(zsh_match);
    } else if (isset(KSHARRAYS)) {
	pm->u.arr = mkarray(ztrdup(getsparam("MATCH")));
    } else
	pm->u.arr = NULL;

    return arrgetfn(pm);
}

static const struct gsu_scalar constant_gsu =
    { strgetfn, NULL, nullunsetfn };

static const struct gsu_scalar sh_edchar_gsu =
    { strvargetfn, edcharsetfn, nullunsetfn };
static const struct gsu_scalar sh_edmode_gsu =
    { strgetfn, nullstrsetfn, nullunsetfn };
static const struct gsu_array sh_match_gsu =
    { matchgetfn, arrsetfn, stdunsetfn };
static const struct gsu_scalar sh_name_gsu =
    { strvargetfn, nullstrsetfn, nullunsetfn };
static const struct gsu_scalar sh_subscript_gsu =
    { strvargetfn, nullstrsetfn, nullunsetfn };

static char *sh_name;
static char *sh_subscript;
static char *sh_edchar;
static char sh_edmode[2];

/*
 * Some parameters listed here do not appear in ksh93.mdd autofeatures
 * because they are only instantiated by ksh93_wrapper() below.  This
 * obviously includes those commented out here.
 */
static struct paramdef partab[] = {
    PARAMDEF(".sh.command", PM_NAMEREF|PM_READONLY, "ZSH_DEBUG_CMD", &constant_gsu),
    PARAMDEF(".sh.edchar", PM_SCALAR|PM_SPECIAL, &sh_edchar, &sh_edchar_gsu),
    PARAMDEF(".sh.edcol", PM_NAMEREF|PM_READONLY, "CURSOR", &constant_gsu),
    PARAMDEF(".sh.edmode", PM_SCALAR|PM_READONLY|PM_SPECIAL, &sh_edmode, &sh_edmode_gsu),
    PARAMDEF(".sh.edtext", PM_NAMEREF|PM_READONLY, "BUFFER", &constant_gsu),
    PARAMDEF(".sh.file", PM_NAMEREF|PM_READONLY, "ZSH_SCRIPT", &constant_gsu),
    /* PARAMDEF(".sh.fun", PM_SCALAR|PM_UNSET, NULL, &constant_gsu), */
    /* PARAMDEF(".sh.level", PM_INTEGER|PM_UNSET, NULL, &constant_gsu), */
    PARAMDEF(".sh.lineno", PM_NAMEREF|PM_READONLY, "LINENO", &constant_gsu),
    PARAMDEF(".sh.match", PM_ARRAY|PM_READONLY, NULL, &sh_match_gsu),
    PARAMDEF(".sh.name", PM_SCALAR|PM_READONLY|PM_SPECIAL, &sh_name, &sh_name_gsu),
    PARAMDEF(".sh.subscript", PM_SCALAR|PM_READONLY|PM_SPECIAL, &sh_subscript, &sh_subscript_gsu),
    PARAMDEF(".sh.subshell", PM_NAMEREF|PM_READONLY, "ZSH_SUBSHELL", &constant_gsu),
    /* SPECIALPMDEF(".sh.value", 0, NULL, NULL, NULL), */
    PARAMDEF(".sh.version", PM_NAMEREF|PM_READONLY, "ZSH_PATCHLEVEL", &constant_gsu)
};

static struct features module_features = {
    bintab, sizeof(bintab)/sizeof(*bintab),
    NULL, 0,
    NULL, 0,
    partab, sizeof(partab)/sizeof(*partab),
    0
};

/**/
static int
ksh93_wrapper(Eprog prog, FuncWrap w, char *name)
{
    Funcstack f;
    Param pm;
    zlong num = funcstack->prev ? getiparam(".sh.level") : 0;

    if (!EMULATION(EMULATE_KSH))
	return 1;

    if (num == 0)
	for (f = funcstack; f; f = f->prev, num++);
    else
	num++;

    queue_signals();
    ++locallevel;		/* Make these local */
    if ((pm = createparam(".sh.level", PM_LOCAL|PM_UNSET))) {
	pm->level = locallevel;	/* Why is this necessary? */
	setiparam(".sh.level", num);
    }
    if ((pm = createparam(".sh.fun", PM_LOCAL|PM_UNSET))) {
	pm->level = locallevel;
	setsparam(".sh.fun", ztrdup(name));
	pm->node.flags |= PM_READONLY;
    }
    if (zleactive) {
	extern mod_import_variable char *curkeymapname;	/* XXX */
	extern mod_import_variable char *varedarg;	/* XXX */
	/* bindkey -v forces VIMODE so this test is as good as any */
	if (curkeymapname && isset(VIMODE) &&
	    strcmp(curkeymapname, "main") == 0)
	    strcpy(sh_edmode, "\033");
	else
	    strcpy(sh_edmode, "");
	if (!sh_edchar)
	    sh_edchar = dupstring(getsparam("KEYS"));
	if (varedarg) {
	    char *ie = itype_end((sh_name = dupstring(varedarg)), INAMESPC, 0);
	    if (ie && *ie) {
		*ie++ = '\0';
		/* Assume bin_vared has validated subscript */
		sh_subscript = dupstring(ie);
		ie = sh_subscript + strlen(sh_subscript);
		*--ie = '\0';
	    } else
		sh_subscript = NULL;
	    if ((pm = createparam(".sh.value", PM_LOCAL|PM_NAMEREF|PM_UNSET))) {
		pm->level = locallevel;
		setloopvar(".sh.value", "BUFFER");	/* Hack */
	    }
	} else
	    sh_name = sh_subscript = NULL;
    } else {
	sh_edchar = sh_name = sh_subscript = NULL;
	strcpy(sh_edmode, "");
	/* TODO:
	 * - disciplines
	 * - special handling of .sh.value in math
	 */
    }
    --locallevel;
    unqueue_signals();

    return 1;
}

static struct funcwrap wrapper[] = {
    WRAPDEF(ksh93_wrapper),
};

/**/
int
setup_(UNUSED(Module m))
{
    return 0;
}

/**/
int
features_(Module m, char ***features)
{
    *features = featuresarray(m, &module_features);
    return 0;
}

/**/
int
enables_(Module m, int **enables)
{
    return handlefeatures(m, &module_features, enables);
}

/**/
int
boot_(Module m)
{
    return addwrapper(m, wrapper);
}

/**/
int
cleanup_(Module m)
{
    struct paramdef *p;

    deletewrapper(m, wrapper);

    /* Clean up namerefs, otherwise deleteparamdef() is confused */
    for (p = partab; p < partab + sizeof(partab)/sizeof(*partab); ++p) {
	if (p->flags & PM_NAMEREF) {
	    HashNode hn = gethashnode2(paramtab, p->name);
	    if (hn)
		((Param)hn)->node.flags &= ~PM_NAMEREF;
	}
    }
    return setfeatureenables(m, &module_features, NULL);
}

/**/
int
finish_(UNUSED(Module m))
{
    return 0;
}
