#ifndef KADU_SOUND_H
#define KADU_SOUND_H

#include <qobject.h>
#include <qstring.h>
#include <qdatetime.h>
#include <qstringlist.h>
#include <qmap.h>
#include <qthread.h>
#include <qmutex.h>
#include <qsemaphore.h>

#include "config_file.h"
#include "gadu.h"
#include "misc.h"
#include "message_box.h"
#include "modules.h"
#include "themes.h"
#include "usergroup.h"
#include "userlist.h"
#include "../notify/notify.h"
/**
 * @defgroup sound Sound
 * The sound module.
 * @{
 */

/**
	Uogólniony deskryptor urządzenia dźwiękowego.
**/
typedef void* SoundDevice;

/**
**/
enum SoundDeviceType {RECORD_ONLY, PLAY_ONLY, PLAY_AND_RECORD};

/**
	To jest klasa używana wewnętrznie przez klasę SoundManager
	i nie powinieneś mieć potrzeby jej używania.
**/
class SamplePlayThread : public QObject, public QThread
{
	Q_OBJECT

	private:
		SoundDevice Device;
		const int16_t* Sample;
		int SampleLen;
		bool Stopped;
		QSemaphore PlayingSemaphore;
		QSemaphore SampleSemaphore;

	protected:
		virtual void run();
		virtual void customEvent(QCustomEvent* event);

	public:
		SamplePlayThread(SoundDevice device);
		void playSample(const int16_t* data, int length);
		void stop();

	signals:
		void samplePlayed(SoundDevice device);
};

/**
	To jest klasa używana wewnętrznie przez klasę SoundManager
	i nie powinieneś mieć potrzeby jej używania.
**/
class SampleRecordThread : public QObject, public QThread
{
	Q_OBJECT

	private:
		SoundDevice Device;
		int16_t* Sample;
		int SampleLen;
		bool Stopped;
		QSemaphore RecordingSemaphore;
		QSemaphore SampleSemaphore;

	protected:
		virtual void run();
		virtual void customEvent(QCustomEvent* event);

	public:
		SampleRecordThread(SoundDevice device);
		void recordSample(int16_t* data, int length);
		void stop();

	signals:
		void sampleRecorded(SoundDevice device);
};

/**
	To jest klasa używana wewnętrznie przez klasę SoundManager
	i nie powinieneś mieć potrzeby jej używania.
**/
class SndParams
{
	public:
		SndParams(QString fm = QString::null, bool volCntrl = false, float vol = 1);
		SndParams(const SndParams &p);

		QString filename;
		bool volumeControl;
		float volume;
};

/**
	To jest klasa używana wewnętrznie przez klasę SoundManager
	i nie powinieneś mieć potrzeby jej używania.
**/
class SoundPlayThread : public QThread
{
	public:
		SoundPlayThread();
		~SoundPlayThread();
		void run();
		void tryPlay(const char *path, bool volCntrl=false, float volume=1.0);
		void endThread();

	private:
		static bool play(const char *path, bool volCntrl=false, float volume=1.0);
		QMutex mutex;
		QSemaphore *semaphore;
		bool end;
		QValueList<SndParams> list;
};

class SoundManager : public Notifier
{
    Q_OBJECT
	private:
		Themes *themes;
		friend class SamplePlayThread;
		friend class SampleRecordThread;
		QTime lastsoundtime;
		bool mute;
		QMap<SoundDevice, SamplePlayThread*> PlayingThreads;
		QMap<SoundDevice, SampleRecordThread*> RecordingThreads;
		SoundPlayThread *play_thread;

		int simple_player_count;
		virtual void connectNotify(const char *signal);
		virtual void disconnectNotify(const char *signal);

	private slots:
		void newChat(Protocol *protocol, UserListElements senders, const QString &msg, time_t t);
		void newMessage(Protocol *protocol, UserListElements senders, const QString &msg, time_t t, bool &grab);
		void connectionError(Protocol *protocol, const QString &message);
		void userChangedStatusToAvailable(const QString &protocolName, UserListElement);
		void userChangedStatusToBusy(const QString &protocolName, UserListElement);
		void userChangedStatusToInvisible(const QString &protocolName, UserListElement);
		void userChangedStatusToNotAvailable(const QString &protocolName, UserListElement);

