/***********************************************
 *  decoder.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_Control *player;
extern XA_TimecodeInfo dinfo, tinfo;
extern XA_PositionInfo pinfo;
extern XA_VolumeInfo vinfo;
extern XA_EqualizerInfo equalizer;
extern ProcessState state;

extern CommandDef com[];

extern DCharBuf *args;

extern PlayerDef *delayed_external_player;

extern EQBandDef eqbands[];

extern char *strinfo, *current, *xa_output, *OnOff[];

extern flag_t decoder_open, exit_flag, stream_open, show_time_stamp;
extern flag_t external_player, output_muted, djmode_enabled, output_open;
extern flag_t equalizer_enabled, auto_recover, prompt_now;
extern flag_t multiple_audio_procs;

extern sighandler_t sigchild_handler;

/************************************
 *          Local Variables         *
 ************************************/

static char *CHANNEL[] = { "", "Mono", "Stereo", "", "", "", "", ""};
static char tmpstr_de[COMM_SIZE];
static flag_t first_startup = TRUE;
static XA_Message message;

/************************************
 *     Internal Decoder control     *
 ************************************/

void stop_internal()
{
    if (decoder_open) {
	control_message_send(player, XA_MSG_COMMAND_STOP);
	set_state(IDLE);
    }
}

void pause_internal()
{
    if (decoder_open) {
	control_message_send(player, XA_MSG_COMMAND_PAUSE);
	set_state(PAUSED);
    }
}

void resume_internal()
{
    if (decoder_open) {
	control_message_send(player, XA_MSG_COMMAND_PLAY);
	set_state(PLAY);
    }
}

void seek_internal(int offset, int scale)
{
    if (decoder_open && stream_open)
	control_message_send(player, XA_MSG_COMMAND_SEEK, offset, scale);
}

void mixer_internal(int bal, int pcm, int mas)
{
    if (decoder_open && output_open) {
	control_message_send(player,
			     XA_MSG_SET_OUTPUT_VOLUME,
			     bal, pcm, mas);
	control_message_send(player, XA_MSG_GET_OUTPUT_VOLUME);
    }
}

void play_internal(char *stream)
{
    if (!decoder_open)
	open_decoder();
    else {
	close_internal_input();

	if (!output_open)
	    control_message_send(player, XA_MSG_COMMAND_OUTPUT_OPEN, 
				 xa_output);

	control_message_send(player, XA_MSG_COMMAND_INPUT_OPEN, stream);
	control_message_send(player, XA_MSG_COMMAND_PLAY);
    }
}

void close_decoder()
{
    if (decoder_open) {
	if (exit_flag)
	    prompt_now = FALSE;

	close_internal_input();
	control_message_send(player, XA_MSG_COMMAND_EXIT);
    } else
	show_user(DEBUG_MSG " Xaudio decoder is not running");
}

void /* user */ open_decoder()
{
    if (decoder_open) {
	show_user(DEBUG_MSG " Xaudio decoder is already running");
	return;
    }

    decoder_open = TRUE;
    output_open = stream_open = equalizer_enabled = FALSE;

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

    if (player_new((XA_Control **) & player, NULL) != XA_SUCCESS)
	decoder_open = FALSE;
    else {
	control_message_send(player,
			     XA_MSG_SET_NOTIFICATION_MASK, NOTIFY_MASK);
	control_message_send(player,
			     XA_MSG_SET_INPUT_TIMECODE_GRANULARITY,
			     DEFAULT_GRANULARITY);
	control_message_send(player, XA_MSG_SET_CODEC_EQUALIZER, NULL);
	control_message_send(player, XA_MSG_GET_CODEC_EQUALIZER);
    }

    signal(SIGTSTP, SIG_IGN);
    signal(SIGTTIN, SIG_IGN);
    signal(SIGTTOU, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGTERM, SIG_IGN);
    signal(SIGINT, (sighandler_t) int_sig_handler);
    signal(SIGCHLD, (sighandler_t) sigchild_handler);
    signal(SIGALRM, (sighandler_t) alarm_sig_handler);
    signal(SIGWINCH, (sighandler_t) winch_sig_handler);

    if (!decoder_open)
	show_user(ERROR_MSG " Xaudio decoder initialization failed");

#ifdef OGG_MODULE
    if (decoder_open) {
	sprintf(tmpstr_de, "function@%x",
		(unsigned int) (*vorbis_codec_module_register));
	control_message_send(player,
			     XA_MSG_COMMAND_CODEC_MODULE_REGISTER,
			     tmpstr_de);
    }
#endif
}

