/***************************************************************************
 *   Copyright (C) 2007 by Sergio Pistone <sergio_pistone@yahoo.com.ar>    *
 *   Heavily based on CoolPlayer by Niek Albers (Copyright (C) 2000-2001)  *
 *                                                                         *
 *   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.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "alsaplayer.h"

#ifdef HAVE_ALSA

#include <alsa/asoundlib.h>

#define BUFFER_FRAMES 4096

ALSAPlayer::ALSAPlayer( bool verbose ):
	m_verbose( verbose ),
	m_playbackHandle( 0 )
{
}

ALSAPlayer::~ALSAPlayer()
{
}

void ALSAPlayer::setVerbose( bool verbose )
{
	m_verbose = verbose;
}

bool ALSAPlayer::isVerbose()
{
	return m_verbose;
}

bool ALSAPlayer::initALSA( Input& input )
{
	if ( ! input.open() )
		return false;

	snd_pcm_hw_params_t* hwParams;
	snd_pcm_sw_params_t *swParams;

	int error;
	if ( ( error = snd_pcm_open( &m_playbackHandle, "plughw", SND_PCM_STREAM_PLAYBACK, 0 ) ) < 0 )
	{
		fprintf( stderr, "Couldn't open audio device (%s)\n", snd_strerror( error ) );
		return false;
	}

	if ( ( error = snd_pcm_hw_params_malloc( &hwParams ) ) < 0 )
	{
		fprintf( stderr, "Couldn't allocate hardware parameter structure (%s)\n", snd_strerror( error ) );
		snd_pcm_close( m_playbackHandle );
		m_playbackHandle = 0;
		return false;
	}

	snd_pcm_format_t sampleFormat;
	switch ( input.getBitsPerSample() )
	{
		case 8:
			sampleFormat = SND_PCM_FORMAT_U8;
			break;
		case 16:
			sampleFormat = SND_PCM_FORMAT_S16_LE;
			break;
		case 24:
			sampleFormat = SND_PCM_FORMAT_S24_LE;
			break;
		case 32:
			sampleFormat = SND_PCM_FORMAT_S32_LE;
			break;
		default:
			fprintf( stderr, "Unknown sample format (%d bits per sample)\n", input.getBitsPerSample() );
			snd_pcm_close( m_playbackHandle );
			m_playbackHandle = 0;
			return false;
	}

	const char* errorMsg = 0;
	unsigned int exactSampleRate = input.getSampleRate();
	if ( ( error = snd_pcm_hw_params_any( m_playbackHandle, hwParams ) ) < 0 )
		errorMsg = snd_strerror( error );
	else if ( ( error = snd_pcm_hw_params_set_access( m_playbackHandle, hwParams, SND_PCM_ACCESS_RW_INTERLEAVED ) ) < 0 )
		errorMsg = snd_strerror( error );
	else if ( ( error = snd_pcm_hw_params_set_format( m_playbackHandle, hwParams, sampleFormat ) ) < 0 )
		errorMsg = snd_strerror( error );
	else if ( ( error = snd_pcm_hw_params_set_rate_near( m_playbackHandle, hwParams, &exactSampleRate , 0 ) ) < 0 )
		errorMsg = snd_strerror( error );
	else if ( ( error = snd_pcm_hw_params_set_channels( m_playbackHandle, hwParams, input.getChannels() ) ) < 0 )
		errorMsg = snd_strerror( error );
	//else if ( ( error = snd_pcm_hw_params_set_buffer_size( m_playbackHandle, hwParams, BUFFER_FRAMES*16 ) ) < 0 )
	//	errorMsg = snd_strerror( error );
	else if ( ( error = snd_pcm_hw_params( m_playbackHandle, hwParams ) ) < 0 )
		errorMsg = snd_strerror( error );

	snd_pcm_hw_params_free( hwParams );

	if ( errorMsg )
	{
		fprintf( stderr, "Couldn't set hw params (%s)\n", snd_strerror( error ) );
		snd_pcm_close( m_playbackHandle );
		m_playbackHandle = 0;
		return false;
	}


	// tell ALSA to wake us up whenever BUFFER_FRAMES or more frames of playback data can be delivered

	if ( ( error = snd_pcm_sw_params_malloc( &swParams ) ) < 0 )
	{
		fprintf( stderr, "Couldn't allocate software parameter structure (%s)\n", snd_strerror( error ) );
		snd_pcm_close( m_playbackHandle );
		m_playbackHandle = 0;
		return false;
	}

	if ( ( error = snd_pcm_sw_params_current( m_playbackHandle, swParams ) ) < 0 )
		errorMsg = snd_strerror( error );
	else if ( ( error = snd_pcm_sw_params_set_avail_min( m_playbackHandle, swParams, BUFFER_FRAMES ) ) < 0 )
		errorMsg = snd_strerror( error );
	else if ( ( error = snd_pcm_sw_params_set_start_threshold( m_playbackHandle, swParams, 0U ) ) < 0 )
		errorMsg = snd_strerror( error );
	else if ( ( error = snd_pcm_sw_params( m_playbackHandle, swParams ) ) < 0 )
		errorMsg = snd_strerror( error );

	snd_pcm_sw_params_free( swParams );

	if ( errorMsg )
	{
		fprintf( stderr, "Couldn't set sw params (%s)\n", snd_strerror( error ) );
		snd_pcm_close( m_playbackHandle );
		m_playbackHandle = 0;
		return false;
	}

	if ( ( error = snd_pcm_prepare( m_playbackHandle ) ) < 0 )
	{
		fprintf ( stderr, "Couln't prepare audio interface for use (%s)\n", snd_strerror ( error ) );
		return false;
	}

	return true;
}

void ALSAPlayer::closeALSA()
{
	snd_pcm_close ( m_playbackHandle );
	m_playbackHandle = 0;
}

bool ALSAPlayer::play( Input& input )
{
	if ( ! initALSA( input ) )
		return false;

	if ( m_verbose )
	{
		int length = input.getTotalTime();
		if ( length > 0 )
			fprintf( stdout, "Duration: %.1f secs\n", length / 1000.0 );
		else
			fprintf( stdout, "Duration: unknown\n" );
		fprintf(
			stdout, "Channels: %d\nSample rate: %d Hz\nBits per sample: %d bits\n",
			input.getChannels(),
			input.getSampleRate(),
			input.getBitsPerSample()
		);
	}

	const unsigned int blockAlign = input.getBlockAlign();
	const unsigned int BUFFER_SIZE = BUFFER_FRAMES*blockAlign;
	const float sampleRate = (float)input.getSampleRate();

	unsigned char buffer[BUFFER_FRAMES*blockAlign];
	unsigned long read;
	unsigned long long samplesWritten = 0;

	bool moreData;
	do
	{
		moreData = input.getPCMChunk( buffer, BUFFER_SIZE, read );

		if ( read )
		{
			 // wait for the device to have enough space for our data
			snd_pcm_wait( m_playbackHandle, -1 );

			samplesWritten += snd_pcm_writei(
				m_playbackHandle,
				buffer,
				read/blockAlign
			);

			if ( m_verbose )
			{
				fprintf( stdout, "Progress: %.1f secs\r", samplesWritten / sampleRate );
				fflush( stdout );
			}
		}
	}
	while ( moreData );

	if ( m_verbose )
		fprintf( stdout, "\n" );

	closeALSA();

	return true;
}

#endif // HAVE_ALSA
