/* CSL - Common Sound Layer
 * Copyright (C) 2000-2001 Stefan Westerfeld and Tim Janik
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#include "adapter_impl.h"
#include "stream_impl.h"
#include "playsample_impl.h"
#include "delayedreturn.h"
#include <debug.h>
#include <audiosubsys.h>

using namespace Arts;
using namespace CSL;
using namespace std;

/* TODO: delete WaitBufferLevel's of inactive streams */
struct Adapter_impl::WaitBufferLevel : public NotificationClient {
	Adapter_impl *adapter;
	Direction direction;
	long streamID;
	long bufferLevel;
	DelayedReturn *dr;

	WaitBufferLevel(Adapter_impl *adapter, Stream stream,
					long bufferLevel, DelayedReturn *dr)
		:adapter(adapter), bufferLevel(bufferLevel), dr(dr)
	{
		direction = stream.direction();
		streamID = stream.ID();
	}

	bool match(long newLevel)
	{
		if(direction == dirInput && newLevel >= bufferLevel)
			return true;
		if(direction == dirOutput && newLevel <= bufferLevel)
			return true;

		return false;
	}

	void notify(const Notification& n)
	{
		long newLevel = n.ID;

		dr->doReturn(newLevel);
		delete this;
	}
};

struct Adapter_impl::WaitSample {
	long ID;
	DelayedReturn *dr;

	WaitSample(long ID, DelayedReturn *dr) :ID (ID), dr(dr)
	{
	}
	bool done(long doneID)
	{
		if(doneID == ID)
		{
			dr->doReturn();
			delete this;
			return true;
		}
		else
		{
			return false;
		}
	}
};

struct Adapter_impl::SampleData {
	long ID;
	std::string role, title;
	Arts::SampleStorageEntry entry;

	SampleData(long ID, const std::string& role, const std::string& title,
			   Arts::SampleStorageEntry entry)
			: ID(ID), role(role), title(title), entry(entry) { }
};

Adapter_impl::Adapter_impl() : nextID(1), nextSampleID(1)
{
	soundServer = Reference("global:Arts_SoundServerV2");

	arts_assert(!soundServer.isNull());
}

long Adapter_impl::open(const string& role, Direction dir)
{
	arts_return_val_if_fail(dir == dirInput || dir == dirOutput, 0);

	Stream_impl *stream;
	if(dir == dirInput)
		stream = new RecordStream_impl(this, role, nextID);
	else
		stream = new PlayStream_impl(this, role, nextID);

	streams.push_back(Stream::_from_base(stream));
	return nextID++;
}

bool Adapter_impl::streamParamsSupported(const StreamParams& params)
{
	if(params.rate < 4000 || params.rate > 200000)
	{
		arts_warning("CSL::Adapter - suspicious sampling rate %d,",params.rate);
		return false;
	}

	if(params.channels < 1 || params.channels > 2)
	{
		arts_warning("CSL::Adapter - %d channels streams unsupported",params.channels);
		return false;
	}

	int format = normalizeFormat(params.format);
	if(format != sf_u8 && format != sf_s16_le &&
	   format != sf_s16_be)
	{
		arts_warning("CSL::Adapter - format %4x unsupported",format);
		return false;
	}

	return true;	
}

bool Adapter_impl::setParams(long streamID, const StreamParams& params)
{
	Stream stream = findStream(streamID);
	arts_return_val_if_fail(!stream.isNull(), false);

	if(streamParamsSupported(params))
	{
		stream.params(params);
		return true;
	}
	else return false;
}

void Adapter_impl::setTitle(long streamID, const string& title)
{
	Stream stream = findStream(streamID);
	arts_return_if_fail(!stream.isNull());

	stream.title(title);
}

void Adapter_impl::close(long streamID)
{
	Stream stream = findStream(streamID);
	arts_return_if_fail(!stream.isNull());

	stream.close();
}

void Adapter_impl::write(long streamID, const vector<mcopbyte>& bytes)
{
	Stream stream = findStream(streamID);
	arts_return_if_fail(!stream.isNull());

	PlayStream playStream = DynamicCast(stream);
	arts_return_if_fail(!playStream.isNull());

	playStream.write(bytes);
}

