/* init.c	(c) copyright 1986 (Dan Heller) */

/* init.c -- functions and whatnot that initialize everything */
#include "mush.h"
#include <pwd.h>

#if defined(BSD) || defined(HPUX) || defined(IRIX4)
#include <netdb.h>
#endif /* BSD || HPUX || IRIX4 */

#if defined(SYSV) && !defined(HPUX) && !defined(IRIX4)
#include <sys/utsname.h>
#endif /* SYSV && !HPUX && !IRIX4 */
  
char *facebuf = NULL;	/* For possible compressed X-Face: image. */

void
init()
{
    FILE		*fp;
    char 		*home, *realname, *argv[4];
    extern char		*getlogin();
    char		buf[MAXPATHLEN];
#if defined(SYSV) && !defined(HPUX) && !defined(IRIX4)
    extern struct passwd *getpwuid();  /* sys-v forgot this in pwd.h! */
    struct utsname ourhost;
#else
    char ourhost[128];
#endif /* SYSV && !HPUX && !IRIX4 */
    register char 	*p;
    struct passwd 	*entry;
    int			cnt;
#if defined(BSD) || defined(HPUX) || defined(IRIX4)
    struct hostent 	*hp;
#endif /* BSD || HPUX || IRIX4 */

    home = getenv("HOME");
    if (realname = getenv("NAME")) {
	(void) strcpy(buf, realname);
    }
    argv[1] = "=";
    argv[3] = NULL;

    if (!(entry = getpwuid(getuid())))
	if (p = getlogin())
	    strdup(login, p);
	else {
	    strdup(login, "unknown");
	    print("I don't know you, but that's ok.\n");
	}
    else {
	strdup(login, entry->pw_name);
	if (!home || !*home)
	    home = entry->pw_dir;
	if (!realname && (realname = entry->pw_gecos)) {
	    if (p = index(realname, ','))
		*p = 0;
	    for (p = buf; *realname; realname++)
		if (*realname == '&')
		    *p++ = upper(*login), p += Strcpy(p, login+1);
		else
		    *p++ = *realname;
	    *p = 0;
	}
	endpwent();
    }
    if (!home || !*home || Access(home, W_OK)) {
	if (home && *home)
	    error(home);
	else
	    print("No home!? ");
	print_more("Using \"%s\" as home.\n", home = ALTERNATE_HOME);
    } else {
	argv[0] = "home";
	argv[2] = home;
	(void) add_option(&set_options, argv);
    }

    if (realname && *buf) {
	/* realname has already been copied to buf */
	argv[0] = "realname";
	argv[2] = buf;
	(void) add_option(&set_options, argv);
    }

#ifdef HOMEMAIL
    strdup(spoolfile, sprintf(buf, "%s/%s", home, MAILFILE));
#else /* HOMEMAIL */
#ifdef ENV_MAIL
    if ((p = getenv("MAIL")) && *p)
	strdup(spoolfile, p);
    else
#endif /* ENV_MAIL */
	strdup(spoolfile, sprintf(buf, "%s/%s", MAILDIR, login));
#endif /* HOMEMAIL */
    mailfile = "";

    crt = 24;
    screen = 18;
    wrapcolumn = 0; /* Default is no wrap */
    escape = DEF_ESCAPE;
    prompt = DEF_PROMPT;

#if defined(BSD) || defined(HPUX) || defined(IRIX4)
    (void) gethostname(ourhost, sizeof ourhost);
    if (!(hp = gethostbyname(ourhost))) {
	if (ourname = (char **)calloc((unsigned)2, sizeof (char *)))
	    strdup(ourname[0], ourhost);
    } else {
	int n = -1;
	cnt = 2; /* 1 for ourhost and 1 for NULL terminator */

        for (p = hp->h_name; p && *p; p = hp->h_aliases[++n])
            if (strcmp(ourhost, p)) /* if host name is different */
                cnt++;

        if (ourname = (char **)malloc((unsigned)cnt * sizeof (char *))) {
            n = -1;
            cnt = 0;
            ourname[cnt++] = savestr(ourhost);
            for (p = hp->h_name; p && *p; p = hp->h_aliases[++n])
                if (strcmp(ourhost, p)) /* if host name is different */
                    ourname[cnt++] = savestr(p);
            ourname[cnt++] = NULL;
        }
    }
#else
#ifdef SYSV
    if (ourname = (char **)calloc((unsigned)2, sizeof (char *))) {
	if ((uname (&ourhost) >= 0) && (*ourhost.nodename))
	    ourname[0] = savestr(ourhost.nodename);
	else {
	    /* Try to use uuname -l to get host's name if uname didn't work */
	    char buff[50];
	    char *p;
	    FILE *F;

	    if (F = popen("exec uuname -l", "r")) {
		if ((fgets(buff, sizeof buff, F) == buff) &&
			(p = strchr(buff, '\n'))) {
		    *p = '\0';		/* eliminate newline */
		    ourname[0] = savestr (buff);
		}
	    (void)pclose(F);
	    }
	}
    }
#endif /* SYSV */
#endif /* BSD || HPUX || IRIX4 */
    if (ourname && ourname[0]) {
	for (p = buf, cnt = 0; ourname[cnt]; cnt++) {
	    if (cnt)
		*p++ = ' ';
	    p += Strcpy(p, ourname[cnt]);
	}
	argv[0] = "hostname";
	argv[2] = buf;
	(void) add_option(&set_options, argv);
    }

    (void) sprintf(buf, "%s/.face", home);
    if (fp = fopen(buf, "r")) {
	static char fbuf[1024];
	facebuf = fbuf;
	facebuf[0] = '\0';
	while (fgets(buf, BUFSIZ, fp) != NULL) {
#ifdef notdef
	    if (facebuf[0] != '\0')
		(void) strcat(facebuf, "\t");
#endif
	    (void) strcat(facebuf, buf);
	}
	(void) fclose(fp);
    }
    else
	facebuf = NULL;

    init_bindings();
}

