#include <alsa/asoundlib.h>
#include "MonitorAudioALSA.h"
#include "../MonitorLogging.h"

MonitorAudioALSA::MonitorAudioALSA(const std::string* name, tSamplerate rate)
: MonitorAudio(name, rate) {
	run = false;
}

MonitorAudioALSA::~MonitorAudioALSA() {
	CloseDevice();
}

bool MonitorAudioALSA::Start(void *format) {
	if (InitDevice() < 0) {
		LOG_ERROR("[ALSA] Error initializing PCM device " << pcm_name)
		exit(10);
	}

	run = true;
	JThread::Start();
	return true;
}

void MonitorAudioALSA::Stop() {
	run = false;
}

int MonitorAudioALSA::InitDevice() {
	if ((pcm_name.length() == 0) || (pcm_rate == 0)) {
		LOG_ERROR("[ALSA] InitDevice Argument Error: pcm_name=" << pcm_name << " pcm_rate=" << pcm_rate )
		return -1;
	}


	int ret;

	snd_pcm_uframes_t pcm_buffer_size = audio_buffer->SampleLen;
	snd_pcm_stream_t pcm_stream = SND_PCM_STREAM_CAPTURE;

	unsigned int periods = 2;
	int direction = 0;

	/* Allocate the snd_pcm_hw_params_t structure on the stack. */
	snd_pcm_hw_params_t *hwparams;
	snd_pcm_hw_params_alloca(&hwparams);
	/* Open PCM. The last parameter of this function is the mode. */
	/* If this is set to 0, the standard mode is used. Possible   */
	/* other values are SND_PCM_NONBLOCK and SND_PCM_ASYNC.       */
	/* If SND_PCM_NONBLOCK is used, read / write access to the    */
	/* PCM device will return immediately. If SND_PCM_ASYNC is    */
	/* specified, SIGIO will be emitted whenever a period has     */
	/* been completely processed by the soundcard.                */
	ret = snd_pcm_open(&pcm_handle, pcm_name.c_str(), pcm_stream, 0);
	if (ret < 0) {
		LOG_ERROR("[ALSA] Error opening PCM device " << pcm_name << " ret:" << ret << snd_strerror(ret))
		return -1;
	}
	/* Init hwparams with full configuration space */
	ret = snd_pcm_hw_params_any(pcm_handle, hwparams);
	if (ret < 0) {
		LOG_ERROR("[ALSA] Can not configure this PCM device " << pcm_name << ". " << ret << "(" << snd_strerror(ret) << ")" )
		return -1;
	}
	/* Set access type. This can be either    */
	/* SND_PCM_ACCESS_RW_INTERLEAVED or       */
	/* SND_PCM_ACCESS_RW_NONINTERLEAVED.      */
	/* There are also access types for MMAPed */
	/* access, but this is beyond the scope   */
	/* of this introduction.                  */
	ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_NONINTERLEAVED);
	if (ret < 0) {
		LOG_ERROR("[ALSA] Error setting access " <<	pcm_name << ". " << ret <<"(" << snd_strerror(ret)<<")")
		return -1;
	}
	/* Set number of channels */
	ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, 2);
	if (ret < 0) {
		LOG_ERROR("[ALSA] Error setting channels " <<	pcm_name << ". " << ret <<"(" << snd_strerror(ret)<<")")
		return -1;
	}
	/* Set sample rate. If the exact rate is not supported */
	/* by the hardware, use nearest possible rate.         */
	ret = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &pcm_rate, &direction);
	if (ret < 0) {
		LOG_ERROR("[ALSA] Error setting rate " <<	pcm_name << ". " << ret <<"(" << snd_strerror(ret)<<")")
		return -1;
	}
	/* Set sample format */
	/* FLOAT LE -1.0 .. 1.0 */
	ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, SND_PCM_FORMAT_FLOAT_LE);
	//ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, SND_PCM_FORMAT_S16_LE);
	if (ret < 0) {
		LOG_ERROR("[ALSA] Error setting format " <<	pcm_name << ". " << ret <<"(" << snd_strerror(ret)<<")")
		return -1;
	}
	/* Set buffer size */
	ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &pcm_buffer_size);
	if (ret < 0) {
		LOG_ERROR("[ALSA] Error setting buffer size " <<	pcm_name << ". " <<  (int)pcm_buffer_size << " - " << ret <<"(" << snd_strerror(ret)<<")")
	}
	/* Set number of periods. Periods used to be called fragments. */
	ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &periods, &direction);
	if (ret < 0) {
		LOG_ERROR("[ALSA] Error setting periods  " <<	pcm_name << ". " << ret <<"(" << snd_strerror(ret)<<")")
	}
	LOG_INFO("[ALSA] Using pcm_buffer_size=" <<(int)pcm_buffer_size << " periods=" << periods )

	/* Apply HW parameter settings to */
	/* PCM device and prepare device  */
	ret = snd_pcm_hw_params(pcm_handle, hwparams);
	if (ret < 0) {
		LOG_ERROR("[ALSA] Error setting HW params " <<	pcm_name << ". " << ret <<"(" << snd_strerror(ret)<<")")
		return -1;
	}

	return 1;
}

int MonitorAudioALSA::CloseDevice() {
	snd_pcm_close(pcm_handle);
	return 0;
}

void* MonitorAudioALSA::Thread() {
	signed int num_samples;

	JThread::ThreadStarted();

	while(run) {
		num_samples = snd_pcm_readn(pcm_handle, (void**)audio_buffer->Ptrs, audio_buffer->SampleLen);
		if (num_samples > 0) {
			audio_buffer->Samples = num_samples;

			DataFromSoundIn(audio_buffer, m_pOwner);
		} else if (num_samples == -EPIPE) {
			int err = snd_pcm_prepare(pcm_handle);
			if (err < 0) {
				LOG_ERROR("[ALSA] Can't recovery from underrun, prepare failed: " << snd_strerror(err))
			}
		} else if (num_samples < 0) {
			LOG_ERROR( "[ALSA] Read error " <<  num_samples << "(" << snd_strerror(num_samples) << ")" )
			/*	Schliessen des Sounddevices und neustarten	*/
			CloseDevice();
			sleep(1);
			if (InitDevice() < 0) {
				LOG_ERROR("[ALSA] Error initializing PCM device " << pcm_name )
			}
		}
	}

	return NULL;
}

// vim: sw=4 ts=4 cindent
