/*********************************************************************//**
*	\brief 3D WAV Sound Library.
*	Knihovna zvuku, ktera umi nacitat zvukove soubory typu WAV a Ogg. Obsahuje 	
*	jednoduche struktury a funkce pro prehravani zvuku a umoznuje sdilet
*	data mezi zvukovymi elementy (zdroji zvuku ve scene), ktere maji
*	prehravat to same. Vyuziva se knihovny OpenAL, ktera je velice podobna
*	OpenGL stylem programovani. Kvuli omezeni poctu zdroju zvuku bylo treba
*	implementovat algoritmus pridelovani zdroju zdrojum virtualnim.
*
*	\author Michal Jirous
*	\date 25.11.2008
*	\file soundslib.h
**********************************************************************/


#ifndef __SOUNDSLIB_H__
#define __SOUNDSLIB_H__

#include <AL/al.h>
#include <AL/alut.h>
#include <string>
#include <map>
#include <vector>
#include <list>

struct CoreSound;

/**  @brief Struktura jednotky zvuku.
*/
struct SoundElement
{
//	ALuint m_uiSourceID;		/*!< @brief Identifikacni cislo zdroje zvuku v ramci OpenAL. */
	CoreSound *pCore;		/*!< @brief Ukazatel na spolecne jadro zvuku, ktere drzi informace o zvukovych datech. */
	ALint m_iState;			/*!< @brief Stav prehravani zvuku. */
	SoundElement();			/*!< @brief Konstruktor inicializuje data. */

	void play();			/*!< @brief Zacne prehravat zvuk. */
	void playIfNotPlaying();
	void pause();			/*!< @brief Pozastavi prehravani zvuku. */
	void stop();			/*!< @brief Vrati se na zacatek zvuku. */
	
	void setPitch( ALfloat pitch );			/*!< @brief Vyska tonu. */
	void setGain( ALfloat gain ); 			/*!< @brief Nastaveni hlasitosti zdroje. */
	void setLooping( ALint loop );				/*!< @brief Nastavi opakovani zvuku. */
	void setPosition( ALfloat x, ALfloat y, ALfloat z );	/*!< @brief Nastavi pozici zvuku v prostoru. */
	void setVelocity( ALfloat x, ALfloat y, ALfloat z );	/*!< @brief Pohyb zdroje zvuku. */
	void setSourceRelative( ALboolean relativity );			/*!< @brief Nastavi jestli je pozice zdroje relativni k posluchaci ci ne. */
	void setPreference( ALboolean preference );				/*!< @brief Nastavi zda tento virtualni zdroj preferuje nejaky zdroj OpenAL. */
	void setPreferedSourceId( int index );					/*!< @brief Nastavi index preferovaneho zdroje. */
	void setRadius( ALfloat radius );
	void setFullVolumeRadius( ALfloat r ) { if( r < radius ) fullradius = r; }
	bool processRangeCheck();
	void update();

	ALint getState();				/*!< @brief Zjistuje aktualni stav prehravani. */

	int preferedSourceId;			/*!< @brief Identifikacni cislo preferovaneho ci naposledy pouziteho zdroje. */
	bool stayWithPreferedSource;	/*!< @brief Urcuje preferovani zdroje #preferedSourceId. */

	ALfloat fullradius;
	ALfloat radius;
	ALfloat pitch;					/*!< @brief Vyska tonu. */
	ALfloat gain;					/*!< @brief Hlasitost zdroje. */
	ALint looping;					/*!< @brief Opakovani zvuku. */
	ALfloat position[3];			/*!< @brief Pozice zvuku. */
	ALfloat velocity[3];			/*!< @brief Smer pohybu zdroje zvuku. */
	ALboolean relativity;			/*!< @brief Relativita pozice vuci posluchaci. */

	unsigned int m_uiId;

	ALboolean shouldPlay;

};

/** Zdroj zvukovych dat. */
struct CoreSound
{
	ALboolean m_bLooping;		/*!< @brief Urcuje opakovani zvuku.  */
	ALuint m_uiBufferID;		/*!< @brief Identifikacni cislo bufferu se zvukovymi daty. */
	std::string m_sKeyFilename;	/*!< @brief Identifikacni retezec a jmeno souboru s daty. */
	unsigned int m_uiSharedIndex;	/*!< @brief Pocitadlo referenci k tomuto zdroji zvukovych dat. */
	CoreSound();			/*!< @brief Konstuktor pro inicializaci promennych. */
};

const int SL_BUFFER_SIZE  =   32768;       /*!< @brief Velikost bufferu pri nacitani dat ze souboru. */

static const char *SL_SOUND_DIRECTORY = "Sounds/";	/*!< @brief Adresar se zvuky. */
const int SL_AUTOMATIC = -1;						/*!< @brief Urcuje automaticke prirazeni zdroje zvuku novemu SoundElement. */
const int SL_MAX_SOURCES = 32;						/*!< @brief Maximalni pocet zdroju zvuku. */

/** @brief Zdroj zvuku. */
struct SourceElement
{
	unsigned int usage;		/*!< @brief Pocet pouziti tohoto zdroje od spusteni programu. */
	bool reserved;			/*!< @brief Urcuje, zda je tento zdroj rezervovany. */
	unsigned int lastSoundElemId;	/*!< @brief Identifikacni cislo posledniho SoundElement, ktery tento zdroj vyuzival. */
	ALuint sourceId;		/*!< @brief Identifikacni cislo zdroje v ramci OpenAL. */
};


const short int SL_RELATIVITY	= 0x0001;	/*!< @brief Jeden z globalnich bitovych parametru, ktery urcuje standardni nastaveni relativity posize zdroju vuci posluchaci. */
const short int SL_FORCE_SOURCE = 0x0002;	/*!< @brief Jeden z globalnich bitovych parametru, ktery urcuje standarni nastaveni preferovani zdroje. */
#include <stack>

