#include "macro-condition-tempvar.hpp"
#include "layout-helpers.hpp"
#include "math-helpers.hpp"

namespace advss {

const std::string MacroConditionTempVar::id = "temp_var";

bool MacroConditionTempVar::_registered = MacroConditionFactory::Register(
	MacroConditionTempVar::id,
	{MacroConditionTempVar::Create, MacroConditionTempVarEdit::Create,
	 "AdvSceneSwitcher.condition.temporaryVariable"});

const static std::map<MacroConditionTempVar::Condition, std::string>
	conditionTypes = {
		{MacroConditionTempVar::Condition::EQUALS,
		 "AdvSceneSwitcher.condition.variable.type.compare"},
		{MacroConditionTempVar::Condition::IS_EMPTY,
		 "AdvSceneSwitcher.condition.variable.type.empty"},
		{MacroConditionTempVar::Condition::IS_NUMBER,
		 "AdvSceneSwitcher.condition.variable.type.number"},
		{MacroConditionTempVar::Condition::LESS_THAN,
		 "AdvSceneSwitcher.condition.variable.type.lessThan"},
		{MacroConditionTempVar::Condition::GREATER_THAN,
		 "AdvSceneSwitcher.condition.variable.type.greaterThan"},
		{MacroConditionTempVar::Condition::VALUE_CHANGED,
		 "AdvSceneSwitcher.condition.variable.type.valueChanged"},
		{MacroConditionTempVar::Condition::EQUALS_VARIABLE,
		 "AdvSceneSwitcher.condition.variable.type.equalsVariable"},
		{MacroConditionTempVar::Condition::LESS_THAN_VARIABLE,
		 "AdvSceneSwitcher.condition.variable.type.lessThanVariable"},
		{MacroConditionTempVar::Condition::GREATER_THAN_VARIABLE,
		 "AdvSceneSwitcher.condition.variable.type.greaterThanVariable"},
};

static bool isNumber(const TempVariable &var)
{
	auto value = var.Value();
	if (!value) {
		return false;
	}

	return GetDouble(*value).has_value();
}

static bool compareNumber(const TempVariable &var, double value, bool less)
{
	auto tempVarValue = var.Value();
	if (!tempVarValue) {
		return false;
	}

	auto doubleValue = GetDouble(*tempVarValue);
	if (!doubleValue.has_value()) {
		return false;
	}
	if (less) {
		return doubleValue < value;
	}
	return doubleValue > value;
}

bool MacroConditionTempVar::Compare(const TempVariable &var) const
{
	auto value = var.Value();
	if (!value) {
		return false;
	}

	if (_regex.Enabled()) {
		return _regex.Matches(*value, _strValue);
	}

	return std::string(_strValue) == *value;
}

bool MacroConditionTempVar::ValueChanged(const TempVariable &var)
{
	auto value = var.Value();
	if (!value) {
		return false;
	}
	bool changed = *value != _lastValue;
	if (changed) {
		_lastValue = *value;
	}
	return changed;
}

bool MacroConditionTempVar::CompareVariables()
{
	auto var1 = _tempVar.GetTempVariable(GetMacro());
	auto var2 = _variable2.lock();
	if (!var1 || !var2) {
		return false;
	}

	auto tempVarValue = var1->Value();
	if (!tempVarValue) {
		return false;
	}
	auto val1 = GetDouble(*tempVarValue);
	auto val2 = var2->DoubleValue();
	bool validNumbers = val1.has_value() && val2.has_value();

	switch (_type) {
	case MacroConditionTempVar::Condition::EQUALS_VARIABLE:
		return *tempVarValue == var2->Value() ||
		       (validNumbers && val1 == val2);
	case MacroConditionTempVar::Condition::LESS_THAN_VARIABLE:
		return validNumbers && val1 < val2;
	case MacroConditionTempVar::Condition::GREATER_THAN_VARIABLE:
		return validNumbers && val1 > val2;
	default:
		blog(LOG_WARNING,
		     "Unexpected call of %s with condition type %d", __func__,
		     static_cast<int>(_type));
	}

	return false;
}

bool MacroConditionTempVar::CheckCondition()
{
	auto var = _tempVar.GetTempVariable(GetMacro());
	if (!var) {
		return false;
	}

	switch (_type) {
	case MacroConditionTempVar::Condition::EQUALS:
		return Compare(*var);
	case MacroConditionTempVar::Condition::IS_EMPTY:
		return var->Value().has_value() && var->Value()->empty();
	case MacroConditionTempVar::Condition::IS_NUMBER:
		return isNumber(*var);
	case MacroConditionTempVar::Condition::LESS_THAN:
		return compareNumber(*var, _numValue, true);
	case MacroConditionTempVar::Condition::GREATER_THAN:
		return compareNumber(*var, _numValue, false);
	case MacroConditionTempVar::Condition::VALUE_CHANGED:
		return ValueChanged(*var);
	case MacroConditionTempVar::Condition::EQUALS_VARIABLE:
		return CompareVariables();
	case MacroConditionTempVar::Condition::LESS_THAN_VARIABLE:
		return CompareVariables();
	case MacroConditionTempVar::Condition::GREATER_THAN_VARIABLE:
		return CompareVariables();
	}

	return false;
}

bool MacroConditionTempVar::Save(obs_data_t *obj) const
{
	MacroCondition::Save(obj);
	_tempVar.Save(obj);
	obs_data_set_string(obj, "variableName",
			    GetWeakVariableName(_variable2).c_str());
	_strValue.Save(obj, "strValue");
	obs_data_set_double(obj, "numValue", _numValue);
	obs_data_set_int(obj, "condition", static_cast<int>(_type));
	_regex.Save(obj);
	return true;
}

bool MacroConditionTempVar::Load(obs_data_t *obj)
{
	MacroCondition::Load(obj);
	_tempVar.Load(obj, GetMacro());
	_variable2 =
		GetWeakVariableByName(obs_data_get_string(obj, "variableName"));
	_strValue.Load(obj, "strValue");
	_numValue = obs_data_get_double(obj, "numValue");
	_type = static_cast<Condition>(obs_data_get_int(obj, "condition"));
	_regex.Load(obj);
	// TODO: remove in future version
	if (obs_data_has_user_value(obj, "regex")) {
		_regex.CreateBackwardsCompatibleRegex(
			obs_data_get_bool(obj, "regex"));
	}
	return true;
}

std::string MacroConditionTempVar::GetShortDesc() const
{
	auto var = _tempVar.GetTempVariable(GetMacro());
	if (!var) {
		return "";
	}
	return var->Name();
}

static inline void populateConditionSelection(QComboBox *list)
{
	for (auto entry : conditionTypes) {
		list->addItem(obs_module_text(entry.second.c_str()));
	}
}

MacroConditionTempVarEdit::MacroConditionTempVarEdit(
	QWidget *parent, std::shared_ptr<MacroConditionTempVar> entryData)
	: QWidget(parent),
	  _tempVars(new TempVariableSelection(this)),
	  _variables2(new VariableSelection(this)),
	  _conditions(new QComboBox()),
	  _strValue(new VariableTextEdit(this, 5, 1, 1)),
	  _numValue(new QDoubleSpinBox()),
	  _regex(new RegexConfigWidget(parent))
{
	_numValue->setMinimum(-9999999999);
	_numValue->setMaximum(9999999999);
	populateConditionSelection(_conditions);

	QWidget::connect(_tempVars,
			 SIGNAL(SelectionChanged(const TempVariableRef &)),
			 this, SLOT(VariableChanged(const TempVariableRef &)));
	QWidget::connect(_variables2, SIGNAL(SelectionChanged(const QString &)),
			 this, SLOT(Variable2Changed(const QString &)));
	QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
			 SLOT(ConditionChanged(int)));
	QWidget::connect(_strValue, SIGNAL(textChanged()), this,
			 SLOT(StrValueChanged()));
	QWidget::connect(_numValue, SIGNAL(valueChanged(double)), this,
			 SLOT(NumValueChanged(double)));
	QWidget::connect(_regex,
			 SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
			 SLOT(RegexChanged(const RegexConfig &)));

