/** 
 * @file audioengine.h
 * @brief Definition of LLAudioEngine base class abstracting the audio support
 *
 * $LicenseInfo:firstyear=2000&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */


#ifndef LL_AUDIOENGINE_H
#define LL_AUDIOENGINE_H

#include <list>
#include <map>

#include "v3math.h"
#include "v3dmath.h"
#include "lltimer.h"
#include "lluuid.h"
#include "llframetimer.h"
#include "llassettype.h"
#include "llextendedstatus.h"

#include "lllistener.h"

#include <boost/signals2.hpp> // <FS:Ansariel> Output device selection

const F32 LL_WIND_UPDATE_INTERVAL = 0.1f;
const F32 LL_WIND_UNDERWATER_CENTER_FREQ = 20.f;

const F32 ATTACHED_OBJECT_TIMEOUT = 5.0f;
const F32 DEFAULT_MIN_DISTANCE = 2.0f;

#define LL_MAX_AUDIO_CHANNELS 30
// <FS:Ansariel> FIRE-4276: Increase number of audio buffers
//#define LL_MAX_AUDIO_BUFFERS 40	// Some extra for preloading, maybe?
#define LL_MAX_AUDIO_BUFFERS 60
// </FS:Ansariel>

class LLAudioSource;
class LLAudioData;
class LLAudioChannel;
class LLAudioChannelOpenAL;
class LLAudioBuffer;
class LLStreamingAudioInterface;
struct SoundData;

//
//  LLAudioEngine definition
//
class LLAudioEngine 
{
	friend class LLAudioChannelOpenAL; // bleh. channel needs some listener methods.
	
public:
	enum LLAudioType
	{
		AUDIO_TYPE_NONE    = 0,
		AUDIO_TYPE_SFX     = 1,
		AUDIO_TYPE_UI      = 2,
		AUDIO_TYPE_AMBIENT = 3,
		AUDIO_TYPE_COUNT   = 4 // last
	};
	
	enum LLAudioPlayState
	{
		// isInternetStreamPlaying() returns an *S32*, with
		// 0 = stopped, 1 = playing, 2 = paused.
		AUDIO_STOPPED = 0,
		AUDIO_PLAYING = 1,
		AUDIO_PAUSED = 2
	};
	
	LLAudioEngine();
	virtual ~LLAudioEngine();

	// initialization/startup/shutdown
	virtual bool init(void *userdata, const std::string &app_title);
	virtual std::string getDriverName(bool verbose) = 0;
	virtual void shutdown();

	// Used by the mechanics of the engine
	//virtual void processQueue(const LLUUID &sound_guid);
	virtual void setListener(LLVector3 pos,LLVector3 vel,LLVector3 up,LLVector3 at);
	virtual void updateWind(LLVector3 direction, F32 camera_height_above_water) = 0;
	virtual void idle();
	virtual void updateChannels();

	//
	// "End user" functionality
	//
	virtual bool isWindEnabled();
	virtual void enableWind(bool state_b);

	// Use these for temporarily muting the audio system.
	// Does not change buffers, initialization, etc. but
	// stops playing new sounds.
	void setMuted(bool muted);
	bool getMuted() const { return mMuted; }
#ifdef USE_PLUGIN_MEDIA
	LLPluginClassMedia* initializeMedia(const std::string& media_type);
#endif
	F32 getMasterGain();
	void setMasterGain(F32 gain);

	F32 getSecondaryGain(S32 type);
	void setSecondaryGain(S32 type, F32 gain);

	F32 getInternetStreamGain();

	virtual void setDopplerFactor(F32 factor);
	virtual F32 getDopplerFactor();
	virtual void setRolloffFactor(F32 factor);
	virtual F32 getRolloffFactor();
	virtual void setMaxWindGain(F32 gain);


