/***********************************************
 *  shell.c                                 
 *                                            
 *  Copyright 2001 Eric Kratzer.              
 *  Includes Xaudio software Copyright (C)
 *           1996-2000 Xaudio Corporation
 *           All Rights Reserved.               
 *
 *  This file is part of imp3sh.
 *                                            
 *  imp3sh is free software; you can distribute
 *  it and/or modify it under the terms of the
 *  GNU General Public License as published by
 *  the Free Software Foundation; either version
 *  2 of the License, or (at your option) any
 *  later version.
 *
 *  imp3sh is distributed in the hope that
 *  it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of 
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE. See the GNU General Public License
 *  for more details.
 *
 *  You should have recieved a copy of the GNU
 *  General Public License along with imp3sh; if
 *  not, write to the Free Software Foundation,
 *  Inc., 59 Temple Place, Suite 330, Boston MA
 *  02111-1307 USA
 ************************************************/

/************************************
 *          Include Files           *
 ************************************/

#include "imp3sh.h"

/************************************
 *       External References        *
 ************************************/

extern CommandDef com[];

extern DCharBuf *args, *chist_list, *aliases;

extern char *cmnd, *c_path, *last_expl, **environ;

extern int comm_len, edit_pos;

extern flag_t ignore_char_input, spackle, prompt_now, parenthesis;
extern flag_t ignore_hist_input;

/************************************
 *        Local variables           *
 ************************************/

static char dir_exf[PATH_MAX], tmp_exf[PATH_MAX], tmp_exc[PATH_MAX];
static char tmpstr_sh[COMM_SIZE];

/************************************
 *   Command History functions      *
 ************************************/

void /* user */ show_chist()
{
    char *name;
    int i, len;

    if (!chist_list->len)
	add_outbuffer(DEBUG_MSG " Command history is empty");
    else {
	name = g_dbuf(args, 1);
	len = get_strlen(name);

	for (i = 0; i < chist_list->len; i++) {
	    if (len && grep_against_args(1, g_dbuf(chist_list, i)) == FALSE)
		continue;
	    add_outbuffer("%d %s", i + 1, g_dbuf(chist_list, i));
	}
    }

    flush_outbuffer();
}

void read_chist()
{
    FILE *fd;

    sprintf(tmpstr_sh, "%s/.imp3sh_hist", getenv("HOME"));
    fd = myfopen(tmpstr_sh, "r");

    if (fd) {
	fgets(tmpstr_sh, COMM_SIZE, fd);
	while (!feof(fd)) {
	    remove_newline(tmpstr_sh);
	    if (strcmp(tmpstr_sh, "") != 0)
		a_dbuf(chist_list, tmpstr_sh);
	    fgets(tmpstr_sh, COMM_SIZE, fd);
	}
	fclose(fd);
    }
}

void write_chist()
{
    FILE *fd;
    int i;

    if (!chist_list->len)
	return;

    sprintf(tmpstr_sh, "%s/.imp3sh_hist", getenv("HOME"));
    fd = myfopen(tmpstr_sh, "w+");

    if (fd) {
	for (i = 0; i < chist_list->len; i++)
	    fprintf(fd, "%s\n", g_dbuf(chist_list, i));
	fclose(fd);
    }
}

void add_chist(char *command)
{
    if (!ignore_hist_input && !is_all_space(command))
        a_dbuf(chist_list, command);
}

/************************************
 *         Aliasing functions       *
 ************************************/

int check_alias(char *str)
{
    char *tmp;
    int i, ndx, len, len2;

    len = get_strlen(str);
    ndx = -1;

    if (len) {
	for (i = 0; i < aliases->len; i++) {
	    tmp = g_dbuf(aliases, i);
	    len2 = get_strlen(tmp);

	    if (tmp &&
		len2 > len &&
		strncmp(str, tmp, len) == 0 &&
		tmp[len] == ALIAS_SEPERATOR) {
		ndx = i;
		break;
	    }
	}
    }

    return ndx;
}

void get_aliasname(char *srch)
{
    char *loc;

    loc = strchr(srch, ALIAS_SEPERATOR);

    /* modifies srch[] */
    if (loc)
	*loc = ZCHAR;
}

