#include "FractoriumPch.h"
#include "Fractorium.h"

/// <summary>
/// Initialize the xforms variations UI.
/// </summary>
void Fractorium::InitXformsVariationsUI()
{
	const auto tree = ui.VariationsTree;
	tree->clear();
	tree->header()->setSectionsClickable(true);
	connect(tree->header(),					SIGNAL(sectionClicked(int)),		 this, SLOT(OnTreeHeaderSectionClicked(int)));
	connect(ui.VariationsFilterLineEdit,	SIGNAL(textChanged(const QString&)), this, SLOT(OnVariationsFilterLineEditTextChanged(const QString&)));
	connect(ui.VariationsFilterClearButton, SIGNAL(clicked(bool)),				 this, SLOT(OnVariationsFilterClearButtonClicked(bool)));
	connect(ui.ActionVariationsDialog,		SIGNAL(triggered(bool)),			 this, SLOT(OnActionVariationsDialog(bool)), Qt::QueuedConnection);
	//Setting dimensions in the designer with a layout is futile, so must hard code here.
	tree->setColumnWidth(0, 170);
	tree->setColumnWidth(1, 80);
	tree->setColumnWidth(2, 20);
	//Set Default variation tree text and background colors for zero and non zero cases.
	m_VariationTreeColorNonZero = Qt::black;
	m_VariationTreeColorZero = Qt::black;
	m_VariationTreeBgColorNonZero = Qt::lightGray;
	m_VariationTreeBgColorZero = Qt::white;
}

/// <summary>
/// Show the variations filter dialog.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnActionVariationsDialog(bool checked)
{
	if (m_VarDialog->exec())
	{
		m_Controller->FilteredVariations();
		Filter();
	}
}

/// <summary>
/// Apply the text passed in, in conjuction with the selections from
/// the variations filter dialog to only show variations whose names
/// contain the substring and are selected.
/// Called when the user types in the variation filter text box and
/// when the variations dialog exits.
/// </summary>
/// <param name="text">The text to filter on</param>
template <typename T>
void FractoriumEmberController<T>::Filter(const QString& text)
{
	const auto& ids = m_Fractorium->m_VarDialog->Map();
	const auto tree = m_Fractorium->ui.VariationsTree;
	const auto xform = CurrentXform();
	tree->setUpdatesEnabled(false);

	for (int i = 0; i < tree->topLevelItemCount(); i++)
	{
		if (auto item = dynamic_cast<VariationTreeWidgetItem*>(tree->topLevelItem(i)))
		{
			auto varName = item->text(0);

			if (xform && xform->GetVariationById(item->Id()))//If it's present then show it no matter what the filter is.
			{
				item->setHidden(false);
			}
			else if (ids.contains(varName))//If the varation is the map of all variations, which is should always be, consider it as well as the filter text.
			{
				item->setHidden(!varName.contains(text, Qt::CaseInsensitive) || !ids[varName].toBool());
			}
			else//Wasn't present, which should never happen, so just consider filter text.
			{
				item->setHidden(!varName.contains(text, Qt::CaseInsensitive));
			}
		}
	}

	m_Fractorium->OnTreeHeaderSectionClicked(m_Fractorium->m_VarSortMode);//Must re-sort every time the filter changes.
	tree->setUpdatesEnabled(true);
}

void Fractorium::Filter()
{
	m_Controller->Filter(ui.VariationsFilterLineEdit->text());
}

template <typename T>
void FractoriumEmberController<T>::FilteredVariations()
{
	const auto& map = m_Fractorium->m_VarDialog->Map();
	m_FilteredVariations.clear();
	m_FilteredVariations.reserve(map.size());

	for (auto i = 0; i < m_VariationList->Size(); i++)
		if (const auto var = m_VariationList->GetVariation(i))
			if (map.contains(var->Name().c_str()) && map[var->Name().c_str()].toBool())
				m_FilteredVariations.push_back(var->VariationId());
}