	// Methods actually related to setting up and removing sounds
	// Owner ID is the owner of the object making the request
	// NaCl - Sound Explorer
	void triggerSound(const LLUUID &sound_id, const LLUUID& owner_id, const F32 gain,
					  const S32 type = LLAudioEngine::AUDIO_TYPE_NONE,
					  const LLVector3d &pos_global = LLVector3d::zero,
					  // <FS:Testy> Optional parameter for setting the audio source UUID
					  // const LLUUID& source_object = LLUUID::null);
					  const LLUUID& source_object = LLUUID::null,
					  const LLUUID& audio_source_id = LLUUID::null);

	// NaCl End
	void triggerSound(SoundData& soundData);

	bool preloadSound(const LLUUID &id);

	void addAudioSource(LLAudioSource *asp);
	void cleanupAudioSource(LLAudioSource *asp);

	LLAudioSource *findAudioSource(const LLUUID &source_id);
	LLAudioData *getAudioData(const LLUUID &audio_uuid);

	// Internet stream implementation manipulation
	LLStreamingAudioInterface *getStreamingAudioImpl();
	void setStreamingAudioImpl(LLStreamingAudioInterface *impl);
	// Internet stream methods - these will call down into the *mStreamingAudioImpl if it exists
	void startInternetStream(const std::string& url);
	void stopInternetStream();
	void pauseInternetStream(S32 pause);
	void updateInternetStream(); // expected to be called often
	LLAudioPlayState isInternetStreamPlaying();
	// use a value from 0.0 to 1.0, inclusive
	void setInternetStreamGain(F32 vol);
	std::string getInternetStreamURL();
	
	// For debugging usage
	virtual LLVector3 getListenerPos();

	LLAudioBuffer *getFreeBuffer(); // Get a free buffer, or flush an existing one if you have to.
	LLAudioChannel *getFreeChannel(const F32 priority); // Get a free channel or flush an existing one if your priority is higher
	void cleanupBuffer(LLAudioBuffer *bufferp);

	bool hasDecodedFile(const LLUUID &uuid);
	bool hasLocalFile(const LLUUID &uuid);

	bool updateBufferForData(LLAudioData *adp, const LLUUID &audio_uuid = LLUUID::null);

 
	// <FS:Ansariel> Asset blacklisting
	void removeAudioData(const LLUUID& audio_uuid);

	// Asset callback when we're retrieved a sound from the asset server.
	void startNextTransfer();
	static void assetCallback(const LLUUID &uuid, LLAssetType::EType type, void *user_data, S32 result_code, LLExtStat ext_status);

	// <FS:Ansariel> Output device selection
	typedef std::map<LLUUID, std::string> output_device_map_t;
	virtual output_device_map_t getDevices();
	virtual void setDevice(const LLUUID& device_uuid) { };

	typedef boost::signals2::signal<void(output_device_map_t output_device_map)> output_device_list_changed_callback_t;
	boost::signals2::connection setOutputDeviceListChangedCallback(const output_device_list_changed_callback_t::slot_type& cb)
	{
		return mOutputDeviceListChangedCallback.connect(cb);
	}

	void OnOutputDeviceListChanged(output_device_map_t output_device_map);
	// </FS:Ansariel>

	friend class LLPipeline; // For debugging
public:
	F32 mMaxWindGain; // Hack.  Public to set before fade in?

protected:
	virtual LLAudioBuffer *createBuffer() = 0;
	virtual LLAudioChannel *createChannel() = 0;

	virtual bool initWind() = 0;
	virtual void cleanupWind() = 0;
	virtual void setInternalGain(F32 gain) = 0;

	void commitDeferredChanges();

	virtual void allocateListener() = 0;


	// listener methods
	virtual void setListenerPos(LLVector3 vec);
	virtual void setListenerVelocity(LLVector3 vec);
	virtual void orientListener(LLVector3 up, LLVector3 at);
	virtual void translateListener(LLVector3 vec);


	F64 mapWindVecToGain(LLVector3 wind_vec);
	F64 mapWindVecToPitch(LLVector3 wind_vec);
	F64 mapWindVecToPan(LLVector3 wind_vec);

protected:
	LLListener *mListenerp;