char *get_alias(int ndx)
{
    char *tm;

    if (ndx >= 0 && ndx < aliases->len) {
	tm = g_dbuf(aliases, ndx);

	if (tm) {
	    tm = strchr(tm, ALIAS_SEPERATOR);
	    return (tm + 1);
	}
    }

    return NULL;
}

void /* user */ remove_alias()
{
    char *name;
    int ndx;

    name = g_dbuf(args, 1);

    if (!name)
	print_cmnd_help(REMOVE_ALIAS);
    else {
	ndx = check_alias(name);

	if (ndx == -1)
	    show_user(DEBUG_MSG " Alias for \"%s\" not found", name);
	else
	    r_dbuf(aliases, ndx);
    }
}

void show_aliases()
{
    int i;

    if (!aliases->len)
	add_outbuffer(DEBUG_MSG " No aliases defined");
    else
	for (i = 0; i < aliases->len; i++)
	    add_outbuffer("%s", g_dbuf(aliases, i));

    flush_outbuffer();
}

void /* user */ add_alias()
{
    char *name, *repl, *cm;
    int i, ndx, cdx;

    name = g_dbuf(args, 1);
    repl = g_dbuf(args, 2);

    if (name && repl) {
	ndx = check_alias(name);
	cdx = find_comm_match(name);

	if (cdx != NO_BUILTIN_COMM) {
	    show_user(ERROR_MSG
		      " Built-in command \"%s\" cannot be aliased", name);
	    return;
	}

	if (strcmp(repl, com[ADD_ALIAS].name) == 0 ||
	    strcmp(repl, com[ADD_EVENT].name) == 0) {
	    show_user(ERROR_MSG
		      " Special command \"%s\" cannot be aliased", repl);
	    return;
	}

	if (ndx == -1) {
	    i = 2;
	    cm = g_dbuf(args, i++);

	    sprintf(tmpstr_sh, "%s%c", name, ALIAS_SEPERATOR);

	    while (cm) {
		strcat(tmpstr_sh, cm);
		cm = g_dbuf(args, i++);
		if (cm)
		    strcat(tmpstr_sh, " ");
	    }

	    a_dbuf(aliases, tmpstr_sh);
	} else
	    show_user(DEBUG_MSG " Alias already exists for \"%s\"", name);
    } else if (name || repl)
	print_cmnd_help(ADD_ALIAS);
    else
	show_aliases();		/* user just wants to see */
}

/************************************
 * ENVIRONMENT control functions    *
 ************************************/

void /* user */ set_environ()
{
    char *name, *val;

    name = g_dbuf(args, 1);
    val = g_dbuf(args, 2);

    if (!name)
	print_cmnd_help(SET_ENVIRON);
    else {
	if (val)
	    setenv(name, val, TRUE);
	else
	    setenv(name, "\0", TRUE);
    }
}

void /* user */ show_environ()
{
    char *name, **tmp;
    int len;

    name = g_dbuf(args, 1);
    tmp = environ;
    len = get_strlen(name);

    while (*tmp) {
	if (len && grep_against_args(1, *tmp) == FALSE) {
	    tmp++;
	    continue;
	}

	add_outbuffer("%s", *tmp);
	tmp++;
    }

    flush_outbuffer();
}

void /* user */ change_dir()
{
    char *dir;
    int ret;

    dir = g_dbuf(args, 1);

    if (dir) {
	if (strcmp(dir, "-") == 0)
	    ret = chdir(getenv("OLDPWD"));
	else
	    ret = chdir(dir);
    } else
	ret = chdir(getenv("HOME"));

    if (ret)
	show_user(ERROR_MSG " cd : %s", strerror(errno));
    else {
	mysetenv("OLDPWD", c_path);
	getcwd(c_path, PATH_MAX);
	mysetenv("PWD", c_path);
    }
}

/**** Explosion (completion) functions ****/
/* these functions are REALLY ugly!! */

DIR *my_open_dir(char *dir)
{
    DIR *tmp;

    if (!get_strlen(dir))
	tmp = opendir(".");
    else {
	tmp = opendir(dir);
	chdir(dir);
    }

    return tmp;
}

