// Copyright 2007-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 "LookConfig.h"
#include "Themes.h"

#include "AudioInput.h"
#include "AudioOutput.h"
#include "MainWindow.h"
#include "SearchDialog.h"
#include "Global.h"

#include <QtCore/QFileSystemWatcher>
#include <QtCore/QStack>
#include <QtCore/QTimer>

const QString LookConfig::name = QLatin1String("LookConfig");

static ConfigWidget *LookConfigNew(Settings &st) {
	return new LookConfig(st);
}

static ConfigRegistrar registrar(1100, LookConfigNew);

LookConfig::LookConfig(Settings &st) : ConfigWidget(st) {
	setupUi(this);

#ifndef Q_OS_MAC
	if (!QSystemTrayIcon::isSystemTrayAvailable())
#endif
		qgbTray->hide();

	qcbLanguage->addItem(tr("System default"));
	QDir d(QLatin1String(":"), QLatin1String("mumble_*.qm"), QDir::Name, QDir::Files);
	foreach (const QString &key, d.entryList()) {
		QString cc        = key.mid(7, key.indexOf(QLatin1Char('.')) - 7);
		QLocale tmpLocale = QLocale(cc);

		// If there is no native language name, use the locale
		QString displayName = cc;
		if (!tmpLocale.nativeLanguageName().isEmpty()) {
			displayName = QString(QLatin1String("%1 (%2)")).arg(tmpLocale.nativeLanguageName()).arg(cc);
		} else if (cc == QLatin1String("eo")) {
			// Can't initialize QLocale for a countryless language (QTBUG-8452, QTBUG-14592).
			// We only have one so special case it.
			displayName = QLatin1String("Esperanto (eo)");
		}

		qcbLanguage->addItem(displayName, QVariant(cc));
	}

	qcbExpand->addItem(tr("None"), Settings::NoChannels);
	qcbExpand->addItem(tr("Only with users"), Settings::ChannelsWithUsers);
	qcbExpand->addItem(tr("All"), Settings::AllChannels);

	qcbChannelDrag->insertItem(Settings::Ask, tr("Ask"), Settings::Ask);
	qcbChannelDrag->insertItem(Settings::DoNothing, tr("Do Nothing"), Settings::DoNothing);
	qcbChannelDrag->insertItem(Settings::Move, tr("Move"), Settings::Move);

	qcbUserDrag->insertItem(Settings::Ask, tr("Ask"), Settings::Ask);
	qcbUserDrag->insertItem(Settings::DoNothing, tr("Do Nothing"), Settings::DoNothing);
	qcbUserDrag->insertItem(Settings::Move, tr("Move"), Settings::Move);

	connect(qrbLCustom, SIGNAL(toggled(bool)), qcbLockLayout, SLOT(setEnabled(bool)));

	QDir userThemeDirectory = Themes::getUserThemesDirectory();
	if (userThemeDirectory.exists()) {
		m_themeDirectoryWatcher = new QFileSystemWatcher(this);

		// Use a timer to cut down floods of directory changes. We only want
		// to trigger a refresh after nothing has happened for 200ms in the
		// watched directory.
		m_themeDirectoryDebouncer = new QTimer(this);
		m_themeDirectoryDebouncer->setSingleShot(true);
		m_themeDirectoryDebouncer->setInterval(200);
		m_themeDirectoryDebouncer->connect(m_themeDirectoryWatcher, SIGNAL(directoryChanged(QString)), SLOT(start()));

		connect(m_themeDirectoryDebouncer, SIGNAL(timeout()), SLOT(themeDirectoryChanged()));
		m_themeDirectoryWatcher->addPath(userThemeDirectory.path());

		QUrl userThemeDirectoryUrl = QUrl::fromLocalFile(userThemeDirectory.path());
		//: This link is located next to the theme heading in the ui config and opens the user theme directory
		qlThemesDirectory->setText(tr("<a href=\"%1\">Browse</a>").arg(userThemeDirectoryUrl.toString()));
		qlThemesDirectory->setOpenExternalLinks(true);
	}

#define ADD_SEARCH_USERACTION(name)                                                                      \
	qcbSearchUserAction->addItem(Search::SearchDialog::toString(Search::SearchDialog::UserAction::name), \
								 static_cast< int >(Search::SearchDialog::UserAction::name))
	ADD_SEARCH_USERACTION(NONE);
	ADD_SEARCH_USERACTION(JOIN);
#undef ADD_SEARCH_USERACTION
#define ADD_SEARCH_CHANNELACTION(name)                                                                         \
	qcbSearchChannelAction->addItem(Search::SearchDialog::toString(Search::SearchDialog::ChannelAction::name), \
									static_cast< int >(Search::SearchDialog::ChannelAction::name))
	ADD_SEARCH_CHANNELACTION(NONE);
	ADD_SEARCH_CHANNELACTION(JOIN);
#undef ADD_SEARCH_CHANNELACTION
}