void close_internal_input()
{
    if (state == PLAY)
	control_message_send(player, XA_MSG_COMMAND_STOP);

    if (stream_open)
	control_message_send(player, XA_MSG_COMMAND_INPUT_CLOSE);
}

int close_internal_output()
{
    if (decoder_open) {
	close_internal_input();

	if (output_open && !multiple_audio_procs) {
	    control_message_send(player, XA_MSG_COMMAND_OUTPUT_CLOSE);
	    return TRUE;
	}
    }

    return FALSE;
}

void /* user */ set_internal_output()
{
    char *outp;

    outp = g_dbuf(args, 1);

    if (!outp)
	print_cmnd_help(SET_XAUDIO_OUTPUT);
    else if (decoder_open) {
	if (xa_output && strcmp(outp, xa_output) == 0)
	    return;

	if (xa_output)
	    free(xa_output);
	xa_output = strdup(outp);
	close_internal_input();

	if (output_open)
	    control_message_send(player, XA_MSG_COMMAND_OUTPUT_CLOSE);
    }
}

void /* user */ mute_output()
{
    if (!decoder_open || !output_open)
	show_user(DEBUG_MSG " Xaudio output not available");
    else {
	if (!output_muted)
	    control_message_send(player, XA_MSG_COMMAND_OUTPUT_MUTE);
	output_muted = TRUE;
    }
}

void /* user */ unmute_output()
{
    if (!decoder_open || !output_open)
	show_user(DEBUG_MSG " Xaudio output not available");
    else {
	if (output_muted)
	    control_message_send(player, XA_MSG_COMMAND_OUTPUT_UNMUTE);
	output_muted = FALSE;
    }
}

void /* user */ disable_equalizer()
{
    if (decoder_open) {
	control_message_send(player, XA_MSG_SET_CODEC_EQUALIZER, NULL);
	equalizer_enabled = FALSE;
    }
}

void /* user */ set_equalizer()
{
    if (decoder_open) {
	control_message_send(player,
			     XA_MSG_SET_CODEC_EQUALIZER, &equalizer);
	control_message_send(player, XA_MSG_GET_CODEC_EQUALIZER);
	equalizer_enabled = TRUE;
    }
}

void set_equalizer_band(int band, char value)
{
    int i;

    for (i = eqbands[band].low; i <= eqbands[band].high; i++) {
	equalizer.left[i] = value;
	equalizer.right[i] = value;
    }

    set_equalizer();
}

void /* user */ show_eq_bands()
{
    int i;

    for (i = 0; i < MAX_EQ_BANDS; i++)
	add_outbuffer(MISC_MSG " Band %d == %d", 
		      i, equalizer.left[eqbands[i].low]);

    if (!equalizer_enabled)
	add_outbuffer(DEBUG_MSG " Equalizer is Disabled");

    flush_outbuffer();
}

