/***********************************************
 *  jobs.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;

extern struct termios savtty, newtty;

extern int thisprgp;

extern flag_t prompt_now, ignore_char_input, exit_flag, piped_io_flag;

extern sighandler_t sigchild_handler;

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

static char *JMODE[] = { 
    "Run (FG)",
    "Running",
    "Stopped",
    "(Internal player)",
    "(External player)"
};
static char tmpstr_jb[JOBB_SIZE];
static ProcessEntry *prs_st = NULL, *prs_en = NULL;

/************************************
 *         Module routines          *
 ************************************/

unsigned int g_jobs_bytec()
{
    ProcessEntry *curr;
    unsigned int bytec;

    protect_procs();
    curr = prs_st;
    bytec = 0;

    while (curr) {
	bytec += sizeof(ProcessEntry);
	if (curr->buff)
	    bytec += g_dbuf_bytec(curr->buff);

	curr = curr->next;
    }

    unprotect_procs();
    return bytec;
}

/************************************
 *   Job Control functions          *
 ************************************/

void protect_procs()
{
    sigchild_handler = count_child_sigs;
    signal(SIGCHLD, (sighandler_t) sigchild_handler);
}

void unprotect_procs()
{
    sigchild_handler = handle_child_sigs;
    signal(SIGCHLD, (sighandler_t) sigchild_handler);
}

void clear_proclist()
{
    ProcessEntry *curr;

    while (prs_st) {
	curr = prs_st->next;
	f_dbuf(prs_st->buff);
	free(prs_st);
	prs_st = curr;
    }
}

/* called from child_sig_handler */
ProcessEntry *get_process(pid_t pid, ProcessEntry ** ret_prev)
{
    ProcessEntry *curr, *prev;

    curr = prs_st;
    prev = NULL;

    while (curr)
	if (curr->pid == pid) {
	    *ret_prev = prev;
	    return curr;
	} else {
	    prev = curr;
	    curr = curr->next;
	}

    *ret_prev = NULL;
    return NULL;
}

/* called from child_sig_handler */
int stop_fg_process(ProcessEntry * curr)
{
    if (!curr)
	return -1;

    if (curr->mode < J_DECINT) {
	killpg(curr->pid, SIGSTOP);

	if (!piped_io_flag) {
	    tcsetpgrp(0, thisprgp);
	    tcgetattr(0, &curr->tty);
	    tcsetattr(0, TCSAFLUSH, &newtty);
	}

	curr->mode = J_STOPPED;
	ignore_char_input = FALSE;
	add_console("\n" STATUS_MSG " [%s] %d %s", curr->name,
		    curr->pid, JMODE[curr->mode]);
    }

    return curr->mode;
}

/* eventually called from child_sig_handler */
void set_proc_term(ProcessEntry * curr)
{
    switch (curr->mode) {
    case J_FGRUN:
	ignore_char_input = FALSE;

	if (!piped_io_flag) {
	    tcsetpgrp(0, thisprgp);
	    tcsetattr(0, TCSAFLUSH, &newtty);
	}
	break;
    case J_BGRUN:
    case J_STOPPED:
	add_console(STATUS_MSG " [%s] %d done.", curr->name, curr->pid);
	break;
    }
}

/* called from child_sig_handler */
int clean_process(ProcessEntry * curr, ProcessEntry * prev)
{
    int jobt;

    jobt = -1;

    if (!curr)
	int_sig_handler();
    else {
	if (curr == prs_en)
	    prs_en = prev;
	if (prev)
	    prev->next = curr->next;
	else
	    prs_st = curr->next;

	clean_procpipes(curr);
	set_proc_term(curr);
	jobt = curr->mode;

	f_dbuf(curr->buff);
	free(curr);
    }

    return jobt;
}

int get_proc_stream(int fd, ProcessEntry * pr)
{
    int len;

    len = read(fd, tmpstr_jb, (JOBB_SIZE - 1));

    if (len > 0) {
	tmpstr_jb[len] = ZCHAR;
	if (pr->mode == J_FGRUN)
	    outstr(tmpstr_jb);
	else
	    a_dbuf(pr->buff, tmpstr_jb);
    }

    return len;
}