		/* from i ule są ignorowane, message wskazuje na plik z dźwiękiem do odtworzenia
		 * jeżeli message==QString::null, to odtwarzany jest standardowy dźwięk dla tego typu
		 * jeżeli mapa jest!=NULL brane są z niej następujące wartości:
		 *		"Force"           - bool (wymuszenie odtwarzania mimo wyciszenia)
		 */
		void message(const QString &from, const QString &message, const QMap<QString, QVariant> *parameters, const UserListElement *ule);
		virtual void externalEvent(const QString &notifyType, const QString &msg, const UserListElements &ules);

	public slots:
		void play(const QString &path, bool force=false);
		void play(const QString &path, bool volCntrl, double vol);
		void setMute(const bool& enable);

	public:

		SoundManager(const QString& name, const QString& configname);
		~SoundManager();

		Themes *theme();

		bool isMuted() const;
		int timeAfterLastSound() const;
		/**
			Otwiera urządzenie dźwiękowe do operacji
			odtwarzania i nagrywania sampli.
			Niektóre implementacje pozwalają na otwarcie
			wielu niezależnie działających "połączeń"
			z urządzeniami. Wystarczy wtedy kilkukrotnie
			wywołać tą metodę.
			Emituje sygnał openDeviceImpl() w celu
			przekazania żądania do konkrentego modułu
			dźwiękowego.
			@param type
			@param sample_rate sample rate - np. 8000 lub 48000
			@param channels ilość kanałów: 1 - mono, 2 - stereo
			@return uogólniony deskryptor urządzenia lub NULL jeśli otwarcie się nie powiodło.
		**/
		SoundDevice openDevice(SoundDeviceType type, int sample_rate, int channels = 1);
		/**
			Zamyka urządzenie dźwiękowe otwarte za pomocą
			metody openDevice().
			Niektóre implementacje pozwalają na otwarcie
			wielu niezależnie działających "połączeń"
			z urządzeniami. Każde otwarte połączenie należy
			zamknąć za pomocą tej metody.
			Jeśli włączyliśmy operacje nieblokujące to
			metoda ta czeka na zakończenie trwających operacji
			i kończy działanie wątków.
			Emituje sygnał closeDeviceImpl() w celu
			przekazania żądania do konkrentego modułu
			dźwiękowego.
			@param device uogólniony deskryptor urządzenia.
		**/
		void closeDevice(SoundDevice device);
		/**
			Powołuje do życia wątki zajmujące się odtwarzaniem
			i nagrywaniem próbek dla danego połączenia z
			urządzeniem dźwiękowym.
			Od tej chwili playSample() i recordSample()
			będą operacjami nieblokującymi.
			@param device uogólniony deskryptor urządzenia.
		**/
		void enableThreading(SoundDevice device);
		/**
			Standardowo po przekazaniu odtwarzanego sampla
			do sterownika dźwiękowego program (w trybie blokującym)
			lub wątek odtwarzający (w trybie nieblokującym) czeka
			na zakończenie emitowania dźwięku przez sterownik, aby
			zagwarantować, że wyjście z funkcji playSample() (w trybie
			blokującym) lub wyemitowanie sygnału samplePlayed() (w
			trybie nieblokującym) nastąpi dopiero po fizycznym
			odegraniu dźwięku. Inaczej mogło by to nastąpić dopiero
			przy wywołaniu metody closeDevice() co ma różne nieprzyjemne
			efekty uboczne.
			Czasem przy odtwarzaniu kilku małych sampli jeden po drugim
			powoduje to powstanie przerw między nimi. Aby tego uniknąć
			możemy użyć setFlushingEnabled(device, false) umożliwiając
			w ten sposób płynne odtwarzanie kilku sampli bezpośrednio
			po sobie.
		**/
		void setFlushingEnabled(SoundDevice device, bool enabled);
		/**
			Odtwarza próbkę dźwiękową. Standardowo jest to
			operacja blokująca. Może być wywołana z innego
			wątku (a nawet powinna).
			Emituje sygnał playSampleImpl() w celu
			przekazania żądania do konkrentego modułu
			dźwiękowego.
			Po uprzednim wywołaniu enableThreading() działanie
			metoda jest nieblokująca i przekazuje jedynie polecenie
			odtwarzania do wątku.
			W takim wypadku należy uważać, aby nie zwolnić pamięci
			zajmowanej przez dane sampla zanim odtwarzanie się nie
			zakończy.
			@param device uogólniony deskryptor urządzenia
			@param data wskaźnik do danych sampla
			@param length długość danych sampla (w bajtach)
			@return true jeśli odtwarzanie zakończyło się powodzeniem.
		**/
		bool playSample(SoundDevice device, const int16_t* data, int length);
		/**
			Nagrywa próbkę dźwiękową. Standardowo jest to
			operacja blokująca. Może być wywołana z innego
			wątku (a nawet powinna).
			Emituje sygnał recordSampleImpl() w celu
			przekazania żądania do konkrentego modułu
			dźwiękowego.
			Po uprzednim wywołaniu enableThreading() działanie
			metoda jest nieblokująca i przekazuje jedynie polecenie
			nagrywania do wątku.
			W takim wypadku należy uważać, aby nie zwolnić pamięci
			bufora na dane sampla zanim nagrywanie się nie
			zakończy.
			@param device uogólniony deskryptor urządzenia
			@param data wskaźnik na bufor dla danych sampla
			@param length długość sampla do nagrania (wielkość bufora w bajtach)
			@return true jeśli nagrywanie zakończyło się powodzeniem.
		**/
		bool recordSample(SoundDevice device, int16_t* data, int length);

