/***********************************************
 *  command.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 XA_TimecodeInfo dinfo, tinfo;

extern CommandDef com[];

extern ListDef *active_list, *history;

extern DCharBuf *chist_list, *args;

extern TermCtrl t_ctrl[];

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

extern int edit_pos, comm_len, tty_width;

extern flag_t prompt_now, show_time_stamp, parenthesis, user_stream, spackle;
extern flag_t external_player, inside_queue, stream_open, piped_io_flag;
extern flag_t running_builtin;

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

static char prompt_str[INFO_SIZE], prompt_str2[INFO_SIZE];
static char comm_al[COMM_SIZE], tmpstr_co[COMM_SIZE], *child_argv[COMM_SIZE];
static int old_edit_pos = 0, prompt_len = 0;
static flag_t end_line_flag = FALSE;

/*** User-command handling ****/

void mybeep()
{
    if (!piped_io_flag)
	beep();
}

int find_comm_match(char *comm)
{
    int i = 0;

    if (comm)
	for (; i < NUM_COMM; i++)
	    if (0 == strcmp(comm, com[i].name)) {
		prompt_now = TRUE;
		return i;
	    }
    
    return NO_BUILTIN_COMM;
}

int test_background_mode(int *mode)
{
    char *str;
    int pos, ln;

    pos = args->len - 1;
    str = g_dbuf(args, pos);

    switch (get_cpos(AMPERSAND, str)) {
    case AMP_END:
	ln = get_strlen(str);
	str[ln - 1] = ZCHAR;
	*mode = J_BGRUN;
	break;
    case AMP_ALL:
	if (pos > 0) {
	    r_dbuf(args, pos);
	    *mode = J_BGRUN;
	}
	break;
    case AMP_FRONT:
    case AMP_MID:
	show_user(ERROR_MSG " Syntax errror: misplaced \"&\"\n");
	return TRUE;
    }

    return FALSE;
}

void setup_child_process(int bg)
{
    int mypid, prgrp;

    mypid = getpid();
    prgrp = mypid;
    setpgid(mypid, prgrp);

    if (bg == J_FGRUN && !piped_io_flag)
	tcsetpgrp(0, prgrp);

    signal(SIGALRM, SIG_DFL);
    signal(SIGPIPE, SIG_DFL);
    signal(SIGTSTP, SIG_DFL);
    signal(SIGTERM, SIG_DFL);
    signal(SIGINT, SIG_DFL);
    signal(SIGTTIN, SIG_DFL);
    signal(SIGTTOU, SIG_DFL);
    signal(SIGWINCH, SIG_DFL);
    signal(SIGCHLD, SIG_DFL);
}

void /* user (indirect) */ try_to_exec()
{
    int i, redir, bg, tmpstdout[2], tmpstderr[2];
    pid_t pid;

    redir = FALSE;
    bg = J_FGRUN;

    if (!args->len || test_background_mode(&bg)) {
	prompt_now = TRUE;
	return;
    }

    if (bg == J_FGRUN && !have_tty())
	bg = J_BGRUN;

    if (bg == J_BGRUN) {
	redir = TRUE;
	if (pipe(tmpstdout) || pipe(tmpstderr)) {
	    show_user(ERROR_MSG " Error creating pipes : %s",
		      strerror(errno));
	    return;
	}
    }

    pid = fork();
    if (pid == -1) {

	show_user(ERROR_MSG " Failed to fork \"%s\" : %s", g_dbuf(args, 0),
		  strerror(errno));
	prompt_now = TRUE;

    } else if (pid != 0) {

	add_proclist(pid, bg, redir, tmpstdout, tmpstderr, g_dbuf(args, 0));
	if (redir)
	    prompt_now = TRUE;

    } else {

	if (redir) {
	    dup2(tmpstdout[1], 1);
	    dup2(tmpstderr[1], 2);
	}

	for (i = 0; i < args->len; i++)
	    child_argv[i] = g_dbuf(args, i);
	child_argv[i] = NULL;

	setup_child_process(bg);
	usleep(50000);
	execvp(*child_argv, child_argv);
	exit(CHILD_EXEC_FAILED);
    }
}