void check_procpipes(fd_set * read_set)
{
    ProcessEntry *curr;

    protect_procs();
    curr = prs_st;

    while (curr) {
	if (curr->redir) {
	    if (FD_ISSET(curr->pipestdout[1], read_set))
		get_proc_stream(curr->pipestdout[0], curr);

	    if (FD_ISSET(curr->pipestderr[1], read_set))
		get_proc_stream(curr->pipestderr[0], curr);

	}
	curr = curr->next;
    }

    unprotect_procs();
}

/* eventually called from child_sig_handler */
void clean_procpipes(ProcessEntry * pr)
{
    DCharBuf *buf;
    struct timeval tv;
    fd_set read_set;
    int i, len, len2, ret, maxfd;;

    if (!pr || !pr->redir)
	return;
	
    do {
	do {
	    FD_ZERO(&read_set);
	    len = len2 = maxfd = 0;

	    FD_SET(pr->pipestdout[1], &read_set);
	    maxfd = pr->pipestdout[1] + 1;

	    FD_SET(pr->pipestderr[1], &read_set);
	    if (maxfd <= pr->pipestderr[1])
		maxfd = pr->pipestderr[1] + 1;

	    tv.tv_sec = tv.tv_usec = 0;

	    ret = select(maxfd, &read_set, NULL, NULL, &tv);

	}
	while (ret == -1 && errno == EINTR);

	if (FD_ISSET(pr->pipestdout[1], &read_set))
	    len = get_proc_stream(pr->pipestdout[0], pr);
	
	if (FD_ISSET(pr->pipestderr[1], &read_set))
	    len2 = get_proc_stream(pr->pipestderr[0], pr);
	
    }
    while ((len > 0) || (len2 > 0));

    /* don't flush external players' buffers?? anybody? */
    if (pr->buff->len && pr->mode < J_DECINT) {
	buf = pr->buff;
	len = pr->buff->len;

	add_console(STATUS_MSG " Buffered output from \"%s\" [pid=%d]:", 
		    pr->name, pr->pid);

	for (i = 0; i < len; i++)
	    add_console("%s", g_dbuf(buf, i));
    }

    close(pr->pipestdout[0]);
    close(pr->pipestderr[0]);
    close(pr->pipestdout[1]);
    close(pr->pipestderr[1]);
}

void set_procpipes(fd_set * read_set, int *maxfd)
{
    ProcessEntry *curr;

    protect_procs();
    curr = prs_st;

    while (curr) {
	if (curr->redir) {
	    FD_SET(curr->pipestdout[1], read_set);
	    if (*maxfd <= curr->pipestdout[1])
		*maxfd = curr->pipestdout[1] + 1;
	    FD_SET(curr->pipestderr[1], read_set);
	    if (*maxfd <= curr->pipestderr[1])
		*maxfd = curr->pipestderr[1] + 1;
	}
	curr = curr->next;
    }

    unprotect_procs();
}

void add_proclist(pid_t pid, int mode, int redir,
		  int stdo[2], int stdr[2], char *name)
{
    protect_procs();

    if (prs_st) {
	prs_en->next = malloc(sizeof(ProcessEntry));
	prs_en = prs_en->next;
    } else {
	prs_st = malloc(sizeof(ProcessEntry));
	prs_en = prs_st;
    }

    prs_en->next = NULL;
    prs_en->redir = redir;
    prs_en->pid = pid;
    prs_en->mode = mode;
    safe_strcpy(prs_en->name, name, 32);

    if (redir) {
	prs_en->buff = i_dbuf(prs_en->name, 256, DCHAR_SHIFT);
	prs_en->pipestdout[0] = stdo[0];
	prs_en->pipestdout[1] = stdo[1];
	prs_en->pipestderr[0] = stdr[0];
	prs_en->pipestderr[1] = stdr[1];
    } else {
	prs_en->buff = NULL;
	prs_en->pipestdout[0] = -1;
	prs_en->pipestdout[1] = -1;
	prs_en->pipestderr[0] = -1;
	prs_en->pipestderr[1] = -1;
    }

    switch (mode) {
    case J_FGRUN:
	ignore_char_input = TRUE;
	if (!piped_io_flag)
	    tcsetattr(0, TCSAFLUSH, &savtty);
	break;
    case J_BGRUN:
	add_console(STATUS_MSG " [%s] %d &", name, pid);
	break;
    }

    unprotect_procs();
}