	std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
		{"{{variables}}", _tempVars},
		{"{{variables2}}", _variables2},
		{"{{conditions}}", _conditions},
		{"{{strValue}}", _strValue},
		{"{{numValue}}", _numValue},
		{"{{regex}}", _regex},
	};

	auto layout = new QHBoxLayout;
	PlaceWidgets(
		obs_module_text("AdvSceneSwitcher.condition.variable.entry"),
		layout, widgetPlaceholders);
	setLayout(layout);

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

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

	_tempVars->SetVariable(_entryData->_tempVar);
	_variables2->SetVariable(_entryData->_variable2);
	_conditions->setCurrentIndex(static_cast<int>(_entryData->_type));
	_strValue->setPlainText(_entryData->_strValue);
	_numValue->setValue(_entryData->_numValue);
	_regex->SetRegexConfig(_entryData->_regex);
	SetWidgetVisibility();
}

void MacroConditionTempVarEdit::VariableChanged(const TempVariableRef &var)
{
	if (_loading || !_entryData) {
		return;
	}

	auto lock = LockContext();
	_entryData->_tempVar = var;
}

void MacroConditionTempVarEdit::Variable2Changed(const QString &text)
{

	if (_loading || !_entryData) {
		return;
	}

	auto lock = LockContext();
	_entryData->_variable2 = GetWeakVariableByQString(text);
}