int check_for_homedir(char *str)
{
    switch (get_cpos(TILDE, str)) {
    case AMP_ALL:
	a_dbuf(args, getenv("HOME"));
	return TRUE;
    case AMP_FRONT:
	safe_strcpy(tmpstr_co, getenv("HOME"), COMM_SIZE);
	strcat(tmpstr_co, &str[1]);
	a_dbuf(args, tmpstr_co);
	return TRUE;
    }

    return FALSE;
}

/* very limited right now  :( */
int check_for_envvars(char *str)
{
    char *tmp;

    if (get_cpos('$', str) == AMP_FRONT) {
	tmp = getenv(&str[1]);
	if (tmp) {
	    a_dbuf(args, tmp);
	    return TRUE;
	}
    }

    return FALSE;
}

int add_arg(char *str)
{
    if (args->len < (args->size - 1)) {
	/* this needs to be redone in a cleaner way! */
	if (!check_for_homedir(str))
	    if (!check_for_envvars(str))
		a_dbuf(args, str);

	return TRUE;
    }

    return FALSE;
}

void make_arg_list_kparen(char *command)
{
    char *c, *t;

    c_dbuf(args);
    c = command;
    t = tmpstr_co;
    *t = ZCHAR;

    while (TRUE) {
	if (*c == SPACE) {
	    if (t != tmpstr_co) {
		*t = ZCHAR;
		t = tmpstr_co;
		a_dbuf(args, tmpstr_co);
	    }
	} else if (*c == ZCHAR) {
	    if (t != tmpstr_co) {
		*t = ZCHAR;
		t = tmpstr_co;
		a_dbuf(args, tmpstr_co);
	    }

	    break;
	} else if (*c == PARENTHESIS) {
	    *t++ = *c++;

	    while (*c != ZCHAR && *c != PARENTHESIS)
		*t++ = *c++;

	    if (*c == PARENTHESIS)
		*t++ = *c;

	    *t = ZCHAR;
	    t = tmpstr_co;
	    a_dbuf(args, tmpstr_co);
	} else
	    *t++ = *c;

	c++;
    }
}

void make_arg_list(char *command)
{
    char *tmem, *cm;

    c_dbuf(args);
    tmem = NULL;
    cm = tokenize(command, &tmem, SPACE);

    while (cm) {
	if (!add_arg(cm))
	    break;
	cm = tokenize(NULL, &tmem, SPACE);
    }
}

void insert_chistory(int ndx, char *start, char *command)
{
    char *ncm;

    ncm = strchr(start, SPACE);

    if (ncm) {
	safe_strcpy(tmpstr_co, g_dbuf(chist_list, ndx), COMM_SIZE);
	strcat(tmpstr_co, ncm);
	safe_strcpy(command, tmpstr_co, COMM_SIZE);
    } else
	safe_strcpy(command, g_dbuf(chist_list, ndx), COMM_SIZE);

    if (command == cmnd) {
	comm_len = edit_pos = get_strlen(command);
	show_user("%s", command);
    }
}

int check_for_bang(char *command)
{
    char *tmp;
    int i, hdx, len;

    tmp = skip_space(command);

    if (tmp && *tmp == EXCLAMATION) {
	hdx = atoi(&tmp[1]);
	len = get_strlen(&tmp[1]);
	i = chist_list->len;

	if (len && i) {
	    if (*(tmp + 1) == EXCLAMATION)
		hdx = chist_list->len;
	    else if (!hdx)
		for (i--; i >= 0; i--)
		    if (strncmp(&tmp[1], g_dbuf(chist_list, i), len) == 0) {
			hdx = i + 1;
			break;
		    }
	}

	if (hdx > 0 && hdx <= chist_list->len)
	    insert_chistory(hdx - 1, tmp, command);
	else {
	    show_user(ERROR_MSG " No history match found!");
	    prompt_now = TRUE;
	    return TRUE;
	}
    }

    return FALSE;
}

