#include "macro-condition-audio.hpp"
#include "audio-helpers.hpp"
#include "layout-helpers.hpp"
#include "macro-helpers.hpp"
#include "selection-helpers.hpp"

namespace advss {

constexpr int64_t nsPerMs = 1000000;
const std::string MacroConditionAudio::id = "audio";

bool MacroConditionAudio::_registered = MacroConditionFactory::Register(
	MacroConditionAudio::id,
	{MacroConditionAudio::Create, MacroConditionAudioEdit::Create,
	 "AdvSceneSwitcher.condition.audio"});

const static std::map<MacroConditionAudio::Type, std::string> checkTypes = {
	{MacroConditionAudio::Type::OUTPUT_VOLUME,
	 "AdvSceneSwitcher.condition.audio.type.output"},
	{MacroConditionAudio::Type::CONFIGURED_VOLUME,
	 "AdvSceneSwitcher.condition.audio.type.volume"},
	{MacroConditionAudio::Type::SYNC_OFFSET,
	 "AdvSceneSwitcher.condition.audio.type.syncOffset"},
	{MacroConditionAudio::Type::MONITOR,
	 "AdvSceneSwitcher.condition.audio.type.monitor"},
	{MacroConditionAudio::Type::BALANCE,
	 "AdvSceneSwitcher.condition.audio.type.balance"},
};

const static std::map<MacroConditionAudio::OutputCondition, std::string>
	audioOutputConditionTypes = {
		{MacroConditionAudio::OutputCondition::ABOVE,
		 "AdvSceneSwitcher.condition.audio.state.above"},
		{MacroConditionAudio::OutputCondition::BELOW,
		 "AdvSceneSwitcher.condition.audio.state.below"},
};

const static std::map<MacroConditionAudio::VolumeCondition, std::string>
	audioVolumeConditionTypes = {
		{MacroConditionAudio::VolumeCondition::ABOVE,
		 "AdvSceneSwitcher.condition.audio.state.above"},
		{MacroConditionAudio::VolumeCondition::EXACT,
		 "AdvSceneSwitcher.condition.audio.state.exact"},
		{MacroConditionAudio::VolumeCondition::BELOW,
		 "AdvSceneSwitcher.condition.audio.state.below"},
		{MacroConditionAudio::VolumeCondition::MUTE,
		 "AdvSceneSwitcher.condition.audio.state.mute"},
		{MacroConditionAudio::VolumeCondition::UNMUTE,
		 "AdvSceneSwitcher.condition.audio.state.unmute"},
};

void MacroConditionAudio::SetType(const Type &type)
{
	_checkType = type;
	SetupTempVars();
}

MacroConditionAudio::~MacroConditionAudio()
{
	obs_volmeter_remove_callback(_volmeter, SetVolumeLevel, this);
	obs_volmeter_destroy(_volmeter);
}

float MacroConditionAudio::GetVolumePeak()
{
	using namespace std::chrono_literals;
	using namespace std::chrono;
	static constexpr std::chrono::milliseconds timeout = 250ms;

	// OBS might rarely not update _peak quickly enough when very low
	// intervals are configured on the General tab.
	// In that case _peak might be set to negative infinity still, which
	// will result in unexpected behavior, so we use the previously valid
	// peak value instead.
	//
	// If no volume update was received within a timeout window, however, it
	// is assumed, that the source no longer produces any audio output and
	// thus a peak volume value of negative infinity is used.

	float peak;

	std::lock_guard<std::mutex> lock(_peakMutex);
	auto msPassedSinceLastUpdate = duration_cast<milliseconds>(
		high_resolution_clock::now() - _lastPeakUpdate);
	if (_lastPeakUpdate.time_since_epoch().count() != 0 &&
	    msPassedSinceLastUpdate > timeout) {
		peak = -std::numeric_limits<float>::infinity();
	} else {
		peak = _peakUpdated ? _peak : _previousPeak;
	}

	_previousPeak = peak;
	_peak = -std::numeric_limits<float>::infinity();
	_peakUpdated = false;

	return peak;
}

bool MacroConditionAudio::CheckOutputCondition()
{
	bool ret = false;
	OBSSourceAutoRelease source =
		obs_weak_source_get_source(_audioSource.GetSource());

	float peak = GetVolumePeak();
	double curVolume = _useDb ? peak : DecibelToPercent(peak) * 100;

	switch (_outputCondition) {
	case OutputCondition::ABOVE:
		if (_useDb) {
			ret = curVolume > _volumeDB;
		} else {
			ret = curVolume > _volumePercent;
		}
		break;
	case OutputCondition::BELOW:
		if (_useDb) {
			ret = curVolume < _volumeDB;
		} else {
			ret = curVolume < _volumePercent;
		}
		break;
	default:
		break;
	}

	SetVariableValue(std::to_string(curVolume));
	SetTempVarValue("output_volume", std::to_string(curVolume));

	// Reset for next check
	if (_audioSource.GetType() == SourceSelection::Type::VARIABLE) {
		ResetVolmeter();
	}

	return ret && source;
}

bool MacroConditionAudio::CheckVolumeCondition()
{
	bool ret = false;
	OBSSourceAutoRelease source =
		obs_weak_source_get_source(_audioSource.GetSource());

	float curVolume =
		_useDb ? PercentToDecibel(obs_source_get_volume(source))
		       : obs_source_get_volume(source);
	bool muted = obs_source_muted(source);

	switch (_volumeCondition) {
	case VolumeCondition::ABOVE:
		if (_useDb) {
			ret = curVolume > _volumeDB;
		} else {
			ret = curVolume > _volumePercent / 100.f;
		}
		SetVariableValue(std::to_string(curVolume));
		break;
	case VolumeCondition::EXACT:
		if (_useDb) {
			ret = curVolume == _volumeDB;
		} else {
			ret = curVolume == _volumePercent / 100.f;
		}
		SetVariableValue(std::to_string(curVolume));
		break;
	case VolumeCondition::BELOW:
		if (_useDb) {
			ret = curVolume < _volumeDB;
		} else {
			ret = curVolume < _volumePercent / 100.f;
		}
		SetVariableValue(std::to_string(curVolume));
		break;
	case VolumeCondition::MUTE:
		ret = muted;
		SetVariableValue("");
		break;
	case VolumeCondition::UNMUTE:
		ret = !muted;
		SetVariableValue("");
		break;
	default:
		break;
	}

	SetTempVarValue("configured_volume", std::to_string(curVolume));
	SetTempVarValue("muted", (source && muted) ? "true" : "false");

	return ret && source;
}

bool MacroConditionAudio::CheckSyncOffset()
{
	if (!_audioSource.GetSource()) {
		return false;
	}

	bool ret = false;
	OBSSourceAutoRelease source =
		obs_weak_source_get_source(_audioSource.GetSource());
	auto curOffset = obs_source_get_sync_offset(source) / nsPerMs;
	if (_outputCondition == OutputCondition::ABOVE) {
		ret = curOffset > _syncOffset;
	} else {
		ret = curOffset < _syncOffset;
	}
	SetVariableValue(std::to_string(curOffset));
	SetTempVarValue("sync_offset", std::to_string(curOffset));
	return ret && source;
}

bool MacroConditionAudio::CheckMonitor()
{
	if (!_audioSource.GetSource()) {
		return false;
	}

	bool ret = false;
	OBSSourceAutoRelease source =
		obs_weak_source_get_source(_audioSource.GetSource());
	ret = obs_source_get_monitoring_type(source) == _monitorType;
	SetVariableValue("");
	SetTempVarValue("monitor",
			std::to_string(obs_source_get_monitoring_type(source)));
	return ret && source;
}

bool MacroConditionAudio::CheckBalance()
{
	if (!_audioSource.GetSource()) {
		return false;
	}

	bool ret = false;
	OBSSourceAutoRelease source =
		obs_weak_source_get_source(_audioSource.GetSource());
	auto curBalance = obs_source_get_balance_value(source);
	if (_outputCondition == OutputCondition::ABOVE) {
		ret = curBalance > _balance;
	} else {
		ret = curBalance < _balance;
	}
	SetVariableValue(std::to_string(curBalance));
	SetTempVarValue("balance", std::to_string(curBalance));
	return ret && source;
}

bool MacroConditionAudio::CheckCondition()
{
	bool ret = false;
	switch (_checkType) {
	case Type::OUTPUT_VOLUME:
		ret = CheckOutputCondition();
		break;
	case Type::CONFIGURED_VOLUME:
		ret = CheckVolumeCondition();
		break;
	case Type::SYNC_OFFSET:
		ret = CheckSyncOffset();
		break;
	case Type::MONITOR:
		ret = CheckMonitor();
		break;
	case Type::BALANCE:
		ret = CheckBalance();
		break;
	}

	if (GetVariableValue().empty()) {
		SetVariableValue(ret ? "true" : "false");
	}

	return ret;
}

bool MacroConditionAudio::Save(obs_data_t *obj) const
{
	MacroCondition::Save(obj);
	_audioSource.Save(obj, "audioSource");
	obs_data_set_int(obj, "monitor", _monitorType);
	_volumePercent.Save(obj, "volume");
	_syncOffset.Save(obj, "syncOffset");
	_balance.Save(obj, "balance");
	obs_data_set_int(obj, "checkType", static_cast<int>(_checkType));
	obs_data_set_int(obj, "outputCondition",
			 static_cast<int>(_outputCondition));
	obs_data_set_int(obj, "volumeCondition",
			 static_cast<int>(_volumeCondition));
	obs_data_set_bool(obj, "useDb", _useDb);
	_volumeDB.Save(obj, "volumeDB");
	obs_data_set_int(obj, "version", 3);
	return true;
}

obs_volmeter_t *AddVolmeterToSource(MacroConditionAudio *entry,
				    obs_weak_source *source)
{
	obs_volmeter_t *volmeter = obs_volmeter_create(OBS_FADER_LOG);
	obs_volmeter_add_callback(volmeter, MacroConditionAudio::SetVolumeLevel,
				  entry);
	OBSSourceAutoRelease audioSource = obs_weak_source_get_source(source);
	if (!obs_volmeter_attach_source(volmeter, audioSource)) {
		const char *name = obs_source_get_name(audioSource);
		blog(LOG_WARNING, "failed to attach volmeter to source %s",
		     name);
	}

	return volmeter;
}

bool MacroConditionAudio::Load(obs_data_t *obj)
{
	MacroCondition::Load(obj);
	_audioSource.Load(obj, "audioSource");
	_monitorType = static_cast<obs_monitoring_type>(
		obs_data_get_int(obj, "monitor"));
	if (!obs_data_has_user_value(obj, "version")) {
		_volumePercent = obs_data_get_int(obj, "volume");
		_syncOffset = obs_data_get_int(obj, "syncOffset");
		_balance = obs_data_get_double(obj, "balance");
	} else {
		_volumePercent.Load(obj, "volume");
		_syncOffset.Load(obj, "syncOffset");
		_balance.Load(obj, "balance");
	}
	_checkType = static_cast<Type>(obs_data_get_int(obj, "checkType"));
	_outputCondition = static_cast<OutputCondition>(
		obs_data_get_int(obj, "outputCondition"));
	_volumeCondition = static_cast<VolumeCondition>(
		obs_data_get_int(obj, "volumeCondition"));
	_volmeter = AddVolmeterToSource(this, _audioSource.GetSource());

	if (obs_data_get_int(obj, "version") < 2) {
		// Set default values for dB handling
		_useDb = false;
		_volumeDB = 0.0;

		// In previous versions the scaling happened in CheckCondition()
		// so these values must now be converted to enable compatibility
		// with the decibel value based checks.
		OBSDataAutoRelease data = obs_data_get_obj(obj, "volume");
		auto oldFixedValue = obs_data_get_int(data, "value");
		_volumeDB = ((double)oldFixedValue / 1.7) - 60;
		_volumePercent = DecibelToPercent(_volumeDB) * 100;
	} else {
		_useDb = obs_data_get_bool(obj, "useDb");
		_volumeDB.Load(obj, "volumeDB");
	}
	return true;
}

std::string MacroConditionAudio::GetShortDesc() const
{
	return _audioSource.ToString();
}

void MacroConditionAudio::SetVolumeLevel(void *data, const float *,
					 const float peak[MAX_AUDIO_CHANNELS],
					 const float *)
{
	auto c = static_cast<MacroConditionAudio *>(data);
	const auto macro = c->GetMacro();
	if (MacroIsPaused(macro)) {
		return;
	}

	std::lock_guard<std::mutex> lock(c->_peakMutex);
	for (int i = 0; i < MAX_AUDIO_CHANNELS; i++) {
		if (peak[i] > c->_peak) {
			c->_peak = peak[i];
		}
	}
	c->_peakUpdated = true;
	c->_lastPeakUpdate = std::chrono::high_resolution_clock::now();
}

void MacroConditionAudio::ResetVolmeter()
{
	obs_volmeter_remove_callback(_volmeter, SetVolumeLevel, this);
	obs_volmeter_destroy(_volmeter);

	_volmeter = AddVolmeterToSource(this, _audioSource.GetSource());
}

void MacroConditionAudio::SetupTempVars()
{
	MacroCondition::SetupTempVars();
	switch (_checkType) {
	case Type::OUTPUT_VOLUME:
		AddTempvar(
			"output_volume",
			obs_module_text(
				"AdvSceneSwitcher.tempVar.audio.output_volume"),
			obs_module_text(
				"AdvSceneSwitcher.tempVar.audio.output_volume.description"));
		break;
	case Type::CONFIGURED_VOLUME:
		AddTempvar(
			"configured_volume",
			obs_module_text(
				"AdvSceneSwitcher.tempVar.audio.configured_volume"),
			obs_module_text(
				"AdvSceneSwitcher.tempVar.audio.configured_volume.description"));
		AddTempvar("muted",
			   obs_module_text(
				   "AdvSceneSwitcher.tempVar.audio.muted"));
		break;
	case Type::SYNC_OFFSET:
		AddTempvar(
			"sync_offset",
			obs_module_text(
				"AdvSceneSwitcher.tempVar.audio.sync_offset"));
		break;
	case Type::MONITOR:
		AddTempvar("monitor",
			   obs_module_text(
				   "AdvSceneSwitcher.tempVar.audio.monitor"));
		break;
	case Type::BALANCE:
		AddTempvar("balance",
			   obs_module_text(
				   "AdvSceneSwitcher.tempVar.audio.balance"));
		break;
	default:
		break;
	}
}

static inline void populateCheckTypes(QComboBox *list)
{
	list->clear();
	for (const auto &[type, name] : checkTypes) {
		if (type == MacroConditionAudio::Type::MONITOR) {
			if (obs_audio_monitoring_available()) {
				list->addItem(obs_module_text(name.c_str()),
					      static_cast<int>(type));
			}
		} else {
			list->addItem(obs_module_text(name.c_str()),
				      static_cast<int>(type));
		}
	}
}

static inline void populateOutputConditionSelection(QComboBox *list)
{
	list->clear();
	for (const auto &[_, name] : audioOutputConditionTypes) {
		list->addItem(obs_module_text(name.c_str()));
	}
}

static inline void populateVolumeConditionSelection(QComboBox *list)
{
	list->clear();
	for (const auto &[_, name] : audioVolumeConditionTypes) {
		list->addItem(obs_module_text(name.c_str()));
	}
}

MacroConditionAudioEdit::MacroConditionAudioEdit(
	QWidget *parent, std::shared_ptr<MacroConditionAudio> entryData)
	: QWidget(parent),
	  _checkTypes(new QComboBox()),
	  _sources(new SourceSelectionWidget(this, QStringList(), true)),
	  _condition(new QComboBox()),
	  _volumePercent(new VariableDoubleSpinBox()),
	  _volumeDB(new VariableDoubleSpinBox),
	  _percentDBToggle(new QPushButton),
	  _syncOffset(new VariableSpinBox()),
	  _monitorTypes(new QComboBox),
	  _balance(new SliderSpinBox(0., 1., ""))
{
	_volumePercent->setSuffix("%");
	_volumePercent->setMaximum(100);
	_volumePercent->setMinimum(0);

	_volumeDB->setMinimum(-100);
	_volumeDB->setMaximum(0);
	_volumeDB->setSuffix("dB");
	_volumeDB->setSpecialValueText("-inf");

	_syncOffset->setMinimum(-950);
	_syncOffset->setMaximum(20000);
	_syncOffset->setSuffix("ms");

	auto sources = GetAudioSourceNames();
	sources.sort();
	_sources->SetSourceNameList(sources);

	QWidget::connect(_checkTypes, SIGNAL(currentIndexChanged(int)), this,
			 SLOT(CheckTypeChanged(int)));
	QWidget::connect(
		_volumePercent,
		SIGNAL(NumberVariableChanged(const NumberVariable<double> &)),
		this,
		SLOT(VolumePercentChanged(const NumberVariable<double> &)));
	QWidget::connect(
		_syncOffset,
		SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
		this, SLOT(SyncOffsetChanged(const NumberVariable<int> &)));
	QWidget::connect(_monitorTypes, SIGNAL(currentIndexChanged(int)), this,
			 SLOT(MonitorTypeChanged(int)));
	QWidget::connect(
		_balance,
		SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
		this, SLOT(BalanceChanged(const NumberVariable<double> &)));
	QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
			 SLOT(ConditionChanged(int)));
	QWidget::connect(_sources,
			 SIGNAL(SourceChanged(const SourceSelection &)), this,
			 SLOT(SourceChanged(const SourceSelection &)));
	QWidget::connect(
		_volumeDB,
		SIGNAL(NumberVariableChanged(const NumberVariable<double> &)),
		this, SLOT(VolumeDBChanged(const NumberVariable<double> &)));
	QWidget::connect(_percentDBToggle, SIGNAL(clicked()), this,
			 SLOT(PercentDBClicked()));

