/*

    Copyright (C) 2000 Stefan Westerfeld
                       stefan@space.twc.de

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.
  
    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.

    */

#include "artsflow.h"
#include "debug.h"
#include "convert.h"
#include "objectmanager.h"
#include "audiosubsys.h"
#include "dispatcher.h"
#include "iomanager.h"
#include "flowsystem.h"
#include "stdsynthmodule.h"
#include <stdio.h>
#include <iostream>

using namespace std;
using namespace Arts;

namespace Arts {

class Synth_PLAY_impl :	virtual public Synth_PLAY_skel,
						virtual public ASProducer,
						virtual public StdSynthModule,
						virtual public IONotify,
						virtual public TimeNotify
{
protected:
	AudioSubSystem *as;
	bool haveSubSys;
	/*
	 * these are to prevent the following situation
	 * 1) audio subsystem needs more data
	 * 2) calculation is started
	 * 3) somehow, some module makes a synchronous invocation to the outside
	 *    world and waits for the result
	 * 4) since the audio subsystem still needs data, and since we are in an
	 *    idle state now, another calculation will be started, which will of
	 *    course fail due to reentrancy
	 * 5) repeat 4) until result is there => lots of wasted CPU cycles (when
	 *    running with realtime priority: system freeze)
	 */
	bool inProgress;		// we are just doing some calculations
	bool restartIOHandling;	// I/O handlers removed upon reaching 4: restart

	int audioReadFD;
	int audioWriteFD;
	bool audioOpen;

	typedef unsigned char uchar;

	unsigned char *outblock;
	unsigned long maxsamples;
	unsigned long channels;
	int format;
	int bits;

	bool retryOpen;
public:
	/*
	 * functions from the SynthModule interface (which is inherited by
	 * SynthPlay)
	 */
	void streamInit() {
		as = AudioSubSystem::the();

		maxsamples = 0;
		outblock = 0;
		retryOpen = false;
		audioOpen = false;
		inProgress = false;

		haveSubSys = as->attachProducer(this);
		if(!haveSubSys)
		{
			arts_info("Synth_PLAY: audio subsystem is already used");
			return;
		}

		audioOpen = as->open();
		if(!audioOpen)
		{
			if(Dispatcher::the()->flowSystem()->suspended())
			{
				arts_info("/dev/dsp currently unavailable (retrying)");
				Dispatcher::the()->ioManager()->addTimer(1000, this);
				retryOpen = true;
			}
			else
			{
				arts_info("Synth_PLAY: audio subsystem init failed");
				arts_info("ASError = %s",as->error());
			}
			audioReadFD = audioWriteFD = -1;
		}
		else
		{
			audioReadFD = as->selectReadFD();
			audioWriteFD = as->selectWriteFD();
		}

		channels = as->channels();
		format = as->format();
		bits = as->bits();
		arts_debug("audio format is %d Hz, %d bits, %d channels",
					as->samplingRate(), bits, channels);
	}

	void notifyTime() {
		assert(retryOpen);

		audioOpen = as->open();

		if(audioOpen)
		{
			audioReadFD = as->selectReadFD();
			audioWriteFD = as->selectWriteFD();

			streamStart();
			arts_info("/dev/dsp ok");
			Dispatcher::the()->ioManager()->removeTimer(this);
			retryOpen = false;
		}
	}

	void streamStart() {
		IOManager *iom = Dispatcher::the()->ioManager();

		if(audioReadFD >= 0)
			iom->watchFD(audioReadFD, IOType::read|IOType::except, this);

		if(audioWriteFD >= 0)
			iom->watchFD(audioWriteFD, IOType::write|IOType::except, this);
	}

	void streamEnd() {
		if(retryOpen)
			Dispatcher::the()->ioManager()->removeTimer(this);

		arts_debug("Synth_PLAY: closing audio fd");
		if(audioReadFD >= 0 || audioWriteFD >= 0)
		{
			IOManager *iom = Dispatcher::the()->ioManager();
			iom->remove(this,IOType::all);
			audioReadFD = audioWriteFD = -1;
		}
		AudioSubSystem::the()->detachProducer();

		if(outblock)
		{
			delete[] outblock;
			outblock = 0;
		}
	}

	AutoSuspendState autoSuspend()
	{
		return static_cast<AutoSuspendState>(asSuspendStop|asConsumer);
	}

	void calculateBlock(unsigned long samples)
	{
		// no audio subsystem, no play
		if(!as->running() || !haveSubSys) return;

		if(samples > maxsamples)
		{
			maxsamples = samples;

			if(outblock) delete[] outblock;
			outblock = new uchar[maxsamples * channels * ( format & ( 8 | 16 | 32 ) ) / 8];
		}

		assert(channels);

		arts_assert(format == 8 || format == 16 || format == 17 || format == 32 );
		if(channels == 1)
		{
			if(format == 8)
				convert_mono_float_8(samples,invalue_left,outblock);
			else if(format == 16)
				convert_mono_float_16le(samples,invalue_left,outblock);
			else if(format == 17)
				convert_mono_float_16be(samples,invalue_left,outblock);
			else if(format == 32)
			{
				as->write( invalue_left, samples );
				return;
			}
		}
		else if(channels == 2)
		{
			if(format == 8)
				convert_stereo_2float_i8(samples,invalue_left,invalue_right,
													outblock);
			else if(format == 16)
				convert_stereo_2float_i16le(samples,invalue_left,invalue_right,
													outblock);
			else if(format == 17)
				convert_stereo_2float_i16be(samples,invalue_left,invalue_right,
													outblock);
			else if(format == 32)
			{
				float * buffer = ( float* )outblock;
				float * end = invalue_left + samples;
				while( invalue_left < end )
				{
					*buffer++ = *invalue_left++;
					*buffer++ = *invalue_right++;
				}
				as->write( outblock, 2 * samples * sizeof( float ) );
				return;
			}
		}
		else arts_warning("channels != 1 && channels != 2?");

		as->write(outblock,channels * (bits / 8) * samples);
	}

	/**
	 * notifyIO from the IONotify interface (IOManager)
	 */
	void notifyIO(int fd, int type)
	{
		arts_return_if_fail(as->running());
		assert(fd == audioReadFD || fd == audioWriteFD);

		if(inProgress)
		{
			if(!restartIOHandling)
			{
				// prevent lots of retries - we just can't do calculations
				// now, so we need to wait until the situation has resolved
				Dispatcher::the()->ioManager()->remove(this,IOType::all);
				restartIOHandling = true;
			}
			return;
		}

		// convert iomanager notification types to audiosubsys notification
		int asType = 0;

		if(type & IOType::read)		asType |= AudioSubSystem::ioRead;
		if(type & IOType::write)	asType |= AudioSubSystem::ioWrite;
		assert(asType != 0);

		restartIOHandling = false;
		inProgress = true;
		as->handleIO(asType);
		inProgress = false;
		if(restartIOHandling) streamStart();
	}

	/**
	 * needmore from the ASProducer interface (AudioSubSystem)
	 */
	void needMore()
	{
		_node()->requireFlow();
	}
	
};

REGISTER_IMPLEMENTATION(Synth_PLAY_impl);

}
// vim: sw=4 ts=4 noet