/* Multiple seperator tokenizing: ' ', ';', and '\0'     */
/* Aliasing only allowed on first argument of each cmnd. */
/* Not very efficient, but solid                         */
void expand_aliases(char *command)
{
    char *t, *c, *al;
    int adx, go, alias;

    *comm_al = ZCHAR;
    c = skip_space(command);
    go = alias = TRUE;

    if (!c)
	return;

    while (go) {
	t = tmpstr_co;
	while (*c != SPACE && *c != ZCHAR && *c != ';' && *c != '&')
	    *t++ = *c++;

	*t = ZCHAR;

	if (alias) {
	    al = skip_space(tmpstr_co);
	    if (al) {
		alias = FALSE;
		adx = check_alias(al);
		if (adx != -1)
		    safe_strcpy(tmpstr_co, get_alias(adx), COMM_SIZE);
	    }
	}

	if (*c == ZCHAR)
	    go = FALSE;
	else if (*c == ';')
	    alias = TRUE;

	strcat(comm_al, tmpstr_co);
	tmpstr_co[0] = *c++;
	tmpstr_co[1] = ZCHAR;
	strcat(comm_al, tmpstr_co);
    }

    safe_strcpy(command, comm_al, COMM_SIZE);
}

int check_for_special_cmnds(char *command)
{
    char *cm;
    int a_len;

    cm = skip_space(command);
    a_len = get_strlen(com[ADD_ALIAS].name);

    if (!cm)
	return FALSE;

    if (strncmp(cm, com[ADD_ALIAS].name, a_len) == 0 &&
	command[a_len] == SPACE) return TRUE;

    a_len = get_strlen(com[ADD_EVENT].name);
    if (strncmp(cm, com[ADD_EVENT].name, a_len) == 0 &&
	command[a_len] == SPACE) return TRUE;

    return FALSE;
}

void run_command(int ndx)
{
    if (ndx < NO_BUILTIN_COMM)
	running_builtin = TRUE;
    (com[ndx].func) ();
    if (ndx < NO_BUILTIN_COMM)
	running_builtin = FALSE;
}

void handle_user_message(char *command)
{
    char *cmem, *cm;
    int ndx;

    if (check_for_bang(command))
	return;

    cmem = cm = NULL;
    ndx = 0;

    add_chist(command);

    if (check_for_special_cmnds(command)) {
	prompt_now = FALSE;
	make_arg_list_kparen(command);
	ndx = find_comm_match(g_dbuf(args, 0));
	run_command(ndx);
    } else {
	expand_aliases(command);

	cm = tokenize(command, &cmem, ';');
	while (cm) {
	    prompt_now = FALSE;
	    make_arg_list(cm);
	    ndx = find_comm_match(g_dbuf(args, 0));
	    run_command(ndx);
	    cm = tokenize(NULL, &cmem, ';');
	}
    }
}

/**** Command line manipulation functions *****/

void clear_comm()
{
    chist_list->pos = chist_list->len;
    last_expl = NULL;
    comm_len = old_edit_pos = edit_pos = 0;
    parenthesis = spackle = end_line_flag = FALSE;
    memset(cmnd, ZCHAR, COMM_SIZE);
}

void print_ctrl_str(int len, int n)
{
    int i;

    if (piped_io_flag)
	return;

    if (len > 0 && t_ctrl[n].string)
	for (i = 0; i < len; i++)
	    outstr(t_ctrl[n].string);
}

void backup_cursor(int len)
{
    int y, p;

    if (!tty_width)
	return;

    y = len / tty_width;
    p = len % tty_width;

    if (p)
	y++;
    else if (end_line_flag) {
	end_line_flag = FALSE;
	y++;
    }

    print_ctrl_str(y - 1, TC_UP);
}

