    /*

    Copyright (C) 2002 Hans Meine
                       hans_meine@gmx.net

    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 <gsl/gsldatahandle.h>
#include <gsl/gslloader.h>
#include <gsl/gslwaveosc.h>
#include "artsflow.h"
#include "stdsynthmodule.h"
#include "debug.h"
#include <algorithm>
#include <cstring>
#include <gslpp/datahandle.h>

using namespace std;

namespace Arts {

static GSL::DataHandle getDHandle(DataHandle handle);

class DataHandle_impl : virtual public DataHandle_skel
{
protected:
	GSL::DataHandle dhandle_;
	long errno_;
	friend GSL::DataHandle getDHandle(DataHandle handle);

public:
	DataHandle_impl(GSL::DataHandle dhandle = GSL::DataHandle::null())
		: dhandle_(dhandle)
	{
		errno_ = dhandle_.isNull() ? 0 : dhandle_.open();
	}

	~DataHandle_impl()
	{
		if(dhandle_.isOpen())
			dhandle_.close();
	}

	long valueCount()
	{
		return dhandle_.valueCount();
	}

	long bitDepth()
	{
		return dhandle_.bitDepth();
	}

	long channelCount()
	{
		return dhandle_.channelCount();
	}

	long errorNo()
	{
		return errno_;
	}

	GSL::DataHandle createCropped(long headCutValueCount,
								  long tailCutValueCount)
	{
		return dhandle_.createCropped(headCutValueCount, tailCutValueCount);
	}

	GSL::DataHandle createCut(long cutOffset,
							  long cutValueCount)
	{
		return dhandle_.createCut(cutOffset, cutValueCount);
	}

	GSL::DataHandle createReversed()
	{
		return dhandle_.createReversed();
	}
};

class ReversedDataHandle_impl : virtual public DataHandle_impl,
								virtual public ReversedDataHandle_skel
{
	void init(DataHandle sourceHandle)
	{
		DataHandle_impl *impl = dynamic_cast<DataHandle_impl *>(sourceHandle._base());
		dhandle_ = impl->createReversed();
	}
};

class CroppedDataHandle_impl : virtual public DataHandle_impl,
							   virtual public CroppedDataHandle_skel
{
	void init(DataHandle sourceHandle,
			  long headCutValueCount,
			  long tailCutValueCount)
	{
		DataHandle_impl *impl = dynamic_cast<DataHandle_impl *>(sourceHandle._base());
		dhandle_ = impl->createCropped(headCutValueCount, tailCutValueCount);
	}
};

class CutDataHandle_impl : virtual public DataHandle_impl,
						   virtual public CutDataHandle_skel
{
	void init(DataHandle sourceHandle,
			  long cutOffset,
			  long cutValueCount)
	{
		DataHandle_impl *impl = dynamic_cast<DataHandle_impl *>(sourceHandle._base());
		dhandle_ = impl->createCut(cutOffset, cutValueCount);
	}
};

static GSL::DataHandle getDHandle(DataHandle handle)
{
	DataHandle_impl *impl = dynamic_cast<DataHandle_impl *>(handle._base());
	if(impl)
		return impl->dhandle_;
	return GSL::DataHandle::null();
}

GslWaveChunk* const_wchunk_from_freq(gpointer wchunk_data, gfloat)
{
	return (GslWaveChunk*)wchunk_data;
}

// FIXME: Think about whether this is needed as a parameter like mixerFrequency
#define OSCILLATOR_FREQUENCY 440.0f

class DataHandlePlay_impl : virtual public DataHandlePlay_skel,
							virtual public StdSynthModule
{
protected:
	DataHandle dhandle_;

	GSL::DataHandle gslDHandle_;
	int errno_;
	GslWaveChunk *wchunk_;
	GslErrorType wchunkError_;
	GslWaveOscData *wosc_;

	float mixerFrequency_;
	unsigned short channelIndex_;
	float speed_;
	unsigned long pos_;
	bool finished_;
	bool paused_;

public:
	DataHandlePlay_impl()
		: dhandle_(DataHandle::null()),
		  gslDHandle_(GSL::DataHandle::null()),
		  wchunk_(NULL),
		  wosc_(NULL),
		  mixerFrequency_(0), // FIXME: doc says soundserver's freq
		  channelIndex_(0),
		  speed_(1.0f),
		  pos_(0),
		  finished_(false),
		  paused_(false)
	{}

	~DataHandlePlay_impl()
	{
		handle(DataHandle::null());
	}

	DataHandle handle()
	{
		return dhandle_;
	}

	void handle(DataHandle handle)
	{
		deleteWaveChunk();

		if(!gslDHandle_.isNull() && !errno_)
			gslDHandle_.close();

		dhandle_ = handle;

		if(handle.isNull())
			gslDHandle_ = GSL::DataHandle::null();
		else
		{
			gslDHandle_ = getDHandle(dhandle_);

			if(gslDHandle_.isNull())
			{
				arts_debug("ERROR: could not get internal GSL::DataHandle!");
				finished(true); // FIXME: can this happen?
			}
			else
			{
				errno_ = gslDHandle_.open();
				if(errno_)
					arts_debug("DataHandlePlay got error from GSL::DataHandle.open(): '%s'",
							   strerror(errno_));
			}
		}
	}

	void streamInit()
	{
		if(!gslDHandle_.isNull() && !wosc_)
		{
			if(!wchunk_)
				createWaveChunk();
			configureWaveOsc();
		}
	}

	float mixerFrequency() { return wosc_ ? wosc_->mix_freq : 0; }
	void mixerFrequency(float f)
	{
		if(wchunk_)
			arts_warning("DataHandlePlay: cannot change mixerFrequency after start of sound processing!");

		if(mixerFrequency() != f)
		{
			mixerFrequency_ = f;
			mixerFrequency_changed(f);
		}
	}

	long channelIndex() { return channelIndex_; }
	void channelIndex(long ci)
	{
		if(channelIndex_ != ci)
		{
			channelIndex_ = ci;
			if(wosc_)
			{
				GslWaveOscConfig config = wosc_->config;
				config.channel = ci;
				gsl_wave_osc_config(wosc_, &config);
			}
			channelIndex_changed(ci);
		}
	}

	float speed() { return speed_; }
	void speed(float s)
	{
		if(speed_ != s)
		{
			speed_ = s;
			if(wosc_)
			{
				GslWaveOscConfig config = wosc_->config;
				config.cfreq = OSCILLATOR_FREQUENCY * speed();
				gsl_wave_osc_config(wosc_, &config);
			}
			speed_changed(s);
		}
	}

	long pos() { return wosc_ ? wosc_->block.offset : 0; }
	void pos(long p)
	{
		if(pos() != p)
		{
			GslWaveOscConfig config = wosc_->config;
			config.start_offset = p;
			gsl_wave_osc_config(wosc_, &config);
			pos_changed(p);
		}
	}

	bool finished() { return finished_; }
	void finished(bool f)
	{
		if(finished_ != f)
		{
			finished_ = f;
			finished_changed(f);
		}
	}

	bool paused() { return paused_; }
	void paused(bool p)
	{
		paused_ = p;
	}

	void deleteWaveChunk()
	{
		deleteWaveOsc();

		if(wchunk_)
		{
			arts_debug("DataHandlePlay_impl: close()ing gsl_wave_chunk");
			gsl_wave_chunk_close(wchunk_);
			gsl_wave_chunk_unref(wchunk_);
			wchunk_ = NULL;
		}
	}

	void createWaveChunk()
	{
		deleteWaveChunk();

		if(!gslDHandle_.isNull() && gslDHandle_.isOpen())
		{
			GslDataCache *dcache = gslDHandle_.createGslDataCache();
			if(!dcache)
			{
				arts_debug("FATAL: creating data cache failed!");
				finished(true); // FIXME: can this happen?
			}
			else
			{
				wchunk_ =
					gsl_wave_chunk_new(dcache,
									   OSCILLATOR_FREQUENCY,
									   mixerFrequency_,
									   GSL_WAVE_LOOP_NONE,
									   0, 0, 0);
				arts_debug("DataHandlePlay_impl: open()ing gsl_wave_chunk");
				wchunkError_ = gsl_wave_chunk_open(wchunk_);
				gsl_data_cache_unref(dcache);
			}
		}
	}

	void deleteWaveOsc()
	{
		if(wosc_)
		{
			gsl_wave_osc_shutdown(wosc_);
			delete wosc_;
			wosc_ = NULL;
		}
	}

	void configureWaveOsc()
	{
		if(wchunk_)
		{
			GslWaveOscConfig config;
			memset(&config, 0, sizeof (GslWaveOscConfig));
			config.start_offset = 0;
			config.play_dir = 1;
			config.wchunk_data = (gpointer)wchunk_;
			config.wchunk_from_freq = &const_wchunk_from_freq;
			config.channel = channelIndex();
			config.cfreq = OSCILLATOR_FREQUENCY * speed();

			if(!wosc_)
			{
				wosc_ = new GslWaveOscData();
				gsl_wave_osc_init(wosc_);
			}

			gsl_wave_osc_config(wosc_, &config);
		}
	}

	void calculateBlock(unsigned long samples)
	{
		if(!paused_ && wchunk_)
		{
			if (!gsl_wave_osc_process(wosc_, samples, 0, 0, 0, outvalue))
				arts_debug("gsl_wave_osc_process failed.");
			finished(wosc_->done);
		}
		else
			for(unsigned long i = 0; i < samples; i++)
				outvalue[i] = 0.f;
		/*
		unsigned long haveSamples = 0;

		while(haveSamples != samples)
		{
			long n = min(gslDHandle_.valueCount() - pos_, samples - haveSamples);
			long got = gslDHandle_.isNull() ?
					   0 : gslDHandle_.read(pos_, n, &outvalue[haveSamples]);
			if(got <= 0)
			{
				for(unsigned long i = haveSamples; i < samples; i++)
					outvalue[i] = 0.f;
				break;
			}
			pos_ += got;
			haveSamples += got;
		}
		finished(pos_ >= gslDHandle_.valueCount());
		*/
	}

	DataHandlePlay clone()
	{
		arts_debug("DataHandlePlay_impl: clone()ing myself");
		DataHandlePlay_impl *result = new DataHandlePlay_impl();

		result->dhandle_ = dhandle_;
		result->gslDHandle_ = gslDHandle_;
		result->errno_ = errno_;
		
		createWaveChunk();
		if(wchunk_)
		{
			result->wchunk_ = wchunk_;
			gsl_wave_chunk_ref(wchunk_);
			result->wchunkError_ = gsl_wave_chunk_open(wchunk_);
		}
		else
			result->wchunkError_ = wchunkError_;

		result->mixerFrequency_ = mixerFrequency_;
		result->channelIndex_ = channelIndex_;
		result->speed_ = speed_;
		result->pos_ = pos_;
		result->finished_ = finished_;
		result->paused_ = paused_;

		return DataHandlePlay::_from_base(result);
	}
};

