/*
*	DSFML - SFML Library wrapper for the D programming language.
*	Copyright (C) 2008 Julien Dagorn (sirjulio13@gmail.com)
*	Copyright (C) 2010 Andreas Hollandt
*	
*	This software is provided 'as-is', without any express or
*	implied warranty. In no event will the authors be held
*	liable for any damages arising from the use of this software.
*
*	Permission is granted to anyone to use this software for any purpose,
*	including commercial applications, and to alter it and redistribute
*	it freely, subject to the following restrictions:
*
*	1.  The origin of this software must not be misrepresented;
*		you must not claim that you wrote the original software.
*		If you use this software in a product, an acknowledgment
*		in the product documentation would be appreciated but
*		is not required.
*
*	2.  Altered source versions must be plainly marked as such,
*		and must not be misrepresented as being the original software.
*
*	3.  This notice may not be removed or altered from any
*		source distribution.
*/

module dsfml.audio.soundstream;

import dsfml.system.alloc;
import dsfml.system.common;
import dsfml.system.vector3;
import dsfml.system.linkedlist;
import dsfml.system.lock;
import dsfml.system.mutex;
import dsfml.system.sleep;
//import dsfml.system.thread;
import core.thread;

import dsfml.audio.sound;
import dsfml.audio.soundstatus;

/**
*	SoundStream is a streamed sound, ie samples are acquired
*	while the sound is playing. Use it for big sounds that would
*	require hundreds of MB in memory, or for streaming sound from the network.
*	
*	SoundStream is a base class and cannot be instanciated directly.
*	
*	$(B onGetData override will be called by a different thread, take care of synchronization issues.) onStart is called by the thread which called .play().
*	
*	------------------------
*	class MySoundStream : SoundStream
*	{
*		this()
*		{
*			super(2, 44100); // you need to initialize the base class before any operation.	
*		}
*		protected bool onGetData(out short[] data)
*		{
*			//You need to fill data array with some samples
*			
*			return true; //or false if you want to stop playback
*		}
*		
*		protected bool onStart()
*		{
*			return true;	
*		}  
*	}
*	------------------------
*/
abstract class SoundStream : DSFMLObject
{
	override void dispose()
	{
		stop();
		sfSoundStream_Destroy(m_ptr);
		s_instances.remove(m_id);
	}
	
	/**
	*	Start playing the stream
	*/		
	void play()
	{
		m_flag = true;			
		sfSoundStream_Play(m_ptr);
		
		if (getStatus() != SoundStatus.PAUSED)
		{
			m_t = new Thread(&threadPoll);
			m_t.start();
		}
	}
	
	/**
	*	Pause the stream
	*/		
	void pause()
	{
		sfSoundStream_Pause(m_ptr);
	}

	/**
	*	Stop the stream
	*/		
	void stop()
	{
		m_flag = false;
		sfSoundStream_Stop(m_ptr);
		m_t.join();
		if (m_dummy !is null)
			delete m_dummy;
	}
	
	/**
	*	Get number of channels of the stream
	*	
	*	Returns:
	*		number of channels			
	*/		
	uint getChannelsCount()
	{
		return m_channelsCount;
	}

	/**
	*	Get the sample rate of the stream
	*	
	*	Returns:
	*		sample rate			
	*/		
	uint getSampleRate()
	{
		return m_sampleRate;
	}

	/**
	*	Get the current status of the stream
	*	
	*	Returns:
	*		Current stream status			
	*/		
	SoundStatus getStatus()
	{
		return sfSoundStream_GetStatus(m_ptr);
	}
	
	/**
	*	Set the sound pitch.
	*	The default pitch is 1
	*
	*	Params:
	*		pitch = New pitch
	*/
	void setPitch(float pitch)
	{
		sfSoundStream_SetPitch(m_ptr, pitch);
	}