void restore_edit_cursor(int len, int len2)
{
    int y, p, cy;

    if (!tty_width)
	return;

    y = len / tty_width;
    p = len % tty_width;
    cy = len2 / tty_width;

    if (len2 % tty_width)
	cy++;

    if (p)
	y++;
    else if (edit_pos) {
	end_line_flag = TRUE;
	y++;
    }

    print_ctrl_str(cy - y, TC_UP);
    print_ctrl_str(1, TC_CR);
    print_ctrl_str(p, TC_ND);
}

void prompt(int flush)
{
    int tmp;

    if (!have_tty() || piped_io_flag)
	return;

    if (user_stream)
	sprintf(prompt_str, "[%s@%s ", NAME, host);
    else if (in_queue() || inside_queue)
	sprintf(prompt_str, "[%d->queue ", history->dcb->pos);
    else if (active_list)
	sprintf(prompt_str, "[%d->%s ", active_list->dcb->pos,
		active_list->dcb->name);
    else
	sprintf(prompt_str, "[%s@%s ", NAME, host);

    if ((external_player || !stream_open) && show_time_stamp) {
	sprintf(prompt_str2, "%.2d:%.2d ", tinfo.m, tinfo.s);
	strcat(prompt_str, prompt_str2);
    } else
	switch (show_time_stamp) {
	case TIME_NORM:
	    sprintf(prompt_str2, "%.2d:%.2d ", tinfo.m, tinfo.s);
	    strcat(prompt_str, prompt_str2);
	    break;
	case TIME_LEFT:
	    {
		tmp = ((dinfo.m * 60) + dinfo.s) - (tinfo.m * 60 + tinfo.s);
		sprintf(prompt_str2, "-%.2d:%.2d ", tmp / 60, tmp % 60);
		strcat(prompt_str, prompt_str2);
		break;
	    }
	}

    sprintf(prompt_str2, "%s]> ", file_from_path(c_path));
    strcat(prompt_str, prompt_str2);

    prompt_len = get_strlen(prompt_str);
    outstr(prompt_str);
    if (flush)
	outflush();
}

void update_prompt(int backup)
{
    if (!have_tty() || piped_io_flag)
	return;

    /* if ok, move cursor up to last prompt
       line                                  */
    if (backup)
	backup_cursor(prompt_len + old_edit_pos);

    /* <cr> cursor, prompt, and clear        */
    print_ctrl_str(1, TC_CR);
    prompt(FALSE);
    outstr(cmnd);
    print_ctrl_str(1, TC_CLR_TOBOT);

    /* if editing, move cursor position back 
       to proper spot                        */
    if (comm_len != edit_pos)
	restore_edit_cursor(prompt_len + edit_pos, prompt_len + comm_len);

    old_edit_pos = edit_pos;
    outflush();
}

void backspace_char()
{
    if (!comm_len || !edit_pos)
	mybeep();
    else {
	if (cmnd[edit_pos - 1] == PARENTHESIS)
	    parenthesis = !parenthesis;
	if (edit_pos == comm_len)
	    cmnd[edit_pos - 1] = ZCHAR;
	else
	    memmove(&cmnd[edit_pos - 1],
		    &cmnd[edit_pos], (comm_len - edit_pos + 1));
	comm_len--;
	edit_pos--;
	last_expl = NULL;
	update_prompt(TRUE);
    }
}

void delete_char()
{
    if (!outb_empty())
	;
    else if (!comm_len || edit_pos == comm_len)
	mybeep();
    else {
	if (cmnd[edit_pos] == PARENTHESIS)
	    parenthesis = !parenthesis;
	memmove(&cmnd[edit_pos],
		&cmnd[edit_pos + 1], (comm_len - edit_pos + 1));
	comm_len--;
	last_expl = NULL;
	update_prompt(TRUE);
    }
}

void insert_char(char in)
{
    memmove(&cmnd[edit_pos + 1], &cmnd[edit_pos], comm_len - edit_pos);
    cmnd[edit_pos] = in;
    comm_len++;
    edit_pos++;
    last_expl = NULL;
    update_prompt(TRUE);
}

void append_char(char in)
{
    cmnd[comm_len++] = in;
    edit_pos = comm_len;
    last_expl = NULL;
    update_prompt(TRUE);
}

