// 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>.

#include "VoiceRecorderDialog.h"

#include "AudioOutput.h"
#include "Log.h"
#include "ServerHandler.h"
#include "VoiceRecorder.h"
#include "Global.h"

#include <QtGui/QCloseEvent>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QMessageBox>

VoiceRecorderDialog::VoiceRecorderDialog(QWidget *p) : QDialog(p), qtTimer(new QTimer(this)) {
	qtTimer->setObjectName(QLatin1String("qtTimer"));
	qtTimer->setInterval(200);
	setupUi(this);

	qleTargetDirectory->setText(Global::get().s.qsRecordingPath);
	qleFilename->setText(Global::get().s.qsRecordingFile);
	qrbDownmix->setChecked(Global::get().s.rmRecordingMode == Settings::RecordingMixdown);
	qrbMultichannel->setChecked(Global::get().s.rmRecordingMode == Settings::RecordingMultichannel);

	QString qsTooltip = QString::fromLatin1("%1"
											"<table>"
											"  <tr>"
											"    <td width=\"25%\">%user</td>"
											"    <td>%2</td>"
											"  </tr>"
											"  <tr>"
											"    <td>%date</td>"
											"    <td>%3</td>"
											"  </tr>"
											"  <tr>"
											"    <td>%time</td>"
											"    <td>%4</td>"
											"  </tr>"
											"  <tr>"
											"    <td>%host</td>"
											"    <td>%5</td>"
											"  </tr>"
											"</table>")
							.arg(tr("Valid variables are:"))
							.arg(tr("Inserts the user's name"))
							.arg(tr("Inserts the current date"))
							.arg(tr("Inserts the current time"))
							.arg(tr("Inserts the hostname"));

	qleTargetDirectory->setToolTip(qsTooltip);
	qleFilename->setToolTip(qsTooltip);

	// Populate available codecs
	Q_ASSERT(VoiceRecorderFormat::kEnd != 0);
	for (int fm = 0; fm < VoiceRecorderFormat::kEnd; fm++) {
		qcbFormat->addItem(VoiceRecorderFormat::getFormatDescription(static_cast< VoiceRecorderFormat::Format >(fm)));
	}

	if (Global::get().s.iRecordingFormat < 0 || Global::get().s.iRecordingFormat > VoiceRecorderFormat::kEnd)
		Global::get().s.iRecordingFormat = 0;

	qcbFormat->setCurrentIndex(Global::get().s.iRecordingFormat);
}

VoiceRecorderDialog::~VoiceRecorderDialog() {
	reset();
}

void VoiceRecorderDialog::closeEvent(QCloseEvent *evt) {
	if (Global::get().sh) {
		VoiceRecorderPtr recorder(Global::get().sh->recorder);
		if (recorder && recorder->isRunning()) {
			int ret = QMessageBox::warning(this, tr("Recorder still running"),
										   tr("Closing the recorder without stopping it will discard unwritten audio. "
											  "Do you really want to close the recorder?"),
										   QMessageBox::Yes | QMessageBox::No, QMessageBox::No);

			if (ret == QMessageBox::No) {
				evt->ignore();
				return;
			}

			recorder->stop(true);
		}
	}

	Global::get().s.qsRecordingPath = qleTargetDirectory->text();
	Global::get().s.qsRecordingFile = qleFilename->text();
	if (qrbDownmix->isChecked())
		Global::get().s.rmRecordingMode = Settings::RecordingMixdown;
	else
		Global::get().s.rmRecordingMode = Settings::RecordingMultichannel;

	int i                            = qcbFormat->currentIndex();
	Global::get().s.iRecordingFormat = (i == -1) ? 0 : i;

	reset();
	evt->accept();

	QDialog::closeEvent(evt);
}