vector<mcopbyte> *Adapter_impl::read(long streamID, long len)
{
	Stream stream = findStream(streamID);
	arts_return_val_if_fail(!stream.isNull(),new vector<mcopbyte>);

	RecordStream recordStream = DynamicCast(stream);
	arts_return_val_if_fail(!recordStream.isNull(),new vector<mcopbyte>);

	return recordStream.read(len);
}

void Adapter_impl::activate(long streamID)
{
	Stream stream = findStream(streamID);
	arts_return_if_fail(!stream.isNull());
	arts_return_if_fail(!stream.active());

	stream.active(true);
}

void Adapter_impl::suspend(long streamID)
{
	Stream stream = findStream(streamID);
	arts_return_if_fail(!stream.isNull());
	arts_return_if_fail(stream.active());

	stream.active(false);
}

bool Adapter_impl::active(long streamID)
{
	Stream stream = findStream(streamID);
	arts_return_val_if_fail(!stream.isNull(), false);

	return stream.active();
}

StreamStatus Adapter_impl::status(long streamID)
{
	StreamStatus status;

	Stream stream = findStream(streamID);
	arts_return_val_if_fail(!stream.isNull(), status);

	status.streamID = streamID;
	status.streamParams = stream.params();
	status.streamBufferUsed = stream.bufferUsed();
	status.streamBufferSize = stream.bufferSize();

	/* TODO: more precise information, either always (which might be expensive
	 *       or on demand
	 */
	status.artsBufferUsed = AudioSubSystem::the()->fragmentSize()
						  * AudioSubSystem::the()->fragmentCount();
	status.artsBufferSize = status.artsBufferUsed;
	return status;
}

void Adapter_impl::clearBuffer(long streamID)
{
	Stream stream = findStream(streamID);
	arts_return_if_fail(!stream.isNull());

	stream.clearBuffer();
}

long Adapter_impl::waitBufferLevel(long streamID, long bufferUsed)
{
	Stream stream = findStream(streamID);
	arts_return_val_if_fail(!stream.isNull(), 0);

	if(stream.direction() == dirInput && stream.bufferUsed() >= bufferUsed)
		return stream.bufferUsed();
	if(stream.direction() == dirOutput && stream.bufferUsed() <= bufferUsed)
		return stream.bufferUsed();

	DelayedReturn *delayedReturn = Dispatcher::the()->delayReturn();
	levelWaits.push_back(
		new WaitBufferLevel(this, stream, bufferUsed, delayedReturn)
	);
	return -1;	/* this return value is swallowed and not sent to the client */
}

void Adapter_impl::levelChanged(Stream_impl *stream_impl, long level)
{
	long streamID = stream_impl->ID();

	list<WaitBufferLevel *>::iterator i = levelWaits.begin();
	while(i != levelWaits.end())
	{
		WaitBufferLevel *w = *i;
		if(w->streamID == streamID && w->match(level))
		{
			Notification n;
			n.ID = level;
			n.receiver = w;
			n.data = n.internal = 0;
			NotificationManager::the()->send(n);
			i = levelWaits.erase(i);
		}
		else i++;
	}
}

void Adapter_impl::waitPlayed(long streamID)
{
	arts_warning("CSL::Adapter::waitPlayed unsupported");
}

vector<string> *Adapter_impl::channelMaps(long streamID)
{
	arts_warning("CSL::Adapter::channelMaps unsupported");
	return new vector<string>;
}

bool Adapter_impl::setChannelMapping(long streamID, long channel, const string& mapping)
{
	arts_warning("CSL::Adapter::setChannelMapping unsupported");
	return false;
}

string Adapter_impl::getChannelMapping(long streamID, long channel)
{
	arts_warning("CSL::Adapter::getChannelMapping unsupported");
	return "";
}

Stream Adapter_impl::findStream(long streamID)
{
	list<Stream>::iterator i;

	for(i = streams.begin(); i != streams.end(); i++)
		if(i->ID() == streamID) return *i;
	
	return Stream::null();
}

StreamFormat Adapter_impl::normalizeFormat(StreamFormat format)
{
	if ((format & sf_size_mask) == 8)
		format = StreamFormat(format & ~sf_endian_mask);

	return format;
}