void clear_scr()
{
    print_ctrl_str(1, TC_CLR);
    update_prompt(FALSE);
}

void decode_char_input(char chr)
{
    switch (chr) {
    case 1 /* ^A */ :
	edit_pos = 0;
	update_prompt(TRUE);
	break;

    case 2:			/* ^B */
	handle_special_char(LEFT_ARROW);
	break;

    case 5 /* ^E */ :
	edit_pos = comm_len;
	update_prompt(TRUE);
	break;

    case 6:			/* ^F */
	handle_special_char(RIGHT_ARROW);
	break;

    case 8:
    case 127 /* backspace */ :
	backspace_char();
	break;

    case 9 /* tab */ :
	explode_fileORcomm();
	break;

    case 11 /* ^K */ :
	if (edit_pos != comm_len) {
	    memset(&cmnd[edit_pos], ZCHAR, COMM_SIZE - edit_pos);
	    last_expl = NULL;
	    comm_len = edit_pos;
	}
	update_prompt(TRUE);
	break;

    case 12 /* ^L */ :
	clear_scr();
	break;

    case 14:			/* ^N */
	handle_special_char(DOWN_ARROW);
	break;

    case 16:			/* ^P */
	handle_special_char(UP_ARROW);
	break;

    case 21:			/* ^U */
	if (edit_pos != 0) {
	    memmove(cmnd, &cmnd[edit_pos], comm_len - edit_pos);
	    last_expl = NULL;
	    comm_len -= edit_pos;
	    edit_pos = 0;
	    memset(&cmnd[comm_len], ZCHAR, COMM_SIZE - comm_len);
	} else
	    mybeep();

	update_prompt(TRUE);
	break;

    case END_COMM:		/* '\n' */
	if (!piped_io_flag)
	    outstr("\n");

	if (comm_len) {
	    cmnd[comm_len++] = ZCHAR;
	    handle_user_message(cmnd);
	} else
	    prompt_now = TRUE;

	clear_comm();
	if (prompt_now) {
	    flush_console_buf();
	    prompt(TRUE);
	}

	break;

    case '"':
	parenthesis = !parenthesis;
    default:

	if (chr < 32 && chr >= 0)
	    break;

	if (comm_len < (COMM_SIZE - 1)) {
	    if (edit_pos == comm_len)
		append_char(chr);
	    else
		insert_char(chr);
	} else {
	    show_user("\n" ERROR_MSG " Command too big, ignoring...\n");
	    clear_comm();
	    prompt(TRUE);
	}
	break;
    }
}

void read_sp_char()
{
    char spchars[16], ins;
    struct timeval tv;
    fd_set readset;
    int i, sps, len, ret;

    sps = 1;
    *spchars = 27;

    do {
	len = 0;

	do {
	    FD_ZERO(&readset);
	    FD_SET(0, &readset);
	    tv.tv_sec = 0;
	    tv.tv_usec = 750000;
	    ret = select(0 + 1, &readset, NULL, NULL, &tv);
	}
	while (ret == -1 && errno == EINTR);

	if (FD_ISSET(0, &readset)) {
	    do {
		len = read(0, &ins, 1);
	    }
	    while (len == -1 && errno == EINTR);

	    if (len <= 0)
		break;
	    else
		spchars[sps++] = ins;

	    if (sps >= 16)
		break;
	    else if (check_special_chars(spchars))
		return;
	}
    }
    while (len);

    if (sps > 1)
	for (i = 0; i < sps; i++)
	    decode_char_input(spchars[i]);
}

int monitor_char_input()
{
    char chr;
    int len;

    len = chr = 0;

    do {
	len = read(0, &chr, 1);
    }
    while (len == -1 && errno == EINTR);

    if (len == 0)
	return FALSE;
    else if (len < 0)
	return TRUE;		/* ignore err */

    if (chr == 27)
	read_sp_char();
    else if (!check_outbuffer(chr))
	decode_char_input(chr);

    return TRUE;
}