QString LookConfig::title() const {
	return tr("User Interface");
}

const QString &LookConfig::getName() const {
	return LookConfig::name;
}

QIcon LookConfig::icon() const {
	return QIcon(QLatin1String("skin:config_ui.png"));
}

void LookConfig::reloadThemes(const boost::optional< ThemeInfo::StyleInfo > configuredStyle) {
	const ThemeMap themes = Themes::getThemes();

	int selectedThemeEntry = 0;

	qcbTheme->clear();
	qcbTheme->addItem(tr("None"));
	for (ThemeMap::const_iterator theme = themes.begin(); theme != themes.end(); ++theme) {
		for (ThemeInfo::StylesMap::const_iterator styleit = theme->styles.begin(); styleit != theme->styles.end();
			 ++styleit) {
			if (configuredStyle && configuredStyle->themeName == styleit->themeName
				&& configuredStyle->name == styleit->name) {
				selectedThemeEntry = qcbTheme->count();
			}

			qcbTheme->addItem(theme->name + QLatin1String(" - ") + styleit->name, QVariant::fromValue(*styleit));
		}
	}

	qcbTheme->setCurrentIndex(selectedThemeEntry);
}

void LookConfig::load(const Settings &r) {
	loadComboBox(qcbLanguage, 0);
	loadComboBox(qcbChannelDrag, 0);
	loadComboBox(qcbUserDrag, 0);

	// Load Layout checkbox state
	switch (r.wlWindowLayout) {
		case Settings::LayoutClassic:
			qrbLClassic->setChecked(true);
			break;
		case Settings::LayoutStacked:
			qrbLStacked->setChecked(true);
			break;
		case Settings::LayoutHybrid:
			qrbLHybrid->setChecked(true);
			break;
		case Settings::LayoutCustom:
		default:
			s.wlWindowLayout = Settings::LayoutCustom;
			qrbLCustom->setChecked(true);
			break;
	}
	qcbLockLayout->setEnabled(r.wlWindowLayout == Settings::LayoutCustom);


	for (int i = 0; i < qcbLanguage->count(); i++) {
		if (qcbLanguage->itemData(i).toString() == r.qsLanguage) {
			loadComboBox(qcbLanguage, i);
			break;
		}
	}

	loadComboBox(qcbAlwaysOnTop, r.aotbAlwaysOnTop);

	loadComboBox(qcbExpand, r.ceExpand);
	loadComboBox(qcbChannelDrag, r.ceChannelDrag);
	loadComboBox(qcbUserDrag, r.ceUserDrag);
	loadCheckBox(qcbUsersTop, r.bUserTop);

	loadComboBox(qcbQuitBehavior, static_cast< int >(r.quitBehavior));

	loadCheckBox(qcbEnableDeveloperMenu, r.bEnableDeveloperMenu);
	loadCheckBox(qcbLockLayout, (r.wlWindowLayout == Settings::LayoutCustom) && r.bLockLayout);
	loadCheckBox(qcbHideTray, r.bHideInTray);
	loadCheckBox(qcbStateInTray, r.bStateInTray);
	loadCheckBox(qcbShowUserCount, r.bShowUserCount);
	loadCheckBox(qcbShowVolumeAdjustments, r.bShowVolumeAdjustments);
	loadCheckBox(qcbShowNicknamesOnly, r.bShowNicknamesOnly);
	loadCheckBox(qcbShowContextMenuInMenuBar, r.bShowContextMenuInMenuBar);
	loadCheckBox(qcbShowTransmitModeComboBox, r.bShowTransmitModeComboBox);
	loadCheckBox(qcbHighContrast, r.bHighContrast);
	loadCheckBox(qcbChatBarUseSelection, r.bChatBarUseSelection);
	loadCheckBox(qcbFilterHidesEmptyChannels, r.bFilterHidesEmptyChannels);

	const boost::optional< ThemeInfo::StyleInfo > configuredStyle = Themes::getConfiguredStyle(r);
	reloadThemes(configuredStyle);

	loadCheckBox(qcbUsersAlwaysVisible, r.talkingUI_UsersAlwaysVisible);
	loadCheckBox(qcbLocalUserVisible, r.bTalkingUI_LocalUserStaysVisible);
	loadCheckBox(qcbAbbreviateChannelNames, r.bTalkingUI_AbbreviateChannelNames);
	loadCheckBox(qcbAbbreviateCurrentChannel, r.bTalkingUI_AbbreviateCurrentChannel);
	loadCheckBox(qcbShowLocalListeners, r.bTalkingUI_ShowLocalListeners);
	qsbRelFontSize->setValue(r.iTalkingUI_RelativeFontSize);
	qsbSilentUserLifetime->setValue(r.iTalkingUI_SilentUserLifeTime);
	qsbChannelHierarchyDepth->setValue(r.iTalkingUI_ChannelHierarchyDepth);
	qsbMaxNameLength->setValue(r.iTalkingUI_MaxChannelNameLength);
	qsbPrefixCharCount->setValue(r.iTalkingUI_PrefixCharCount);
	qsbPostfixCharCount->setValue(r.iTalkingUI_PostfixCharCount);
	qleAbbreviationReplacement->setText(r.qsTalkingUI_AbbreviationReplacement);

	qleChannelSeparator->setText(r.qsHierarchyChannelSeparator);

	loadComboBox(qcbSearchUserAction, static_cast< int >(r.searchUserAction));
	loadComboBox(qcbSearchChannelAction, static_cast< int >(r.searchChannelAction));
}