	/**
	*	Set the sound volume.
	*	The default volume is 100
	*
	*	Params:
	*		volume = Volume (in range [0, 100])
	*/
	void setVolume(float volume)
	in
	{
		assert(volume >= 0.f && volume <= 100.f);
	}
	body
	{
		sfSoundStream_SetVolume(m_ptr, volume);
	}

	/*
	*	Set the sound position (take 3 values).
	*	The default position is (0, 0, 0)
	*
	*	Params:
	*	  x, y, z = Position of the sound in the world
	*/

	void setPosition(float x, float y, float z)
	{
		sfSoundStream_SetPosition(m_ptr, x, y, z);
	}

	/**
	*	Set the sound position (take 3 values).
	*	The default position is (0, 0, 0)
	*
	*	Params:
	*	 vec = Position of the sound in the world
	*
	*/

	void setPosition(Vector3f vec)
	{
		sfSoundStream_SetPosition(m_ptr, vec.x, vec.y, vec.z);
	}

	/**
	*	Set the minimum distance - closer than this distance,
	*	the listener will hear the sound at its maximum volume.
	*	The default minimum distance is 1.0
	*
	*	Params:
	*		minDistance = New minimum distance for the sound
	*/
	void setMinDistance(float minDistance)
	{
		sfSoundStream_SetMinDistance(m_ptr, minDistance);
	}

	/**
	*	Set the attenuation factor - the higher the attenuation, the
	*	more the sound will be attenuated with distance from listener.
	*	The default attenuation factor 1.0
	*
	*	Params: 
	*		attenuation = New attenuation factor for the sound
	*
	*/
	void setAttenuation(float attenuation)
	{
		sfSoundStream_SetAttenuation(m_ptr, attenuation);
	}

	/**
	*	Get the pitch
	*
	*	Returns:
	*		Pitch value
	*/

	float getPitch()
	{
		return sfSoundStream_GetPitch(m_ptr);
	}
	/**
	*	Get the volume
	*
	*	Returns:
	*		Volume value (in range [1, 100])
	*
	*/

	float getVolume()
	{
		return sfSoundStream_GetVolume(m_ptr);
	}

	/**
	*	Get the sound position
	*	
	*	Returns:
	*		Sound position				
	*/		
	Vector3f getPosition()
	{
		Vector3f vec;
		sfSoundStream_GetPosition(m_ptr, &vec.x, &vec.y, &vec.z);
		return vec;
	}

	/**
	*	Get the minimum distance
	*	
	*	Returns:
	*		Get the minimum distance of the sound			
	*/		
	float getMinDistance()
	{
		return sfSoundStream_GetMinDistance(m_ptr);
	}

	/**
	*	Get the attenuation
	*	
	*	Returns:
	*		Get the attenuation of the sound			
	*/		
	float getAttenuation()
	{
		return sfSoundStream_GetAttenuation(m_ptr);
	}

	/**
	*	Get the current playing offset of the stream
	*	
	*	Returns:
	*		current playing offset, in seconds.			
	*/		
	float getPlayingOffset()
	{
		return sfSoundStream_GetPlayingOffset(m_ptr);
	}

	/**
	*	Tell whether or not the stream is looping
	*
	*	Returns:
	*		True if the music is looping, false otherwise
	*/
	bool getLoop()
	{
		if (m_ptr !is null)
			return cast(bool)sfSoundStream_GetLoop(m_ptr);
		return false;
	}

	/**
	*	Set the stream loop state.
	*	
	*	Disabled by default.
	*	
	*	Params:
	*		loop = true to play in loop, false to play once					
	*/		
	void setLoop(bool loop)
	{
		if (m_ptr !is null)
			sfSoundStream_SetLoop(m_ptr, loop);
	}
	
	/**
	 * Make the sound stream's position relative to the listener's position, or absolute.
	 * The default value is false (absolute)
	 * 
	 *	Params:
	 *		relative = True to set the position relative, false to set it absolute
	 */
	void setRelativeToListener(bool relative)
	{
		sfSoundStream_SetRelativeToListener(m_ptr, relative);
	}