	populateCheckTypes(_checkTypes);
	PopulateMonitorTypeSelection(_monitorTypes);

	QHBoxLayout *switchLayout = new QHBoxLayout;
	std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
		{"{{checkType}}", _checkTypes},
		{"{{audioSources}}", _sources},
		{"{{volume}}", _volumePercent},
		{"{{syncOffset}}", _syncOffset},
		{"{{monitorTypes}}", _monitorTypes},
		{"{{balance}}", _balance},
		{"{{condition}}", _condition},
		{"{{volumeDB}}", _volumeDB},
		{"{{percentDBToggle}}", _percentDBToggle},
	};
	PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.audio.entry"),
		     switchLayout, widgetPlaceholders);

	QVBoxLayout *mainLayout = new QVBoxLayout;
	mainLayout->addLayout(switchLayout);
	mainLayout->addWidget(_balance);
	setLayout(mainLayout);

	_entryData = entryData;
	UpdateEntryData();
	_loading = false;
}

void MacroConditionAudioEdit::UpdateVolmeterSource()
{
	delete _volMeter;
	OBSSourceAutoRelease soruce = obs_weak_source_get_source(
		_entryData->_audioSource.GetSource());
	_volMeter = new VolControl(soruce.Get());

	auto layout = this->layout();
	layout->addWidget(_volMeter);

	QWidget::connect(_volMeter->GetSlider(),
			 &DoubleSlider::DoubleValChanged,
			 [=](double) { SyncSliderAndValueSelection(true); });

	// Slider will default to 0 so set it manually once
	SyncSliderAndValueSelection(false);
}

