/*
    Copyright (C) 2004 Matthias Kretz <kretz@kde.org>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License version 2 as published by the Free Software Foundation.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.

*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef HAVE_LIBJACK

#include <jack/jack.h>
#include <jack/ringbuffer.h>

#include "debug.h"
#include "audioio.h"
#include "audiosubsys.h"
#include "iomanager.h"
#include "dispatcher.h"

#ifndef MIN
#define MIN(a,b)        ((a) < (b) ? (a) : (b))
#endif

#undef DEBUG_WAVEFORM
#ifdef DEBUG_WAVEFORM
#include <fstream>
#endif

#include <cstdlib>
#include <cstring>

namespace Arts {

class AudioIOJack : public AudioIO, public TimeNotify {
private:
#ifdef DEBUG_WAVEFORM
	std::ofstream plotfile;
#endif
	char * processBuffer;
	size_t buffersize;

protected:
	jack_client_t *jack;
	jack_port_t *outleft, *outright;
	jack_port_t *inleft, *inright;
	jack_ringbuffer_t *olb, *orb, *ilb, *irb;

public:
	AudioIOJack();

	void notifyTime();

	void setParam( AudioParam p, int & val );
	int getParam(AudioParam param);

	static int jackCallback( jack_nframes_t, void * );

	bool open();
	void close();
	int read(void *buffer, int size);
	int write(void *buffer, int size);
};

REGISTER_AUDIO_IO(AudioIOJack,"jack","Jack Audio Connection Kit");
}

using namespace std;
using namespace Arts;

AudioIOJack::AudioIOJack()
	:
#ifdef DEBUG_WAVEFORM
	plotfile( "/dev/shm/audioiojack.plot" ),
#endif
	jack( 0 )
	, outleft( 0 )
	, outright( 0 )
	, inleft( 0 )
	, inright( 0 )
{
	/*
	 * default parameters
	 */
	param( samplingRate ) = 44100;
	paramStr( deviceName ) = "jack";
	param( fragmentSize ) = 512;
	param( fragmentCount ) = 2;
	param( channels ) = 2;
	param( direction ) = 2;
	param( format ) = 32;
}

bool AudioIOJack::open()
{
	string& _error = paramStr( lastError );
	jack = jack_client_new( "artsd" );
	if( jack == 0 )
	{
		_error = "Couldn't connect to jackd";
		return false;
	}

	int& _sampleRate = param(samplingRate);
	_sampleRate = jack_get_sample_rate( jack );
	int& _fragmentSize = param(fragmentSize);
	int& _fragmentCount = param(fragmentCount);

	/*
	 * don't allow unreasonable large fragmentSize/Count combinations,
	 * because "real" hardware also doesn't
	 */

	if(_fragmentSize > 1024*8) _fragmentSize = 1024*8;

	while(_fragmentSize * _fragmentCount > 1024*128)
		_fragmentCount--;

	jack_set_process_callback( jack, Arts::AudioIOJack::jackCallback, this );

	if( param( direction ) & directionWrite )
	{
		outleft = jack_port_register( jack, "out_1",
				JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 );
		outright = jack_port_register( jack, "out_2",
				JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 );
		olb = jack_ringbuffer_create(
				sizeof( jack_default_audio_sample_t ) *
				_fragmentSize * _fragmentCount );
		orb = jack_ringbuffer_create(
				sizeof( jack_default_audio_sample_t ) *
				_fragmentSize * _fragmentCount );
	}
	if( param( direction ) & directionRead )
	{
		inleft = jack_port_register( jack, "in_1",
				JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 );
		inright = jack_port_register( jack, "in_2",
				JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 );
		ilb = jack_ringbuffer_create(
				sizeof( jack_default_audio_sample_t ) *
				1024 * 64 );
		irb = jack_ringbuffer_create(
				sizeof( jack_default_audio_sample_t ) *
				1024 * 64 );
	}

	if( jack_activate( jack ) )
	{
		_error = "Activating as jack client failed.";
		return false;
	}

	const char **ports;
	if( param( direction ) & directionRead )
	{
		ports = jack_get_ports( jack, 0, 0, JackPortIsPhysical
				| JackPortIsOutput );
		if( ports == 0 )
		{
			arts_warning( "Cannot find any capture ports to"
					" connect to. You need to manually connect"
					" the capture ports in jack" );
		}
		else
		{
			if( ports[ 0 ] != 0 )
				jack_connect( jack, ports[ 0 ],
						jack_port_name( inleft ) );
			if( ports[ 1 ] != 0 )
				jack_connect( jack, ports[ 1 ],
						jack_port_name( inright ) );
			free( ports );
		}
	}
	if( param( direction ) & directionWrite )
	{
		ports = jack_get_ports( jack, 0, 0, JackPortIsPhysical
				| JackPortIsInput );
		if( ports == 0 )
		{
			arts_warning( "Cannot find any playback ports to"
					" connect to. You need to manually connect"
					" the playback ports in jack" );
		}
		else
		{
			if( ports[ 0 ] != 0 )
				jack_connect( jack, jack_port_name( outleft ),
						ports[ 0 ] );
			if( ports[ 1 ] != 0 )
				jack_connect( jack, jack_port_name( outright ),
						ports[ 1 ] );
			free( ports );
		}
	}

	// Install the timer
	Dispatcher::the()->ioManager()->addTimer(10, this);

	return true;
}