/** @brief Operacni trida knihovny zvuku. */
class CSoundLibrary
{
	typedef std::map<std::string,CoreSound> soundmap_t;

	soundmap_t m_SoundMap;	//databaze zvuku
	CoreSound *getCoreSound( std::string filename );
	bool LoadOGG( const char *fileName, std::vector<char> &buffer, ALenum &format, ALsizei &freq );
	CoreSound *addEntry( std::string filename );

	bool loadSoundInternal( SoundElement &element, std::string sFilename, int iSourceElementId );
	struct SoundEvent
	{
		unsigned int targettime;
		SoundElement *pElement;
	};
	std::list<SoundEvent> m_PostSoundList;

	friend struct SoundElement;


	int m_iNumSources;
	SourceElement m_MySources[SL_MAX_SOURCES];	//maximum

	unsigned int m_uiParameters;
	int m_iDefaultSourceId;
	std::stack<unsigned int> m_Stack;
	int determineSourceAdept();

	unsigned int m_uiSoundElementIdentificator;
	template <int SIZE>
	friend class SoundElemPool;
	ALfloat m_fListenerPos[3];
public:
	
	bool loadSound( SoundElement &element, std::string sFilename, int iSourceElementId = SL_AUTOMATIC );	/*!< @brief Nacte zvukova data ze souboru a vytvori novy zvukovy element. */
	void unloadSound( SoundElement &element );	/*!< @brief Snizi pocet referenci na data zvuku. */
	void checkSoundsUsage();			/*!< @brief Otestuje pocet referenci u kazdeho CoreSound a pokud je nektere rovno nule, tak ho smaze. */
	void setListenerPosition( ALfloat x, ALfloat y, ALfloat z );	/*!< @brief Nastaveni pozice posluchace. */
	void setListenerVelocity( ALfloat x, ALfloat y, ALfloat z );	/*!< @brief Nastaveni pohybu posluchace. */
	void setListenerOrientation( ALfloat at_x, ALfloat at_y, ALfloat at_z, ALfloat up_x, ALfloat up_y, ALfloat up_z );	/*!< @brief nastaveni orientace posluchace. */
	void setListenerPosition( ALfloat *values );	/*!< @brief Nastaveni pozice posluchace. */
	void setListenerVelocity( ALfloat *values );	/*!< @brief Nastaveni pohybu posluchace. */
	void setListenerOrientation( ALfloat *values );	/*!< @brief nastaveni orientace posluchace. */
	void init( int *argc, char *argv[] );			/*!< @brief Inicializace knihovny zvuku. */
	
	void postSoundPlay( SoundElement *element, unsigned int delay_ms );	/*!< @brief Registrace prehravani zvuku s casovou prodlevou. */
	
	void play( SoundElement &element );	/*!< @brief Spusteni prehravani zvuku. */
	void stop( SoundElement &element );	/*!< @brief Zastaveni prehravani zvuku. */
	void pause( SoundElement &element );	/*!< @brief Pozastaveni prehravani zvuku. */

	void clearPostSounds();	/*!< @brief Vymaze pamet zvuku, ktere se maji prehravat s casovou prodlevou. */
	void run();				/*!< @brief Aktualizace knihovny, hlavne pak, zda se nema spustit prehravani nejakeho zvuku s casovou prodlevou. */
	void destroy();			/*!< @brief Deinicializace dat. */
	void quit();			/*!< @brief Ukonceni funkce knihovny. */

	void setDefaultRelativity( ALboolean relativity );	/*!< @brief Nastaveni standardni relativity pozice zdroje vuci posluchaci. */
	void setDefaultForceSourceUse( ALboolean force );	/*!< @brief Nastaveni standardniho preferovani zdroje. */
	void setDefaultSourdeId( int Id );					/*!< @brief Nastaveni preferovaneho Id zdroje. */

	void pushAttrib();			/*!< @brief Ulozi soucasne nastaveni knihovny na vrchol zasobniku. */
	void popAttrib();			/*!< @brief Vyzvedne nastaveni knihovny z vrcholu zasobnika. */

	bool reserveSource( int source_elem_id );	/*!< @brief Pokusi se rezervovat zdroj. */
	bool reserveFreeSource( int *source_elem_id );	/*!< @brief Nalezne volny zdroj a rezervuje ho/ */
	bool freeSource( int source_elem_id );		/*!< @brief uvolni zdroj. */

	void setDistanceModel( ALenum model )	{ alDistanceModel( model ); }
};


extern CSoundLibrary soundLibrary;	/*!< @brief Instance knihovny zvuku. */


template <int SIZE>
class SoundElemPool
{
	int m_iTotalSounds;
	SoundElement m_StoredSounds[SIZE];
public:
	int addSound( std::string filename, int position = -1 )
	{
		if( position == -1 )
			position = m_iTotalSounds;
		if( position < SIZE )
		{
			if( soundLibrary.loadSound( m_StoredSounds[position], filename ) )
			{
				m_iTotalSounds++;
				return (m_iTotalSounds - 1);
			}
		}

		return -1;
	}

	void use( const int id, SoundElement &target )
	{
		if( id < SIZE )
		{
			target.pCore = m_StoredSounds[id].pCore;
			target.m_uiId = soundLibrary.m_uiSoundElementIdentificator++;
		}
	}


	void destroy()
	{	
		for( int i = 0; i < SIZE; ++i )
			soundLibrary.unloadSound( m_StoredSounds[i] );
		
	}

	SoundElemPool()
	{
		m_iTotalSounds = 0;
	}
};



#endif