void MacroConditionAudioEdit::SourceChanged(const SourceSelection &source)
{
	if (_loading || !_entryData) {
		return;
	}

	{
		auto lock = LockContext();
		_entryData->_audioSource = source;
		_entryData->ResetVolmeter();
	}
	UpdateVolmeterSource();
	SetWidgetVisibility();
	emit HeaderInfoChanged(
		QString::fromStdString(_entryData->GetShortDesc()));
}

void MacroConditionAudioEdit::VolumePercentChanged(
	const NumberVariable<double> &vol)
{
	if (_loading || !_entryData) {
		return;
	}
	{
		auto lock = LockContext();
		_entryData->_volumePercent = vol;
	}

	SyncSliderAndValueSelection(false);
}

void MacroConditionAudioEdit::SyncOffsetChanged(const NumberVariable<int> &value)
{
	if (_loading || !_entryData) {
		return;
	}

	auto lock = LockContext();
	_entryData->_syncOffset = value;
}

void MacroConditionAudioEdit::MonitorTypeChanged(int value)
{
	if (_loading || !_entryData) {
		return;
	}

	auto lock = LockContext();
	_entryData->_monitorType = static_cast<obs_monitoring_type>(value);
}

void MacroConditionAudioEdit::BalanceChanged(const NumberVariable<double> &value)
{
	if (_loading || !_entryData) {
		return;
	}

	auto lock = LockContext();
	_entryData->_balance = value;
}