/// <summary>
/// Dynamically populate the variation tree widget with VariationTreeWidgetItem and VariationTreeDoubleSpinBox
/// templated with the correct type.
/// This will clear any previous contents.
/// Called upon initialization, or controller type change.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::SetupVariationsTree()
{
	T fMin = TLOW;
	T fMax = TMAX;
	const QSize hint0(170, 16);
	const QSize hint1(80, 16);
	const QSize hint2(20, 16);
	static vector<string> dc{ "m_ColorX" };
	static vector<string> assign{ "outPoint->m_X =", "outPoint->m_Y =", "outPoint->m_Z =",
								  "outPoint->m_X=", "outPoint->m_Y=", "outPoint->m_Z=" };
	auto tree = m_Fractorium->ui.VariationsTree;
	tree->clear();
	tree->blockSignals(true);
	int iconSize_ = 20;

	for (size_t i = 0; i < m_VariationList->Size(); i++)
	{
		auto var = m_VariationList->GetVariation(i);
		auto parVar = dynamic_cast<const ParametricVariation<T>*>(var);
		//First add the variation, with a spinner for its weight.
		auto item = new VariationTreeWidgetItem(var->VariationId(), tree);
		auto spinBox = new VariationTreeDoubleSpinBox(tree, item, var->VariationId(), "");
		item->setText(0, QString::fromStdString(var->Name()));
		item->setSizeHint(0, hint0);
		item->setSizeHint(1, hint1);
		item->setSizeHint(2, hint2);
		QPixmap pixmap(iconSize_ * 3, iconSize_);
		auto mask = pixmap.createMaskFromColor(QColor("transparent"), Qt::MaskOutColor);
		pixmap.setMask(mask);
		QPainter paint(&pixmap);
		paint.fillRect(QRect(0, 0, iconSize_ * 3, iconSize_), QColor(0, 0, 0, 0));

		if (var->VarType() == eVariationType::VARTYPE_REG)
		{
			if (SearchVar(var, assign, false))
				paint.fillRect(QRect(0, 0, iconSize_, iconSize_), QColor(255, 0, 0));
		}
		else if (var->VarType() == eVariationType::VARTYPE_PRE || var->VarType() == eVariationType::VARTYPE_POST)
		{
			if (var->AssignType() == eVariationAssignType::ASSIGNTYPE_SUM)
				paint.fillRect(QRect(0, 0, iconSize_, iconSize_), QColor(255, 0, 0));
		}

		bool isDc = SearchVar(var, dc, false);

		if (isDc)
			paint.fillRect(QRect(iconSize_, 0, iconSize_, iconSize_), QColor(0, 255, 0));

		if (!var->StateOpenCLString().empty())
			paint.fillRect(QRect(iconSize_ * 2, 0, iconSize_, iconSize_), QColor(0, 0, 255));

		QIcon qi(pixmap);
		item->setIcon(2, qi);
		spinBox->setRange(fMin, fMax);
		spinBox->DoubleClick(true);
		spinBox->DoubleClickZero(1);
		spinBox->DoubleClickNonZero(0);
		spinBox->SmallStep(0.001);
		tree->setItemWidget(item, 1, spinBox);
		m_Fractorium->connect(spinBox, SIGNAL(valueChanged(double)), SLOT(OnVariationSpinBoxValueChanged(double)), Qt::QueuedConnection);

		//Check to see if the variation was parametric, and add a tree entry with a spinner for each parameter.
		if (parVar)
		{
			auto params = parVar->Params();

			for (size_t j = 0; j < parVar->ParamCount(); j++)
			{
				if (!params[j].IsPrecalc())
				{
					auto paramWidget = new VariationTreeWidgetItem(var->VariationId(), item);
					auto varSpinBox = new VariationTreeDoubleSpinBox(tree, paramWidget, parVar->VariationId(), params[j].Name());
					paramWidget->setText(0, params[j].Name().c_str());
					paramWidget->setSizeHint(0, hint0);
					paramWidget->setSizeHint(1, hint1);
					varSpinBox->setRange(params[j].Min(), params[j].Max());
					varSpinBox->setValue(params[j].ParamVal());
					varSpinBox->DoubleClick(true);
					varSpinBox->DoubleClickZero(1);
					varSpinBox->DoubleClickNonZero(0);

					if (params[j].Type() == eParamType::INTEGER || params[j].Type() == eParamType::INTEGER_NONZERO)
					{
						varSpinBox->setSingleStep(1);
						varSpinBox->Step(1);
						varSpinBox->SmallStep(1);
					}

					tree->setItemWidget(paramWidget, 1, varSpinBox);
					m_Fractorium->connect(varSpinBox, SIGNAL(valueChanged(double)), SLOT(OnVariationSpinBoxValueChanged(double)), Qt::QueuedConnection);
				}
			}
		}
	}

	Filter("");
	tree->blockSignals(false);
}

