/*
*   DSFML - SFML Library binding in D language.
*   Copyright (C) 2008 Julien Dagorn (sirjulio13@gmail.com)
*   
*   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 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.launch();
        }
    }
    
    /**
    *   Pause the stream
    */        
    void pause()
    {
        sfSoundStream_Pause(m_ptr);
    }

    /**
    *   Stop the stream
    */        
    void stop()
    {
        m_flag = false;
        sfSoundStream_Stop(m_ptr);
        m_t.wait();
        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);
    }
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(void* dummy)
    {
        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;

        
        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 this()
    {
        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");
    }
}