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

/// <summary>
/// Initialize the xforms color UI.
/// </summary>
void Fractorium::InitXformsColorUI()
{
	int spinHeight = 20, row = 0;
	m_XformColorValueItem = new QTableWidgetItem();
	//Can't set this in the designer, so do it here.
	m_XformColorValueItem->setToolTip("The index in the palette the current xform uses.\r\n\r\n"
									  "This value can be changed by scrolling the mouse wheel in the box displaying the value or by dragging the scroll bar.");
	ui.XformColorIndexTable->setItem(0, 0, m_XformColorValueItem);
	m_PaletteRefItem = new QTableWidgetItem();
	ui.XformPaletteRefTable->setItem(0, 0, m_PaletteRefItem);
	ui.XformPaletteRefTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
	connect(ui.XformPaletteRefTable->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), this, SLOT(OnXformRefPaletteResized(int, int, int)), Qt::QueuedConnection);
	connect(ui.RandomColorIndicesButton, SIGNAL(clicked(bool)),  this, SLOT(OnRandomColorIndicesButtonClicked(bool)), Qt::QueuedConnection);
	connect(ui.ToggleColorIndicesButton, SIGNAL(clicked(bool)),  this, SLOT(OnToggleColorIndicesButtonClicked(bool)), Qt::QueuedConnection);
	connect(ui.RandomColorSpeedButton,   SIGNAL(clicked(bool)),  this, SLOT(OnRandomColorSpeedButtonClicked(bool)),   Qt::QueuedConnection);
	connect(ui.ToggleColorSpeedsButton,   SIGNAL(clicked(bool)), this, SLOT(OnToggleColorSpeedsButtonClicked(bool)),  Qt::QueuedConnection);
	SetupSpinner<DoubleSpinBox, double>(ui.XformColorIndexTable,  this, row, 1, m_XformColorIndexSpin,  spinHeight,  0, 1, 0.01, SIGNAL(valueChanged(double)), SLOT(OnXformColorIndexChanged(double)),  false,   0,   1, 0);
	SetupSpinner<DoubleSpinBox, double>(ui.XformColorValuesTable, this, row, 1, m_XformColorSpeedSpin,  spinHeight, -1, 1,  0.1, SIGNAL(valueChanged(double)), SLOT(OnXformColorSpeedChanged(double)),   true, 0.5, 0.5, 0);
	SetupSpinner<DoubleSpinBox, double>(ui.XformColorValuesTable, this, row, 1, m_XformOpacitySpin,	    spinHeight,  0, 1,  0.1, SIGNAL(valueChanged(double)), SLOT(OnXformOpacityChanged(double)),	     true,   1,   1, 0);
	SetupSpinner<DoubleSpinBox, double>(ui.XformColorValuesTable, this, row, 1, m_XformDirectColorSpin, spinHeight,  0, 1,  0.1, SIGNAL(valueChanged(double)), SLOT(OnXformDirectColorChanged(double)),	 true,	 1,   1, 0);
	m_XformColorIndexSpin->setDecimals(3);
	m_XformColorSpeedSpin->setDecimals(3);
	m_XformOpacitySpin->setDecimals(3);
	m_XformDirectColorSpin->setDecimals(3);
	connect(ui.XformColorScroll,  SIGNAL(valueChanged(int)), this, SLOT(OnXformScrollColorIndexChanged(int)),  Qt::QueuedConnection);
	connect(ui.SoloXformCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSoloXformCheckBoxStateChanged(int)), Qt::QueuedConnection);
}

