    /*

    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.

    */

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

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/stat.h>

#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>		// Needed on some systems.
#endif

#ifdef HAVE_SYS_SOUNDCARD_H
#include <sys/soundcard.h>
#endif

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <algorithm>
#include <cstring>

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

#define DEFAULT_DEVICE_NAME "/dev/dsp"

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

using namespace std;
using namespace Arts;

//--- automatic startup class

static AudioSubSystemStart aStart;

void AudioSubSystemStart::startup()
{
	_instance = new AudioSubSystem();
}

void AudioSubSystemStart::shutdown()
{
	delete _instance;
}

//--- AudioSubSystemPrivate data

class Arts::AudioSubSystemPrivate
{
public:
#ifdef DEBUG_WAVEFORM
	ofstream plotfile;
#endif
	AudioIO *audioIO;
	string audioIOName;
	bool audioIOInit;

	unsigned int adjustDuplexOffsetIndex;
	int adjustDuplexOffset[4];
	int adjustDuplexCount;
};

//--- AudioSubSystem implementation

AudioSubSystem *AudioSubSystem::the()
{
	return aStart.the();
}

const char *AudioSubSystem::error()
{
	return _error.c_str();
}

AudioSubSystem::AudioSubSystem()
{
	d = new AudioSubSystemPrivate;
#ifdef DEBUG_WAVEFORM
	d->plotfile.open( "/dev/shm/audiosubsystem.plot" );
#endif
	d->audioIO = 0;
	d->audioIOInit = false;

	_running = false;
	consumer = 0;
	producer = 0;
	fragment_buffer = 0;
}

AudioSubSystem::~AudioSubSystem()
{
	delete d->audioIO;
	delete d;
}

bool AudioSubSystem::attachProducer(ASProducer *producer)
{
	assert(producer);
	if(this->producer) return false;

	this->producer = producer;
	return true;
}

bool AudioSubSystem::attachConsumer(ASConsumer *consumer)
{
	assert(consumer);
	if(this->consumer) return false;

	this->consumer = consumer;
	return true;
}

void AudioSubSystem::detachProducer()
{
	assert(producer);
	producer = 0;

	if(_running) close();
}

void AudioSubSystem::detachConsumer()
{
	assert(consumer);
	consumer = 0;

	if(_running) close();
}

/* initially creates default AudioIO */
void AudioSubSystem::initAudioIO()
{
	/* auto detect */
	if(!d->audioIOInit)
	{
		string bestName;
		int bestValue = 0;

		arts_debug("autodetecting driver: ");
		for(int i = 0; i < AudioIO::queryAudioIOCount(); i++)
		{
			string name = AudioIO::queryAudioIOParamStr(i, AudioIO::name);
			AudioIO *aio = AudioIO::createAudioIO(name.c_str());
			int value = aio->getParam(AudioIO::autoDetect);

			arts_debug(" - %s: %d", name.c_str(), value);
			if(value > bestValue)
			{
				bestName = name;
				bestValue = value;
			}
			delete aio;
		}
		if(bestValue)
		{
			arts_debug("... which means we'll default to %s", bestName.c_str());
			audioIO(bestName);
		}
		else
		{
			arts_debug("... nothing we could use as default found");
		}
	}
}

void AudioSubSystem::audioIO(const string& audioIO)
{
	if(d->audioIO)
		delete d->audioIO;

	d->audioIOName = audioIO;
	d->audioIO = AudioIO::createAudioIO(audioIO.c_str());
	d->audioIOInit = true;
}

string AudioSubSystem::audioIO()
{
	initAudioIO();

	return d->audioIOName;
}

void AudioSubSystem::deviceName(const string& deviceName)
{
	initAudioIO();
	if(!d->audioIO) return;

	d->audioIO->setParamStr(AudioIO::deviceName, deviceName.c_str());
}

string AudioSubSystem::deviceName()
{
	initAudioIO();
	if(!d->audioIO) return "";

	return d->audioIO->getParamStr(AudioIO::deviceName);
}

void AudioSubSystem::fragmentCount(int fragmentCount)
{
	initAudioIO();
	if(!d->audioIO) return;

	d->audioIO->setParam(AudioIO::fragmentCount, fragmentCount);
}

int AudioSubSystem::fragmentCount()
{
	initAudioIO();
	if(!d->audioIO) return 0;

	return d->audioIO->getParam(AudioIO::fragmentCount);
}

void AudioSubSystem::fragmentSize(int fragmentSize)
{
	initAudioIO();
	if(!d->audioIO) return;

	d->audioIO->setParam(AudioIO::fragmentSize, fragmentSize);
}

int AudioSubSystem::fragmentSize()
{
	initAudioIO();
	if(!d->audioIO) return 0;

	return d->audioIO->getParam(AudioIO::fragmentSize);
}

void AudioSubSystem::samplingRate(int samplingRate)
{
	initAudioIO();
	if(!d->audioIO) return;

	d->audioIO->setParam(AudioIO::samplingRate, samplingRate);
}

