// Copyright 2010-2023 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.

#ifndef MUMBLE_MUMBLE_VOICERECORDER_H_
#define MUMBLE_MUMBLE_VOICERECORDER_H_

#include <QtCore/QtGlobal>

#ifdef Q_OS_WIN
#	include "win.h"
#endif

#ifndef Q_MOC_RUN
#	include <boost/scoped_ptr.hpp>
#	include <boost/shared_array.hpp>
#endif

#include <QtCore/QDateTime>
#include <QtCore/QHash>
#include <QtCore/QMutex>
#include <QtCore/QObject>
#include <QtCore/QThread>
#include <QtCore/QWaitCondition>

#ifdef Q_OS_WIN
#	define ENABLE_SNDFILE_WINDOWS_PROTOTYPES 1
#endif

#include <sndfile.h>

class ClientUser;
class RecordUser;
class Timer;

/// Utilities and enums for voice recorder format handling
namespace VoiceRecorderFormat {

/// List of all formats currently supported by the recorder.
enum Format {
	/// WAVE Format
	WAV = 0,
// When switching between a non vorbis capable lib and a vorbis capable one
// this can mess up the selection stored in the config
#ifndef NO_VORBIS_RECORDING
	/// Ogg Vorbis Format
	VORBIS,
#endif
	/// AU Format
	AU,
	/// FLAC Format
	FLAC,
#ifdef USE_SNDFILE_OPUS
	// OPUS Format
	OPUS,
#endif
#ifdef USE_SNDFILE_MP3
	MP3,
#endif
	kEnd
};

/// Returns a human readable description of the format id.
QString getFormatDescription(VoiceRecorderFormat::Format fm);

/// Returns the default extension for the given format.
QString getFormatDefaultExtension(VoiceRecorderFormat::Format fm);

} // namespace VoiceRecorderFormat

/// Class for recording audio data.
///
/// Runs as a separate thread accepting audio data through the addBuffer method
/// which is then encoded using one of the formats of VoiceRecordingFormat::Format
/// and written to disk.
///
class VoiceRecorder : public QThread {
	Q_OBJECT
public:
	/// Possible error conditions inside the recorder
	enum Error { Unspecified, CreateDirectoryFailed, CreateFileFailed, InvalidSampleRate };

	/// Structure for holding configuration of VoiceRecorder object
	struct Config {
		/// The current sample rate of the recorder.
		int sampleRate;

		/// The path to store recordings.
		QString fileName;

		/// True if multi channel recording is disabled.
		bool mixDownMode;

		/// The current recording format.
		VoiceRecorderFormat::Format recordingFormat;
	};

	/// Creates a new VoiceRecorder instance.
	VoiceRecorder(QObject *p, const Config &config);
	~VoiceRecorder() Q_DECL_OVERRIDE;

	/// The main event loop of the thread, which writes all buffers to files.
	void run() Q_DECL_OVERRIDE;

	/// Stops the main loop.
	/// @param force If true buffers are discarded. Otherwise the thread will not stop before writing everything.
	void stop(bool force = false);

	/// Remembers the current time for a set of coming addBuffer calls
	void prepareBufferAdds();

	/// Adds an audio buffer which contains |samples| audio samples to the recorder.
	/// The audio data will be assumed to be recorded at the time
	/// prepareBufferAdds was last called.
	/// @param clientUser User for which to add the audio data. nullptr in mixdown mode.
	void addBuffer(const ClientUser *clientUser, boost::shared_array< float > buffer, int samples);

	/// Returns the elapsed time since the recording started.
	quint64 getElapsedTime() const;

	/// Returns a reference to the record user which is used to record local audio.
	RecordUser &getRecordUser() const;

	/// Returns true if the recorder is recording mixed down data instead of multichannel
	bool isInMixDownMode() const;
signals:
	/// Emitted if an error is encountered
	void error(int err, QString strerr);

	/// Emitted when recording is started
	void recording_started();
	/// Emitted when recording is stopped
	void recording_stopped();

private:
	/// Stores information about a recording buffer.
	struct RecordBuffer {
		/// Constructs a new RecordBuffer object.
		RecordBuffer(int recordInfoIndex_, boost::shared_array< float > buffer_, int samples_,
					 quint64 absoluteStartSample_);

		/// Hashmap index for the user
		const int recordInfoIndex;

		/// The buffer.
		boost::shared_array< float > buffer;

		/// The number of samples in the buffer.
		int samples;

		/// Absolute sample number at the start of this buffer
		quint64 absoluteStartSample;
	};

	/// Stores the recording state for one user.
	struct RecordInfo {
		RecordInfo(const QString &userName_);
		~RecordInfo();

		/// Name of the user being recorded
		const QString userName;

		/// libsndfile's handle.
		SNDFILE *soundFile;

		/// The last absolute sample we wrote for this users
		quint64 lastWrittenAbsoluteSample;
	};

	typedef QHash< int, boost::shared_ptr< RecordInfo > > RecordInfoMap;

	/// Removes invalid characters in a path component.
	QString sanitizeFilenameOrPathComponent(const QString &str) const;

	/// Expands the template variables in |path| for the given |userName|.
	QString expandTemplateVariables(const QString &path, const QString &userName) const;

	/// Returns the RecordInfo hashmap index for the given user
	int indexForUser(const ClientUser *clientUser) const;

	/// Create a sndfile SF_INFO structure describing the currently configured recording format
	SF_INFO createSoundFileInfo() const;

	/// Opens the file for the given recording information
	/// Helper function for run method. Will abort recording on failure.
	bool ensureFileIsOpenedFor(SF_INFO &soundFileInfo, boost::shared_ptr< RecordInfo > &ri);

	/// Hash which maps the |uiSession| of all users for which we have to keep a recording state to the corresponding
	/// RecordInfo object.
	RecordInfoMap m_recordInfo;

	/// List containing all unprocessed RecordBuffer objects.
	QList< boost::shared_ptr< RecordBuffer > > m_recordBuffer;

	/// The user which is used to record local audio.
	boost::scoped_ptr< RecordUser > m_recordUser;

	/// High precision timer for buffer timestamps.
	boost::scoped_ptr< Timer > m_timestamp;

	/// Protects the buffer list |qlRecordBuffer|.
	QMutex m_bufferLock;

	/// Wait condition and mutex to block until there is new data.
	QMutex m_sleepLock;
	QWaitCondition m_sleepCondition;

	/// Configuration for this instance
	const Config m_config;

	/// True if the main loop is active.
	bool m_recording;

	/// Tells the recorder to not finish writing its buffers before returning
	bool m_abort;

	/// The timestamp where the recording started.
	const QDateTime m_recordingStartTime;

	/// Absolute sample position to assume for buffer adds
	quint64 m_absoluteSampleEstimation;
};

typedef boost::shared_ptr< VoiceRecorder > VoiceRecorderPtr;

#endif