void LookConfig::save() const {
	const QString oldLanguage = s.qsLanguage;
	if (qcbLanguage->currentIndex() == 0)
		s.qsLanguage = QString();
	else
		s.qsLanguage = qcbLanguage->itemData(qcbLanguage->currentIndex()).toString();

	if (s.qsLanguage != oldLanguage) {
		s.requireRestartToApply = true;
	}

	// Save Layout radioboxes state
	if (qrbLClassic->isChecked()) {
		s.wlWindowLayout = Settings::LayoutClassic;
	} else if (qrbLStacked->isChecked()) {
		s.wlWindowLayout = Settings::LayoutStacked;
	} else if (qrbLHybrid->isChecked()) {
		s.wlWindowLayout = Settings::LayoutHybrid;
	} else {
		s.wlWindowLayout = Settings::LayoutCustom;
	}

	s.ceExpand      = static_cast< Settings::ChannelExpand >(qcbExpand->currentIndex());
	s.ceChannelDrag = static_cast< Settings::ChannelDrag >(qcbChannelDrag->currentIndex());
	s.ceUserDrag    = static_cast< Settings::ChannelDrag >(qcbUserDrag->currentIndex());

	if (qcbUsersTop->isChecked() != s.bUserTop) {
		s.bUserTop              = qcbUsersTop->isChecked();
		s.requireRestartToApply = true;
	}

	s.aotbAlwaysOnTop           = static_cast< Settings::AlwaysOnTopBehaviour >(qcbAlwaysOnTop->currentIndex());
	s.quitBehavior              = static_cast< QuitBehavior >(qcbQuitBehavior->currentIndex());
	s.bEnableDeveloperMenu      = qcbEnableDeveloperMenu->isChecked();
	s.bLockLayout               = qcbLockLayout->isChecked();
	s.bHideInTray               = qcbHideTray->isChecked();
	s.bStateInTray              = qcbStateInTray->isChecked();
	s.bShowUserCount            = qcbShowUserCount->isChecked();
	s.bShowVolumeAdjustments    = qcbShowVolumeAdjustments->isChecked();
	s.bShowNicknamesOnly        = qcbShowNicknamesOnly->isChecked();
	s.bShowContextMenuInMenuBar = qcbShowContextMenuInMenuBar->isChecked();
	s.bShowTransmitModeComboBox = qcbShowTransmitModeComboBox->isChecked();
	s.bHighContrast             = qcbHighContrast->isChecked();
	s.bChatBarUseSelection      = qcbChatBarUseSelection->isChecked();
	s.bFilterHidesEmptyChannels = qcbFilterHidesEmptyChannels->isChecked();

	QVariant themeData = qcbTheme->itemData(qcbTheme->currentIndex());
	if (themeData.isNull()) {
		Themes::setConfiguredStyle(s, boost::none, s.requireRestartToApply);
	} else {
		Themes::setConfiguredStyle(s, themeData.value< ThemeInfo::StyleInfo >(), s.requireRestartToApply);
	}

	s.talkingUI_UsersAlwaysVisible        = qcbUsersAlwaysVisible->isChecked();
	s.bTalkingUI_LocalUserStaysVisible    = qcbLocalUserVisible->isChecked();
	s.bTalkingUI_AbbreviateChannelNames   = qcbAbbreviateChannelNames->isChecked();
	s.bTalkingUI_AbbreviateCurrentChannel = qcbAbbreviateCurrentChannel->isChecked();
	s.bTalkingUI_ShowLocalListeners       = qcbShowLocalListeners->isChecked();
	s.iTalkingUI_RelativeFontSize         = qsbRelFontSize->value();
	s.iTalkingUI_SilentUserLifeTime       = qsbSilentUserLifetime->value();
	s.iTalkingUI_ChannelHierarchyDepth    = qsbChannelHierarchyDepth->value();
	s.iTalkingUI_MaxChannelNameLength     = qsbMaxNameLength->value();
	s.iTalkingUI_PrefixCharCount          = qsbPrefixCharCount->value();
	s.iTalkingUI_PostfixCharCount         = qsbPostfixCharCount->value();
	s.qsTalkingUI_AbbreviationReplacement = qleAbbreviationReplacement->text();

	s.qsHierarchyChannelSeparator = qleChannelSeparator->text();

	s.searchUserAction = static_cast< Search::SearchDialog::UserAction >(qcbSearchUserAction->currentData().toInt());
	s.searchChannelAction =
		static_cast< Search::SearchDialog::ChannelAction >(qcbSearchChannelAction->currentData().toInt());
}