	/**
	 * Tell if the sound stream's position is relative to the listener's
	 * position, or if it's absolute
	 * 
	 *	Returns:
	 *		true if the position is relative, sfFalse if it's absolute
	 */
	bool isRelativeToListener()
	{
		return sfSoundStream_IsRelativeToListener(m_ptr);
	}

protected:
	/**
	*	Protected constructor
	*		
	*	Params:
	*		channelsCount = number of channel
	*		sampleRate = sample rate of the stream
	*
	*/		
	this(uint channelsCount, uint sampleRate)
	{
		m_channelsCount = channelsCount;
		m_sampleRate = sampleRate;
		super(sfSoundStream_Create(&externalOnStart, &externalOnGetData, channelsCount, sampleRate, &m_id));

		m_mutex = new Mutex();
		
		m_samples = new LinkedList!(Data);
		
		m_t = new Thread(&this.threadPoll);
		
		m_id = ++s_seed;
		s_instances[m_id] = this;
	}
	
	/**
	*	Called each time the stream restart
	*	
	*	Returns:
	*		false to abort the playback			
	*/		
	abstract bool onStart();
	
	/**
	*	Called each time the stream needs new data.
	*	This method will be call by an other thread, take care of possible synchronisation issues.
	*	
	*	Params:
	*		data = array of samples to stream
	*	
	*	Returns:
	*		true to continue streaming, false to stop						
	*/		
	abstract bool onGetData(out short[] data);
private:

	// Called sync when user calling play()
	extern(C) static int externalOnStart(void* user)
	{
		int id;
		if ((id = *cast(int*) user) in s_instances)
		{
			SoundStream temp = s_instances[id];
			return (temp.m_flag = temp.onStart());
		}
		return true;
	}
	
	// C Thread callback (no allocation can be done)
	extern (C) static int externalOnGetData(sfSoundStreamChunk* data, void* user)
	{
		int id, flag = false;
		// Get calling soundStream
		if ((id = *cast(int*) user) in s_instances)
		{
			SoundStream temp = s_instances[id];
			//if no samples are available but streaming is not stopped, we sleep the thread
			while (temp.m_samples.empty && temp.m_flag)
				sleep(0.01f);
			
			scope Lock l = new Lock(temp.m_mutex);
			if (!temp.m_samples.empty)
			{
				if (temp.m_dummy !is null)
					delete temp.m_dummy;
					
				temp.m_dummy = temp.m_samples.dequeue;
				
				if ((flag = temp.m_dummy.Flag) == true)
				{
					data.Samples = temp.m_dummy.Samples.ptr;
					data.NbSamples = temp.m_dummy.Samples.length;
				}
				else
				{
					data.Samples = null;
					data.NbSamples = 0;
				}
			}
		}
		return flag;
	}
	
	// Managed thread loop
	void threadPoll()
	{
		short[] data;
		bool ret = true;
		// while streaming is active ...
		while (ret && m_flag)
		{
			{
				scope Lock l = new Lock(m_mutex);
				// see how many samples are available (keep always 2 samples ready)
				if (m_samples.getCount < 2)
				{
					// if we need new samples, lock and call derived class
					ret = onGetData(data);
					m_samples.enqueue(new Data(data, ret));
				}
			}
			sleep(0.1f);
		}
	}
	
	private class Data
	{
		short[] Samples;
		bool Flag;
		
		mixin Alloc;
		
		this (short[] samples, bool flag)
		{
			this.Samples = samples;
			this.Flag = flag;
		}
	}
		
	Thread m_t;
	Mutex m_mutex;
	LinkedList!(Data) m_samples;
	Data m_dummy;
	
	bool m_flag;
	
	uint m_channelsCount;
	uint m_sampleRate;
	
	int m_id;
	static SoundStream[int] s_instances;
	static int s_seed = 0;
	
// External ====================================================================