void beep_expl(char *s)
{
    mybeep();
    last_expl = s;
}

void spack_expl(char *s, char *str)
{
    if (str)
	add_outbuffer("%s (%s)", s, str);
    else
	add_outbuffer("%s", s);
}

int find_good_length(char *one, char *two)
{
    int i, len1, len2;

    len1 = get_strlen(one);
    len2 = get_strlen(two);

    if (len2 < len1)
	len1 = len2;

    for (i = 0; i < len1; i++)
	if (one[i] != two[i])
	    return i;

    return len1;
}

int check_file_type(int ftype, mode_t mode)
{
    switch (ftype) {
    case ANY_FILE:
	return TRUE;
	break;
    case EXEC_FILE_ONLY:
	if (((S_IXUSR | S_IXGRP | S_IXOTH) & mode) && !S_ISDIR(mode))
	    return TRUE;
	else
	    return FALSE;
	break;
    case EXEC_AND_DIRS:
	if (((S_IXUSR | S_IXGRP | S_IXOTH) & mode) || S_ISDIR(mode))
	    return TRUE;
	else
	    return FALSE;
	break;
    }

    return FALSE;
}

/* searches COLON seperated path list <dir_s> for best
   match to <file>.                                      */
void find_file_match(int ftype, char *dir_s, char *file,
		     int flen, int *gdl, mode_t * type, char *match)
{
    struct dirent *dirent;
    struct stat sstat;
    char *dir, *tmem;
    DIR *dirstr;
    int gdl2;

    *gdl = 0;
    *match = ZCHAR;
    gdl2 = 0;
    tmem = NULL;
    dir = tokenize(dir_s, &tmem, COLON);

    do {
	dirstr = my_open_dir(dir);
	if (dirstr) {
	    dirent = readdir(dirstr);
	    while (dirent) {
		if (strncmp(dirent->d_name, file, flen) == 0) {
		    stat(dirent->d_name, &sstat);
		    if (check_file_type(ftype, sstat.st_mode)) {
			if (spackle)
			    spack_expl(dirent->d_name, NULL);
			else if (*gdl) {
			    gdl2 = find_good_length(match, dirent->d_name);
			    if (gdl2 < *gdl)
				*gdl = gdl2;
			    else {
				safe_strcpy(match, dirent->d_name,
					    INFO_SIZE);
				*type = sstat.st_mode;
			    }
			} else {
			    safe_strcpy(match, dirent->d_name, INFO_SIZE);
			    *type = sstat.st_mode;
			    *gdl = get_strlen(match);
			}
		    }
		}
		dirent = readdir(dirstr);
	    }
	    closedir(dirstr);
	}

	dir = tokenize(NULL, &tmem, COLON);
    }
    while (dir);

    chdir(c_path);
}

void explode_file(char *srch, int file_type)
{
    char *file, match[INFO_SIZE];
    int gdl, len, flen, dlen;
    mode_t type;

    /* setup vars */
    strcpy(dir_exf, srch);
    file = file_from_path(srch);
    flen = get_strlen(file);
    dlen = get_strlen(dir_exf);
    type = 0;
    gdl = 0;

    /* seperate file from path */
    if (flen) {
	dir_exf[dlen - flen] = ZCHAR;
	dlen -= flen;
    }

    /* check for tilde */
    if (dlen && *dir_exf == TILDE) {
	safe_strcpy(tmp_exf, getenv("HOME"), PATH_MAX);
	if (dlen > 1)
	    strcat(tmp_exf, &dir_exf[1]);
	safe_strcpy(dir_exf, tmp_exf, PATH_MAX);
    }

    if (flen || spackle) {
	memset(match, ZCHAR, INFO_SIZE);
	find_file_match(file_type, dir_exf, file, flen, &gdl, &type, match);
	if (gdl > flen) {
	    len = get_strlen(match);

	    /* append the file/dir to cmnd[] */
	    if (gdl < len)
		match[gdl] = ZCHAR;
	    strcat(cmnd, &match[flen]);

	    if (gdl == len) {
		if (S_ISDIR(type))
		    strcat(cmnd, "/");
		else if (parenthesis) {
		    parenthesis = FALSE;
		    strcat(cmnd, "\" ");
		    gdl++;
		} else
		    strcat(cmnd, " ");
		gdl++;
	    } else
		beep_expl(srch);

	    /* output what we found */
	    comm_len += gdl - flen;
	    edit_pos += gdl - flen;
	    update_prompt(TRUE);
	} else
	    beep_expl(srch);
    } else
	beep_expl(srch);
}