/// <summary>
/// Set the color index of the selected xforms.
/// Update the color index scrollbar to match.
/// Called when spinner in the color index cell in the palette ref table is changed.
/// Optionally resets the rendering process.
/// </summary>
/// <param name="d">The color index, 0-1/</param>
/// <param name="updateRender">True to reset the rendering process, else don't.</param>
/// <param name="updateSpinner">True to update the color index spinner with the new value, else don't.</param>
/// <param name="updateScroll">True to update the color index scroll bar with the new value, else don't.</param>
/// <param name="update">The type of updating to do, default: eXformUpdate::UPDATE_SELECTED.</param>
/// <param name="index">The index of the xform to update. Ignored unless update is eXformUpdate::UPDATE_SPECIFIC. Default: 0.</param>
template <typename T>
void FractoriumEmberController<T>::XformColorIndexChanged(double d, bool updateRender, bool updateSpinner, bool updateScroll, eXformUpdate update, size_t index)
{
	bool updateGUI = update != eXformUpdate::UPDATE_SPECIFIC || index == m_Fractorium->ui.CurrentXformCombo->currentIndex();

	if (updateRender)//False when just updating GUI in response to a change elsewhere, true when in response to a GUI change so update values and reset renderer.
	{
		UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex)
		{
			xform->m_ColorX = Clamp<T>(d, 0, 1);
		}, update, updateRender, eProcessAction::FULL_RENDER, index);
	}

	//Only do this is coming from elsewhere, like the palette editor. Otherwise, normal events will handle updating the spinner.
	if (updateSpinner && updateGUI)
	{
		m_Fractorium->m_XformColorIndexSpin->SetValueStealth(CurrentXform()->m_ColorX);
	}

	if (updateScroll && updateGUI)
	{
		auto scroll = m_Fractorium->ui.XformColorScroll;
		int scrollVal = d * scroll->maximum();
		scroll->blockSignals(true);
		scroll->setValue(scrollVal);
		scroll->blockSignals(false);
	}

	if (updateGUI)
		m_Fractorium->ui.XformColorIndexTable->item(0, 0)->setBackgroundColor(ColorIndexToQColor(d));//Grab the current color from the index and assign it to the first cell of the first table.
}

void Fractorium::OnXformColorIndexChanged(double d) { OnXformColorIndexChanged(d, true, false, true, eXformUpdate::UPDATE_SELECTED, std::numeric_limits<size_t>::max()); }
void Fractorium::OnXformColorIndexChanged(double d, bool updateRender, bool updateSpinner, bool updateScroll, eXformUpdate update, size_t index) { m_Controller->XformColorIndexChanged(d, updateRender, updateSpinner, updateScroll, update, index); }

/// <summary>
/// Set the color index of the current xform.
/// Will trigger an update which will cause the color index cell in the palette ref table to match.
/// Called when color index scrollbar is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The color index, 0-1.</param>
void Fractorium::OnXformScrollColorIndexChanged(int d)
{
	OnXformColorIndexChanged(d / double(ui.XformColorScroll->maximum()), true, true, false);//Update spinner, but not scrollbar. Trigger render update.
}

/// <summary>
/// Set all xform color indices to a random value between 0 and 1, inclusive.
/// Called when the Random Indices button is clicked.
/// Resets the rendering process.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::RandomColorIndicesButtonClicked()
{
	UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex) { xform->m_ColorX = m_Rand.Frand01<T>(); }, eXformUpdate::UPDATE_ALL, false);//Don't update renderer here...
	m_Fractorium->m_XformColorIndexSpin->setValue(CurrentXform()->m_ColorX);//...do it via GUI. This will set scrollbar value as well.
}
void Fractorium::OnRandomColorIndicesButtonClicked(bool b) { m_Controller->RandomColorIndicesButtonClicked(); }

/// <summary>
/// Resets the rendering process.
/// Set all xform color indices to either 0 and 1, sequentially toggling.
/// Called when the Toggle Indices button is clicked.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::ToggleColorIndicesButtonClicked()
{
	char ch = 1;
	UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex) { xform->m_ColorX = T(ch ^= 1); }, eXformUpdate::UPDATE_ALL, false);//Don't update renderer here...
	m_Fractorium->m_XformColorIndexSpin->setValue(CurrentXform()->m_ColorX);//...do it via GUI. This will set scrollbar value as well.
}
void Fractorium::OnToggleColorIndicesButtonClicked(bool b) { m_Controller->ToggleColorIndicesButtonClicked(); }

