
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/spinctrl.h>
#include "spinsliderd.h"
//#include "kernel.h"


using namespace VRUT;

#ifdef USE_wxDEFINE_EVENT
// emitted when SpinSlider changes value
wxDEFINE_EVENT(wxEVT_COMMAND_SPINSLIDER_UPDATED, wxCommandEvent);
#endif

// Register with wxWidgets' dynamic class subsystem.
IMPLEMENT_DYNAMIC_CLASS(SpinCtrlDoubleXmlHandler, wxXmlResourceHandler)

SliderDouble::SliderDouble(wxWindow *parent, wxWindowID id, double value, double minValue, double maxValue, SliderMode mode, unsigned int resolution, const wxPoint &pos, const wxSize &size, long style, const wxValidator &validator, const wxString &name)
	: wxSlider(parent, id, 0, 0, 100, pos, size, style, validator, name), m_minValue(0.0), m_maxValue(1.0), m_resolution(100), m_mode(mode)
{
	if ( SetRange(minValue, maxValue, resolution) )
		SetPosition(value);
}

bool SliderDouble::SetRange(double low, double high, unsigned int resolution)
{
	if ( low > high )
		return false;

	m_minValue = low;
	m_maxValue = high;
	m_resolution = resolution;

	if (m_mode == EE_SLIDER_SCALE)
	{
		if (m_minValue > -2.0)
			m_minValue = -2.0;
		if (m_maxValue < 2.0)
			m_maxValue = 2.0;
	}

	wxSlider::SetRange(0, (int)m_resolution);

	return true;
}

bool SliderDouble::SetPosition(double position)
{
	if (position < m_minValue || position > m_maxValue)
		return false;

	int sliderMinValue = wxSlider::GetMin();
	int sliderMaxValue = wxSlider::GetMax();
	double linearPosition;

	switch (m_mode)
	{
		case EE_SLIDER_LOGARITHMIC:
			{
				double logLow = log10(m_minValue);
				double logHigh = log10(m_maxValue);

				double logPosition = log10(position);
				double logOffset = logPosition - logLow;
				linearPosition = logOffset*(sliderMaxValue - sliderMinValue) / (logHigh - logLow);
			}
			break;
		case EE_SLIDER_SCALE:
			{
				// position > 0 => sqrt(abs(value-1))
				// position > 0 => sqrt(abs(value+1))
				double sqrtLow = sqrt(std::abs(m_minValue+1.0));
				double sqrtLow1 = sqrt(std::abs(0.0+1.0));				// = 1
				double sqrtHigh1 = sqrt(std::abs(0.0-1.0));				// = 1
				double sqrtHigh = sqrt(std::abs(m_maxValue-1.0));

				double offset;

				if (position < 0.0)
				{
					offset = sqrt(std::abs(position + 1.0));
					if (position > -1.0) // -1 < position < 0
					{
						offset += sqrtLow;
					}
					else // position <= -1
					{
						offset = sqrtLow - offset;
					}
				}
				else
				{
					offset = sqrt(std::abs(position - 1.0));
					if (position < 1.0) // 0 <= position < 1  
					{
						offset = sqrtHigh1 - offset;
					}
					else // 1 <= position
					{
						offset += sqrtHigh1;
					}
					offset += sqrtLow + sqrtLow1;
				}

				linearPosition = offset*(sliderMaxValue - sliderMinValue) / (sqrtLow + sqrtLow1 + sqrtHigh1 + sqrtHigh);
			}
			break;
		case EE_SLIDER_LINEAR:
		default:
			{
				double offset = position - m_minValue;
				linearPosition = offset*(sliderMaxValue - sliderMinValue) / (m_maxValue - m_minValue);
			}
			break;
	}

	int newPosition = (int)(linearPosition + 0.5);

	if (newPosition < 0)
		newPosition = 0; // roundoff issues may create this

	wxSlider::SetValue(newPosition);

	return true;
}

double SliderDouble::GetPosition() const
{
	int sliderMinValue = wxSlider::GetMin();
	int sliderMaxValue = wxSlider::GetMax();
	int linearPosition = wxSlider::GetValue();
	double result;

	switch (m_mode)
	{
		case EE_SLIDER_LOGARITHMIC:
			{
				double logLow = log10(m_minValue);
				double logHigh = log10(m_maxValue);

				double position = linearPosition * (logHigh - logLow) / (sliderMaxValue - sliderMinValue);

				double adjustedPosition = logLow + position;
				result = pow(10.0, adjustedPosition);
			}
			break;
		case EE_SLIDER_SCALE:
			{
				double sqrtLow = sqrt(std::abs(m_minValue+1.0));
				double sqrtLow1 = sqrt(std::abs(0.0+1.0));				// = 1
				double sqrtHigh1 = sqrt(std::abs(0.0-1.0));				// = 1
				double sqrtHigh = sqrt(std::abs(m_maxValue-1.0));

				double position = linearPosition * (sqrtLow + sqrtLow1 + sqrtHigh1 + sqrtHigh) / (sliderMaxValue - sliderMinValue);

				if (position < sqrtLow)
				{
					position = sqrtLow - position;
					result = -position*position - 1.0;
				}
				else if (position < sqrtLow+sqrtLow1)
				{
					position -= sqrtLow;
					result = position*position - 1.0;
				}
				else if (position < sqrtLow+sqrtLow1+sqrtHigh1)
				{
					position = sqrtHigh1 - (position - sqrtLow - sqrtLow1);
					result = -position*position + 1.0;
				}
				else
				{
					position = position - sqrtLow - sqrtLow1 - sqrtHigh1;
					result = position*position + 1.0;
				}
			}
			break;
		case EE_SLIDER_LINEAR:
		default:
			{
				double position = linearPosition * (m_maxValue - m_minValue) / (sliderMaxValue - sliderMinValue);
				result = m_minValue + position;
			}
			break;
	}

	return result;
}