/// <summary>
/// Set every spinner in the variation tree, including params, to zero.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::ClearVariationsTree()
{
	const auto tree = m_Fractorium->ui.VariationsTree;

	for (int i = 0; i < tree->topLevelItemCount(); i++)
	{
		const auto item = tree->topLevelItem(i);

		if (auto spinBox = dynamic_cast<VariationTreeDoubleSpinBox*>(tree->itemWidget(item, 1)))
		{
			spinBox->SetValueStealth(0);

			for (int j = 0; j < item->childCount(); j++)//Iterate through all of the children, which will be the params.
			{
				if (const auto varSpinBox = dynamic_cast<VariationTreeDoubleSpinBox*>(tree->itemWidget(item->child(j), 1)))//Cast the child widget to the VariationTreeDoubleSpinBox type.
					varSpinBox->SetValueStealth(0);
			}
		}
	}
}

/// <summary>
/// Copy the value of a variation or param spinner to its corresponding value
/// in the selected xforms.
/// Called when any spinner in the variations tree is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The spinner value</param>
template <typename T>
void FractoriumEmberController<T>::VariationSpinBoxValueChanged(double d)//Would be awesome to make this work for all.//TODO
{
	bool update = false;
	const auto objSender = m_Fractorium->sender();
	const auto tree = m_Fractorium->ui.VariationsTree;
	const auto sender = dynamic_cast<VariationTreeDoubleSpinBox*>(objSender);

	if (sender)
	{
		UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
		{
			const auto var = m_VariationList->GetVariation(sender->GetVariationId());//The variation attached to the sender, for reference only.
			const auto parVar = dynamic_cast<const ParametricVariation<T>*>(var);//The parametric cast of that variation.
			const auto xformVar = xform->GetVariationById(var->VariationId());//The corresponding variation in the currently selected xform.
			const auto widgetItem = sender->WidgetItem();
			const auto isParam = parVar && sender->IsParam();

			if (isParam)
			{
				//Do not take action if the xform doesn't contain the variation which this param is part of.
				if (const auto xformParVar = dynamic_cast<ParametricVariation<T>*>(xformVar))//The parametric cast of the xform's variation.
					if (xformParVar->SetParamVal(sender->ParamName().c_str(), d))
						update = true;
			}
			else
			{
				//If they spun down to zero, and it wasn't a parameter item,
				//and the current xform contained the variation, then remove the variation.
				if (IsNearZero(d))
				{
					if (xformVar)
						xform->DeleteVariationById(var->VariationId());

					widgetItem->setTextColor(0, m_Fractorium->m_VariationTreeColorZero);
					widgetItem->setBackgroundColor(0, m_Fractorium->m_VariationTreeBgColorZero);
				}
				else
				{
					if (xformVar)//The xform already contained this variation, which means they just went from a non-zero weight to another non-zero weight (the simple case).
					{
						xformVar->m_Weight = d;
					}
					else
					{
						//If the item wasn't a param and the xform did not contain this variation,
						//it means they went from zero to a non-zero weight, so add a new copy of this xform.
						const auto newVar = var->Copy();//Create a new one with default values.
						newVar->m_Weight = d;
						xform->AddVariation(newVar);
						widgetItem->setTextColor(0, m_Fractorium->m_VariationTreeColorNonZero);
						widgetItem->setBackgroundColor(0, m_Fractorium->m_VariationTreeBgColorNonZero);

						//If they've added a new parametric variation, then grab the values currently in the spinners
						//for the child parameters and assign them to the newly added variation.
						if (parVar)
						{
							const auto newParVar = dynamic_cast<ParametricVariation<T>*>(newVar);

							for (int i = 0; i < widgetItem->childCount(); i++)//Iterate through all of the children, which will be the params.
							{
								const auto childItem = widgetItem->child(i);//Get the child.
								const auto itemWidget = tree->itemWidget(childItem, 1);//Get the widget for the child.

								if (const auto spinBox = dynamic_cast<VariationTreeDoubleSpinBox*>(itemWidget))//Cast the widget to the VariationTreeDoubleSpinBox type.
								{
									string s = childItem->text(0).toStdString();//Use the name of the child, and the value of the spinner widget to assign the param.
									newParVar->SetParamVal(s.c_str(), spinBox->value());
								}
							}
						}
					}
				}

				update = true;
			}
		}, eXformUpdate::UPDATE_CURRENT_AND_SELECTED, false);

		if (update)
			UpdateRender();
	}
}

void Fractorium::OnVariationSpinBoxValueChanged(double d) { m_Controller->VariationSpinBoxValueChanged(d); }

/// <summary>
/// Fill in the variations tree with the values from the current xform.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::FillVariationTreeWithCurrentXform()
{
	FillVariationTreeWithXform(CurrentXform());
}