void MacroConditionAudioEdit::VolumeDBChanged(
	const NumberVariable<double> &value)
{
	if (_loading || !_entryData) {
		return;
	}
	{
		auto lock = LockContext();
		_entryData->_volumeDB = value;
	}
	SyncSliderAndValueSelection(false);
}

void MacroConditionAudioEdit::PercentDBClicked()
{
	if (_loading || !_entryData) {
		return;
	}

	auto lock = LockContext();
	_entryData->_useDb = !_entryData->_useDb;
	SetWidgetVisibility();
}

void MacroConditionAudioEdit::SyncSliderAndValueSelection(bool sliderMoved)
{
	if (!_entryData) {
		return;
	}

	if (sliderMoved) {
		auto sliderPosition = _volMeter->GetSlider()->DoubleValue();
		// Adjust to the dB scale on the volume meter widget
		auto dBScaleValue = sliderPosition - 100.0;

		if (_entryData->_useDb) {
			_volumeDB->SetFixedValue(dBScaleValue);
			const QSignalBlocker blocker(this);
			_volumePercent->SetFixedValue(
				DecibelToPercent(dBScaleValue) * 100);
		} else {
			_volumePercent->SetFixedValue(
				DecibelToPercent(dBScaleValue) * 100);
			const QSignalBlocker blocker(this);
			_volumeDB->SetFixedValue(dBScaleValue);
		}
	} else {
		const QSignalBlocker blocker(this);
		double dBValue =
			_entryData->_useDb
				? _entryData->_volumeDB.GetFixedValue()
				: PercentToDecibel(_entryData->_volumePercent /
						   100.0);
		auto sliderPosition = (dBValue + 100);
		_volMeter->GetSlider()->SetDoubleVal(sliderPosition);
	}
}