	extern (C)
	{
		struct sfSoundStreamChunk{ short* Samples; uint NbSamples; }
		
		alias int function(void*) sfSoundStreamStartCallback;
		alias int function (sfSoundStreamChunk*, void*) sfSoundStreamGetDataCallback;
		
		typedef void* function(sfSoundStreamStartCallback, sfSoundStreamGetDataCallback, uint, uint, void*) pf_sfSoundStream_Create;
		typedef void function(void*) pf_sfSoundStream_Destroy;
		typedef void function(void*) pf_sfSoundStream_Play;
		typedef void function(void*) pf_sfSoundStream_Pause;
		typedef void function(void*) pf_sfSoundStream_Stop;
		typedef SoundStatus function(void*) pf_sfSoundStream_GetStatus;		
		typedef uint function(void*) pf_sfSoundStream_GetChannelsCount;
		typedef uint function(void*) pf_sfSoundStream_GetSampleRate;
		typedef void function(void*, float) pf_sfSoundStream_SetPitch;
		typedef void function(void*, float) pf_sfSoundStream_SetVolume;
		typedef void function(void*, float, float, float) pf_sfSoundStream_SetPosition;		
		typedef void function(void*, float) pf_sfSoundStream_SetMinDistance;
		typedef void function(void*, float) pf_sfSoundStream_SetAttenuation;
		typedef float function(void*) pf_sfSoundStream_GetPitch;
		typedef float function(void*) pf_sfSoundStream_GetVolume;
		typedef void function(void*, float*, float*, float*) pf_sfSoundStream_GetPosition;
		typedef float function(void*) pf_sfSoundStream_GetMinDistance;
		typedef float function(void*) pf_sfSoundStream_GetAttenuation;
		typedef float function(void*) pf_sfSoundStream_GetPlayingOffset;
		typedef int function(void*) pf_sfSoundStream_GetLoop;
		typedef void function(void*, int) pf_sfSoundStream_SetLoop;

		typedef bool function(void*)						pf_sfSoundStream_IsRelativeToListener;
		typedef void function(void*, bool)					pf_sfSoundStream_SetRelativeToListener;


		static pf_sfSoundStream_Create sfSoundStream_Create;
		static pf_sfSoundStream_Destroy sfSoundStream_Destroy;
		static pf_sfSoundStream_Play sfSoundStream_Play;
		static pf_sfSoundStream_Pause sfSoundStream_Pause;
		static pf_sfSoundStream_Stop sfSoundStream_Stop;
		static pf_sfSoundStream_GetStatus sfSoundStream_GetStatus;	
		static pf_sfSoundStream_GetChannelsCount sfSoundStream_GetChannelsCount;
		static pf_sfSoundStream_GetSampleRate sfSoundStream_GetSampleRate;
		static pf_sfSoundStream_SetPitch sfSoundStream_SetPitch;
		static pf_sfSoundStream_SetVolume sfSoundStream_SetVolume;
		static pf_sfSoundStream_SetPosition sfSoundStream_SetPosition;	
		static pf_sfSoundStream_SetMinDistance sfSoundStream_SetMinDistance;
		static pf_sfSoundStream_SetAttenuation sfSoundStream_SetAttenuation;
		static pf_sfSoundStream_GetPitch sfSoundStream_GetPitch;
		static pf_sfSoundStream_GetVolume sfSoundStream_GetVolume;
		static pf_sfSoundStream_GetPosition sfSoundStream_GetPosition;
		static pf_sfSoundStream_GetMinDistance sfSoundStream_GetMinDistance;
		static pf_sfSoundStream_GetAttenuation sfSoundStream_GetAttenuation;
		static pf_sfSoundStream_GetPlayingOffset sfSoundStream_GetPlayingOffset;
		static pf_sfSoundStream_GetLoop sfSoundStream_GetLoop;
		static pf_sfSoundStream_SetLoop sfSoundStream_SetLoop;
		
		
		static pf_sfSoundStream_IsRelativeToListener	sfSoundStream_IsRelativeToListener;
		static pf_sfSoundStream_SetRelativeToListener	sfSoundStream_SetRelativeToListener;
	}

