/*
    Copyright (C) Dima Simakov <dima@p4.f106.n5000.z2.fidonet.org>

    This program is free software; you can redistribute 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.

    This program 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 received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    esd_a.c -- functions to play sound on Enlightened Sound Daemon by
    Carsten Haitzler (aka The Rasterman).

*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <esd.h>
#include <glib.h>

#include "timidity.h"
#include "common.h"
#include "output.h"
#include "controls.h"
#include "timer.h"
#include "instrum.h"
#include "playmidi.h"
#include "miditrace.h"

static int open_output(void); /* 0=success, 1=warning, -1=fatal error */
static void close_output(void);
static void output_data(int32 *buf, int32 count);
static int flush_output(void);
static void purge_output(void);
static int32 current_samples(void);
static int play_loop(void);

extern int default_play_event(void *);

static int    esd; /* esd socket */
static int32 play_counter, reset_samples;
static double play_start_time;

static char esd_stream_name[] = "Timidity";

#define dpm esd_play_mode
PlayMode dpm = {
    DEFAULT_RATE, PE_16BIT|PE_SIGNED, PF_NEED_INSTRUMENTS|PF_CAN_TRACE,
    -1,
    {0},
    "EsounD network audio", 'e',
    "localhost:5001",
    default_play_event,
    open_output,
    close_output,
    output_data,
    flush_output,
    purge_output,
    current_samples,
    play_loop
};

static int open_output(void)
{
    gint   rate, bits, channels, mode, func, format;
    
    if(dpm.encoding & PE_ALAW) {
	ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
		  "%s: A-Law is not supported by EsounD", dpm.name);
	return -1;
    }
    
    if(dpm.encoding & PE_ULAW) {
	ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
		  "%s: U-Law is not supported by EsounD", dpm.name);
	return -1;
    }
    
    channels = (dpm.encoding & PE_MONO) ? ESD_MONO : ESD_STEREO;
    rate = dpm.rate;
    bits = (dpm.encoding & PE_16BIT) ? ESD_BITS16 : ESD_BITS8;
    mode = ESD_STREAM;
    func = ESD_PLAY;
    format = bits | channels | mode | func;

    esd = 0;
    esd = esd_play_stream( format, rate, dpm.name, esd_stream_name );
    
    if ( esd == -1) {
	ctl->cmsg(CMSG_WARNING, VERB_NORMAL,
		  "Cannot open connection to EsounD daemon");
	ctl->cmsg(CMSG_WARNING, VERB_NORMAL,
		  "Tyring to start daemon ;)");
	system("esd &");
	sleep(3);
	esd = esd_play_stream( format, rate, dpm.name,
				esd_stream_name );
	if ( esd == -1) {
	    ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
		      "Cannot connect to daemon... sorry ;(");
	    return -1;
	}
    }
    return(0);
}

static void add_sample_counter(int32 count)
{
    current_samples(); /* update offset_samples */
    play_counter += count;
}

static void output_data(int32 *buf, int32 count)
{
    int32 add_count = count;

    if (!(dpm.encoding & PE_MONO)) count*=2;
    
    if (dpm.encoding & PE_16BIT) {
	s32tos16(buf, count);
	count *= 2;
    }
    else
	s32tos8(buf, count);
    
    add_sample_counter(add_count);
    if (write( esd, buf, count ) < 0 ) {
	ctl->cmsg(CMSG_ERROR,VERB_NORMAL,"EsounD write FAILED");
	ctl->close();
	exit(1);
    }
}

static void close_output(void)
{
    close( esd );
    play_counter = reset_samples = 0;
    dpm.fd=-1;
}

static int flush_output(void)
{
    int rc;
    
    while(trace_loop()) {
	rc = check_apply_control();
	if(RC_IS_SKIP_FILE(rc)) {
	    purge_output();
	    return rc;
	}
    }
    
    do
	{
	    rc = check_apply_control();
	    if(RC_IS_SKIP_FILE(rc)) {
		purge_output();
		return rc;
	    }
	    current_samples();
#ifdef HAVE_USLEEP
	    usleep(100000);
#endif
	} while(play_counter > 0);
    
    play_counter = reset_samples = 0;
    
    return RC_NONE;
}

static void purge_output(void)
{
    play_counter = reset_samples = 0;
}

static int play_loop(void)
{
    return 0;
}

static int32 current_samples(void)
{
    double realtime, es;
    
    realtime = get_current_calender_time();
    if(play_counter == 0) {
	    play_start_time = realtime;
	    return reset_samples;
    }
    es = dpm.rate * (realtime - play_start_time);
    if(es >= play_counter) {
	reset_samples += play_counter;
	play_counter = 0;
	play_start_time = realtime;
	return reset_samples;
    }
    if(es < 0)
	return 0;
    return (int32)es + reset_samples;
}
