/***********************************************
 *  player.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 DCharBuf *args;

extern PlayerDef *delayed_external_player, *player_list;

extern char *strinfo;

extern int thisprgp;

extern flag_t external_paused, prompt_now, external_player;
extern flag_t multiple_audio_procs;

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

static char *COUTPUT[] = { 
    "Normal",
    "Supressed",
    "Buffered",
    "n/a"
};
static char *SIGNALS[] = { 
    "SIGTERM", 
    "SIGKILL", 
    "n/a" 
};
static int external_signal = SIGNAL_TERM;
static pid_t player_pid = 0;

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

unsigned int g_player_bytec()
{
    PlayerDef *curr;
    unsigned int bytec;

    curr = player_list->next;
    bytec = 0;

    while (curr) {
	bytec += sizeof(PlayerDef);
	bytec += get_strlen(curr->name);
	bytec += get_strlen(curr->command);
	bytec += get_strlen(curr->sup_types);
	curr = curr->next;
    }

    return bytec;
}

/************************************
 *   External player-list manip     *
 ************************************/

void clear_players()
{
    PlayerDef *curr, *next;

    curr = player_list->next;

    while (curr) {
	next = curr->next;
	free(curr->name);
	free(curr->command);
	free(curr->sup_types);
	free(curr);
	curr = next;
    }
}

void /* user */ add_player()
{
    char *opt, *name, *types, *command;
    int output, signl, ndx, i;
    PlayerDef *curr;

    output = OUTP_NORM;
    signl = SIGNAL_TERM;
    i = ndx = 1;

    while ((opt = g_dbuf(args, i++)))
	if (strcmp(opt, "-q") == 0) {
	    output = OUTP_QUIET;
	    ndx++;
	} else if (strcmp(opt, "-b") == 0) {
	    output = OUTP_BUFFERED;
	    ndx++;
	} else if (strcmp(opt, "-k") == 0) {
	    signl = SIGNAL_KILL;
	    ndx++;
	}

    name = g_dbuf(args, ndx++);
    command = g_dbuf(args, ndx++);
    types = g_dbuf(args, ndx);

    if (!name || !command || !types)
	print_cmnd_help(ADD_PLAYER);
    else {
	curr = player_list;

	while (curr->next)
	    curr = curr->next;

	curr->next = malloc(sizeof(PlayerDef));
	curr = curr->next;

	curr->name = strdup(name);
	curr->command = strdup(command);
	curr->sup_types = strdup(types);
	curr->output = output;
	curr->signal = signl;
	curr->next = NULL;
    }
}

void /* user */ print_players()
{
    PlayerDef *curr;

    curr = player_list;

    while (curr) {
	add_outbuffer(MISC_MSG " %s", curr->name);
	add_outbuffer("\tShell command       : %s", curr->command);
	add_outbuffer("\tSupported format(s) : %s", curr->sup_types);
	add_outbuffer("\tConsole output      : %s", COUTPUT[curr->output]);
	add_outbuffer("\tTerminate signal    : %s", SIGNALS[curr->signal]);
	curr = curr->next;
    }

    flush_outbuffer();
}

/************************************
 *     External Player control      *
 ************************************/

void pause_external()
{
    external_paused = TRUE;
    killpg(player_pid, SIGSTOP);
}

void resume_external()
{
    external_paused = FALSE;
    killpg(player_pid, SIGCONT);
}

void stop_external()
{
    int i, sig;

    i = 0;
    alarm(0);
    external_player = external_paused = FALSE;

    if (external_signal == SIGNAL_KILL)
	sig = SIGKILL;
    else
	sig = SIGTERM;

    while (-1 == killpg(player_pid, sig) && errno == ESRCH) {
	sleep(1);
	if (i++ > 1) {
	    add_dlog(ERROR_MSG " Unable to stop external player!");
	    return;
	}
    }
   
    sleep(2);			/* SIGCHLD will bust */
    if (!multiple_audio_procs)
	usleep(80000);		/* make sure audio resources are freed */
}

void handle_external_death(pid_t pid)
{
    if (external_player && pid == player_pid) {
	alarm(0);
	external_player = external_paused = FALSE;
	post_event(ESTREAM_EVENT);
	if (!multiple_audio_procs)
	    usleep(80000);
	determine_next_stream();
    }
}

void play_external(PlayerDef * plyr, char *stream)
{
    char *cm, *cmem, tmp[INFO_SIZE], *arg[INFO_SIZE];
    int i, fd, mypid, prgrp, output, pstdo[2], pstdr[2];

    output = plyr->output;
    cm = cmem = NULL;
    i = 0;

    set_state(EXTERNAL);
    external_player = TRUE;

    if (close_internal_output()) {
	delayed_external_player = plyr;
	return; /* get out, must wait for audio resources to free up */
    }

    sprintf(strinfo, "Using \"%s\"", plyr->name);
    mysetenv(CURR_INFO, strinfo);
    clear_duration();

    safe_strcpy(tmp, plyr->command, INFO_SIZE);
    cm = tokenize(tmp, &cmem, SPACE);
    while (cm) {
	arg[i++] = cm;
	cm = tokenize(NULL, &cmem, SPACE);
    }

    arg[i++] = stream;
    arg[i] = NULL;

    if (output == OUTP_BUFFERED && (pipe(pstdo) || pipe(pstdr))) {
	add_console(ERROR_MSG " Failed to create player pipes : %s",
		    strerror(errno));
	output = OUTP_NORM;
    }

    player_pid = fork();

    if (player_pid == -1) {
	set_state(IDLE);
	external_player = FALSE;
	add_console(ERROR_MSG
		    " Failed to fork() external player \"%s\" : %s",
		    plyr->command, strerror(errno));
    } else if (player_pid != 0) {
	switch (output) {
	case OUTP_NORM:
	case OUTP_QUIET:
	    add_proclist(player_pid, J_DECEXT, FALSE, NULL, NULL, *arg);
	    break;
	case OUTP_BUFFERED:
	    add_proclist(player_pid, J_DECEXT, TRUE, pstdo, pstdr, *arg);
	    break;
	}

	external_signal = plyr->signal;
	clear_time();
	alarm(1);
    } else if (player_pid == 0) {
	mypid = getpid();
	prgrp = mypid;
	setpgid(mypid, prgrp);

	signal(SIGINT, SIG_DFL);
	signal(SIGTSTP, SIG_IGN);
	signal(SIGTTIN, SIG_IGN);
	signal(SIGTTOU, SIG_IGN);
	signal(SIGTERM, SIG_DFL);
	signal(SIGCHLD, SIG_DFL);
	signal(SIGPIPE, SIG_DFL);
	signal(SIGALRM, SIG_DFL);
	signal(SIGSTOP, SIG_DFL);
	signal(SIGCONT, SIG_DFL);
	signal(SIGWINCH, SIG_IGN);

	switch (output) {
	case OUTP_QUIET: {
	    fd = open("/dev/null", O_WRONLY);
	    
	    if (fd != -1) {
		dup2(fd, 1);
		dup2(fd, 2);
	    }
	    break;
	}
	case OUTP_BUFFERED:
	    dup2(pstdo[1], 1);
	    dup2(pstdr[1], 2);
	    break;
	}

	usleep(50000);
	execvp(*arg, arg);
	exit(DECODER_EXEC_FAILED);
    }
}