void /* user */ set_eq_band_comm()
{
    char *band, *val, *bmem, *vmem, *bm, *vm;
    int i, bandi, vali;

    band = g_dbuf(args, 1);
    val = g_dbuf(args, 2);
    bandi = vali = 0;

    if (!band || !val) {
	print_cmnd_help(SET_EQ_BAND);
	return;
    }

    if (strcmp(band, "*") == 0) {
	vali = atoi(val);

	if (vali >= -128 && vali <= 127) {
	    for (i = 0; i < XA_DECODER_EQUALIZER_NB_BANDS; i++)
		equalizer.left[i] = equalizer.right[i] = vali;

	    set_equalizer();
	} else
	    show_user(DEBUG_MSG " Invalid band val: %d. Range is [%d, %d]",
		      vali, EQ_BAND_VAL_LOW, EQ_BAND_VAL_HIGH);
    } else {
	bmem = vmem = NULL;
	bm = tokenize(band, &bmem, ',');
	vm = tokenize(val, &vmem, ',');

	while (bm && vm) {
	    bandi = atoi(bm);
	    vali = atoi(vm);

	    if (bandi >= 0 && bandi < MAX_EQ_BANDS) {
		if (vali >= -128 && vali <= 127)
		    set_equalizer_band(bandi, vali);
		else
		    show_user(DEBUG_MSG
			      " Invalid band val: %d. Range is [%d, %d]", 
			      vali, EQ_BAND_VAL_LOW, EQ_BAND_VAL_HIGH);
	    } else
		show_user(DEBUG_MSG " Invalid band: %d. Range is [0, %d]",
			  bandi, MAX_EQ_BANDS - 1);

	    bm = tokenize(NULL, &bmem, ',');
	    vm = tokenize(NULL, &vmem, ',');
	}
    }
}

int get_mixer_param(char *param, int saved)
{
    int val;

    if (*param == '+')
	val = saved + atoi((param + 1));
    else if (*param == '-')
	val = saved - atoi((param + 1));
    else
	val = atoi(param);

    if (val > 100)
	val = 100;
    else if (val < 0)
	val = 0;

    return val;
}

void /* user */ set_mixer()
{
    char *param;
    int master, pcm, bal;

    master = pcm = bal = 0;

    if (args->len != 4) {
	print_cmnd_help(SET_MIXER);
	return;
    }

    param = g_dbuf(args, 1);
    if (param)
	master = get_mixer_param(param, vinfo.master_level);

    param = g_dbuf(args, 2);
    if (param)
	pcm = get_mixer_param(param, vinfo.pcm_level);

    param = g_dbuf(args, 3);
    if (param)
	bal = get_mixer_param(param, vinfo.balance);

    mixer_internal(bal, pcm, master);
}

void /* user */ toggle_dj_mode()
{
    if (!decoder_open) {
	show_user(DEBUG_MSG " Xaudio decoder unavailable");
	return;
    }

    djmode_enabled = !djmode_enabled;

    if (djmode_enabled)
	control_message_send(player,
			     XA_MSG_SET_INPUT_TIMECODE_GRANULARITY,
			     DJ_GRANULARITY);
    else
	control_message_send(player,
			     XA_MSG_SET_INPUT_TIMECODE_GRANULARITY,
			     DEFAULT_GRANULARITY);

    show_user(STATUS_MSG " DJ mode %s", OnOff[djmode_enabled]);
}

void handle_decoder_state_change(unsigned int st)
{
    switch (st) {
    case XA_PLAYER_STATE_PLAYING:
	set_state(PLAY);
	break;
    case XA_PLAYER_STATE_PAUSED:
	set_state(PAUSED);
	break;
    case XA_PLAYER_STATE_STOPPED:
	set_state(IDLE);
	break;
    case XA_PLAYER_STATE_EOS:
	post_event(ESTREAM_EVENT);
	determine_next_stream();
	break;
    default:
	break;
    }
}