/// <summary>
/// Fill the variation tree values from passed in xform and apply the current sorting mode.
/// Called when the currently selected xform changes.
/// </summary>
/// <param name="xform">The xform whose variation values will be used to fill the tree</param>
template <typename T>
void FractoriumEmberController<T>::FillVariationTreeWithXform(Xform<T>* xform)
{
	const auto tree = m_Fractorium->ui.VariationsTree;
	tree->blockSignals(true);
	m_Fractorium->Filter();

	for (int i = 0; i < tree->topLevelItemCount(); i++)
	{
		const auto item = dynamic_cast<VariationTreeWidgetItem*>(tree->topLevelItem(i));
		const auto var = xform->GetVariationById(item->Id());//See if this variation in the tree was contained in the xform.
		const auto parVar = dynamic_cast<ParametricVariation<T>*>(var);//Attempt cast to parametric variation for later.
		const auto origParVar = dynamic_cast<const ParametricVariation<T>*>(m_VariationList->GetVariation(item->Id()));

		if (const auto spinBox = dynamic_cast<VariationTreeDoubleSpinBox*>(tree->itemWidget(item, 1)))//Get the widget for the item, and cast the widget to the VariationTreeDoubleSpinBox type.
		{
			if (var)//Ensure it's visible, even if it's supposed to be filtered.
				item->setHidden(false);

			spinBox->SetValueStealth(var ? var->m_Weight : 0);//If the variation was present, set the spin box to its weight, else zero.
			item->setTextColor(0, var ? m_Fractorium->m_VariationTreeColorNonZero :  m_Fractorium->m_VariationTreeColorZero);
			item->setBackgroundColor(0, var ? m_Fractorium->m_VariationTreeBgColorNonZero :  m_Fractorium->m_VariationTreeBgColorZero);

			for (int j = 0; j < item->childCount(); j++)//Iterate through all of the children, which will be the params if it was a parametric variation.
			{
				T* param = nullptr;
				const auto childItem = item->child(j);//Get the child.
				const auto childItemWidget = tree->itemWidget(childItem, 1);//Get the widget for the child.

				if (const auto childSpinBox = dynamic_cast<VariationTreeDoubleSpinBox*>(childItemWidget))//Cast the widget to the VariationTreeDoubleSpinBox type.
				{
					string s = childItem->text(0).toStdString();//Get the name of the child.

					if (parVar)
					{
						if ((param = parVar->GetParam(s.c_str())))//Retrieve pointer to the param.
							childSpinBox->SetValueStealth(*param);
					}
					else if (origParVar)//Parametric variation was not present in this xform, so set child values to defaults.
					{
						if ((param = origParVar->GetParam(s.c_str())))
							childSpinBox->SetValueStealth(*param);
						else
							childSpinBox->SetValueStealth(0);//Will most likely never happen, but just to be safe.
					}
				}
			}
		}
	}

	tree->blockSignals(false);
	m_Fractorium->OnTreeHeaderSectionClicked(m_Fractorium->m_VarSortMode);
}

/// <summary>
/// Change the sorting to be either by variation ID, or by weight.
/// If sorting by variation ID, repeated clicks will alternate ascending or descending.
/// Called when user clicks the tree headers.
/// </summary>
/// <param name="logicalIndex">Column index of the header clicked. Sort by name if 0, sort by weight if 1.</param>
void Fractorium::OnTreeHeaderSectionClicked(int logicalIndex)
{
	if (logicalIndex <= 1)
	{
		m_VarSortMode = logicalIndex;
		ui.VariationsTree->sortItems(m_VarSortMode, m_VarSortMode == 0 ? Qt::AscendingOrder : Qt::DescendingOrder);

		if (m_VarSortMode == 1)
			ui.VariationsTree->scrollToTop();
	}
}

/// <summary>
/// Apply the text in the variation filter text box to only show variations whose names
/// contain the substring.
/// Called when the user types in the variation filter text box.
/// </summary>
/// <param name="text">The text to filter on</param>
void Fractorium::OnVariationsFilterLineEditTextChanged(const QString& text)
{
	Filter();
}

/// <summary>
/// Clear the variation name filter, which will display all variations.
/// Called when clear variations filter button is clicked.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnVariationsFilterClearButtonClicked(bool checked)
{
	ui.VariationsFilterLineEdit->clear();
}

template class FractoriumEmberController<float>;

#ifdef DO_DOUBLE
	template class FractoriumEmberController<double>;
#endif
