/*  SoundEngine.h

    flatzebra - SDL-based sound renderer
    Copyright (C) 2011 Pierre Sarrazin <http://sarrazip.com/>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public
    License along with this program; if not, write to the Free
    Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA  02110-1301, USA.
*/

#pragma once

#include <flatzebra/roundbeetle/Bus.h>
#include <flatzebra/roundbeetle/WaveFileBuffer.h>
#include <flatzebra/roundbeetle/ADSR.h>
#include <flatzebra/roundbeetle/FrequencyFunction.h>

#include <string>
#include <map>


namespace roundbeetle {


class SampleSource;
class FrameSource;
class FrameSourceAdder;
class WaveFileBuffer;
class PausableSource;
class ADSRSource;
class ADSR;
class FrequencyFunction;


class SoundEngine
{
private:

    SoundEngine(float mainBusVolumeMeterWindowLengthInSeconds);

    static SoundEngine *inst;

public:

    // Creates the single instance of this SoundEngine.
    // The caller must call destroy() when finished.
    //
    // _volumeMeterWindowLengthInSeconds: no VolumeMeter added to main bus if 0.
    // Main bus is pausable.
    //
    // Returns 0 upon success, or an error code otherwise.
    //
    static int create(int rendererFreq,
                      float mainBusVolumeMeterWindowLengthInSeconds,
                      const char *rawRecordingFilename);

    static SoundEngine &instance();

    // Destroys the SoundEngine instance created by create().
    //
    static void destroy();

    // Must be called after all buses have been configured
    // (e.g., after having called addVolumeMeter() on a bus).
    // No bus must change after calling this.
    //
    void postBusInit();

    ~SoundEngine();

    Bus &getMainBus();

    // Returns -1 on error.
    //
    int requestWaveFileSound(const WaveFileBuffer &wfb, Bus &bus);

    // Returns -1 on error.
    //
    int requestSine(float _toneFreq, float _linVol, float _durationInSeconds, Bus &bus);

    // loopCount: number of loops to perform; 0 means infinity.
    // Returns -1 on error.
    //
    int requestSquareWave(float _freq, const ADSR &adsr, size_t loopCount, Bus &bus);

    // Same as previous, bus frequency changes according to 'freqFunc'.
    // A pointer is kept on 'freqFunc', which is not destroyed by this engine.
    //
    int requestSquareWave(FrequencyFunction &freqFunc, const ADSR &adsr, size_t loopCount, Bus &bus);

    // Only works on square wave started with a frequency, not with a FrequencyFunction.
    //
    bool setSquareWaveFreq(int reqHandle, float _newFreq);

    int requestWhiteNoise(const ADSR &adsr, size_t loopCount, Bus &bus);

    bool isRequestAlive(int reqHandle) const;

    bool pauseRequest(int reqHandle);

    bool resumeRequest(int reqHandle);

    bool stopRequest(int reqHandle);

    // Pauses the engine and suspends engine's CPU consumption.
    // May be called even if this engine is already paused.
    // But then, resumeEngine() must be called an equal number of times
    // to let the engine run again.
    // Returns true on success, false on failure.
    //
    bool pauseEngine();

    // Must be called as many times as pauseEngine() was called
    // to let the engine run again.
    // Does nothing if the engine is already running.
    // Returns true if the engine has started running again,
    // or if the engine was already running (i.e., was not paused).
    // Returns false if the engine is still paused (because
    // pauseEngine() was called more than once).
    //
    bool resumeEngine();

    typedef void (*RequestFinishedCallback)(int, void *);

    // The callback will be invoked under the renderer mutex.
    //
    void registerRequestFinishedCallback(RequestFinishedCallback cb, void *userData);

private:

    bool isPostBusInitDone() const;

    int addSampleSourceToBus(SampleSource *ss, const ADSR *adsr, size_t loopCount, Bus &bus);

    static void onChildRemovedStatic(FrameSource *child, void *userData, FrameSourceAdder *adder);

    void onChildRemoved(FrameSource *child, FrameSourceAdder *adder);

    struct Desc
    {
        SampleSource *clientSampleSource;  // source requested by engine client (e.g., SquareWaveSource)
        ADSRSource *adsrSource;  // optional
        FrameSource *frameSource;  // tree added on top of clientSampleSource (e.g., SampleToFramePanner)
        Bus *bus;  // bus designated by engine client

        Desc(SampleSource *_clientSampleSource = NULL,
             ADSRSource *_adsrSource = NULL,
             FrameSource *_frameSource = NULL,
             Bus *_bus = NULL)
        :   clientSampleSource(_clientSampleSource),
            adsrSource(_adsrSource),
            frameSource(_frameSource),
            bus(_bus)
        {
        }
    };

    typedef std::map<int, Desc> RequestMap;
    RequestMap::iterator findReqHandle(FrameSource *child);

    PausableSource *getPausableSourceFromRequestHandle(int reqHandle);

private:

    SoundEngine(const SoundEngine &);
    SoundEngine &operator = (const SoundEngine &);

private:

    Bus mainBus;
    RequestMap reqTable;
    int reqHandleGenerator;
    size_t pauseCounter;
    RequestFinishedCallback onRequestFinishedCallback;
    void *onRequestFinishedUserData;

};


}  // namespace roundbeetle