/// <summary>
/// Set all xform color speeds to a random value between 0 and 1, inclusive.
/// Called when the Random Color Speed button is clicked.
/// Resets the rendering process.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::RandomColorSpeedButtonClicked()
{
	UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex) { xform->m_ColorSpeed = m_Rand.Frand01<T>(); }, eXformUpdate::UPDATE_ALL);
	m_Fractorium->m_XformColorSpeedSpin->SetValueStealth(CurrentXform()->m_ColorSpeed);
}
void Fractorium::OnRandomColorSpeedButtonClicked(bool b) { m_Controller->RandomColorSpeedButtonClicked(); }

/// <summary>
/// Set all xform color speeds to either 0 and 0.5, sequentially toggling.
/// Called when the Toggle Color Speed button is clicked.
/// Resets the rendering process.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::ToggleColorSpeedsButtonClicked()
{
	char ch = 1;
	UpdateXform([&](Xform<T>* xform, size_t xfindex, size_t selIndex) { xform->m_ColorSpeed = (T(ch ^= 1) ? 0.5 : 0.0); }, eXformUpdate::UPDATE_ALL);
	m_Fractorium->m_XformColorSpeedSpin->SetValueStealth(CurrentXform()->m_ColorSpeed);
}
void Fractorium::OnToggleColorSpeedsButtonClicked(bool b) { m_Controller->ToggleColorSpeedsButtonClicked(); }

/// <summary>
/// Set the color speed of the selected xforms.
/// Called when xform color speed spinner is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The color speed, -1-1.</param>
template <typename T>
void FractoriumEmberController<T>::XformColorSpeedChanged(double d) { UpdateXform([&] (Xform<T>* xform, size_t xfindex, size_t selIndex) { xform->m_ColorSpeed = d; }, eXformUpdate::UPDATE_SELECTED); }
void Fractorium::OnXformColorSpeedChanged(double d) { m_Controller->XformColorSpeedChanged(d); }

/// <summary>
/// Set the opacity of the selected xforms.
/// Called when xform opacity spinner is changed.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The opacity, 0-1.</param>
template <typename T>
void FractoriumEmberController<T>::XformOpacityChanged(double d) { UpdateXform([&] (Xform<T>* xform, size_t xfindex, size_t selIndex) { xform->m_Opacity = d; }, eXformUpdate::UPDATE_SELECTED); }
void Fractorium::OnXformOpacityChanged(double d) { m_Controller->XformOpacityChanged(d); }

/// <summary>
/// Set the direct color percentage of the selected xforms.
/// Called when xform direct color spinner is changed.
/// Note this only affects xforms that include a dc_ variation.
/// Resets the rendering process.
/// </summary>
/// <param name="d">The direct color percentage, 0-1.</param>
template <typename T>
void FractoriumEmberController<T>::XformDirectColorChanged(double d) { UpdateXform([&] (Xform<T>* xform, size_t xfindex, size_t selIndex) { xform->m_DirectColor = d; }, eXformUpdate::UPDATE_SELECTED); }
void Fractorium::OnXformDirectColorChanged(double d) { m_Controller->XformDirectColorChanged(d); }