	bool mMuted;
	void* mUserData;

	S32 mLastStatus;
	
	bool mEnableWind;

	LLUUID mCurrentTransfer; // Audio file currently being transferred by the system
	LLFrameTimer mCurrentTransferTimer;

	// A list of all audio sources that are known to the viewer at this time.
	// This is most likely a superset of the ones that we actually have audio
	// data for, or are playing back.
	typedef std::map<LLUUID, LLAudioSource *> source_map;
	typedef std::map<LLUUID, LLAudioData *> data_map;

	source_map mAllSources;
	data_map mAllData;

    std::array<LLAudioChannel*, LL_MAX_AUDIO_CHANNELS> mChannels;

	// Buffers needs to change into a different data structure, as the number of buffers
	// that we have active should be limited by RAM usage, not count.
    std::array<LLAudioBuffer*, LL_MAX_AUDIO_BUFFERS> mBuffers;
	
	F32 mMasterGain;
	F32 mInternalGain;			// Actual gain set; either mMasterGain or 0 when mMuted is true.
	F32 mSecondaryGain[AUDIO_TYPE_COUNT];

	F32 mNextWindUpdate;

	LLFrameTimer mWindUpdateTimer;

	// <FS:Ansariel> Output device selection
	output_device_list_changed_callback_t mOutputDeviceListChangedCallback;

private:
	void setDefaults();
	LLStreamingAudioInterface *mStreamingAudioImpl;

	// <FS:ND> Protect against corrupted sounds

	std::map<LLUUID,U32> mCorruptData;

public:
	void markSoundCorrupt( LLUUID const & );
	bool isCorruptSound( LLUUID const& ) const;

	// </FS:ND>
};




//
// Standard audio source.  Can be derived from for special sources, such as those attached to objects.
//


class LLAudioSource
{
public:
	// owner_id is the id of the agent responsible for making this sound
	// play, for example, the owner of the object currently playing it
	// NaCl - Sound Explorer
	LLAudioSource(const LLUUID &id, const LLUUID& owner_id, const F32 gain, const S32 type = LLAudioEngine::AUDIO_TYPE_NONE, const LLUUID& source_id = LLUUID::null, const bool isTrigger = true);
	// NaCl End
	virtual ~LLAudioSource();

	virtual void update();						// Update this audio source
	void updatePriority();

	void preload(const LLUUID &audio_id); // Only used for preloading UI sounds, now.

	void addAudioData(LLAudioData *adp, bool set_current = TRUE);

	void setForcedPriority(const bool ambient)						{ mForcedPriority = ambient; }
	bool isForcedPriority() const									{ return mForcedPriority; }

	void setLoop(const bool loop)							{ mLoop = loop; }
	bool isLoop() const										{ return mLoop; }

	void setSyncMaster(const bool master)					{ mSyncMaster = master; }
	bool isSyncMaster() const								{ return mSyncMaster; }

	void setSyncSlave(const bool slave)						{ mSyncSlave = slave; }
	bool isSyncSlave() const								{ return mSyncSlave; }

	void setQueueSounds(const bool queue)					{ mQueueSounds = queue; }
	bool isQueueSounds() const								{ return mQueueSounds; }

	void setPlayedOnce(const bool played_once)				{ mPlayedOnce = played_once; }

	void setType(S32 type)                                  { mType = type; }
	S32 getType()                                           { return mType; }

	void setPositionGlobal(const LLVector3d &position_global)		{ mPositionGlobal = position_global; }
	LLVector3d getPositionGlobal() const							{ return mPositionGlobal; }
	LLVector3 getVelocity()	const									{ return mVelocity; }				
	F32 getPriority() const											{ return mPriority; }

	// Gain should always be clamped between 0 and 1.
	F32 getGain() const												{ return mGain; }
	virtual void setGain(const F32 gain)							{ mGain = llclamp(gain, 0.f, 1.f); }