void VoiceRecorderDialog::on_qpbStart_clicked() {
	if (!Global::get().uiSession || !Global::get().sh) {
		QMessageBox::critical(this, tr("Recorder"), tr("Unable to start recording. Not connected to a server."));
		reset();
		return;
	}

	if (Global::get().sh->m_version < Version::fromComponents(1, 2, 3)) {
		QMessageBox::critical(this, tr("Recorder"),
							  tr("The server you are currently connected to is version 1.2.2 or older. "
								 "For privacy reasons, recording on servers of versions older than 1.2.3 "
								 "is not possible.\nPlease contact your server administrator for further "
								 "information."));
		return;
	}

	if (Global::get().sh->recorder) {
		QMessageBox::information(this, tr("Recorder"), tr("There is already a recorder active for this server."));
		return;
	}

	// Check validity of input
	int ifm = qcbFormat->currentIndex();
	if (ifm == -1) {
		QMessageBox::critical(this, tr("Recorder"), tr("Please select a recording format."));
		return;
	}

	QString dstr = qleTargetDirectory->text();
	if (dstr.isEmpty()) {
		on_qpbTargetDirectoryBrowse_clicked();
		dstr = qleTargetDirectory->text();
		if (dstr.isEmpty())
			return;
	}
	QDir dir(dstr);

	QFileInfo fi(qleFilename->text());
	QString basename(fi.baseName());
	QString suffix(fi.completeSuffix());
	if (suffix.isEmpty())
		suffix = VoiceRecorderFormat::getFormatDefaultExtension(static_cast< VoiceRecorderFormat::Format >(ifm));


	if (basename.isEmpty()) {
		basename = QLatin1String("%user");
	}

	qleFilename->setText(basename);

	AudioOutputPtr ao(Global::get().ao);
	if (!ao)
		return;

	// Create the recorder
	VoiceRecorder::Config config;
	config.sampleRate      = static_cast< int >(ao->getMixerFreq());
	config.fileName        = dir.absoluteFilePath(basename + QLatin1Char('.') + suffix);
	config.mixDownMode     = qrbDownmix->isChecked();
	config.recordingFormat = static_cast< VoiceRecorderFormat::Format >(ifm);

	if (config.sampleRate == 0) {
		// If we don't catch this here, Mumble will crash because VoiceRecorder expects the sample rate to be non-zero
		Global::get().l->log(Log::Warning,
							 tr("Unable to start recording - the audio output is miconfigured (0Hz sample rate)"));

		// Close this dialog
		reject();

		return;
	}

	Global::get().sh->announceRecordingState(true);

	Global::get().sh->recorder.reset(new VoiceRecorder(this, config));
	VoiceRecorderPtr recorder(Global::get().sh->recorder);

	// Wire it up
	connect(&*recorder, SIGNAL(recording_started()), this, SLOT(onRecorderStarted()));
	connect(&*recorder, SIGNAL(recording_stopped()), this, SLOT(onRecorderStopped()));
	connect(&*recorder, SIGNAL(error(int, QString)), this, SLOT(onRecorderError(int, QString)));

	recorder->start();

	qpbStart->setDisabled(true);
	qpbStop->setEnabled(true);
	qpbStop->setFocus();
	qgbMode->setDisabled(true);
	qgbOutput->setDisabled(true);
}

void VoiceRecorderDialog::on_qpbStop_clicked() {
	if (!Global::get().sh) {
		reset();
		return;
	}

	VoiceRecorderPtr recorder(Global::get().sh->recorder);
	if (!recorder) {
		reset();
		return;
	}

	// Stop clock and recording
	qtTimer->stop();
	recorder->stop();

	// Disable stop button to indicate we reacted
	qpbStop->setDisabled(true);
	qpbStop->setText(tr("Stopping"));
}

void VoiceRecorderDialog::on_qtTimer_timeout() {
	if (!Global::get().sh) {
		reset();
		return;
	}

	if (!Global::get().uiSession) {
		reset(false);
		return;
	}

	VoiceRecorderPtr recorder(Global::get().sh->recorder);
	if (!Global::get().sh->recorder) {
		reset();
		return;
	}

	const QTime elapsedTime = QTime(0, 0).addMSecs(static_cast< int >(recorder->getElapsedTime() / 1000));
	qlTime->setText(elapsedTime.toString());
}

void VoiceRecorderDialog::on_qpbTargetDirectoryBrowse_clicked() {
	QString dir = QFileDialog::getExistingDirectory(this, tr("Select target directory"), QString(),
													QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
	if (!dir.isEmpty())
		qleTargetDirectory->setText(dir);
}

void VoiceRecorderDialog::reset(bool resettimer) {
	qtTimer->stop();

	if (Global::get().sh) {
		VoiceRecorderPtr recorder(Global::get().sh->recorder);
		if (recorder) {
			Global::get().sh->recorder.reset();
			Global::get().sh->announceRecordingState(false);
		}
	}

	qpbStart->setEnabled(true);
	qpbStart->setFocus();
	qpbStop->setDisabled(true);
	qpbStop->setText(tr("S&top"));

	qgbMode->setEnabled(true);
	qgbOutput->setEnabled(true);

	if (resettimer)
		qlTime->setText(QLatin1String("00:00:00"));
}

void VoiceRecorderDialog::onRecorderStopped() {
	reset(false);
}

void VoiceRecorderDialog::onRecorderStarted() {
	qlTime->setText(QLatin1String("00:00:00"));
	qtTimer->start();
}

void VoiceRecorderDialog::onRecorderError(int err, QString strerr) {
	Q_UNUSED(err);
	QMessageBox::critical(this, tr("Recorder"), strerr);
	reset(false);
}