int AudioSubSystem::samplingRate()
{
	initAudioIO();
	if(!d->audioIO) return 0;

	return d->audioIO->getParam(AudioIO::samplingRate);
}

void AudioSubSystem::channels(int channels)
{
	initAudioIO();
	if(!d->audioIO) return;

	d->audioIO->setParam(AudioIO::channels, channels);
}

int AudioSubSystem::channels()
{
	initAudioIO();
	if(!d->audioIO) return 0;

	return d->audioIO->getParam(AudioIO::channels);
}

void AudioSubSystem::format(int format)
{
	initAudioIO();
	if(!d->audioIO) return;

	d->audioIO->setParam(AudioIO::format, format);
}

int AudioSubSystem::format()
{
	initAudioIO();
	if(!d->audioIO) return 0;

	return d->audioIO->getParam(AudioIO::format);
}

int AudioSubSystem::bits()
{
	int _format = format();
	arts_assert(_format == 0 || _format == 8 || _format == 16 || _format == 17 || _format == 32);
	return (_format & (32 | 16 | 8));
}

void AudioSubSystem::fullDuplex(bool fullDuplex)
{
	initAudioIO();
	if(!d->audioIO) return;

	int direction = fullDuplex?3:2;
	d->audioIO->setParam(AudioIO::direction, direction);
}

bool AudioSubSystem::fullDuplex()
{
	initAudioIO();
	if(!d->audioIO) return false;

	return d->audioIO->getParam(AudioIO::direction) == 3;
}

int AudioSubSystem::selectReadFD()
{
	initAudioIO();
	if(!d->audioIO) return false;

	return d->audioIO->getParam(AudioIO::selectReadFD);
}

int AudioSubSystem::selectWriteFD()
{
	initAudioIO();
	if(!d->audioIO) return false;

	return d->audioIO->getParam(AudioIO::selectWriteFD);
}

bool AudioSubSystem::check()
{
        bool ok = open();

	if(ok) close();
	return ok;
}

bool AudioSubSystem::open()
{
	assert(!_running);

	initAudioIO();
	if(!d->audioIO)
	{
		if(d->audioIOName.empty())
			_error = "couldn't auto detect which audio I/O method to use";
		else
			_error = "unable to select '"+d->audioIOName+"' style audio I/O";
		return false;
	}

	if(d->audioIO->open())
	{
		_running = true;

		_fragmentSize = d->audioIO->getParam(AudioIO::fragmentSize);
		_fragmentCount = d->audioIO->getParam(AudioIO::fragmentCount);

		// allocate global buffer to do I/O
		assert(fragment_buffer == 0);
		fragment_buffer = new char[_fragmentSize];

		d->adjustDuplexCount = 0;
		return true;
	}
	else
	{
		_error = d->audioIO->getParamStr(AudioIO::lastError);
		return false;
	}
}

void AudioSubSystem::close()
{
	assert(_running);
	assert(d->audioIO);

	d->audioIO->close();

	wBuffer.clear();
	rBuffer.clear();

	_running = false;
	if(fragment_buffer)
	{
		delete[] fragment_buffer;
		fragment_buffer = 0;
	}
}

bool AudioSubSystem::running()
{
	return _running;
}

void AudioSubSystem::handleIO(int type)
{
	assert(d->audioIO);

	if(type & ioRead)
	{
		int len = d->audioIO->read(fragment_buffer,_fragmentSize);

		if(len > 0)
		{
			if(rBuffer.size() < _fragmentSize * _fragmentCount * bits() / 8 * channels())
			{
				rBuffer.write(len,fragment_buffer);
#ifdef DEBUG_WAVEFORM
				float * end = (float *)(fragment_buffer + len);
				float * floatbuffer = (float *)fragment_buffer;
				while(floatbuffer < end)
				{
					d->plotfile << *floatbuffer++ << "\n";
					++floatbuffer;
				}
#endif
			}
			else
			{
				arts_debug( "AudioSubSystem: rBuffer is too full" );
			}
		}
	}

	if(type & ioWrite)
	{
		/*
		 * make sure that we have a fragment full of data at least
		 */
Rewrite:
		while(wBuffer.size() < _fragmentSize)
		{
			long wbsz = wBuffer.size();
			producer->needMore();

			if(wbsz == wBuffer.size())
			{
				/*
				 * Even though we asked the client to supply more
				 * data, he didn't give us more. So we can't supply
				 * output data as well. Bad luck. Might produce a
				 * buffer underrun - but we can't help here.
				 */
				arts_info("full duplex: no more data available (underrun)");
				return;
			}
		}

		/*
		 * look how much we really can write without blocking
		 */
		int space = d->audioIO->getParam(AudioIO::canWrite);
		int can_write = min(space, _fragmentSize);

		if(can_write > 0)
		{
			/*
			 * ok, so write it (as we checked that our buffer has enough data
			 * to do so and the soundcardbuffer has enough data to handle this
			 * write, nothing can go wrong here)
			 */
			int rSize = wBuffer.read(can_write,fragment_buffer);
			assert(rSize == can_write);

			int len = d->audioIO->write(fragment_buffer,can_write);
			if(len != can_write)
				arts_fatal("AudioSubSystem::handleIO: write failed\n"
				"len = %d, can_write = %d, errno = %d (%s)\n\n"
				"This might be a sound hardware/driver specific problem"
				" (see aRts FAQ)",len,can_write,errno,strerror(errno));

			if(fullDuplex())
			{
				/*
				 * if we're running full duplex, here is a good place to check
				 * for full duplex drift
				 */
				d->adjustDuplexCount += can_write;
				if(d->adjustDuplexCount > samplingRate())
				{
					adjustDuplexBuffers();
					d->adjustDuplexCount = 0;
				}
			}
		}

                // If we can write a fragment more, then do so right now:
                if (space >= _fragmentSize*2) goto Rewrite;
	}

	assert((type & ioExcept) == 0);
}

