/*
** var.c
**
** Provide "public" functions for adding and removing variables from the
** symbol table.
*/

#include <ctype.h>
#include <string.h>

#include "rc.h"

#include "readline.h"

static void colonassign(char *, List *, bool);
static void listassign(char *, List *, bool);
static char *hasalias(char *);


/* to_lower and to_upper from library string2.c by MAD */

extern char *
to_lower (char *s)
{
	int loop = 0, len = strlen (s);
	
	for (; loop < len; loop ++) {
		*(s + loop) = tolower (*(s + loop));
	}
	return s;
}

extern char *
to_upper (char *s)
{
	int loop = 0, len = strlen (s);
	
	for (; loop < len; loop ++) {
		*(s + loop) = toupper (*(s + loop));
	}
	return s;
}


/* assign a variable in List form to a name, stacking if appropriate */

extern void varassign(char *name, List *def, bool stack) {
	Variable *new;
	List *newdef = listcpy(def, ealloc); /* important to do the listcpy first; get_var_place() frees old values */
	new = get_var_place(name, stack);
	new->def = newdef;
	new->extdef = NULL;
#ifdef READLINE /* need to reset readline() every time TERM or TERMCAP or TERMINFO changes */
	if (tcgetpgrp (0) == getpid () && (streq(name, "TERM") || streq(name, "TERMCAP") || streq (name, "TERMINFO"))) {
		new_term ();
	}
#endif
}

/* assign a variable in string form. Check to see if it is aliased (e.g., PATH and path) */

extern bool varassign_string(char *extdef) {
	char *name = get_name(extdef);
	Variable *new;
	char *c;
	if (name == NULL)
		return FALSE; /* add it to bozo env */
	if ((c = hasalias (name))) {
		if (name [1] >= 'a' && lookup_var (c)) return TRUE; /* don't alias variables that are already set in upper case */
	}
	new = get_var_place(name, FALSE);
	new->def = NULL;
	new->extdef = ealloc(strlen(extdef) + 1);
	strcpy(new->extdef, extdef);
	if (c) alias (name, varlookup(name), FALSE);
	return TRUE;
}

/*
   Return a List based on a name lookup. If the list is in external (string) form,
   convert it to internal (List) form. Treat $n (n is an integer) specially as $*(n).
   Also check to see if $status is being dereferenced. (we lazily evaluate the List
   associated with $status)
*/

extern List *varlookup(char *name) {
	Variable *look;
	List *ret, *l;
	int sub;
	if (streq(name, "status"))
		return sgetstatus();
	if (streq(name, "apids"))
		return sgetapids();
	if (*name != '\0' && (sub = a2u(name)) != -1) { /* handle $1, $2, etc. */
		for (l = varlookup("*"); l != NULL && sub != 0; --sub)
			l = l->n;
		if (l == NULL)
			return NULL;
		ret = nnew(List);
		ret->w = l->w;
		ret->m = NULL;
		ret->n = NULL;
		return ret;
	}
	look = lookup_var(name);
	if (look == NULL)
		return NULL; /* not found */
	if (look->def != NULL)
		return look->def;
	if (look->extdef == NULL)
		return NULL; /* variable was set to null, e.g., a=() echo foo */
	ret = parse_var(name, look->extdef);
	if (ret == NULL) {
		look->extdef = NULL;
		return NULL;
	}
	return look->def = ret;
}

/* lookup a variable in external (string) form, converting if necessary. Used by makeenv() */

extern char *varlookup_string(char *name) {
	Variable *look;
	look = lookup_var(name);
	if (look == NULL)
		return NULL;
	if (look->extdef != NULL)
		return look->extdef;
	if (look->def == NULL)
		return NULL;
	return look->extdef = mprint("%F=%-L", name, look->def, "\001");
}

/* remove a variable from the symtab. "stack" determines whether a level of scoping is popped or not */

extern void varrm(char *name, bool stack) {
 	char *c = hasalias (name);
	if (streq(name, "*") && !stack) { /* when assigning () to $*, we want to preserve $0 */
		varassign("*", varlookup("0"), FALSE);
		return;
	}
	delete_var(name, stack);
	if (c) delete_var (c, stack);
}

/* assign a value (List) to a variable, using array "a" as input. Used to assign $* */

extern void starassign(char *dollarzero, char **a, bool stack) {
	List *s, *var;
	var = nnew(List);
	var->w = dollarzero;
	if (*a == NULL) {
		var->n = NULL;
		varassign("*", var, stack);
		varassign ("optind", word ("1", NULL), FALSE);
		return;
	}
	var->n = s = nnew(List);
	while (1) {
		s->w = *a++;
		if (*a == NULL) {
			s->n = NULL;
			break;
		} else
			s = s->n = nnew(List);
	}
	varassign("*", var, stack);
	varassign ("optind", word ("1", NULL), FALSE);
}

/* (ugly name, huh?) assign a colon-separated value to a variable (e.g., PATH) from a List (e.g., path) */

static void colonassign(char *name, List *def, bool stack) {
	List dud;
	if (def == NULL) {
		varassign(name, NULL, stack);
		return;
	}
	dud.w = nprint("%-L", def, ":");
	dud.n = NULL;
	varassign(name, &dud, stack);
}

/* assign a List variable (e.g., path) from a colon-separated string (e.g., PATH) */

static void listassign(char *name, List *def, bool stack) {
	List *val, *r;
	char *v, *w;
	if (def == NULL) {
		varassign(name, NULL, stack);
		return;
	}
	v = def->w;
	r = val = nnew(List);
	while ((w = strchr(v, ':')) != NULL) {
		*w = '\0';
		r->w = ncpy(v);
		*w = ':';
		v = w + 1;
		r->n = nnew(List);
		r = r->n;
	}
	r->w = ncpy(v);
	r->n = NULL;
	varassign(name, val, stack);
}

/* check to see if a particular variable is aliased; return NULL on failure, or the alias */
/* alias HOME to home and variables XPATH to xpath where X and x are strings in the corresponding case */

static char *hasalias (char *name) {
	char *c = NULL;
	if (streq (name, "HOME")) {
		return "home";
	} else if (streq (name, "home")) {
		return "HOME";
	} else {
		int i = strlen (name) - 4;
		if (i >= 0) {
			if (streq (name + i, "PATH")) {
				c = to_lower (ecpy (name));
			} else if (streq (name + i, "path")) {
				c = to_upper (ecpy (name));
			}
		}
	}
	return c;
}

/* alias a variable to its lowercase equivalent. function pointers are used to specify the conversion function */

extern void alias(char *name, List *s, bool stack) {
	char *c = hasalias (name);
	if (c) {
		if (streq (name, "HOME") || streq (name, "home")) {
			varassign (c, s, stack);
		} else if (name [1] >= 'a') {
			colonassign (c, s, stack);
		} else {
			listassign (c, s, stack);
		}
	}
}

extern void prettyprint_var(int fd, char *name, List *s) {
	int i;
	static const char * const keywords[] = {
		"if", "in", "fn", "for", "else", "switch", "while", "case"
	};
	if (s == NULL) {
		fprint(fd, "%S=()\n", name);
		return;
	}
	if (streq(name, "*")) {
		s = s->n;
		if (s == NULL)
			return; /* Don't print $0, and if $* is not set, skip it */
	}
	for (i = 0; i < arraysize(keywords); i++)
		if (streq(keywords[i], name)) {
			fprint(fd, "%#S=", name);
			goto value;
		}
	fprint(fd, "%S=", name);
value:
	fprint(fd, s->n == NULL ? "%L\n" : "(%L)\n", s, " ");
}