	const LLUUID &getID() const		{ return mID; }
	// NaCl - Sound Explorer
	const LLUUID &getLogID() const { return mLogID; }
	// NaCl End
	bool isDone() const;
	bool isMuted() const { return mSourceMuted; }

	LLAudioData *getCurrentData();
	LLAudioData *getQueuedData();
	LLAudioBuffer *getCurrentBuffer();

	bool setupChannel();

    // Stop the audio source, reset audio id even if muted
    void stop();

    // Start the audio source playing,
    // takes mute into account to preserve previous id if nessesary
    bool play(const LLUUID &audio_id);

	bool hasPendingPreloads() const;	// Has preloads that haven't been done yet

	friend class LLAudioEngine;
	friend class LLAudioChannel;
protected:
	void setChannel(LLAudioChannel *channelp);
	LLAudioChannel *getChannel() const						{ return mChannelp; }
	// NaCl - Sound Explorer
	static void logSoundPlay(const LLUUID& id, LLVector3d position, S32 type, const LLUUID& assetid, const LLUUID& ownerid, const LLUUID& sourceid, bool is_trigger, bool is_looped);
	static void logSoundStop(const LLUUID& id);
	static void pruneSoundLog();
	static S32 sSoundHistoryPruneCounter;
	// NaCl End

protected:
	LLUUID			mID; // The ID of the source is that of the object if it's attached to an object.
	LLUUID			mOwnerID;	// owner of the object playing the sound
	F32				mPriority;
	F32				mGain;
	bool			mSourceMuted;
	bool			mForcedPriority; // ignore mute, set high priority, researved for sound preview and UI
	bool			mLoop;
	bool			mSyncMaster;
	bool			mSyncSlave;
	bool			mQueueSounds;
	bool			mPlayedOnce;
	bool            mCorrupted;
	S32             mType;
	LLVector3d		mPositionGlobal;
	LLVector3		mVelocity;
	// NaCl - Sound explorer
	LLUUID			mLogID;
	LLUUID			mSourceID;
	bool			mIsTrigger;
	// NaCl end

	//LLAudioSource	*mSyncMasterp;	// If we're a slave, the source that we're synced to.
	LLAudioChannel	*mChannelp;		// If we're currently playing back, this is the channel that we're assigned to.
	LLAudioData		*mCurrentDatap;
	LLAudioData		*mQueuedDatap;

	typedef std::map<LLUUID, LLAudioData *> data_map;
	data_map mPreloadMap;

	LLFrameTimer mAgeTimer;
};




//
// Generic metadata about a particular piece of audio data.
// The actual data is handled by the derived LLAudioBuffer classes which are
// derived for each audio engine.
//


class LLAudioData
{
  public:
    LLAudioData(const LLUUID &uuid);
    bool load();

    LLUUID         getID() const { return mID; }
    LLAudioBuffer *getBuffer() const { return mBufferp; }

    bool hasLocalData() const { return mHasLocalData; }
    bool hasDecodedData() const { return mHasDecodedData; }
    bool hasCompletedDecode() const { return mHasCompletedDecode; }
    bool hasDecodeFailed() const { return mHasDecodeFailed; }
    bool hasWAVLoadFailed() const { return mHasWAVLoadFailed; }

    void setHasLocalData(const bool hld) { mHasLocalData = hld; }
    void setHasDecodedData(const bool hdd) { mHasDecodedData = hdd; }
    void setHasCompletedDecode(const bool hcd) { mHasCompletedDecode = hcd; }
    void setHasDecodeFailed(const bool hdf) { mHasDecodeFailed = hdf; }
    void setHasWAVLoadFailed(const bool hwlf) { mHasWAVLoadFailed = hwlf; }

    friend class LLAudioEngine;  // Severe laziness, bad.

