/*
 * iaxclient_lib: An Inter-Asterisk eXchange communication library
 *
 * Module: audio_alsa
 * Purpose: Audio code to output to ALSA
 * based on audio_file, originally Developed by: Steve Kann
 * Developed by: Panfilov Dmitry
 * Creation Date: Febr 9, 2006
 *
 * This program is free software, distributed under the terms of
 * the GNU Lesser (Library) General Public License
 *
 * IAX library Copyright (c) 2001 Linux Support Services
 * IAXlib is free software, distributed under the terms of
 * the GNU Lesser (Library) General Public License
 *
 */

#include "iaxclient_lib.h"
#define _POSIX_C_SOURCE 1
#include <alsa/asoundlib.h>

static snd_pcm_t *stream_out;
static snd_pcm_t *stream_in;
static double input_level = 1;
static double output_level = 1;

#define FRAMES_PER_BUFFER 80 /* 80 frames == 10ms */


static int alsa_play_sound(struct iaxc_sound *inSound, int ring) {
  return 0;
}

int alsa_stop_sound(int soundID) {
  return 0; 
}


int alsa_start (struct iaxc_audio_driver *d ) {
    return 0;
}

int alsa_stop (struct iaxc_audio_driver *d ) {
    return 0;
}

void alsa_shutdown_audio() 
{
    return;
}


int alsa_input(struct iaxc_audio_driver *d, void *samples, int *nSamples) {

    /* we don't return partial buffers */
    long r;
    long byteread=*nSamples;
    static int h;
    void *ptr = samples;
    *nSamples=0;
    snd_pcm_start(stream_in);	    
    if(h==1) { h=0; return 0;}
    do{
	r = snd_pcm_readi(stream_in, ptr, byteread);
        if (r == -EAGAIN){
            continue;
	}
	if (r == - EPIPE) {
	    snd_pcm_prepare(stream_in);
	    continue;
	}
	 ptr += (r * 2);
	 byteread -= r;
	 *nSamples += r;
    }while(r >=0 && byteread >0);
    h=1;
    
    /* software mute, but keep data flowing for sync purposes */
    if (input_level == 0) {
	memset(samples, 0, 2 * *nSamples);
    }
    
    return 0;
}

int alsa_output(struct iaxc_audio_driver *d, void *samples, int nSamples) {

        long r;
	snd_pcm_start(stream_out);	
	/* software mute, but keep data flowing for sync purposes */
        if (output_level == 0) {
		/* FIXME: changing user supplied data! */
		memset(samples, 0, 2 * nSamples);
	}
        while (nSamples > 0) {
                r = snd_pcm_writei(stream_out, samples, nSamples);
                if (r == -EAGAIN){
                    continue;
		}
		if (r == - EPIPE) {
		    snd_pcm_prepare(stream_out);
		    continue;
		}
                if (r < 0) {
		    printf("r=%d\n",r);
		}
                samples += r * 2;
                nSamples -= r;
        }
        return 0;
}

int alsa_select_devices (struct iaxc_audio_driver *d, int input, int output, int ring) {
    return 0;
}

int alsa_selected_devices (struct iaxc_audio_driver *d, int *input, int *output, int *ring) {
    *input = 0;
    *output = 0;
    *ring = 0;
    return 0;
}

int alsa_destroy (struct iaxc_audio_driver *d ) 
{
	/* TODO: something should happen here */
    return 0;
}

double alsa_input_level_get(struct iaxc_audio_driver *d){
    return input_level;
}

double alsa_output_level_get(struct iaxc_audio_driver *d){
    return output_level;
}

int alsa_input_level_set(struct iaxc_audio_driver *d, double level){
    input_level = (level < 0.5) ? 0 : 1;
    return 0;
}

int alsa_output_level_set(struct iaxc_audio_driver *d, double level){
    input_level = (level < 0.5) ? 0 : 1;
    return 0;
}