void AudioIOJack::close()
{
	jack_client_close( jack );
    Dispatcher::the()->ioManager()->removeTimer(this);
}

int AudioIOJack::jackCallback( jack_nframes_t nframes, void * args )
{
	AudioIOJack * that = static_cast<AudioIOJack*>( args );

	that->buffersize = nframes * sizeof( jack_default_audio_sample_t );
	if( that->outleft )
	{
		if( jack_ringbuffer_read_space( that->olb ) < that->buffersize )
		{
			that->processBuffer = static_cast<char *>(
					jack_port_get_buffer( that->outleft, nframes ) );
			memset( that->processBuffer, 0, that->buffersize );
			that->processBuffer = static_cast<char *>(
					jack_port_get_buffer( that->outright, nframes ) );
			memset( that->processBuffer, 0, that->buffersize );
		}
		else
		{
			that->processBuffer = static_cast<char *>(
					jack_port_get_buffer( that->outleft, nframes ) );
			jack_ringbuffer_read( that->olb, that->processBuffer, that->buffersize );
			that->processBuffer = static_cast<char *>(
					jack_port_get_buffer( that->outright, nframes ) );
			jack_ringbuffer_read( that->orb, that->processBuffer, that->buffersize );
		}
	}
	if( that->inleft )
	{
		that->processBuffer = static_cast<char *>(
				jack_port_get_buffer( that->inleft, nframes ) );
		jack_ringbuffer_write( that->ilb, that->processBuffer, that->buffersize );
		that->processBuffer = static_cast<char *>(
				jack_port_get_buffer( that->inright, nframes ) );
		jack_ringbuffer_write( that->irb, that->processBuffer, that->buffersize );
	}
	return 0;
}

void AudioIOJack::setParam( AudioParam p, int& val )
{
	// don't change the format - jack only supports 32 bit float
	if( p == format )
		return;
	AudioIO::setParam( p, val );
}

int AudioIOJack::getParam(AudioParam p)
{
	switch(p)
	{
		case canRead:
			return MIN( jack_ringbuffer_read_space( ilb ), jack_ringbuffer_read_space( irb ) ) * param( channels );
		case canWrite:
			return MIN( jack_ringbuffer_write_space( olb ), jack_ringbuffer_write_space( orb ) ) * param( channels );
		default:
			return AudioIO::getParam( p );
	}
}

int AudioIOJack::read(void *buffer, int size)
{
	float * floatbuffer = static_cast<float *>( buffer );
	if( param( channels ) == 2 )
	{
		float * end = ( float * )( static_cast<char *>( buffer ) + size );
		while( floatbuffer < end )
		{
			jack_ringbuffer_read( ilb, ( char* )( floatbuffer++ ), sizeof( float ) );
#ifdef DEBUG_WAVEFORM
			plotfile << *( floatbuffer - 1 ) << "\n";
#endif
			jack_ringbuffer_read( irb, ( char* )( floatbuffer++ ), sizeof( float ) );
		}
	}
	else if( param( channels ) == 1 )
	{
		jack_ringbuffer_read( ilb, ( char* )( floatbuffer ), size );
	}
	return size;
}

int AudioIOJack::write(void *buffer, int size)
{
	float * floatbuffer = static_cast<float *>( buffer );
	if( param( channels ) == 2 )
	{
		float * end = ( float * )( static_cast<char *>( buffer ) + size );
		while( floatbuffer < end )
		{
			jack_ringbuffer_write( olb, ( char* )( floatbuffer++ ), sizeof( float ) );
			jack_ringbuffer_write( orb, ( char* )( floatbuffer++ ), sizeof( float ) );
		}
	}
	else if( param( channels ) == 1 )
	{
		jack_ringbuffer_write( olb, ( char* )( floatbuffer ), size );
	}
	return size;
}

void AudioIOJack::notifyTime()
{
    int& _direction = param(direction);
    int& _fragmentSize = param(fragmentSize);

	for( ;; )
	{
		int todo = 0;
		if( ( _direction & directionRead ) && ( getParam( canRead ) >= _fragmentSize ) )
			todo |= AudioSubSystem::ioRead;

		if( ( _direction & directionWrite ) && ( getParam( canWrite ) >= _fragmentSize ) )
			todo |= AudioSubSystem::ioWrite;

		if( ! todo )
			return;

		AudioSubSystem::the()->handleIO( todo );
	}
}

#endif