/***********************************************
 *  main.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
 *
 *
 *
 *  imp3sh is an interactive mp3 shell using the
 *  Xaudio Async library for decoding.  imp3sh was 
 *  NOT created to be a suitable replacement for 
 *  fully-featured shells like bash or tcsh, I just
 *  implemented the shell features I used most (and 
 *  not even all of those yet.)  
 *                                            
 *  [This has been pieced together in  
 *   my spare time and is an unsightly mess of 
 *   code, although very stable.]
 ***********************************************/

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

#include "imp3sh.h"

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

extern struct termios savtty, newtty;

extern XA_Control *player;

extern XA_TimecodeInfo tinfo;

extern ProcessState state;

extern CommandDef com[];

extern DCharBuf *args;

extern TermCtrl t_ctrl[];

extern char *host, *c_path;

extern int thisprgp, tty_width, tty_height, tab_length;

extern flag_t external_player, show_time_stamp, piped_io_flag, prompt_now;
extern flag_t external_paused, running_builtin, exit_flag, decoder_open;
extern flag_t ignore_hist_input, ignore_char_input;

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

static char piped_outstr[512], piped_instr[512], tmpstr_ma[COMM_SIZE];
static char *termtype = NULL, *prog_name = NULL;
static int fdstdin = 0, child_sigs = 0;
static flag_t piped_input = FALSE, piped_output = FALSE;

/************************************
 *         Output Functions         *
 ************************************/

void outstrpipe(char *str, int nl)
{
    int len;

    len = get_strlen(str);

    if (len && write(1, str, len) > 0 && nl)
	write(1, "\n", 1);
}

void outstr(char *str)
{
    if (str) {
	if (piped_io_flag)
	    outstrpipe(str, FALSE);
	else
	    fputs(str, stdout);
    }
}

void outstrnl(char *str)
{
    if (str) {
	if (piped_io_flag)
	    outstrpipe(str, TRUE);
	else
	    puts(str);
    }
}

void outflush()
{
    if (!piped_io_flag)
	fflush(stdout);
}

int piped_io()
{
    if (piped_input || piped_output || !isatty(0) || !isatty(1))
	return TRUE;
    else
	return FALSE;
}

int have_tty()
{
    if (!ignore_char_input && outb_empty())
	return TRUE;
    else
	return FALSE;
}

void open_inpipe()
{
    int fd;

    fd = open(piped_instr, O_RDONLY | O_NONBLOCK);

    if (fd != -1) {
	dup2(fd, fdstdin);
	piped_input = TRUE;
    } else {
	fprintf(stderr, "%s: error opening input: %s : %s\n", prog_name,
		piped_instr, strerror(errno));
	exit(1);
    }
}

void err_outpipe()
{
    fprintf(stderr, "%s: error opening output: %s : %s\n", prog_name,
	    piped_outstr, strerror(errno));
    exit(1);
}

void open_outpipe()
{
    int fd, fd2;

    fd = open(piped_outstr, O_RDONLY | O_NONBLOCK);

    if (fd == -1)
	err_outpipe();
    else {
	fd2 = open(piped_outstr, O_WRONLY);

	if (fd2 == -1) {
	    close(fd);
	    err_outpipe();
	} else {
	    piped_output = TRUE;
	    dup2(fd2, 1);
	    dup2(fd2, 2);
	    close(fd);
	}
    }
}

void setup_monitor(fd_set * read_set, int *max_fd)
{
    FD_ZERO(read_set);
    *max_fd = 0;

    if (!ignore_char_input) {
	FD_SET(0, read_set);
	*max_fd = 1;
    }

    if (decoder_open) {
	FD_SET(player->command_read_pipe, read_set);
	*max_fd = player->command_read_pipe + 1;
    }

    set_procpipes(read_set, max_fd);
}

void check_monitor(fd_set * read_set)
{
    if (!ignore_char_input && FD_ISSET(0, read_set) && !monitor_char_input()
	&& piped_io_flag) {
	close(0);
	if (piped_input)
	    open_inpipe();
	else
	    ignore_char_input = TRUE;
    }

    if (decoder_open && FD_ISSET(player->command_read_pipe, read_set))
	monitor_decoder();

    check_procpipes(read_set);
}

void monitor()
{
    struct timeval tv;
    fd_set read_set;
    int ret, max_fd;

    while (TRUE) {
	clear_child_sigs();

	do {
	    setup_monitor(&read_set, &max_fd);

	    tv.tv_sec = 0;
	    tv.tv_usec = SELECT_TIMEOUT;

	    ret = select(max_fd, &read_set, NULL, NULL, &tv);
	}
	while (ret == -1 && errno == EINTR);

	check_monitor(&read_set);
	check_events();
    }
}

/****Signal handlers****/

void int_sig_handler()
{
    if (!piped_io_flag) {
	tcsetpgrp(0, thisprgp);
	tcsetattr(0, TCSAFLUSH, &newtty);
    }
    ignore_char_input = FALSE;
}