void MacroConditionAudioEdit::ConditionChanged(int cond)
{
	if (_loading || !_entryData) {
		return;
	}

	auto lock = LockContext();
	if (_entryData->GetType() == MacroConditionAudio::Type::OUTPUT_VOLUME ||
	    _entryData->GetType() == MacroConditionAudio::Type::BALANCE ||
	    _entryData->GetType() == MacroConditionAudio::Type::SYNC_OFFSET) {
		_entryData->_outputCondition =
			static_cast<MacroConditionAudio::OutputCondition>(cond);
	} else {
		_entryData->_volumeCondition =
			static_cast<MacroConditionAudio::VolumeCondition>(cond);
	}
	SetWidgetVisibility();
}

void MacroConditionAudioEdit::CheckTypeChanged(int idx)
{
	if (_loading || !_entryData) {
		return;
	}

	auto lock = LockContext();
	_entryData->SetType(static_cast<MacroConditionAudio::Type>(
		_checkTypes->itemData(idx).toInt()));

	const QSignalBlocker b(_condition);
	if (_entryData->GetType() == MacroConditionAudio::Type::OUTPUT_VOLUME ||
	    _entryData->GetType() == MacroConditionAudio::Type::BALANCE ||
	    _entryData->GetType() == MacroConditionAudio::Type::SYNC_OFFSET) {
		populateOutputConditionSelection(_condition);
	} else if (_entryData->GetType() ==
		   MacroConditionAudio::Type::CONFIGURED_VOLUME) {
		populateVolumeConditionSelection(_condition);
	}
	SetWidgetVisibility();
}