/* initialize audio driver */
int alsa_initialize (struct iaxc_audio_driver *d ,int sample_rate) {
    int i;
    int err;
    short buf[128];
    snd_pcm_hw_params_t *hw_params;
    snd_pcm_sw_params_t *sw_params;
    
    if ((err = snd_pcm_open (&stream_out, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
        fprintf (stderr, "cannot open audio device default (%s)\n", 
        snd_strerror (err));
	exit (1);
    }
    if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
        fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n",
	snd_strerror (err));
	exit (1);
    }
    if ((err = snd_pcm_hw_params_any (stream_out, hw_params)) < 0) {
	fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n",
	snd_strerror (err));
	exit (1);
    }
    if ((err = snd_pcm_hw_params_set_access (stream_out, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
	fprintf (stderr, "cannot set access type (%s)\n",
	snd_strerror (err));
	exit (1);
    }
    if ((err = snd_pcm_hw_params_set_format (stream_out, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
	fprintf (stderr, "cannot set sample format (%s)\n",
	snd_strerror (err));
	exit (1);
    }
    if ((err = snd_pcm_hw_params_set_rate (stream_out, hw_params, sample_rate, 0)) < 0) {
        fprintf (stderr, "cannot set sample rate (%s)\n",
	snd_strerror (err));
	exit (1);
    }
    if ((err = snd_pcm_hw_params_set_channels (stream_out, hw_params, 1)) < 0) {
	fprintf (stderr, "cannot set channel count (%s)\n",
	snd_strerror (err));
	exit (1);
    }
    if ((err = snd_pcm_hw_params (stream_out, hw_params)) < 0) {
	fprintf (stderr, "cannot set parameters (%s)\n",
	snd_strerror (err));
	exit (1);
    }
    
    snd_pcm_sw_params_malloc(&sw_params);
    
    err = snd_pcm_sw_params_current(stream_out, sw_params);
    if (err < 0) {
	printf("Unable to determine current swparams for playback: %s\n", snd_strerror(err));
        return err;
    }
    err = snd_pcm_sw_params_set_start_threshold(stream_out, sw_params, 80);
    if (err < 0) {
        printf("Unable to set start threshold mode for playback: %s\n", snd_strerror(err));
        return err;
    }
    err = snd_pcm_sw_params(stream_out, sw_params);
    if (err < 0) {
        printf("Unable to set sw params for playback: %s\n", snd_strerror(err));
        return err;
    }

    if ((err = snd_pcm_open (&stream_in, "default", SND_PCM_STREAM_CAPTURE, 0)) < 0) {
        fprintf (stderr, "cannot open audio device default (%s)\n", 
        snd_strerror (err));
	exit (1);
    }
    if ((err = snd_pcm_hw_params_any (stream_in, hw_params)) < 0) {
	fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n",
	snd_strerror (err));
	exit (1);
    }
    if ((err = snd_pcm_hw_params_set_access (stream_in, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
	fprintf (stderr, "cannot set access type (%s)\n",
	snd_strerror (err));
	exit (1);
    }
    if ((err = snd_pcm_hw_params_set_format (stream_in, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
	fprintf (stderr, "cannot set sample format (%s)\n",
	snd_strerror (err));
	exit (1);
    }
    if ((err = snd_pcm_hw_params_set_rate (stream_in, hw_params, sample_rate, 0)) < 0) {
        fprintf (stderr, "cannot set sample rate (%s)\n",
	snd_strerror (err));
	exit (1);
    }
    if ((err = snd_pcm_hw_params_set_channels (stream_in, hw_params, 1)) < 0) {
	fprintf (stderr, "cannot set channel count (%s)\n",
	snd_strerror (err));
	exit (1);
    }
    if ((err = snd_pcm_hw_params (stream_in, hw_params)) < 0) {
	fprintf (stderr, "cannot set parameters (%s)\n",
	snd_strerror (err));
	exit (1);
    }
    
    err = snd_pcm_sw_params_current(stream_in, sw_params);
    if (err < 0) {
	printf("Unable to determine current swparams for playback: %s\n", snd_strerror(err));
        return err;
    }
    err = snd_pcm_sw_params_set_start_threshold(stream_in, sw_params, 80);
    if (err < 0) {
        printf("Unable to set start threshold mode for playback: %s\n", snd_strerror(err));
        return err;
    }
    err = snd_pcm_sw_params(stream_in, sw_params);
    if (err < 0) {
        printf("Unable to set sw params for playback: %s\n", snd_strerror(err));
        return err;
    }
    
    
    if ((err = snd_pcm_prepare (stream_in)) < 0) {
        fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
	snd_strerror (err));
	 exit (1);
    }

    if ((err = snd_pcm_prepare (stream_out)) < 0) {
        fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
	snd_strerror (err));
	 exit (1);
    }
									         
    d->initialize = alsa_initialize;
    d->destroy = alsa_destroy;
    d->select_devices = alsa_select_devices;
    d->selected_devices = alsa_selected_devices;
    d->start = alsa_start;
    d->stop = alsa_stop;
    d->output = alsa_output;
    d->input = alsa_input;
    d->input_level_get = alsa_input_level_get;
    d->input_level_set = alsa_input_level_set;
    d->output_level_get = alsa_output_level_get;
    d->output_level_set = alsa_output_level_set;
    d->play_sound = alsa_play_sound;
    d->stop_sound = alsa_stop_sound;

    return 0;
}