void MacroConditionTempVarEdit::ConditionChanged(int value)
{
	if (_loading || !_entryData) {
		return;
	}

	auto lock = LockContext();
	_entryData->_type =
		static_cast<MacroConditionTempVar::Condition>(value);
	SetWidgetVisibility();
}

void MacroConditionTempVarEdit::StrValueChanged()
{
	if (_loading || !_entryData) {
		return;
	}

	auto lock = LockContext();
	_entryData->_strValue = _strValue->toPlainText().toStdString();
	adjustSize();
	updateGeometry();
}

void MacroConditionTempVarEdit::NumValueChanged(double val)
{
	if (_loading || !_entryData) {
		return;
	}

	auto lock = LockContext();
	_entryData->_numValue = val;
}

void MacroConditionTempVarEdit::RegexChanged(const RegexConfig &conf)
{
	if (_loading || !_entryData) {
		return;
	}

	auto lock = LockContext();
	_entryData->_regex = conf;
	adjustSize();
	updateGeometry();
}

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

	_regex->setVisible(_entryData->_type ==
			   MacroConditionTempVar::Condition::EQUALS);
	_strValue->setVisible(_entryData->_type ==
			      MacroConditionTempVar::Condition::EQUALS);
	_numValue->setVisible(
		_entryData->_type ==
			MacroConditionTempVar::Condition::LESS_THAN ||
		_entryData->_type ==
			MacroConditionTempVar::Condition::GREATER_THAN);
	_numValue->setVisible(
		_entryData->_type ==
			MacroConditionTempVar::Condition::LESS_THAN ||
		_entryData->_type ==
			MacroConditionTempVar::Condition::GREATER_THAN);
	_variables2->setVisible(
		_entryData->_type ==
			MacroConditionTempVar::Condition::EQUALS_VARIABLE ||
		_entryData->_type ==
			MacroConditionTempVar::Condition::LESS_THAN_VARIABLE ||
		_entryData->_type ==
			MacroConditionTempVar::Condition::GREATER_THAN_VARIABLE);

	adjustSize();
	updateGeometry();
}

} // namespace advss