void flush_proc_buf(ProcessEntry * pr)
{
    int i;

    if (pr->buff) {
	for (i = 0; i < pr->buff->len; i++)
	    outstr(pr->buff->buf[i].str);

	c_dbuf(pr->buff);
    }
}

void set_proc_xg(ProcessEntry * curr, int mode)
{
    if (mode == J_FGRUN) {
	if (curr->redir)
	    flush_proc_buf(curr);

	if (!piped_io_flag) {
	    tcsetpgrp(0, curr->pid);
	    if (curr->mode == J_STOPPED)
		tcsetattr(0, TCSAFLUSH, &curr->tty);
	    else
		tcsetattr(0, TCSAFLUSH, &savtty);
	}

	prompt_now = FALSE;
	ignore_char_input = TRUE;
    } else {
	ignore_char_input = FALSE;

	if (!piped_io_flag) {
	    tcsetpgrp(0, thisprgp);
	    tcsetattr(0, TCSAFLUSH, &newtty);
	}

	show_user(STATUS_MSG " [%s] %d &", curr->name, curr->pid);
    }

    curr->mode = mode;
    killpg(curr->pid, SIGCONT);
}

void send_job_2xg(int mode)
{
    ProcessEntry *curr;
    char *num;
    int i, ndx;

    num = g_dbuf(args, 1);
    curr = prs_st;
    ndx = -1;
    i = 0;

    if (num) {
	ndx = atoi(num);
	if (ndx <= 0) {
	    show_user(ERROR_MSG " Invalid <job_number>");
	    return;
	}
    }

    if (!curr) {
	show_user(DEBUG_MSG " Process list is empty");
	return;
    } else
	while (curr) {
	    i++;
	    if ((curr->mode < J_DECINT) &&
		((num && ndx == i && curr->mode != mode) ||
		 (!num && curr->mode != mode))) {
		set_proc_xg(curr, mode);
		return;
	    }
	    curr = curr->next;
	}

    show_user(DEBUG_MSG " No viable process found");
}

void /* user */ send_job_2fg()
{
    protect_procs();
    send_job_2xg(J_FGRUN);
    unprotect_procs();
}

void /* user */ send_job_2bg()
{
    protect_procs();
    send_job_2xg(J_BGRUN);
    unprotect_procs();
}

void /* user */ flush_job_buff()
{
    ProcessEntry *curr;
    DCharBuf *buf;
    char *ndx;
    int i, num, len;

    ndx = g_dbuf(args, 1);
    i = num = 0;

    if (!ndx || !is_all_digits(ndx)) {
	print_cmnd_help(FLUSH_JOB_BUFF);
	return;
    }

    protect_procs();
    curr = prs_st;
    num = atoi(ndx);

    while (curr && ++i != num)
	curr = curr->next;

    if (!curr)
	show_user(ERROR_MSG " Invalid job number: %d", num);
    else if (!curr->buff)
	show_user(DEBUG_MSG " No Buffer allocated for \"%s\"", curr->name);
    else if (curr->buff->len == 0)
	show_user(DEBUG_MSG " Buffer for \"%s\" is empty", curr->name);
    else {
	buf = curr->buff;
	len = curr->buff->len;

	show_user(STATUS_MSG " Flushing job buffer for \"%s\" [pid=%d]:",
		  curr->name, curr->pid);

	for (i = 0; i < len; i++)
	    show_user_n(g_dbuf(buf, i));

	c_dbuf(buf);
    }

    unprotect_procs();
}

void /* user */ print_jobs()
{
    ProcessEntry *curr;
    char tmp[8];
    int i;

    protect_procs();
    curr = prs_st;
    i = 0;

    if (!curr)
	add_outbuffer(DEBUG_MSG " Process List is empty");
    else {
	while (curr) {
	    add_outbuffer(MISC_MSG " [%d] \"%s\"", ++i, curr->name);
	    add_outbuffer("\tPID     : %d", curr->pid);
	    add_outbuffer("\tState   : %s", JMODE[curr->mode]);
	    if (curr->buff) {
		if (curr->buff->len)
		    strcpy(tmp, "[*]");
		else
		    strcpy(tmp, "[ ]");
		add_outbuffer("\tBuffer  : %s (%d bytes allocated)", tmp,
			      curr->buff->bytec);
	    } else
		add_outbuffer("\tBuffer  : None");
	    curr = curr->next;
	}
    }

    unprotect_procs();
    flush_outbuffer();
}