void AudioSubSystem::read(void *buffer, int size)
{
	/* if not enough data can be read, produce some */
	while(rBuffer.size() < size)
		adjustInputBuffer(1);

	/* finally, just take the data out of the input buffer */
	int rSize = rBuffer.read(size,buffer);
	assert(rSize == size);
}

void AudioSubSystem::write(void *buffer, int size)
{
	wBuffer.write(size,buffer);
}

float AudioSubSystem::outputDelay()
{
	int fsize = _fragmentSize;
	int fcount = _fragmentCount;

	if(fsize > 0 && fcount > 0)	// not all AudioIO classes need to support this
	{
		double hardwareBuffer = fsize * fcount;
		double freeOutputSpace = d->audioIO->getParam(AudioIO::canWrite);
   		double playSpeed = channels() * samplingRate() * (bits() / 8);

		return (hardwareBuffer - freeOutputSpace) / playSpeed;
	}
	else return 0.0;
}

void AudioSubSystem::adjustDuplexBuffers()
{
	int fsize = _fragmentSize;
	int fcount = _fragmentCount;

	if(fsize > 0 && fcount > 0)	// not all AudioIO classes need to support this
	{
		int bound = 2; //max(fcount/2, 1);
		int optimalOffset = fsize * (fcount + bound);
		int minOffset = fsize * fcount;
		int maxOffset = fsize * (fcount + 2 * bound);

		int canRead = d->audioIO->getParam(AudioIO::canRead);
		if(canRead < 0)
		{
			arts_warning("AudioSubSystem::adjustDuplexBuffers: canRead < 0?");
			canRead = 0;
		}

		int canWrite = d->audioIO->getParam(AudioIO::canWrite);
		if(canWrite < 0)
		{
			arts_warning("AudioSubSystem::adjustDuplexBuffers: canWrite < 0?");
			canWrite = 0;
		}

		int currentOffset = rBuffer.size() + wBuffer.size()
                          + canRead + max((fsize * fcount) - canWrite, 0);

		d->adjustDuplexOffset[d->adjustDuplexOffsetIndex++ & 3] = currentOffset;
		if(d->adjustDuplexOffsetIndex <= 4) return;

		int avgOffset;
		avgOffset = d->adjustDuplexOffset[0]
				  + d->adjustDuplexOffset[1]
				  + d->adjustDuplexOffset[2]
				  + d->adjustDuplexOffset[3];
		avgOffset /= 4;

		/*
		printf("offset: %d   avg %d min %d opt %d max %d\r", currentOffset,
				  avgOffset, minOffset, optimalOffset, maxOffset);
		fflush(stdout);
		*/
		if(minOffset <= avgOffset && avgOffset <= maxOffset)
			return;

		d->adjustDuplexOffsetIndex = 0;
		int adjust = (optimalOffset - currentOffset) / _fragmentSize;
		arts_debug("AudioSubSystem::adjustDuplexBuffers(%d)", adjust);
	}
}

void AudioSubSystem::adjustInputBuffer(int count)
{
	if(format() == 8)
	{
		memset( fragment_buffer, 0x80, _fragmentSize );
	}
	else
	{
		memset( fragment_buffer, 0, _fragmentSize );
	}

	while(count > 0 && rBuffer.size() < _fragmentSize * _fragmentCount * 4)
	{
		rBuffer.write(_fragmentSize, fragment_buffer);
#ifdef DEBUG_WAVEFORM
		float * end = (float *)(fragment_buffer + _fragmentSize);
		float * floatbuffer = (float *)fragment_buffer;
		while(floatbuffer < end)
		{
			d->plotfile << *floatbuffer++ << "\n";
			++floatbuffer;
		}
#endif
		count--;
	}

	while(count < 0 && rBuffer.size() >= _fragmentSize)
	{
		rBuffer.read(_fragmentSize, fragment_buffer);
		count++;
	}
}

void AudioSubSystem::emergencyCleanup()
{
	if(producer || consumer)
	{
		fprintf(stderr, "AudioSubSystem::emergencyCleanup\n");

		if(producer)
			detachProducer();
		if(consumer)
			detachConsumer();
	}
}