void winch_sig_handler(int a)
{
    if (!piped_io_flag && tgetent(NULL, termtype) == 1) {
	tty_width = tgetnum("co");
	tty_height = tgetnum("li");

	if (tty_width == ERR || tty_height == ERR) {
	    tty_width = 80;
	    tty_height = 20;
	}
    }
}

/* external player timer */
void alarm_sig_handler(int a)
{
    if (state == EXTERNAL) {
	alarm(1);

	if (!external_paused) {
	    if (external_player) {
		if (tinfo.s == 59) {
		    tinfo.s = 0;
		    tinfo.m++;
		} else
		    tinfo.s++;
	    }

	    if (show_time_stamp && !ignore_char_input && !running_builtin)
		update_prompt(TRUE);
	}
    }
}

void clear_child_sigs()
{
    int i, sigs;

    sigs = child_sigs;

    if (sigs) {
	protect_procs();

	for (i = 0; i < sigs; i++)
	    handle_child_sigs();

	unprotect_procs();
	child_sigs -= sigs;
    }
}

void count_child_sigs(int a)
{
    if (!exit_flag)
	child_sigs++;
}

void handle_child_sigs(int a)
{
    char procname[32];
    ProcessEntry *prev, *pptr;
    int stat, jobt;
    pid_t pid;

    if (exit_flag)
	return;

    pid = waitpid(-1, &stat, WNOHANG | WUNTRACED);

    while (pid > 0) {
	prev = NULL;
	pptr = get_process(pid, &prev);
	jobt = -1;

	if (pptr)
	    safe_strcpy(procname, pptr->name, 32);
	else
	    *procname = ZCHAR;

	if (WIFSTOPPED(stat)) {
	    jobt = stop_fg_process(pptr);

	    if (jobt == -1)
		add_console(DEBUG_MSG
			    " Unregistered child stopped [pid=%d]", pid);
	} else {
	    jobt = clean_process(pptr, prev);
	    stat = WEXITSTATUS(stat);

	    switch (jobt) {
	    case J_DECEXT:
		handle_external_death(pid);
		break;
	    case -1:
		add_console(DEBUG_MSG " Unregistered child died [pid=%d]",
			    pid);
		break;
	    }

	    switch (stat) {
	    case CHILD_EXEC_FAILED:
		add_console(ERROR_MSG " Command \"%s\" failed to execute",
			    procname);
		break;
	    case DECODER_EXEC_FAILED:
		add_console(ERROR_MSG
			    " External player \"%s\" failed to execute",
			    procname);
		break;
	    }
	}

	if (!ignore_hist_input && have_tty())
	    switch (jobt) {
	    case J_BGRUN:
	    case -1:
		update_prompt(FALSE);
		break;
	    case J_STOPPED:
	    case J_FGRUN:
		flush_console_buf();
		prompt(TRUE);
		break;
	    }

	pid = waitpid(-1, &stat, WNOHANG | WUNTRACED);
    }
}


void source_file(char *file)
{
    char *strptr;
    FILE *fd;
    flag_t setflag;

    setflag = FALSE;
    fd = myfopen(file, "r");

    if (!ignore_hist_input) {
	ignore_hist_input = TRUE;
	setflag = TRUE;
    }

    if (fd == NULL)
	show_user(DEBUG_MSG " Open failed for: \"%s\": %s", file,
		  strerror(errno));
    else {
	get_fline(fd, tmpstr_ma, (COMM_SIZE - 1));
	while (!feof(fd)) {
	    strptr = skip_space(tmpstr_ma);
	    if (get_strlen(strptr) && *strptr != '#')
		handle_user_message(strptr);
	    get_fline(fd, tmpstr_ma, (COMM_SIZE - 1));
	}

	fclose(fd);
    }

    /* Don't unset flag unless we
       are top-level call         */
    if (setflag)
	ignore_hist_input = FALSE;
}

void /* user */ source_comm()
{
    char *file;

    file = g_dbuf(args, 1);

    if (file)
	source_file(file);
    else
	print_cmnd_help(SOURCE_FILE);
}