Adapter_impl::SampleData *Adapter_impl::findSampleData(long sampleID)
{
	list<SampleData *>::iterator i;

	for(i=samples.begin(); i != samples.end(); i++)
		if((*i)->ID == sampleID) return *i;

	return 0;
}

long Adapter_impl::sampleNew(const string& name, const string& role,
							 const string& title)
{
	SampleStorageEntry entry = soundServer.sampleStorage().createEntry(name);

	arts_assert(findSampleData(nextSampleID) == 0);
	samples.push_back(new SampleData(nextSampleID,role,title,entry));
	return nextSampleID++;
}

void Adapter_impl::sampleWrite(long sampleID, const std::vector<mcopbyte>& bytes)
{
	SampleData *data = findSampleData(sampleID);
	arts_return_if_fail(data != 0);

	arts_return_if_fail(data->entry.completed() == false);
	data->entry.write(bytes);
}

void Adapter_impl::sampleWriteDone(long sampleID)
{
	SampleData *data = findSampleData(sampleID);
	arts_return_if_fail(data != 0);

	arts_return_if_fail(data->entry.completed() == false);
	data->entry.finish();
}

bool Adapter_impl::samplePlay(long sampleID)
{
	SampleData *data = findSampleData(sampleID);
	arts_return_val_if_fail(data != 0,false);
	arts_return_val_if_fail(data->entry.completed(),false);

	PlaySample sample = PlaySample::_from_base(new PlaySample_impl(this,
		data->entry.filename(), data->role, data->title, sampleID));

	if(sample.finished())
		return false;
	else
	{
		activeSamples.push_back(sample);
		return true;
	}
}

void Adapter_impl::sampleStop(long sampleID)
{
	list<PlaySample>::iterator i;

	for(i = activeSamples.begin(); i != activeSamples.end(); i++)
	{
		if(i->ID() == sampleID)
		{
			sampleDone(*i);
			return;
		}
	}
}


bool Adapter_impl::sampleIsPlaying(long sampleID)
{
	list<PlaySample>::iterator i;
	for(i = activeSamples.begin(); i != activeSamples.end(); i++)
	{
		if(i->ID() == sampleID)
			return true;
	}
	return false;
}

void Adapter_impl::sampleWaitDone(long sampleID)
{
	if(sampleIsPlaying(sampleID))
	{
		DelayedReturn *delayedReturn = Dispatcher::the()->delayReturn();
		sampleWaits.push_back(new WaitSample(sampleID, delayedReturn));
	}
}

void Adapter_impl::sampleRelease(long sampleID)
{
	SampleData *data = findSampleData(sampleID);
	arts_return_if_fail(data != 0);

	samples.remove(data);
	delete data;
}

void Adapter_impl::sampleCacheAdd(long sampleID)
{
	SampleData *data = findSampleData(sampleID);
	arts_return_if_fail(data != 0);

	arts_return_if_fail(data->entry.completed());
	soundServer.sampleStorage().addEntry(data->entry);
}

long Adapter_impl::sampleCacheFind(const std::string& name, const string& role,
								   const string& title)
{
	SampleStorageEntry entry = soundServer.sampleStorage().findEntry(name);

	if(entry.isNull())
		return 0;

	arts_assert(findSampleData(nextSampleID) == 0);
	samples.push_back(new SampleData(nextSampleID,role,title,entry));
	return nextSampleID++;
}

void Adapter_impl::sampleCacheRemove(long sampleID)
{
	SampleData *data = findSampleData(sampleID);
	arts_return_if_fail(data != 0);
	arts_return_if_fail(data->entry.completed());

	soundServer.sampleStorage().removeEntry(data->entry);
}

void Adapter_impl::sampleDone(PlaySample sample)
{
	list<PlaySample>::iterator i = activeSamples.begin();

	/* remove sample from list */
	while(i != activeSamples.end())
	{
		if(i->_isEqual(sample))
		{
			i = activeSamples.erase(i);
		}
		else i++;
	}

	/* notify pending delayed returns */
	list<WaitSample *>::iterator wi = sampleWaits.begin();
	while(wi != sampleWaits.end())
	{
		if((*wi)->done(sample.ID()))
			wi = sampleWaits.erase(wi);
		else wi++;
	}
}

namespace CSL {
	REGISTER_IMPLEMENTATION(Adapter_impl);
}
