/*
*   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.soundrecorder;

import dsfml.audio.soundbuffer;

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

/**
*   SoundRecorder is an interface for capturing sound data.
*   
*   $(B onProcessSamples and onStop will be called by a different thread, take care of synchronization issues.)
*   
*   Examples:
*   ------- 
*   class MySoundRecorder : SoundRecorder
*   {
*       this()
*       {
*             
*       }
*       
*       protected bool onStart()
*       {
*           return true;   
*       }
*   
*       protected void onStop()
*       {
*             
*       }   
*       
*       protected bool onProcessSamples(out short[])
*       {
*           // Process data here
*           
*           return true; //return true to continue capture, else return false  
*       }
*   } 
*   -------
*/
abstract class SoundRecorder : DSFMLObject
{
    override void dispose()
    {
        if (m_flag)
            stop();

        m_instances.remove(m_id);
        sfSoundRecorder_Destroy(m_ptr);
    }


    /**
    *   Start the capture.
    *   
    *   Only one capture can happen at the same time
    *
    *   Params: 
    *       sampleRate : Sound frequency (the more samples, the higher the quality)
    *                    (44100 by default = CD quality)
    */
    void start(uint sampleRate = 44100)
    {
        sfSoundRecorder_Start(m_ptr, sampleRate);
        m_t = new Thread(&threadPoll);
        m_t.launch();
    }

    /**
    *   Stop the capture
    */
    void stop()
    {
        sfSoundRecorder_Stop(m_ptr);
        m_flag = false;
        m_t.wait();
        m_t = null;
    }

    /**
    *   Get the sample rate
    *
    *   Returns: 
    *       Frequency, in samples per second
    */
    uint getSampleRate() 
    {
        return sfSoundRecorder_GetSampleRate(m_ptr);
    }

    /**
    *   Tell if the system supports sound capture.
    *   If not, this class won't be usable
    *
    *   Returns:
    *       True if audio capture is supported
    *
    */
    static bool canCapture()
    {
        return cast(bool)sfSoundRecorder_CanCapture();
    }

protected:
    /**
    *   Protected constructor
    */   
    this()
    {
        m_id = ++seed;
        m_instances[m_id] = this;
        super(sfSoundRecorder_Create(&internalOnStart, &internalCallback, &internalOnStop, &m_id));
        
        init(true);
    }

    this(void* ptr)
    {
        super(ptr);

        init(false);
    }

    /**
    *   Start recording audio data
    *
    *   Returns:
    *       False to abort recording audio data, true to start
    */
    abstract bool onStart();
    
    /**
    *   Stop recording audio data
    */
    abstract void onStop();
    
    /**
    *   callback function
    *   
    *   Parameters:
    *       samples = Array of samples
    *       
    *   Returns:
    *       true to continue recording, false to stop.            
    */        
    abstract bool onProcessSamples(short[] samples);

    bool m_disposed;  
private:
    /*
    *   an init function to initialize id of the object.
    */    
    void init(bool flag)
    {
        if (flag)
        {
            m_list = new LinkedList!(Samples)();
            
            m_flag = true;
            m_continue = true;
            
            m_mutex = new Mutex();
        }
    }
        
    void* m_userData;
    int m_id;
    
    static int seed = 0;
    static SoundRecorder[int] m_instances;
    
    /*
    *   Extern C callback function
    *           
    *   This function must be static for C interop. To retrieve the current
    *   instance, we retrieve id of the sender in the user data, and search associated instance
    *   in the associative array.
    *   
    *   We don't call delegate or derived class on that thread because GC is not aware of this thread
    *   instead we enqueue data informations in a queue and poll this queue with a managed thread.                                  
    */
    extern(C) static int internalCallback(short* s, size_t size, void* user)
    {
        int id;
        // retrieve instance
        if ((id = *cast(int*)(user)) in m_instances)
        {
            SoundRecorder temp = m_instances[id];
            scope Lock l = new Lock(temp.m_mutex);
            if (temp.m_continue)
                // this new is allowed because Samples is an custom alloc class.
                temp.m_list.enqueue(new Samples(s, size));
            return temp.m_continue;
        }
        return false; 
    }

    extern(C) static int internalOnStart(void* user)
    {
        int id;
        bool ret = false;
        if ((id = *cast(int*)(user)) in m_instances)
        {
            SoundRecorder temp = m_instances[id];
            ret = temp.onStart();
        }
    
        return ret;
    }

    extern(C) static void internalOnStop(void* user)
    {
        // Nothing to do
    }
    
    /*
    *   Managed thread loop
    */    
    void threadPoll(void* user)
    {
        while (m_flag)
        {
            sleep(0.05f);
            // if samples are available
            if (!m_list.empty)
            {
                // Lock ressources
                scope Lock l = new Lock(m_mutex);
                
                Samples s = m_list.dequeue;
                m_continue = this.onProcessSamples(s.data[0..s.length].dup);
                
                delete s;
                
                if (!m_continue)
                {
                    // delete all samples left
                    foreach(Samples dummy; m_list)
                        delete dummy;
                    break;
                }
            }
        }
        
        onStop();
    }
    
    Mutex m_mutex;
    
    bool m_flag;
    bool m_continue = true;
    LinkedList!(Samples) m_list;
    Thread m_t;
    
// External ====================================================================

    extern (C)
    {
        typedef void* function(int function(void*), int function(short*, size_t, void*), void function(void*), void*) pf_sfSoundRecorder_Create;
    	typedef void function(void*) pf_sfSoundRecorder_Destroy;
    	typedef void function(void*, uint SampleRate) pf_sfSoundRecorder_Start;
    	typedef void function(void*) pf_sfSoundRecorder_Stop;
    	typedef uint function(void*) pf_sfSoundRecorder_GetSampleRate;
    	typedef int function() pf_sfSoundRecorder_CanCapture;
    
    	static pf_sfSoundRecorder_Create sfSoundRecorder_Create;
    	static pf_sfSoundRecorder_Destroy sfSoundRecorder_Destroy;
    	static pf_sfSoundRecorder_Start sfSoundRecorder_Start;
    	static pf_sfSoundRecorder_Stop sfSoundRecorder_Stop;
    	static pf_sfSoundRecorder_GetSampleRate sfSoundRecorder_GetSampleRate;
    	static pf_sfSoundRecorder_CanCapture sfSoundRecorder_CanCapture;
    }

    static this()
    {
        DllLoader dll = DllLoader.load("csfml-audio");
        
        sfSoundRecorder_Create = cast(pf_sfSoundRecorder_Create)dll.getSymbol("sfSoundRecorder_Create");
        sfSoundRecorder_Destroy = cast(pf_sfSoundRecorder_Destroy)dll.getSymbol("sfSoundRecorder_Destroy");
        sfSoundRecorder_Start = cast(pf_sfSoundRecorder_Start)dll.getSymbol("sfSoundRecorder_Start");
        sfSoundRecorder_Stop = cast(pf_sfSoundRecorder_Stop)dll.getSymbol("sfSoundRecorder_Stop");
        sfSoundRecorder_GetSampleRate = cast(pf_sfSoundRecorder_GetSampleRate)dll.getSymbol("sfSoundRecorder_GetSampleRate");
        sfSoundRecorder_CanCapture = cast(pf_sfSoundRecorder_CanCapture)dll.getSymbol("sfSoundRecorder_CanCapture");
    }

}

// Use explicit alloc to allow instaciation by C thread
private class Samples
{
    mixin Alloc;
    
    this(short* data, size_t length)
    {
        this.data = data;
        this.length = length;
    }

    public short* data;
    public size_t length;
}