void setup_terminal()
{
    char *cttype, *ttype;
    int i;

    tcsetpgrp(0, thisprgp);
    cttype = getenv("COLORTERM");
    ttype = getenv("TERM");

    if (!cttype && !ttype) {
	ttype = "VT100";
	fprintf(stderr, "%s: defaulting to a \"%s\" terminal\n",
		prog_name, ttype);
    }

    if (cttype && tgetent(NULL, cttype) == 1)
	termtype = cttype;
    else if (tgetent(NULL, ttype) == 1)
	termtype = ttype;
    else {
	if (cttype)
	    fprintf(stderr, "%s: terminal \"%s\": tgetent() failed : %s\n",
		    prog_name, cttype, strerror(errno));
	if (ttype)
	    fprintf(stderr, "%s: terminal \"%s\": tgetent() failed : %s\n",
		    prog_name, ttype, strerror(errno));
	exit(1);
    }

    for (i = 0; i < (NUM_TERM_CTRL - NUM_TERM_USER); i++) {
	t_ctrl[i].string = tgetstr(t_ctrl[i].name, 0);
	if (!t_ctrl[i].string)
	    fprintf(stderr,
		    "%s: terminal \"%s\": string \"%s\" not supported!\n",
		    prog_name, termtype, t_ctrl[i].name);
    }

    tty_width = tgetnum("co");
    tty_height = tgetnum("li");
    tab_length = tgetnum("it");

    if (tab_length == ERR || tab_length == 0) {
	fprintf(stderr,
		"%s: terminal \"%s\": reporting bad <tab> character length!\n",
		prog_name, termtype);
	tab_length = 8;
    }

    if (tty_width == ERR || tty_height == ERR) {
	fprintf(stderr, "%s: terminal \"%s\": screen size not available!\n",
		prog_name, termtype);
	fprintf(stderr, "%s: defaulting to 80 columns x 20 lines\n",
		prog_name);
	tty_width = 80;
	tty_height = 20;
    }

    if (tcgetattr(0, &savtty) == -1) {
	fprintf(stderr, "%s: terminal \"%s\": tcgetattr() failed : %s\n",
		prog_name, termtype, strerror(errno));
	exit(1);
    }

    cfmakeraw(&newtty);
    newtty.c_iflag &= ~IGNCR;
    newtty.c_iflag |= (ICRNL | IMAXBEL);
    newtty.c_lflag |= ISIG;
    newtty.c_oflag |= (OPOST | ONLCR);
    newtty.c_cc[VMIN] = 1;
    newtty.c_cc[VTIME] = 0;

    if (tcsetattr(0, TCSAFLUSH, &newtty) == -1) {
	fprintf(stderr, "%s: terminal \"%s\": tcsetattr() failed : %s\n",
		prog_name, termtype, strerror(errno));
	exit(1);
    }
}

void shut_down()
{
    write_chist();
    deallocate_vars();

    if (!piped_io_flag)
	tcsetattr(0, TCSAFLUSH, &savtty);

    exit(0);
}

void /* user */ exit_imp3sh()
{
    post_event(EXIT_EVENT);
    check_events();

    exit_flag = ignore_char_input = TRUE;
    prompt_now = FALSE;

    if (external_player)
	stop_external();

    if (decoder_open)
	close_decoder();
    else
	shut_down();
}

void print_usage()
{
    fprintf(stderr,
    "usage: %s [-r <resource_file>] [-i <input_pipe>] [-o <output_pipe>]\n",
	    prog_name);
    exit(1);
}

void parse_args(int argc, char **argv)
{
    int i, ret, argsleft;

    argsleft = argc - 1;

    if (argc <= 1)
	return;

    for (i = 1; i < argc; i++) {
	if (strcmp(argv[i], "-i") == 0) {
	    if (--argsleft) {
		ret = mkfifo(argv[++i], 0777);

		argsleft--;
		if (ret == 0 || (ret == -1 && errno == EEXIST)) {
		    strcpy(piped_instr, argv[i]);
		    open_inpipe();
		} else {
		    fprintf(stderr,
			    "%s: error creating named pipe \"%s\" : %s\n",
			    prog_name, argv[i], strerror(errno));
		    exit(1);
		}
	    } else
		print_usage();
	} else if (strcmp(argv[i], "-o") == 0) {
	    if (--argsleft) {
		ret = mkfifo(argv[++i], 0777);

		argsleft--;
		if (ret == 0 || (ret == -1 && errno == EEXIST)) {
		    strcpy(piped_outstr, argv[i]);
		    open_outpipe();
		} else {
		    fprintf(stderr,
			    "%s: error creating named pipe \"%s\" : %s\n",
			    prog_name, argv[i], strerror(errno));
		    exit(1);
		}
	    } else
		print_usage();
	} else if (strcmp(argv[i], "-r") == 0) {
	    if (--argsleft) {
		argsleft--;
		strcpy(tmpstr_ma, argv[++i]);
	    } else
		print_usage();
	} else
	    print_usage();
    }
}

int main(int argc, char **argv)
{
    struct timeval tv;

    thisprgp = getpid();
    setpgid(thisprgp, thisprgp);

    prog_name = *argv;
    dup2(0, fdstdin);
    gettimeofday(&tv, NULL);
    srandom((unsigned int) tv.tv_usec);

    setenv("IMP3_VERSION", VERSION, TRUE);
    sprintf(tmpstr_ma, "%s/.imp3rc", getenv("HOME"));
    parse_args(argc, argv);

    if (piped_io())
	piped_io_flag = TRUE;
    else
	setup_terminal();

    allocate_vars();
    open_decoder();
    print_info();

    if (!piped_io_flag)
	show_user("\nType \"%s\" for a list of built-in commmands\n",
		  com[PRINT_HELP].name);

    gethostname(host, INFO_SIZE);
    getcwd(c_path, PATH_MAX);
    setenv("OLDPWD", c_path, TRUE);

    read_chist();
    clear_info();

    show_user("...executing resource file:");
    source_file(tmpstr_ma);

    clear_comm();
    clear_outbuffer();

    prompt(TRUE);
    monitor();

    return 0;
}