  protected:
    LLUUID         mID;
    LLAudioBuffer *mBufferp;             // If this data is being used by the audio system, a pointer to the buffer will be set here.
    bool           mHasLocalData;        // Set true if the encoded sound asset file is available locally
    bool           mHasDecodedData;      // Set true if the decoded sound file is available on disk
    bool           mHasCompletedDecode;  // Set true when the sound is decoded
    bool           mHasDecodeFailed;     // Set true if decoding failed, meaning the sound asset is bad
    bool mHasWAVLoadFailed;  // Set true if loading the decoded WAV file failed, meaning the sound asset should be decoded instead if
                             // possible
};


//
// Base class for an audio channel, i.e. a channel which is capable of playing back a sound.
// Management of channels is done generically, methods for actually manipulating the channel
// are derived for each audio engine.
//


class LLAudioChannel
{
public:
	LLAudioChannel();
	virtual ~LLAudioChannel();

	virtual void setSource(LLAudioSource *sourcep);
	LLAudioSource *getSource() const			{ return mCurrentSourcep; }

	void setSecondaryGain(F32 gain)             { mSecondaryGain = gain; }
	F32 getSecondaryGain()                      { return mSecondaryGain; }

	friend class LLAudioEngine;
	friend class LLAudioSource;
protected:
	virtual void play() = 0;
	virtual void playSynced(LLAudioChannel *channelp) = 0;
	virtual void cleanup() = 0;
	virtual bool isPlaying() = 0;
	void setWaiting(const bool waiting)			{ mWaiting = waiting; }
	bool isWaiting() const						{ return mWaiting; }

	virtual bool updateBuffer(); // Check to see if the buffer associated with the source changed, and update if necessary.
	virtual void update3DPosition() = 0;
	virtual void updateLoop() = 0; // Update your loop/completion status, for use by queueing/syncing.
protected:
	LLAudioSource	*mCurrentSourcep;
	LLAudioBuffer	*mCurrentBufferp;
	bool			mLoopedThisFrame;
	bool			mWaiting;	// Waiting for sync.
	F32             mSecondaryGain;
};




// Basically an interface class to the engine-specific implementation
// of audio data that's ready for playback.
// Will likely get more complex as we decide to do stuff like real streaming audio.


class LLAudioBuffer
{
public:
	virtual ~LLAudioBuffer() {};
	virtual bool loadWAV(const std::string& filename) = 0;
	virtual U32 getLength() = 0;

	friend class LLAudioEngine;
	friend class LLAudioChannel;
	friend class LLAudioData;
protected:
	bool mInUse;
	LLAudioData *mAudioDatap;
	LLFrameTimer mLastUseTimer;
};

struct SoundData
{
	LLUUID audio_uuid;
	LLUUID owner_id;
	F32 gain;
	S32 type;
	LLVector3d pos_global;

	SoundData(const LLUUID &audio_uuid, 
		const LLUUID& owner_id, 
		const F32 gain, 					  
		const S32 type = LLAudioEngine::AUDIO_TYPE_NONE,
		const LLVector3d &pos_global = LLVector3d::zero)
	{
		this->audio_uuid = audio_uuid;
		this->owner_id = owner_id;
		this->gain = gain;
		this->type = type;
		this->pos_global = pos_global;
	}
};

extern LLAudioEngine* gAudiop;

// NaCl - Sound explorer
struct LLSoundHistoryItem
{
	LLUUID mID;
	LLVector3d mPosition;
	S32 mType;
	bool mPlaying;
	LLUUID mAssetID;
	LLUUID mOwnerID;
	LLUUID mSourceID;
	bool mIsTrigger;
	bool mIsLooped;
	F64 mTimeStarted;
	F64 mTimeStopped;
	bool mReviewed;
	bool mReviewedCollision;

	LLSoundHistoryItem()
	  : mType(0)
	  , mPlaying(0)
	  , mIsTrigger(0)
	  , mIsLooped(0)
	  , mTimeStarted(0.f)
	  , mTimeStopped(0.f)
	  , mReviewed(false)
	  , mReviewedCollision(false)
	{
	}
};
extern std::map<LLUUID, LLSoundHistoryItem> gSoundHistory;
// NaCl End

#endif