void MacroConditionAudioEdit::UpdateEntryData()
{
	if (!_entryData) {
		return;
	}

	_sources->SetSource(_entryData->_audioSource);
	_volumePercent->SetValue(_entryData->_volumePercent);
	_volumeDB->SetValue(_entryData->_volumeDB);
	_syncOffset->SetValue(_entryData->_syncOffset);
	_monitorTypes->setCurrentIndex(_entryData->_monitorType);
	_balance->SetDoubleValue(_entryData->_balance);
	_checkTypes->setCurrentIndex(
		_checkTypes->findData(static_cast<int>(_entryData->GetType())));

	if (_entryData->GetType() == MacroConditionAudio::Type::OUTPUT_VOLUME ||
	    _entryData->GetType() == MacroConditionAudio::Type::BALANCE ||
	    _entryData->GetType() == MacroConditionAudio::Type::SYNC_OFFSET) {
		populateOutputConditionSelection(_condition);
		_condition->setCurrentIndex(
			static_cast<int>(_entryData->_outputCondition));
	} else if (_entryData->GetType() ==
		   MacroConditionAudio::Type::CONFIGURED_VOLUME) {
		populateVolumeConditionSelection(_condition);
		_condition->setCurrentIndex(
			static_cast<int>(_entryData->_volumeCondition));
	}

	UpdateVolmeterSource();
	SetWidgetVisibility();
}