/// <summary>
/// Set whether the current xform should be rendered solo.
/// If checked, current is solo, if unchecked, none are solo.
/// Solo means that all other xforms will have their opacity temporarily
/// set to zero while rendering so that only the effect of current xform is visible.
/// This will not permanently alter the opacities of ember, as the temporary opacity values will be applied
/// right before rendering and reset right after.
/// Called when solo xform check box is checked.
/// Resets the rendering process.
/// </summary>
/// <param name="state">The state of the checkbox</param>
/// <param name="index">The index which has been specified as the solo xform, -1 to specify none.</param>
template <typename T>
void FractoriumEmberController<T>::SoloXformCheckBoxStateChanged(int state, int index)
{
	if (state == Qt::Checked)
	{
		m_Ember.m_Solo = index;
		m_Fractorium->ui.SoloXformCheckBox->setText("Solo (" + ToString(index + 1) + ")");
	}
	else if (state == Qt::Unchecked)
	{
		m_Ember.m_Solo = -1;
		m_Fractorium->ui.SoloXformCheckBox->setText("Solo");
	}

	UpdateRender();
}

void Fractorium::OnSoloXformCheckBoxStateChanged(int state) { m_Controller->SoloXformCheckBoxStateChanged(state, ui.CurrentXformCombo->currentIndex()); }

/// <summary>
/// Redraw the palette ref table.
/// Called on resize.
/// </summary>
/// <param name="logicalIndex">Ignored</param>
/// <param name="oldSize">Ignored</param>
/// <param name="newSize">Ignored</param>
void Fractorium::OnXformRefPaletteResized(int logicalIndex, int oldSize, int newSize)
{
	QPixmap pixmap(QPixmap::fromImage(m_Controller->FinalPaletteImage()));
	SetPaletteTableItem(&pixmap, ui.XformPaletteRefTable, m_PaletteRefItem, 0, 0);
}

/// <summary>
/// Look up the passed in index in the current ember's palette
/// and return the QColor equivalent.
/// </summary>
/// <param name="d">The palette index to look up, 0-1.</param>
/// <returns>The palette color at the given index as a QColor</returns>
template <typename T>
QColor FractoriumEmberController<T>::ColorIndexToQColor(double d)
{
	v4F entry = m_Ember.m_Palette[Clamp<size_t>(d * (m_Ember.m_Palette.Size() - 1), 0, m_Ember.m_Palette.Size())];
	entry.r *= 255;
	entry.g *= 255;
	entry.b *= 255;
	QRgb rgb = uint(entry.r) << 16 | uint(entry.g) << 8 | uint(entry.b);
	return QColor::fromRgb(rgb);
}

/// <summary>
/// Set the color index, speed and opacity spinners with the values of the passed in xform.
/// Set the cells of the palette ref table as well.
/// </summary>
/// <param name="xform">The xform whose values will be copied to the GUI</param>
template <typename T>
void FractoriumEmberController<T>::FillColorWithXform(Xform<T>* xform)
{
	m_Fractorium->m_XformColorIndexSpin->SetValueStealth(xform->m_ColorX);//Probably ought to put scroll bar update here too.
	m_Fractorium->m_XformColorSpeedSpin->SetValueStealth(xform->m_ColorSpeed);
	m_Fractorium->m_XformOpacitySpin->SetValueStealth(xform->m_Opacity);
	m_Fractorium->m_XformDirectColorSpin->SetValueStealth(xform->m_DirectColor);
	m_Fractorium->OnXformColorIndexChanged(xform->m_ColorX, false, false, true);//Had to call stealth before to avoid doing an update, now manually update related controls, still without doing an update.
}

/// <summary>
/// Set the cell at the row and column in the passed in table to the passed in pixmap.
/// </summary>
/// <param name="pixmap">The pixmap to assign</param>
/// <param name="table">The table whose cell will be filled with the image</param>
/// <param name="item">The QTableWidgetItem in the cell</param>
/// <param name="row">The row of the cell</param>
/// <param name="col">The column of the cell</param>
void Fractorium::SetPaletteTableItem(QPixmap* pixmap, QTableWidget* table, QTableWidgetItem* item, int row, int col)
{
	if (pixmap && !pixmap->isNull())
	{
		QSize size(table->columnWidth(col), table->rowHeight(row) + 1);
		item->setData(Qt::DecorationRole, pixmap->scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
	}
}

template class FractoriumEmberController<float>;

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