class WaveDataHandle_impl : virtual public DataHandle_impl,
							virtual public WaveDataHandle_skel
{
	GSL::WaveDataHandle wdhandle_;

public:
	WaveDataHandle_impl() : wdhandle_(GSL::WaveDataHandle::null()) {}

	float mixerFrequency()
	{
		return wdhandle_.mixerFrequency();
	}

	float oscillatorFrequency()
	{
		return wdhandle_.oscillatorFrequency();
	}

	bool load(const string& filename)
	{
		return load(filename, 0, 0);
	}

	bool load(const string& filename,
			  long waveIndex,
			  long chunkIndex)
	{
		wdhandle_ = GSL::WaveDataHandle(filename, waveIndex, chunkIndex);
		if(dhandle_.isOpen())
			dhandle_.close();
		dhandle_ = wdhandle_;
		errno_ = dhandle_.isNull() ? 0 : dhandle_.open();
		return isLoaded();
	}

	bool isLoaded()
	{
		if(wdhandle_.isNull())
			return false;

		return !wdhandle_.error();
	}

	DataHandlePlay createPlayer()
	{
		DataHandlePlay_impl *result= new DataHandlePlay_impl();

		result->mixerFrequency(mixerFrequency());
		result->handle(WaveDataHandle::_from_base(this->_copy()));

		return DataHandlePlay::_from_base(result);
	}
};

REGISTER_IMPLEMENTATION(DataHandlePlay_impl);
REGISTER_IMPLEMENTATION(DataHandle_impl);
REGISTER_IMPLEMENTATION(CroppedDataHandle_impl);
REGISTER_IMPLEMENTATION(CutDataHandle_impl);
REGISTER_IMPLEMENTATION(ReversedDataHandle_impl);
REGISTER_IMPLEMENTATION(WaveDataHandle_impl);

}