	signals:
		void playSound(const QString &sound, bool volCntrl, double vol);
		/**
			Sygnał emitowany gdy odtwarzanie sampla się
			zakończyło (odnosi się tylko do sytuacji gdy
			włączone są operacje nieblokujące).
		**/
		void samplePlayed(SoundDevice device);
		/**
			Sygnał emitowany gdy nagrywanie sampla się
			zakończyło (odnosi się tylko do sytuacji gdy
			włączone są operacje nieblokujące).
		**/
		void sampleRecorded(SoundDevice device);
		/**
			Pod ten sygnał powinien podpiąć się moduł
			dźwiękowy jeśli obsługuje funkcję odtwarzania
			próbki dźwiękowej.
			Wyemitowanie sygnału oznacza żądanie
			otwarcia urządzenia dźwiękowego do operacji
			odtwarzania i nagrywania sampli.
			@param type
			@param sample_rate sample rate - np. 8000 lub 48000
			@param channels ilość kanałów: 1 - mono, 2 - stereo
			@device zwrócony uogólniony deskryptor urządzenia lub NULL jeśli otwarcie się nie powiodło.
		**/
		void openDeviceImpl(SoundDeviceType type, int sample_rate, int channels, SoundDevice& device);
		/**
			Pod ten sygnał powinien podpiąć się moduł
			dźwiękowy jeśli obsługuje funkcję odtwarzania
			próbki dźwiękowej.
			Wyemitowanie sygnału oznacza żądanie
			Zamknięcia urządzenia dźwiękowego otwartegp za pomocą
			metody openDevice().
			@param device uogólniony deskryptor urządzenia.
		**/
		void closeDeviceImpl(SoundDevice device);
		/**
			Pod ten sygnał powinien podpiąć się moduł
			dźwiękowy jeśli obsługuje funkcję odtwarzania
			próbki dźwiękowej.
			Wyemitowanie sygnału oznacza żądanie
			odtworzenia próbki dźwiękowej.
			Moduł powinien oddać sterowanie dopiero po
			odtworzeniu próbki.
			Sygnał zazwyczaj będzie emitowany z innego
			wątku i slot musi być do tego przystosowany.
			@param device uogólniony deskryptor urządzenia
			@param data wskaźnik do danych sampla
			@param length długość danych sampla (w bajtach)
			@param result zwrócony rezultat operacji - true jeśli odtwarzanie zakończyło się powodzeniem.
		**/
		void playSampleImpl(SoundDevice device, const int16_t* data, int length, bool& result);
		/**
			Pod ten sygnał powinien podpiąć się moduł
			dźwiękowy jeśli obsługuje funkcję odtwarzania
			próbki dźwiękowej.
			Wyemitowanie sygnału oznacza żądanie
			nagrania próbki dźwiękowej.
			Moduł powinien oddać sterowanie dopiero po
			nagraniu próbki.
			Sygnał zazwyczaj będzie emitowany z innego
			wątku i slot musi być do tego przystosowany.
			@param device uogólniony deskryptor urządzenia
			@param data wskaźnik na bufor dla danych sampla
			@param length długość sampla do nagrania (wielkość bufora w bajtach)
			@param result zwrócony rezultat operacji - true jeśli nagrywanie zakończyło się powodzeniem.
		**/
		void recordSampleImpl(SoundDevice device, int16_t* data, int length, bool& result);
		/**
		**/
		void setFlushingEnabledImpl(SoundDevice device, bool enabled);
};

extern SoundManager* sound_manager;
/** @} */

#endif