SpinSliderDouble::SpinSliderDouble(wxWindow *parent, wxWindowID id, const wxString &title, double value, double minValue, double maxValue, SliderDouble::SliderMode mode, double valueIncrement)
	: wxWindow(parent, id), m_value(value), m_minValue(minValue), m_maxValue(maxValue), m_valueInc(valueIncrement), m_mode(mode)
{
	m_spinCtrl = new SpinCtrlDouble(this, wxID_ANY, wxString::Format(wxT("%g"), value), wxDefaultPosition, wxSize(80,-1), wxSP_ARROW_KEYS|wxTE_PROCESS_ENTER|wxTE_RIGHT/*4608L*//*16896L*/, m_minValue, m_maxValue, m_value, m_valueInc);
	m_slider = new SliderDouble(this, wxID_ANY, m_value, m_minValue, m_maxValue, m_mode, 200, wxDefaultPosition, wxSize(200,-1));

	//m_spinCtrl->SetDigits(5);
	SetValue(value);

	wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);

	sizer->Add(new wxStaticText(this, wxID_ANY, title), 0, wxALL|wxALIGN_CENTER_VERTICAL, 5);
	sizer->Add(m_spinCtrl, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5);
	sizer->Add(m_slider, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5);

	SetSizerAndFit(sizer);

	m_spinCtrl->Connect(wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED, wxSpinDoubleEventHandler(SpinSliderDouble::OnSpinUpdated), NULL, this);
	m_spinCtrl->Connect(wxID_ANY, wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler(SpinSliderDouble::OnSpinTextEntered), NULL, this);
	// m_spinCtrl->Connect(wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(SpinSliderDouble::OnSpin), NULL, this);
	m_slider->Connect(wxID_ANY, wxEVT_COMMAND_SLIDER_UPDATED, wxCommandEventHandler(SpinSliderDouble::OnSlider), NULL, this);
}

void SpinSliderDouble::SetValue(double value)
{
	m_value = value;
	m_textValue = wxString::Format(wxT("%g"), m_value);

	m_spinCtrl->SetValue(m_value);
	m_slider->SetPosition(m_value);
}

void SpinSliderDouble::OnSpinUpdated(wxSpinDoubleEvent &evt)
{
	m_value = m_spinCtrl->GetValue();
	m_textValue = wxString::Format(wxT("%g"), m_value);
	m_slider->SetPosition(m_value);

#ifdef USE_wxDEFINE_EVENT
	wxCommandEvent updateEvent(wxEVT_COMMAND_SPINSLIDER_UPDATED, this->GetId());
	updateEvent.SetEventObject(this);
	this->ProcessEvent(updateEvent);
#endif
}

void SpinSliderDouble::OnSpinTextEntered(wxCommandEvent &evt)
{
	double newValue;

	if ( !evt.GetString().ToCDouble(&newValue) )
		return;

	m_value = newValue;
	m_textValue = evt.GetString();

	m_slider->SetPosition(m_value);

#ifdef USE_wxDEFINE_EVENT
	wxCommandEvent updateEvent(wxEVT_COMMAND_SPINSLIDER_UPDATED, this->GetId());
	updateEvent.SetEventObject(this);
	this->ProcessEvent(updateEvent);
#endif
}

void SpinSliderDouble::OnSlider(wxCommandEvent &evt)
{
	m_value = m_slider->GetPosition();
	m_textValue = wxString::Format(wxT("%g"), m_value);
	m_spinCtrl->SetValue(m_value);

#ifdef USE_wxDEFINE_EVENT
	wxCommandEvent updateEvent(wxEVT_COMMAND_SPINSLIDER_UPDATED, this->GetId());
	updateEvent.SetEventObject(this);
	this->ProcessEvent(updateEvent);
#endif
}

void SpinSlider::OnSpin(wxCommandEvent &evt)
{
	value = spin->GetValue();
	textValue = evt.GetString();
	slider->SetValue(value);

	wxCommandEvent new_evt(wxEVT_COMMAND_SLIDER_UPDATED, this->GetId());
	new_evt.SetEventObject(this);
	this->ProcessEvent(new_evt);
}

void SpinSlider::OnSlider(wxCommandEvent &evt)
{
	value = slider->GetValue();
	textValue = wxString::Format(wxT("%i"), value);
	spin->SetValue(value);

	wxCommandEvent new_evt(wxEVT_COMMAND_SLIDER_UPDATED, this->GetId());
	new_evt.SetEventObject(this);
	this->ProcessEvent(new_evt);
}