void LookConfig::accept() const {
	Global::get().mw->setShowDockTitleBars((Global::get().s.wlWindowLayout == Settings::LayoutCustom)
										   && !Global::get().s.bLockLayout);
}

void LookConfig::themeDirectoryChanged() {
	qWarning() << "Theme directory changed";
	QVariant themeData = qcbTheme->itemData(qcbTheme->currentIndex());
	if (themeData.isNull()) {
		reloadThemes(boost::none);
	} else {
		reloadThemes(themeData.value< ThemeInfo::StyleInfo >());
	}
}

void LookConfig::on_qcbAbbreviateChannelNames_stateChanged(int state) {
	bool abbreviateNames = (state == Qt::Checked) || (state == Qt::PartiallyChecked);

	// Only enable the abbreviation related settings if abbreviation is actually enabled
	qcbAbbreviateCurrentChannel->setEnabled(abbreviateNames);
	qsbChannelHierarchyDepth->setEnabled(abbreviateNames);
	qsbMaxNameLength->setEnabled(abbreviateNames);
	qsbPrefixCharCount->setEnabled(abbreviateNames);
	qsbPostfixCharCount->setEnabled(abbreviateNames);
	qleAbbreviationReplacement->setEnabled(abbreviateNames);
}

void LookConfig::on_qcbUsersAlwaysVisible_stateChanged(int state) {
	bool usersAlwaysVisible = state == Qt::Checked;

	// Only enable the local user visibility setting when all users are not always visible
	qcbLocalUserVisible->setEnabled(!usersAlwaysVisible);
	// Only enable the user visibility timeout settings when all users are not always visible
	qsbSilentUserLifetime->setEnabled(!usersAlwaysVisible);
}