void handle_decoder_message()
{
    switch (message.code) {
    case XA_MSG_NOTIFY_READY:

	add_proclist(player->decoder_pid, J_DECINT, FALSE, NULL, NULL, 
		     "xaudio decoder");

	if (!first_startup && !external_player && state != EXTERNAL && 
	    *current != ZCHAR) {
	    control_message_send(player, XA_MSG_COMMAND_INPUT_OPEN,
				 current);
	    control_message_send(player, XA_MSG_COMMAND_PLAY);
	}

	first_startup = FALSE;
	break;
    case XA_MSG_NOTIFY_EXITED:
	player_delete((void *) player);
	decoder_open = stream_open = output_open = FALSE;

	if (exit_flag)
	    shut_down();

	break;
    case XA_MSG_NOTIFY_CODEC_EQUALIZER:

	if (message.data.equalizer)
	    equalizer = *message.data.equalizer;

	break;
    case XA_MSG_NOTIFY_OUTPUT_STATE:

	switch (message.data.state) {
	case XA_OUTPUT_STATE_OPEN:
	    output_open = TRUE;
	    break;
	case XA_OUTPUT_STATE_CLOSED:
	    output_open = FALSE;
	    if (delayed_external_player) {
		usleep(80000);	/* audio resources should be freed by now...
				   but good to be safe                       */
		play_external(delayed_external_player, current);
		delayed_external_player = NULL;
	    }
	    break;
	}
	break;

    case XA_MSG_NOTIFY_INPUT_STATE:

	switch (message.data.state) {
	case XA_INPUT_STATE_OPEN:
	    stream_open = TRUE;
	    break;
	case XA_OUTPUT_STATE_CLOSED:
	    stream_open = FALSE;
	    break;
	}

	break;
    case XA_MSG_NOTIFY_PLAYER_STATE:
	handle_decoder_state_change(message.data.state);
	break;
    case XA_MSG_NOTIFY_INPUT_POSITION:
	pinfo = message.data.position;
	break;
    case XA_MSG_NOTIFY_INPUT_TIMECODE:
	tinfo = message.data.timecode;
	if (show_time_stamp) {
	    if (djmode_enabled) {
		if (tinfo.f == 0)
		    update_prompt(TRUE);
	    } else
		update_prompt(TRUE);
	}
	break;
    case XA_MSG_NOTIFY_OUTPUT_BALANCE:
	vinfo.balance = message.data.volume.balance;
	break;
    case XA_MSG_NOTIFY_OUTPUT_PCM_LEVEL:
	vinfo.pcm_level = message.data.volume.pcm_level;
	break;
    case XA_MSG_NOTIFY_OUTPUT_MASTER_LEVEL:
	vinfo.master_level = message.data.volume.master_level;
	break;
    case XA_MSG_NOTIFY_STREAM_DURATION:
	dinfo.h = message.data.duration / 3600;
	dinfo.m = (message.data.duration - (dinfo.h * 3600)) / 60;
	dinfo.s = message.data.duration % 60;
	break;
    case XA_MSG_NOTIFY_STREAM_PARAMETERS:
	sprintf(strinfo, "%s %dkbps %ldHz",
		CHANNEL[message.data.stream_parameters.nb_channels],
		message.data.stream_parameters.bitrate,
		message.data.stream_parameters.frequency);
	mysetenv(CURR_INFO, strinfo);
	break;
    case XA_MSG_NOTIFY_ERROR:
	if (XA_ERROR_TIMEOUT != message.data.error.code) {
	    sprintf(tmpstr_de, ERROR_MSG " Error %d (%s)",
		    message.data.error.code, message.data.error.message);
	    add_dlog(tmpstr_de);
	}
	break;
    case XA_MSG_NOTIFY_DEBUG:
	sprintf(tmpstr_de, DEBUG_MSG " %s", message.data.debug.message);
	add_dlog(tmpstr_de);
	break;
    default:
	break;
    }
}

void monitor_decoder()
{
    int status;

    status = control_message_get((void *) player, &message);
    while (status == 1) {
	handle_decoder_message();
	status = control_message_get((void *) player, &message);
    }

    if (status == XA_ERROR_PIPE_CLOSED) {
	decoder_open = FALSE;

	if (auto_recover)
	    sprintf(tmpstr_de, 
		    ERROR_MSG" Xaudio decoder went down unexpectedly!");
	else
	    sprintf(tmpstr_de, ERROR_MSG
		    " Xaudio decoder went down unexpectedly! Try \"%s\"",
		    com[START_DECODER].name);

	add_dlog(tmpstr_de);
	add_console("%s", tmpstr_de);

	if (auto_recover) {
	    add_console(DEBUG_MSG " Restarting decoder...");
	    open_decoder();
	}
    } else if (status < 0) {
	sprintf(tmpstr_de, 
		ERROR_MSG " Error %d in control_message_get()", status);
	add_dlog(tmpstr_de);
    }
}