	static this()
	{
	debug
		DllLoader dll = DllLoader.load("csfml-audio-d");
	else
		DllLoader dll = DllLoader.load("csfml-audio");
		
		sfSoundStream_Create = cast(pf_sfSoundStream_Create)dll.getSymbol("sfSoundStream_Create");
		sfSoundStream_Destroy = cast(pf_sfSoundStream_Destroy)dll.getSymbol("sfSoundStream_Destroy");
		sfSoundStream_Play = cast(pf_sfSoundStream_Play)dll.getSymbol("sfSoundStream_Play");
		sfSoundStream_Pause = cast(pf_sfSoundStream_Pause)dll.getSymbol("sfSoundStream_Pause");
		sfSoundStream_Stop = cast(pf_sfSoundStream_Stop)dll.getSymbol("sfSoundStream_Stop");
		sfSoundStream_GetStatus = cast(pf_sfSoundStream_GetStatus)dll.getSymbol("sfSoundStream_GetStatus");
		sfSoundStream_GetChannelsCount = cast(pf_sfSoundStream_GetChannelsCount)dll.getSymbol("sfSoundStream_GetChannelsCount");
		sfSoundStream_GetSampleRate = cast(pf_sfSoundStream_GetSampleRate)dll.getSymbol("sfSoundStream_GetSampleRate");
		sfSoundStream_SetPitch = cast(pf_sfSoundStream_SetPitch)dll.getSymbol("sfSoundStream_SetPitch");
		sfSoundStream_SetVolume = cast(pf_sfSoundStream_SetVolume)dll.getSymbol("sfSoundStream_SetVolume");
		sfSoundStream_SetPosition = cast(pf_sfSoundStream_SetPosition)dll.getSymbol("sfSoundStream_SetPosition");
		sfSoundStream_SetMinDistance = cast(pf_sfSoundStream_SetMinDistance)dll.getSymbol("sfSoundStream_SetMinDistance");
		sfSoundStream_SetAttenuation = cast(pf_sfSoundStream_SetAttenuation)dll.getSymbol("sfSoundStream_SetAttenuation");
		sfSoundStream_GetPitch = cast(pf_sfSoundStream_GetPitch)dll.getSymbol("sfSoundStream_GetPitch");
		sfSoundStream_GetVolume = cast(pf_sfSoundStream_GetVolume)dll.getSymbol("sfSoundStream_GetVolume");
		sfSoundStream_GetPosition = cast(pf_sfSoundStream_GetPosition)dll.getSymbol("sfSoundStream_GetPosition");
		sfSoundStream_GetMinDistance = cast(pf_sfSoundStream_GetMinDistance)dll.getSymbol("sfSoundStream_GetMinDistance");
		sfSoundStream_GetAttenuation = cast(pf_sfSoundStream_GetAttenuation)dll.getSymbol("sfSoundStream_GetAttenuation");
		sfSoundStream_GetPlayingOffset = cast(pf_sfSoundStream_GetPlayingOffset)dll.getSymbol("sfSoundStream_GetPlayingOffset");
		sfSoundStream_GetLoop = cast(pf_sfSoundStream_GetLoop)dll.getSymbol("sfSoundStream_GetLoop");
		sfSoundStream_SetLoop = cast(pf_sfSoundStream_SetLoop)dll.getSymbol("sfSoundStream_SetLoop");
		
		sfSoundStream_IsRelativeToListener	= cast(pf_sfSoundStream_IsRelativeToListener)	dll.getSymbol("sfSoundStream_IsRelativeToListener");
		sfSoundStream_SetRelativeToListener	= cast(pf_sfSoundStream_SetRelativeToListener)	dll.getSymbol("sfSoundStream_SetRelativeToListener");
	}
}