void explode_comm(char *srch)
{
    char *cm, match[INFO_SIZE], match2[INFO_SIZE];
    int i, len, gdl, gdl2, cgdl, clen;
    mode_t type;

    clen = get_strlen(srch);
    gdl = gdl2 = cgdl = 0;
    type = 0;

    if (clen || spackle) {
	memset(match, ZCHAR, INFO_SIZE);
	/* scan built-ins and aliases */
	for (i = 0; i < NUM_COMM + aliases->len; i++) {
	    if (i < NUM_COMM)
		cm = com[i].name;
	    else {
		safe_strcpy(tmp_exc, g_dbuf(aliases, i - NUM_COMM),
			    PATH_MAX);
		cm = strchr(tmp_exc, ALIAS_SEPERATOR);
		if (cm)
		    *cm = ZCHAR;
		cm = tmp_exc;
	    }

	    if (strncmp(cm, srch, clen) == 0) {
		if (spackle) {
		    if (i < NUM_COMM)
			spack_expl(cm, "built-in");
		    else
			spack_expl(cm, "alias");
		} else if (gdl) {
		    gdl2 = find_good_length(cm, match);
		    if (gdl2 < gdl)
			gdl = gdl2;
		    else
			safe_strcpy(match, cm, INFO_SIZE);
		} else {
		    safe_strcpy(match, cm, INFO_SIZE);
		    gdl = get_strlen(match);
		}
	    }
	}

	/* scan $PATH */
	if (getenv("PATH")) {
	    safe_strcpy(tmp_exc, getenv("PATH"), PATH_MAX);
	    memset(match2, ZCHAR, INFO_SIZE);
	    find_file_match(EXEC_FILE_ONLY, tmp_exc,
			    srch, clen, &cgdl, &type, match2);

	    /* use best len and match */
	    if (cgdl) {
		if (!gdl) {
		    safe_strcpy(match, match2, INFO_SIZE);
		    gdl = cgdl;
		} else if (cgdl < gdl)
		    gdl = cgdl;
	    }
	}

	if (gdl > clen) {
	    len = get_strlen(match);

	    /* append the file/dir to cmnd[] */
	    if (gdl < len)
		match[gdl] = ZCHAR;

	    strcat(cmnd, &match[clen]);
	    if (gdl != len)
		beep_expl(srch);
	    else {
		strcat(cmnd, " ");
		gdl++;
	    }

	    /* output what we found */
	    comm_len += gdl - clen;
	    edit_pos += gdl - clen;
	    update_prompt(TRUE);
	} else
	    beep_expl(srch);
    } else
	beep_expl(srch);
}

void explode_fileORcomm()
{
    char *srch;
    int i, path;

    srch = cmnd;
    path = FALSE;

    /* get search token */
    for (i = edit_pos; i >= 0; i--) {
	if (cmnd[i] == '/')
	    path = TRUE;
	if ((cmnd[i] == SPACE && !parenthesis) ||
	    (parenthesis && cmnd[i] == PARENTHESIS)) {
	    srch = &cmnd[++i];
	    break;
	}
    }

    /* see if user wants spackled */
    if (last_expl == srch) {
	spackle = TRUE;
	outstr("\n");
    }

    if (srch == cmnd) {
	if (path)
	    explode_file(srch, EXEC_AND_DIRS);
	else
	    explode_comm(srch);
    } else
	explode_file(srch, ANY_FILE);

    if (spackle) {
	spackle = FALSE;
	last_expl = NULL;
	prompt_now = FALSE;
	if (outb_empty())
	    update_prompt(FALSE);
	else {
	    sort_outbuffer();
	    flush_outbuffer();
	}
    }
}
