/***

  Olive - Non-Linear Video Editor
  Copyright (C) 2022 Olive Team
  Modifications Copyright (C) 2025 mikesolar

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.

***/

#include "nodeparamviewwidgetbridge.h"

#include <QCheckBox>
#include <QFontComboBox>
#include <QVector2D>
#include <QVector3D>
#include <QVector4D>

#include "common/qtutils.h"
#include "core.h"
#include "nodeparambutton.h"
#include "node/group/group.h"
#include "node/node.h"
#include "node/nodeundo.h"
#include "node/project/sequence/sequence.h"
#include "nodeparamviewarraywidget.h"
#include "nodeparamviewtextedit.h"
#include "undo/undostack.h"
#include "widget/bezier/bezierwidget.h"
#include "widget/colorbutton/colorbutton.h"
#include "widget/filefield/filefield.h"
#include "widget/slider/floatslider.h"
#include "widget/slider/integerslider.h"
#include "widget/slider/rationalslider.h"

//#include <OpenImageIO/detail/fmt/base.h>

namespace olive
{

NodeParamViewWidgetBridge::NodeParamViewWidgetBridge(NodeInput input,
													 QObject *parent)
	: QObject(parent)
{
	do {
		input_hierarchy_.append(input);

		connect(input.node(), &Node::ValueChanged, this,
				&NodeParamViewWidgetBridge::InputValueChanged);
		connect(input.node(), &Node::InputPropertyChanged, this,
				&NodeParamViewWidgetBridge::PropertyChanged);
		connect(input.node(), &Node::InputDataTypeChanged, this,
				&NodeParamViewWidgetBridge::InputDataTypeChanged);
	} while (NodeGroup::GetInner(&input));

	CreateWidgets();
}

int GetSliderCount(NodeValue::Type type)
{
	return NodeValue::get_number_of_keyframe_tracks(type);
}

void NodeParamViewWidgetBridge::CreateWidgets()
{
	QWidget *parent = dynamic_cast<QWidget *>(this->parent());

	if (GetInnerInput().IsArray() && GetInnerInput().element() == -1) {
		NodeParamViewArrayWidget *w = new NodeParamViewArrayWidget(
			GetInnerInput().node(), GetInnerInput().input(), parent);
		connect(w, &NodeParamViewArrayWidget::DoubleClicked, this,
				&NodeParamViewWidgetBridge::ArrayWidgetDoubleClicked);
		widgets_.append(w);

	} else {
		// We assume the first data type is the "primary" type
		NodeValue::Type t = GetDataType();
		switch (t) {
		// None of these inputs have applicable UI widgets
		case NodeValue::kNone:
		case NodeValue::kTexture:
		case NodeValue::kMatrix:
		case NodeValue::kSamples:
		case NodeValue::kVideoParams:
		case NodeValue::kAudioParams:
		case NodeValue::kSubtitleParams:
		case NodeValue::kBinary:
		case NodeValue::kDataTypeCount:
			break;
		case NodeValue::kInt: {
			CreateSliders<IntegerSlider>(1, parent);
			break;
		}
		case NodeValue::kRational: {
			CreateSliders<RationalSlider>(1, parent);
			break;
		}
		case NodeValue::kFloat:
		case NodeValue::kVec2:
		case NodeValue::kVec3:
		case NodeValue::kVec4: {
			CreateSliders<FloatSlider>(GetSliderCount(t), parent);
			break;
		}
		case NodeValue::kCombo:
		case NodeValue::kStrCombo: {
			QComboBox *combobox = new QComboBox(parent);

			QStringList items = GetInnerInput().GetComboBoxStrings();
			QStringList values =
				GetInnerInput().GetProperty("combo_value_str").toStringList();
			const bool use_value_data =
				(t == NodeValue::kStrCombo) && !values.isEmpty();
			for (int i = 0; i < items.size(); ++i) {
				const QString &label = items.at(i);
				if (use_value_data && i < values.size()) {
					combobox->addItem(label, values.at(i));
				} else {
					combobox->addItem(label);
				}
			}

			widgets_.append(combobox);
			connect(combobox,
					static_cast<void (QComboBox::*)(int)>(
						&QComboBox::currentIndexChanged),
					this, &NodeParamViewWidgetBridge::WidgetCallback);
			break;
		}
		case NodeValue::kFile: {
			FileField *file_field = new FileField(parent);
			widgets_.append(file_field);
			connect(file_field, &FileField::FilenameChanged, this,
					&NodeParamViewWidgetBridge::WidgetCallback);
			break;
		}
		case NodeValue::kColor: {
			if (GetInnerInput().GetProperty("color_semantic")
					.toString() == QStringLiteral("scalar")) {
				CreateSliders<FloatSlider>(4, parent);
			} else {
				ColorButton *color_button = new ColorButton(
					GetInnerInput().node()->project()->color_manager(),
					parent);
				widgets_.append(color_button);
				connect(
					color_button, &ColorButton::ColorChanged, this,
					&NodeParamViewWidgetBridge::WidgetCallback);
			}
			break;
		}
		case NodeValue::kText: {
			NodeParamViewTextEdit *line_edit =
				new NodeParamViewTextEdit(parent);
			widgets_.append(line_edit);
			connect(line_edit, &NodeParamViewTextEdit::textEdited, this,
					&NodeParamViewWidgetBridge::WidgetCallback);
			connect(line_edit, &NodeParamViewTextEdit::RequestEditInViewer,
					this, &NodeParamViewWidgetBridge::RequestEditTextInViewer);
			break;
		}
		case NodeValue::kBoolean: {
			QCheckBox *check_box = new QCheckBox(parent);
			widgets_.append(check_box);
			connect(check_box, &QCheckBox::clicked, this,
					&NodeParamViewWidgetBridge::WidgetCallback);
			break;
		}
		case NodeValue::kFont: {
			QFontComboBox *font_combobox = new QFontComboBox(parent);
			widgets_.append(font_combobox);
			connect(font_combobox, &QFontComboBox::currentFontChanged, this,
					&NodeParamViewWidgetBridge::WidgetCallback);
			break;
		}
		case NodeValue::kBezier: {
			BezierWidget *bezier = new BezierWidget(parent);
			widgets_.append(bezier);

			connect(bezier->x_slider(), &FloatSlider::ValueChanged, this,
					&NodeParamViewWidgetBridge::WidgetCallback);
			connect(bezier->y_slider(), &FloatSlider::ValueChanged, this,
					&NodeParamViewWidgetBridge::WidgetCallback);
			connect(bezier->cp1_x_slider(), &FloatSlider::ValueChanged, this,
					&NodeParamViewWidgetBridge::WidgetCallback);
			connect(bezier->cp1_y_slider(), &FloatSlider::ValueChanged, this,
					&NodeParamViewWidgetBridge::WidgetCallback);
			connect(bezier->cp2_x_slider(), &FloatSlider::ValueChanged, this,
					&NodeParamViewWidgetBridge::WidgetCallback);
			connect(bezier->cp2_y_slider(), &FloatSlider::ValueChanged, this,
					&NodeParamViewWidgetBridge::WidgetCallback);
			break;
		}
		case NodeValue::kPushButton: {
			NodeInput input=GetInnerInput();
			NodeParamButton *button=new NodeParamButton(input.name(),parent);
			widgets_.append(button);
			plugin::PluginNode* plugin_node=dynamic_cast<plugin::PluginNode*>(input.node());
			connect(button, &NodeParamButton::onPressed, plugin_node, &plugin::PluginNode::pushButtonClicked);
		}
		}

		// Check all properties
		UpdateProperties();

		UpdateWidgetValues();

		// Install event filter to disable widgets picking up scroll events
		foreach (QWidget *w, widgets_) {
			w->installEventFilter(&scroll_filter_);
		}
	}
}

void NodeParamViewWidgetBridge::SetInputValue(const QVariant &value, int track)
{
	MultiUndoCommand *command = new MultiUndoCommand();

	SetInputValueInternal(value, track, command, true);

	Core::instance()->undo_stack()->push(command, GetCommandName());
}

void NodeParamViewWidgetBridge::SetInputValueInternal(
	const QVariant &value, int track, MultiUndoCommand *command,
	bool insert_on_all_tracks_if_no_key)
{
	Node::SetValueAtTime(GetInnerInput(), GetCurrentTimeAsNodeTime(), value,
						 track, command, insert_on_all_tracks_if_no_key);
}

void NodeParamViewWidgetBridge::ProcessSlider(NumericSliderBase *slider,
											  int slider_track,
											  const QVariant &value)
{
	if (slider->IsDragging()) {
		// While we're dragging, we block the input's normal signalling and create our own
		if (!dragger_.IsStarted()) {
			rational node_time = GetCurrentTimeAsNodeTime();

			dragger_.Start(NodeKeyframeTrackReference(GetInnerInput(),
													  slider_track),
						   node_time);
		}

		dragger_.Drag(value);

	} else if (dragger_.IsStarted()) {
		// We were dragging and just stopped
		dragger_.Drag(value);

		MultiUndoCommand *command = new MultiUndoCommand();
		dragger_.End(command);
		Core::instance()->undo_stack()->push(command, GetCommandName());

	} else {
		// No drag was involved, we can just push the value
		SetInputValue(value, slider_track);
	}
}

void NodeParamViewWidgetBridge::WidgetCallback()
{
	switch (GetDataType()) {
	// None of these inputs have applicable UI widgets
	case NodeValue::kNone:
	case NodeValue::kTexture:
	case NodeValue::kMatrix:
	case NodeValue::kSamples:
	case NodeValue::kVideoParams:
	case NodeValue::kAudioParams:
	case NodeValue::kSubtitleParams:
	case NodeValue::kDataTypeCount:
		break;
	case NodeValue::kInt: {
		// Widget is a IntegerSlider
		IntegerSlider *slider = static_cast<IntegerSlider *>(sender());

		ProcessSlider(slider, QVariant::fromValue(slider->GetValue()));
		break;
	}
	case NodeValue::kFloat: {
		// Widget is a FloatSlider
		FloatSlider *slider = static_cast<FloatSlider *>(sender());

		ProcessSlider(slider, slider->GetValue());
		break;
	}
	case NodeValue::kRational: {
		// Widget is a RationalSlider
		RationalSlider *slider = static_cast<RationalSlider *>(sender());
		ProcessSlider(slider, QVariant::fromValue(slider->GetValue()));
		break;
	}
	case NodeValue::kVec2: {
		// Widget is a FloatSlider
		FloatSlider *slider = static_cast<FloatSlider *>(sender());

		ProcessSlider(slider, slider->GetValue());
		break;
	}
	case NodeValue::kVec3: {
		// Widget is a FloatSlider
		FloatSlider *slider = static_cast<FloatSlider *>(sender());

		ProcessSlider(slider, slider->GetValue());
		break;
	}
	case NodeValue::kVec4: {
		// Widget is a FloatSlider
		FloatSlider *slider = static_cast<FloatSlider *>(sender());

		ProcessSlider(slider, slider->GetValue());
		break;
	}
	case NodeValue::kFile: {
		SetInputValue(static_cast<FileField *>(sender())->GetFilename(), 0);
		break;
	}
	case NodeValue::kColor: {
		if (GetInnerInput().GetProperty("color_semantic")
				.toString() == QStringLiteral("scalar")) {
			FloatSlider *slider =
				static_cast<FloatSlider *>(sender());
			ProcessSlider(slider, slider->GetValue());
		} else {
			// Sender is a ColorButton
			ManagedColor c =
				static_cast<ColorButton *>(sender())->GetColor();

			MultiUndoCommand *command = new MultiUndoCommand();

			SetInputValueInternal(c.red(), 0, command, false);
			SetInputValueInternal(c.green(), 1, command, false);
			SetInputValueInternal(c.blue(), 2, command, false);
			SetInputValueInternal(c.alpha(), 3, command, false);

			Node *n = GetInnerInput().node();
			n->blockSignals(true);
			n->SetInputProperty(
				GetInnerInput().input(), QStringLiteral("col_input"),
				c.color_input());
			n->SetInputProperty(
				GetInnerInput().input(), QStringLiteral("col_display"),
				c.color_output().display());
			n->SetInputProperty(
				GetInnerInput().input(), QStringLiteral("col_view"),
				c.color_output().view());
			n->SetInputProperty(
				GetInnerInput().input(), QStringLiteral("col_look"),
				c.color_output().look());
			n->blockSignals(false);

			Core::instance()->undo_stack()->push(command,
											 GetCommandName());
		}
		break;
	}
	case NodeValue::kText: {
		// Sender is a NodeParamViewRichText
		SetInputValue(static_cast<NodeParamViewTextEdit *>(sender())->text(),
					  0);
		break;
	}
	case NodeValue::kBinary: {
		QString text = static_cast<NodeParamViewTextEdit *>(sender())->text();
		QByteArray raw = text.toUtf8();
		QByteArray decoded = QByteArray::fromBase64(raw);
		if (decoded.isEmpty() && !raw.isEmpty()) {
			decoded = raw;
		}
		SetInputValue(decoded, 0);
		break;
	}
	case NodeValue::kBoolean: {
		// Widget is a QCheckBox
		SetInputValue(static_cast<QCheckBox *>(sender())->isChecked(), 0);
		break;
	}
	case NodeValue::kFont: {
		// Widget is a QFontComboBox
		SetInputValue(
			static_cast<QFontComboBox *>(sender())->currentFont().family(), 0);
		break;
	}
	case NodeValue::kCombo: {
		// Widget is a QComboBox
		QComboBox *cb = static_cast<QComboBox *>(widgets_.first());
		int index = cb->currentIndex();

		// Subtract any splitters up until this point
		for (int i = index - 1; i >= 0; i--) {
			if (cb->itemData(i, Qt::AccessibleDescriptionRole).toString() ==
				QStringLiteral("separator")) {
				index--;
			}
		}

		SetInputValue(index, 0);
		break;
	}
	case NodeValue::kStrCombo: {
		QComboBox *cb = static_cast<QComboBox *>(widgets_.first());
		const QVariant data = cb->currentData();
		if (data.isValid()) {
			SetInputValue(data.toString(), 0);
		} else {
			SetInputValue(cb->currentText(), 0);
		}
		break;
	}
	case NodeValue::kBezier: {
		// Widget is a FloatSlider (child of BezierWidget)
		BezierWidget *bw = static_cast<BezierWidget *>(widgets_.first());
		FloatSlider *fs = static_cast<FloatSlider *>(sender());

		int index = -1;
		if (fs == bw->x_slider()) {
			index = 0;
		} else if (fs == bw->y_slider()) {
			index = 1;
		} else if (fs == bw->cp1_x_slider()) {
			index = 2;
		} else if (fs == bw->cp1_y_slider()) {
			index = 3;
		} else if (fs == bw->cp2_x_slider()) {
			index = 4;
		} else if (fs == bw->cp2_y_slider()) {
			index = 5;
		}

		if (index != -1) {
			ProcessSlider(fs, index, fs->GetValue());
		}
		break;
	}
	}
}

template <typename T>
void NodeParamViewWidgetBridge::CreateSliders(int count, QWidget *parent)
{
	for (int i = 0; i < count; i++) {
		T *fs = new T(parent);
		fs->SliderBase::SetDefaultValue(
			GetInnerInput().GetSplitDefaultValueForTrack(i));
		fs->SetLadderElementCount(2);

		// HACK: Force some spacing between sliders
		fs->setContentsMargins(
			0, 0,
			QtUtils::QFontMetricsWidth(fs->fontMetrics(),
									   QStringLiteral("        ")),
			0);

		widgets_.append(fs);
		connect(fs, &T::ValueChanged, this,
				&NodeParamViewWidgetBridge::WidgetCallback);
	}
}

void NodeParamViewWidgetBridge::UpdateWidgetValues()
{
	if (GetInnerInput().IsArray() && GetInnerInput().element() == -1) {
		return;
	}

	rational node_time;
	if (GetInnerInput().IsKeyframing()) {
		node_time = GetCurrentTimeAsNodeTime();
	}

	// We assume the first data type is the "primary" type
	switch (GetDataType()) {
	// None of these inputs have applicable UI widgets
	case NodeValue::kNone:
	case NodeValue::kTexture:
	case NodeValue::kMatrix:
	case NodeValue::kSamples:
	case NodeValue::kVideoParams:
	case NodeValue::kAudioParams:
	case NodeValue::kSubtitleParams:
	case NodeValue::kDataTypeCount:
		break;
	case NodeValue::kBinary: {
		NodeParamViewTextEdit *e =
			static_cast<NodeParamViewTextEdit *>(widgets_.first());
		QByteArray bytes = GetInnerInput().GetValueAtTime(node_time).toByteArray();
		e->setTextPreservingCursor(QString::fromUtf8(bytes.toBase64()));
		break;
	}
	case NodeValue::kInt: {
		static_cast<IntegerSlider *>(widgets_.first())
			->SetValue(GetInnerInput().GetValueAtTime(node_time).toLongLong());
		break;
	}
	case NodeValue::kFloat: {
		static_cast<FloatSlider *>(widgets_.first())
			->SetValue(GetInnerInput().GetValueAtTime(node_time).toDouble());
		break;
	}
	case NodeValue::kRational: {
		static_cast<RationalSlider *>(widgets_.first())
			->SetValue(
				GetInnerInput().GetValueAtTime(node_time).value<rational>());
		break;
	}
	case NodeValue::kVec2: {
		QVector2D vec2 =
			GetInnerInput().GetValueAtTime(node_time).value<QVector2D>();

		static_cast<FloatSlider *>(widgets_.at(0))
			->SetValue(static_cast<double>(vec2.x()));
		static_cast<FloatSlider *>(widgets_.at(1))
			->SetValue(static_cast<double>(vec2.y()));
		break;
	}
	case NodeValue::kVec3: {
		QVector3D vec3 =
			GetInnerInput().GetValueAtTime(node_time).value<QVector3D>();

		static_cast<FloatSlider *>(widgets_.at(0))
			->SetValue(static_cast<double>(vec3.x()));
		static_cast<FloatSlider *>(widgets_.at(1))
			->SetValue(static_cast<double>(vec3.y()));
		static_cast<FloatSlider *>(widgets_.at(2))
			->SetValue(static_cast<double>(vec3.z()));
		break;
	}
	case NodeValue::kVec4: {
		QVector4D vec4 =
			GetInnerInput().GetValueAtTime(node_time).value<QVector4D>();

		static_cast<FloatSlider *>(widgets_.at(0))
			->SetValue(static_cast<double>(vec4.x()));
		static_cast<FloatSlider *>(widgets_.at(1))
			->SetValue(static_cast<double>(vec4.y()));
		static_cast<FloatSlider *>(widgets_.at(2))
			->SetValue(static_cast<double>(vec4.z()));
		static_cast<FloatSlider *>(widgets_.at(3))
			->SetValue(static_cast<double>(vec4.w()));
		break;
	}
	case NodeValue::kFile: {
		FileField *ff = static_cast<FileField *>(widgets_.first());
		ff->SetFilename(GetInnerInput().GetValueAtTime(node_time).toString());
		break;
	}
	case NodeValue::kColor: {
		if (GetInnerInput().GetProperty("color_semantic")
				.toString() == QStringLiteral("scalar")) {
			Color c = GetInnerInput()
					  .GetValueAtTime(node_time)
					  .value<Color>();
			static_cast<FloatSlider *>(widgets_.at(0))
				->SetValue(static_cast<double>(c.red()));
			static_cast<FloatSlider *>(widgets_.at(1))
				->SetValue(static_cast<double>(c.green()));
			static_cast<FloatSlider *>(widgets_.at(2))
				->SetValue(static_cast<double>(c.blue()));
			static_cast<FloatSlider *>(widgets_.at(3))
				->SetValue(static_cast<double>(c.alpha()));
		} else {
			ManagedColor mc = GetInnerInput()
							  .GetValueAtTime(node_time)
							  .value<Color>();

			mc.set_color_input(
				GetInnerInput().GetProperty("col_input").toString());

			QString d = GetInnerInput()
						.GetProperty("col_display")
						.toString();
			QString v = GetInnerInput()
						.GetProperty("col_view")
						.toString();
			QString l = GetInnerInput()
						.GetProperty("col_look")
						.toString();

			mc.set_color_output(ColorTransform(d, v, l));

			static_cast<ColorButton *>(widgets_.first())
				->SetColor(mc);
		}
		break;
	}
	case NodeValue::kText: {
		NodeParamViewTextEdit *e =
			static_cast<NodeParamViewTextEdit *>(widgets_.first());
		e->setTextPreservingCursor(
			GetInnerInput().GetValueAtTime(node_time).toString());
		break;
	}
	case NodeValue::kBoolean:
		static_cast<QCheckBox *>(widgets_.first())
			->setChecked(GetInnerInput().GetValueAtTime(node_time).toBool());
		break;
	case NodeValue::kFont: {
		QFontComboBox *fc = static_cast<QFontComboBox *>(widgets_.first());
		fc->blockSignals(true);
		fc->setCurrentFont(
			GetInnerInput().GetValueAtTime(node_time).toString());
		fc->blockSignals(false);
		break;
	}
	case NodeValue::kCombo: {
		QComboBox *cb = static_cast<QComboBox *>(widgets_.first());
		cb->blockSignals(true);
		int index = GetInnerInput().GetValueAtTime(node_time).toInt();
		for (int i = 0; i < cb->count(); i++) {
			if (cb->itemData(i).toInt() == index) {
				cb->setCurrentIndex(i);
			}
		}
		cb->blockSignals(false);
		break;
	}
	case NodeValue::kStrCombo: {
		QComboBox *cb = static_cast<QComboBox *>(widgets_.first());
		cb->blockSignals(true);
		const QString current =
			GetInnerInput().GetValueAtTime(node_time).toString();
		for (int i = 0; i < cb->count(); ++i) {
			const QVariant data = cb->itemData(i);
			if ((data.isValid() && data.toString() == current) ||
				(!data.isValid() && cb->itemText(i) == current)) {
				cb->setCurrentIndex(i);
				break;
			}
		}
		cb->blockSignals(false);
		break;
	}
	case NodeValue::kBezier: {
		BezierWidget *bw = static_cast<BezierWidget *>(widgets_.first());
		bw->SetValue(GetInnerInput().GetValueAtTime(node_time).value<Bezier>());
		break;
	}
	}
}

rational NodeParamViewWidgetBridge::GetCurrentTimeAsNodeTime() const
{
	if (GetTimeTarget()) {
		return GetAdjustedTime(GetTimeTarget(), GetInnerInput().node(),
							   GetTimeTarget()->GetPlayhead(),
							   Node::kTransformTowardsInput);
	} else {
		return 0;
	}
}

QString NodeParamViewWidgetBridge::GetCommandName() const
{
	NodeInput i = GetInnerInput();
	return tr("Edited Value Of %1 - %2")
		.arg(i.node()->GetLabelAndName(), i.node()->GetInputName(i.input()));
}

void NodeParamViewWidgetBridge::SetTimebase(const rational &timebase)
{
	if (GetDataType() == NodeValue::kRational) {
		static_cast<RationalSlider *>(widgets_.first())->SetTimebase(timebase);
	}
}

void NodeParamViewWidgetBridge::TimeTargetDisconnectEvent(ViewerOutput *v)
{
	disconnect(v, &ViewerOutput::PlayheadChanged, this,
			   &NodeParamViewWidgetBridge::UpdateWidgetValues);
}

void NodeParamViewWidgetBridge::TimeTargetConnectEvent(ViewerOutput *v)
{
	connect(v, &ViewerOutput::PlayheadChanged, this,
			&NodeParamViewWidgetBridge::UpdateWidgetValues);
}

void NodeParamViewWidgetBridge::InputValueChanged(const NodeInput &input,
												  const TimeRange &range)
{
	if (GetTimeTarget() && GetInnerInput() == input && !dragger_.IsStarted() &&
		range.in() <= GetTimeTarget()->GetPlayhead() &&
		range.out() >= GetTimeTarget()->GetPlayhead()) {
		// We'll need to update the widgets because the values have changed on our current time
		UpdateWidgetValues();
	}
}

void NodeParamViewWidgetBridge::SetProperty(const QString &key,
											const QVariant &value)
{
	NodeValue::Type data_type = GetDataType();

	// Parameters for all types
	bool key_is_disable = key.startsWith(QStringLiteral("disable"));
	if (key_is_disable || key.startsWith(QStringLiteral("enabled"))) {
		bool e = value.toBool();
		if (key_is_disable) {
			e = !e;
		}

		if (key.size() == 7) { // just the word "disable" or "enabled"
			for (int i = 0; i < widgets_.size(); i++) {
				widgets_.at(i)->setEnabled(e);
			}
		} else { // set specific track/widget
			bool ok;
			int element = key.mid(7).toInt(&ok);
			int tracks = NodeValue::get_number_of_keyframe_tracks(data_type);

			if (ok && element >= 0 && element < tracks) {
				widgets_.at(element)->setEnabled(e);
			}
		}
	}

	if (key == QStringLiteral("tooltip")) {
		for (int i = 0; i < widgets_.size(); i++) {
			widgets_.at(i)->setToolTip(value.toString());
		}
	}

	// Parameters for integers, floats, and vectors
	if (NodeValue::type_is_numeric(data_type) ||
		NodeValue::type_is_vector(data_type)) {
		if (key == QStringLiteral("min")) {
			switch (data_type) {
			case NodeValue::kInt:
				static_cast<IntegerSlider *>(widgets_.first())
					->SetMinimum(value.value<int64_t>());
				break;
			case NodeValue::kFloat:
				static_cast<FloatSlider *>(widgets_.first())
					->SetMinimum(value.toDouble());
				break;
			case NodeValue::kRational:
				static_cast<RationalSlider *>(widgets_.first())
					->SetMinimum(value.value<rational>());
				break;
			case NodeValue::kVec2: {
				QVector2D min = value.value<QVector2D>();
				static_cast<FloatSlider *>(widgets_.at(0))->SetMinimum(min.x());
				static_cast<FloatSlider *>(widgets_.at(1))->SetMinimum(min.y());
				break;
			}
			case NodeValue::kVec3: {
				QVector3D min = value.value<QVector3D>();
				static_cast<FloatSlider *>(widgets_.at(0))->SetMinimum(min.x());
				static_cast<FloatSlider *>(widgets_.at(1))->SetMinimum(min.y());
				static_cast<FloatSlider *>(widgets_.at(2))->SetMinimum(min.z());
				break;
			}
			case NodeValue::kVec4: {
				QVector4D min = value.value<QVector4D>();
				static_cast<FloatSlider *>(widgets_.at(0))->SetMinimum(min.x());
				static_cast<FloatSlider *>(widgets_.at(1))->SetMinimum(min.y());
				static_cast<FloatSlider *>(widgets_.at(2))->SetMinimum(min.z());
				static_cast<FloatSlider *>(widgets_.at(3))->SetMinimum(min.w());
				break;
			}
			default:
				break;
			}
		} else if (key == QStringLiteral("max")) {
			switch (data_type) {
			case NodeValue::kInt:
				static_cast<IntegerSlider *>(widgets_.first())
					->SetMaximum(value.value<int64_t>());
				break;
			case NodeValue::kFloat:
				static_cast<FloatSlider *>(widgets_.first())
					->SetMaximum(value.toDouble());
				break;
			case NodeValue::kRational:
				static_cast<RationalSlider *>(widgets_.first())
					->SetMaximum(value.value<rational>());
				break;
			case NodeValue::kVec2: {
				QVector2D max = value.value<QVector2D>();
				static_cast<FloatSlider *>(widgets_.at(0))->SetMaximum(max.x());
				static_cast<FloatSlider *>(widgets_.at(1))->SetMaximum(max.y());
				break;
			}
			case NodeValue::kVec3: {
				QVector3D max = value.value<QVector3D>();
				static_cast<FloatSlider *>(widgets_.at(0))->SetMaximum(max.x());
				static_cast<FloatSlider *>(widgets_.at(1))->SetMaximum(max.y());
				static_cast<FloatSlider *>(widgets_.at(2))->SetMaximum(max.z());
				break;
			}
			case NodeValue::kVec4: {
				QVector4D max = value.value<QVector4D>();
				static_cast<FloatSlider *>(widgets_.at(0))->SetMaximum(max.x());
				static_cast<FloatSlider *>(widgets_.at(1))->SetMaximum(max.y());
				static_cast<FloatSlider *>(widgets_.at(2))->SetMaximum(max.z());
				static_cast<FloatSlider *>(widgets_.at(3))->SetMaximum(max.w());
				break;
			}
			default:
				break;
			}
		} else if (key == QStringLiteral("offset")) {
			int tracks = NodeValue::get_number_of_keyframe_tracks(data_type);

			QVector<QVariant> offsets =
				NodeValue::split_normal_value_into_track_values(data_type,
																value);

			for (int i = 0; i < tracks; i++) {
				static_cast<NumericSliderBase *>(widgets_.at(i))
					->SetOffset(offsets.at(i));
			}

			UpdateWidgetValues();

		} else if (key.startsWith(QStringLiteral("color"))) {
			QColor c(value.toString());

			int tracks = NodeValue::get_number_of_keyframe_tracks(data_type);

			if (key.size() == 5) {
				// Set for all tracks
				for (int i = 0; i < tracks; i++) {
					static_cast<SliderBase *>(widgets_.at(i))->SetColor(c);
				}
			} else {
				bool ok;
				int element = key.mid(5).toInt(&ok);
				if (ok && element >= 0 && element < tracks) {
					static_cast<SliderBase *>(widgets_.at(element))->SetColor(c);
				}
			}

		} else if (key == QStringLiteral("base")) {
			double d = value.toDouble();
			for (int i = 0; i < widgets_.size(); i++) {
				static_cast<NumericSliderBase *>(widgets_.at(i))
					->SetDragMultiplier(d);
			}
		}
	}

	// ComboBox strings changing
	if (data_type == NodeValue::kCombo || data_type == NodeValue::kStrCombo) {
		if (key == QStringLiteral("combo_str")) {
			QComboBox *cb = static_cast<QComboBox *>(widgets_.first());

			int old_index = cb->currentIndex();

			// Block the combobox changed signals since we anticipate the index will be the same and not require a re-render
			cb->blockSignals(true);

			cb->clear();

			QStringList items = value.toStringList();
			QStringList values =
				GetInnerInput().GetProperty("combo_value_str").toStringList();
			const bool use_value_data =
				(data_type == NodeValue::kStrCombo) && !values.isEmpty();
			int index = 0;
			for (int i = 0; i < items.size(); ++i) {
				const QString &s = items.at(i);
				if (s.isEmpty()) {
					cb->insertSeparator(cb->count());
					cb->setItemData(cb->count() - 1, -1);
				} else {
					if (use_value_data && i < values.size()) {
						cb->addItem(s, values.at(i));
					} else {
						cb->addItem(s, index);
						index++;
					}
				}
			}

			cb->setCurrentIndex(old_index);

			cb->blockSignals(false);

			// In case the amount of items is LESS and the previous index cannot be set, NOW we trigger a re-cache since the
			// value has changed
			if (cb->currentIndex() != old_index) {
				WidgetCallback();
			}
		}
	}

	// Parameters for floats and vectors only
	if (data_type == NodeValue::kFloat ||
		NodeValue::type_is_vector(data_type)) {
		if (key == QStringLiteral("view")) {
			FloatSlider::DisplayType display_type =
				static_cast<FloatSlider::DisplayType>(value.toInt());

			foreach (QWidget *w, widgets_) {
				static_cast<FloatSlider *>(w)->SetDisplayType(display_type);
			}
		} else if (key == QStringLiteral("decimalplaces")) {
			int dec_places = value.toInt();

			foreach (QWidget *w, widgets_) {
				static_cast<FloatSlider *>(w)->SetDecimalPlaces(dec_places);
			}
		} else if (key == QStringLiteral("autotrim")) {
			bool autotrim = value.toBool();

			foreach (QWidget *w, widgets_) {
				static_cast<FloatSlider *>(w)->SetAutoTrimDecimalPlaces(
					autotrim);
			}
		}
	}

	if (data_type == NodeValue::kRational) {
		if (key == QStringLiteral("view")) {
			RationalSlider::DisplayType display_type =
				static_cast<RationalSlider::DisplayType>(value.toInt());

			foreach (QWidget *w, widgets_) {
				static_cast<RationalSlider *>(w)->SetDisplayType(display_type);
			}
		} else if (key == QStringLiteral("viewlock")) {
			bool locked = value.toBool();

			foreach (QWidget *w, widgets_) {
				static_cast<RationalSlider *>(w)->SetLockDisplayType(locked);
			}
		}
	}

	// Parameters for files
	if (data_type == NodeValue::kFile) {
		FileField *ff = static_cast<FileField *>(widgets_.first());

		if (key == QStringLiteral("placeholder")) {
			ff->SetPlaceholder(value.toString());
		} else if (key == QStringLiteral("directory")) {
			ff->SetDirectoryMode(value.toBool());
		} else if (key == QStringLiteral("filter")) {
			ff->SetNameFilter(value.toString());
		}
	}

	// Parameters for text
	if (data_type == NodeValue::kText) {
		NodeParamViewTextEdit *tex =
			static_cast<NodeParamViewTextEdit *>(widgets_.first());

		if (key == QStringLiteral("vieweronly")) {
			tex->SetEditInViewerOnlyMode(value.toBool());
		}
	}
}

void NodeParamViewWidgetBridge::InputDataTypeChanged(const QString &input,
													 NodeValue::Type type)
{
	if (sender() == GetOuterInput().node() &&
		input == GetOuterInput().input()) {
		// Delete all widgets
		qDeleteAll(widgets_);
		widgets_.clear();

		// Create new widgets
		CreateWidgets();

		// Signal that widgets are new
		emit WidgetsRecreated(GetOuterInput());
	}
}

void NodeParamViewWidgetBridge::PropertyChanged(const QString &input,
												const QString &key,
												const QVariant &value)
{
	bool found = false;

	for (auto it = input_hierarchy_.cbegin(); it != input_hierarchy_.cend();
		 it++) {
		if (it->input() == input) {
			found = true;
			break;
		}
	}

	if (found) {
		UpdateProperties();
	}
}

void NodeParamViewWidgetBridge::UpdateProperties()
{
	// Set properties from the last entry (the innermost input) to the first (the outermost)
	for (auto it = input_hierarchy_.crbegin(); it != input_hierarchy_.crend();
		 it++) {
		auto input_properties = it->node()->GetInputProperties(it->input());
		for (auto jt = input_properties.cbegin(); jt != input_properties.cend();
			 jt++) {
			SetProperty(jt.key(), jt.value());
		}
	}
}

bool NodeParamViewScrollBlocker::eventFilter(QObject *watched, QEvent *event)
{
	Q_UNUSED(watched)

	if (event->type() == QEvent::Wheel) {
		// Block this event
		return true;
	}

	return false;
}

}