/*
 * Source a file, or just the default file.  Since sourcing files
 * means reading possible aliases, don't expand the ! as history
 * by setting the IGN_BANG flag.  Since a command in the sourced file
 * may call source on another file, this routine may be called from
 * within itself.  Continue to ignore ! chars by setting save_bang (local).
 *
 * Try opening the file passed to us.  If not given, check for the correct
 * .rc file which is found in the user's home dir.
 *
 * return -1 for filesystem errors, -2 for attempting to read a directory.
 */
source(argc, argv)
char **argv;
{
    register char *p;
    FILE 	 *fp;
    char 	  file[MAXPATHLEN];
    u_long	  save_bang = ison(glob_flags, IGN_BANG);
    int		  line_no = 0;

    if (argc && *++argv && !strcmp(*argv, "-?"))
	return help(0, "source", cmd_help);
    if (argc && *argv)
	(void) strcpy(file, *argv);
    else if ((p = getenv("MUSHRC")) && *p || (p = getenv("MAILRC")) && *p)
	(void) strcpy(file, p);
    else {
	char *home = do_set(set_options, "home");
	if (!home || !*home)
	    home = ALTERNATE_HOME;
	if (Access(sprintf(file, "%s/%s", home, MAILRC), R_OK)
		&& Access(sprintf(file, "%s/%s", home, ALTERNATE_RC), R_OK))
	    if (argc || argv)
		(void) strcpy(file, DEFAULT_RC);
	    else
		return -1;
    }

    argc = 0; /* don't ignore ENOENT */
    p = getpath(file, &argc);
    /* Try the ALT_DEF_RC if DEFAULT_RC fails */
    if (argc && !strcmp(file, DEFAULT_RC)) {
	argc = 0; /* don't ignore ENOENT */
	(void) strcpy(file, ALT_DEF_RC);
	p = getpath(file, &argc);
    }
    if (argc) {
	/* Don't print error messages for missing default files */
	if (strcmp(file, ALT_DEF_RC))
	    if (argc == -1) {
		print("%s: %s\n", file, p);
		return -1;
	    } else {
		print("%s is a directory.\n", file);
		return -2;
	    }
	return -1;
    }
    if (!(fp = fopen(p, "r"))) {
	if (errno != ENOENT)
	    error("Can't open %s", p);
	return -1;
    }
    turnon(glob_flags, IGN_BANG); /* ignore ! when reading record files */
    (void) strcpy(file, p);
    (void) src_parse(file, fp, 0, 0, &line_no);
    /* if we entered the routine ignoring !, leave it that way. */
    if (!save_bang)
	turnoff(glob_flags, IGN_BANG);
    /* Sourcing might change things, so abort pipes/macros */
    return 0 - (in_pipe() || in_macro());
}