bool MacroConditionAudioEdit::HasVolumeControl() const
{
	return _entryData->GetType() ==
		       MacroConditionAudio::Type::OUTPUT_VOLUME ||
	       (_entryData->GetType() ==
			MacroConditionAudio::Type::CONFIGURED_VOLUME &&
		(_entryData->_volumeCondition ==
			 MacroConditionAudio::VolumeCondition::ABOVE ||
		 _entryData->_volumeCondition ==
			 MacroConditionAudio::VolumeCondition::EXACT ||
		 _entryData->_volumeCondition ==
			 MacroConditionAudio::VolumeCondition::BELOW));
}

void MacroConditionAudioEdit::SetWidgetVisibility()
{
	if (!_entryData) {
		return;
	}

	_condition->setVisible(
		_entryData->GetType() ==
			MacroConditionAudio::Type::OUTPUT_VOLUME ||
		_entryData->GetType() ==
			MacroConditionAudio::Type::CONFIGURED_VOLUME ||
		_entryData->GetType() == MacroConditionAudio::Type::BALANCE ||
		_entryData->GetType() ==
			MacroConditionAudio::Type::SYNC_OFFSET);
	_syncOffset->setVisible(_entryData->GetType() ==
				MacroConditionAudio::Type::SYNC_OFFSET);
	_monitorTypes->setVisible(_entryData->GetType() ==
				  MacroConditionAudio::Type::MONITOR);
	_balance->setVisible(_entryData->GetType() ==
			     MacroConditionAudio::Type::BALANCE);
	_volMeter->setVisible(_entryData->GetType() ==
			      MacroConditionAudio::Type::OUTPUT_VOLUME);
	_volumePercent->setVisible(HasVolumeControl() && !_entryData->_useDb);
	_volumeDB->setVisible(HasVolumeControl() && _entryData->_useDb);
	_percentDBToggle->setText(_entryData->_useDb ? "dB" : "%");
	_percentDBToggle->setVisible(HasVolumeControl());
	adjustSize();
}

} // namespace advss