/*
 * Do the actual file parsing for source().  The first argument should
 * be the name of the file referenced by the second argument.  The third
 * argument is used for handling nested if_else_endif expressions.  The
 * fourth argument is used to keep track of the recursion depth, and the
 * last argument keeps track of the line number in the current file.
 *
 * This function calls itself recursively.  It also calls do_command(),
 * which may in turn call source() recursively.
 *
 * If-then-else nesting algorithm:
 *  On any "if" (whether parsing or not), increment if_else
 *  On true "if" when parsing, evaluate by recursion
 *  On false "if" when parsing, set find_else equal to if_else
 *  On any "if" when not parsing, set find_endif equal to if_else
 *  On "else", invert parsing only when find_else equals if_else
 *  When "if" was false and there is nesting, recur for "else"
 *  Skip nested "if...endif" when find_else or find_endif true
 *  On "endif" or when recursion returns, decrement if_else
 *  On "endif", test both find_endif and find_else against if_else:
 *   when either matches, reset that one;
 *   when the lesser (less nested) matches, resume parsing
 *  On "endif", when if_else hits 0, continue (depth 0) or return
 */
src_parse(file, fp, if_else, depth, line_no)
char	*file;
FILE	*fp;
int 	 if_else, depth, *line_no;
{
    register char *p, *p2, **newargv;
    static int    exited;
    int 	  parsing = 1, cont_line = 0;
    int		  find_else = 0, find_endif = 0;
    char 	  line[BUFSIZ];
    int		  argc;

    exited = 0;

    while (p = fgets(&line[cont_line], BUFSIZ - cont_line - 1, fp)) {
	(*line_no)++;
	if (p2 = index(p, '\n'))
	    while (p2 > p && *p2 == '\n' || isspace(*p2))
		*p2-- = 0;  /* get rid of newline and trailing spaces */
	else {
	    print("%s: line %d:%s line too long, truncated at %d characters.\n",
		file, *line_no, cont_line? " continued" : "", BUFSIZ);
	    p2 = no_newln(p);
	}
	if (*p2 == '\\') {
	    *p2++ = ' ';
	    cont_line = p2 - line;
	    continue;
	} else
	    cont_line = 0;
	/* don't consider comments (#) in lines. check if # is within quotes */
	if (p = any(line, "\"'#\\")) {
	    register int balanced = 1;
	    do {
		if (*p == '\\' && p[1])
		    p = any(p+2, "\"'#\\");
		else if (*p != '#') {
		    /* first find matching quote */
		    register char *quote = index(p+1, *p);
		    if (!quote) {
			print("%s: line %d: unbalanced %c.\n",
				file, *line_no, *p);
			balanced = 0;
		    } else
			p = any(quote+1, "\"'#\\");
		}
	    } while (p && *p != '#' && balanced);
	    if (!balanced)
		continue;
	    if (p && *p == '#')
		*p = 0; /* found a Comment: null terminate line at comment */
	}
	if (!*line || !parsing && !(newargv = mk_argv(line, &argc, 0))
	|| parsing && !(newargv = make_command(line, TRPL_NULL, &argc))) {
	    if (!strncmp(line, "if", 2))
		find_else = ++if_else, parsing = FALSE;
	    continue;
	}
	if (!strcmp(newargv[0], "endif")) {
	    if (!if_else)
		print("%s: line %d: endif with no \"if\".\n", file, *line_no);
	    else {
		/* If looking for an else or endif, reset parsing */
		if (find_endif && find_endif == if_else) {
		    if (find_endif <= find_else || !find_else)
			parsing = 1, find_else = 0;
		    find_endif = 0;
		}
		/* Note: find_else never < find_endif */
		if (find_else && find_else == if_else)
		    parsing = !parsing, find_else = 0;
		/* Decrement if_else and check depth */
		if (--if_else == 0)
		    /* Resume parsing if at the top */
		    if (depth == 0)
			parsing = 1;
		    /* Return if not at the top */
		    else
			return 1;
	    }
	    goto bad;
	} else if (!strcmp(newargv[0], "else")) {
	    if (!if_else)
		print("%s: line %d: if-less \"else\".\n", file, *line_no);
	    /* If inside an else, ignore nested else;
	     *  otherwise, recur when if_else > 1 */
	    else if (!find_else && !find_endif && !parsing) {
		parsing = src_parse(file, fp, 1, depth + 1, line_no);
		--if_else;
	    } else if (find_else == if_else || if_else == 1) {
		find_else = 0;
		parsing = !parsing;
		if (!parsing)
		    find_endif = if_else;
	    }
	    goto bad;
	} else if (!strcmp(newargv[0], "if")) {
	    /* if statements are of the form:
	     *     if expr
	     *     if !expr  or  if ! expr
	     *     if expr == expr   or   if expr != expr
	     */
	    int equals = TRUE, pattern = FALSE;
	    register char *lhs = newargv[1], *rhs = NULL;

	    if_else++;
	    /* If parsing, set parsing to 0 until
	     *  evaluating the "if" proves otherwise.
	     * If not parsing, skip to the "endif".
	     */
	    if (parsing)
		parsing = 0;
	    else {
		if (!find_endif)
		    find_endif = if_else;
		goto bad;
	    }
	    if (!lhs || !*lhs) {
		print("%s: line %d: if what?\n", file, *line_no);
		goto bad;
	    }
	    /* "lhs" is the left hand side of the equation
	     * In this instance, we're doing case 2 above (check for negation).
	     */
	    if (*lhs == '!') {
		if (!*++lhs && !(lhs = newargv[2])) {
		    print("%s: line %d: syntax error: \"if ! <what?>\"\n",
			file, *line_no);
		    goto bad;
		}
		equals = FALSE;
	    }
	    if (*lhs == '-' && (lhs[1] == 'e' || lhs[1] == 'z') && !lhs[2]) {
		char *path;
		int n = 1; /* ignore ENOENT, I'll handle it here */
		struct stat statb;

		/* check for existence or zero-length folders/files */
		if (argc > 3 + (!equals)) {
		    print("%s: line %d: if %s \"filename\"\n",
			file, *line_no, lhs);
		    goto bad;
		}
		path = getpath(newargv[argc-1], &n);
		parsing = !equals ^ (n == -1 || n == 1 && lhs[1] == 'e' ||
		    !stat(path, &statb) && (lhs[1] == 'e' || !statb.st_size));
	    } else {
		if (equals && argc > 2) {
		    if (argc != 4) {
			print("%s: %d: argument count error: %d args.\n",
			    file, *line_no, argc);
			goto bad;
		    }
		    /* now check newargv[2] for == or != or =~ or !~ */
		    if (!strcmp(newargv[2], "!=") ||
			    (pattern = !strcmp(newargv[2], "!~")))
			equals = !equals;
		    else if (!strcmp(newargv[2], "=~"))
			pattern = TRUE;
		    else if (strcmp(newargv[2], "==")) {
			print("%s: %d: use `==' or `!=' only.\n",
				file, *line_no);
			goto bad;
		    }
		    rhs = newargv[3];
		}
		if (rhs) {
		    /* Some fun tricks with booleans here.
		     * Extra ! ops make sure all == are on 0 or 1;
		     * aside from that, we want (glob == equals)
		     * or (!strcmp == equals).  Make sense?  
		     */
		    if (pattern && !glob(lhs,rhs) == !equals)
			parsing = 1;
		    else if (!pattern && !strcmp(lhs, rhs) == !!equals)
			parsing = 1;
		} else if (isdigit(*lhs))
		    parsing = !!(atoi(lhs) ? equals : !equals);
		else if (!strcmp(lhs, "redirect") && (!isatty(0) != !equals)
			  /* (ison(glob_flags, REDIRECT) && equals ||
			   isoff(glob_flags, REDIRECT) && !equals) */
		    || !strcmp(lhs, "is_shell") && (!is_shell == !equals)
		    || !strcmp(lhs, "is_sending") &&
			  (ison(glob_flags, IS_SENDING) && equals ||
			   isoff(glob_flags, IS_SENDING) && !equals)
		    || !strcmp(lhs, "hdrs_only") &&
			  (hdrs_only && equals || !hdrs_only && !equals)
		    || !strcmp(lhs, "istool") &&
			  (istool && equals || !istool && !equals)
		    || !strcmp(lhs, "iscurses") &&
			  ((iscurses || ison(glob_flags, PRE_CURSES)) && equals
			  || (isoff(glob_flags, PRE_CURSES) &&
			      !iscurses && !equals)))
			parsing = 1;
	    }
	    if (parsing) {
		parsing = src_parse(file, fp, 1, depth + 1, line_no);
		--if_else;
	    }
	    else
		find_else = if_else; /* Look for a matching else */
bad:
	    free_vec(newargv);
	    continue;
	}
	if (parsing && argc > 0)
	    if (!strcmp(newargv[0], "exit")) {
		if_else = find_else = find_endif = 0;
		exited = 1;
		break;
	    } else {
		(void) do_command(argc, newargv, msg_list);
		exited = 0;
	    }
	else
	    free_vec(newargv);
    }
    if (if_else && !exited)
	print("%s: missing endif\n", file);
    if (depth == 0)
	(void) fclose(fp);
    else
	(void) fseek(fp, 0L, 2); /* Skip ahead to the end */
    return 0;
}
