#include <cmath>
#include <wx/wx.h>
#include <wx/sizer.h>
#include <wx/slider.h>
#include <wx/stattext.h>
#include "numberinput.h"
#include "spinsliderd.h"

using namespace VRUT;

#ifdef USE_wxDEFINE_EVENT
// emitted when NumberInput changes value
wxDEFINE_EVENT(EVT_NUMBER_INPUT, wxCommandEvent);
#endif

////////////////////////////////////////////////////////////////////////////////
/// NumberInputPopup
////////////////////////////////////////////////////////////////////////////////

NumberInputPopup::NumberInputPopup(wxWindow *parent, const wxString &text)
	: wxPopupWindow(parent)
{
	panel = new wxPanel(this);
	panel->SetBackgroundColour(*wxLIGHT_GREY);
	topSizer = new wxBoxSizer(wxVERTICAL);

	staticText = new wxStaticText(panel, wxID_ANY, text);
	topSizer->Add(staticText, 0, wxALL, 5);
	panel->SetSizer(topSizer);
	topSizer->Fit(panel);
	topSizer->Fit(this);
}

void NumberInputPopup::SetText(const wxString &text)
{
	staticText->SetLabelText(text);
	topSizer->Fit(panel);
	topSizer->Fit(this);
}


////////////////////////////////////////////////////////////////////////////////
/// NumberInput
////////////////////////////////////////////////////////////////////////////////

NumberInput::NumberInput()
{
	InitEmpty();
}

NumberInput::NumberInput(wxWindow *parent, wxWindowID id, const wxString &title, NumberType minValue, NumberType maxValue, NumberType value, NumberType valueIncrement, long flags, const wxPoint& pos, const wxSize& size, long style, const wxString& name)
{
	InitEmpty();
	Create(parent, id, title, minValue, maxValue, value, valueIncrement, flags, pos, size, style, name);
}

bool NumberInput::Create(wxWindow *parent, wxWindowID id, const wxString &title, NumberType minValue, NumberType maxValue, NumberType value, NumberType valueIncrement, long flags, const wxPoint& pos, const wxSize& size, long style, const wxString& name)
{
	if (!wxWindow::Create(parent, id, pos, size, style, name))
	{
		return false;
	}

	Init(title, minValue, maxValue, value, valueIncrement, flags);

	return true;
}

NumberInput::NumberType NumberInput::GetValue() const
{
	return m_value;
}

wxString NumberInput::GetTextValue() const
{
	return ValueToText(m_value);
}

void NumberInput::SetValue(NumberType value)
{
	SetValueWithEvent(value, false);
}

void NumberInput::SetValue(const wxString &textValue)
{
	NumberType value; 
	if (TextToValue(textValue, value))
	{
		SetValueWithEvent(value, false);
	}
}

void NumberInput::SetRange(NumberType minValue, NumberType maxValue)
{
	m_minValue = minValue;
	m_maxValue = maxValue;

	// Fix the range
	if ((m_minValue != m_minValue) || (m_minValue < std::numeric_limits<NumberType>::lowest())) // Test for NaN and -INF
	{
		m_minValue = std::numeric_limits<NumberType>::lowest();
	}
	if ((m_maxValue != m_maxValue) || (m_maxValue > std::numeric_limits<NumberType>::max())) // Test for NaN and +INF
	{
		m_maxValue = std::numeric_limits<NumberType>::max();
	}
	if (m_minValue > m_maxValue) // Test for negative range
	{
		m_minValue = m_maxValue;
	}

	m_slider->SetRange(m_minValue, m_maxValue, 200);
	m_slider->SetPosition(m_value);

	SetValueWithEvent(m_value, false);
}

void NumberInput::SetIncrement(NumberType valueIncrement)
{
	m_valueIncrement = NewValueIncrement(valueIncrement, 0);
}

long NumberInput::GetFlags() const
{
	return m_flags;
}

void NumberInput::SetFlags(long flags)
{
	m_flags = flags;
	UpdateFlags();
}

void NumberInput::SetSliderWidth(int width)
{
	wxSize size(width, -1);
	m_slider->SetMinSize(size);
	m_sizer->SetSizeHints(this);
}

void NumberInput::InitEmpty()
{
	m_sizer = nullptr;
	m_staticText = nullptr;
	m_textCtrl = nullptr;
	m_slider = nullptr;
	m_popup = nullptr;
	m_minValue = 0.0;
	m_maxValue = 0.0;
	m_value = 0.0;
	m_valueIncrement = 0.0;
	m_dragStartValue = 0.0;
	m_dragStartX = 0;
	m_isDragging = false;
	m_flags = 0;
}

void NumberInput::Init(const wxString &title, NumberType minValue, NumberType maxValue, NumberType value, NumberType valueIncrement, long flags)
{
	m_dragStartValue = 0.0;
	m_dragStartX = 0;
	m_isDragging = false;

	m_flags = flags;

	m_staticText = new wxStaticText(this, wxID_ANY, title);

	m_textCtrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(80,-1), wxTE_PROCESS_ENTER, wxDefaultValidator, wxTextCtrlNameStr);

	m_slider = new SliderDouble(this, wxID_ANY, 0, minValue, maxValue, VRUT::SliderDouble::EE_SLIDER_LINEAR, 200, wxDefaultPosition/*, wxSize(200, -1)*/);
	
	m_sizer = new wxBoxSizer(wxHORIZONTAL);

	m_sizer->Add(m_staticText, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 5);
	m_sizer->Add(m_textCtrl, 0, wxALIGN_CENTER_VERTICAL, 5);
	m_sizer->Add(m_slider, 1, wxLEFT|wxALIGN_CENTER_VERTICAL, 5);

	m_staticText->Show(!title.IsEmpty());
	m_slider->Show(true);
	
	m_popup = new NumberInputPopup(m_textCtrl);

	SetRange(minValue, maxValue);
	SetIncrement(valueIncrement);
	SetValueWithEvent(value, false);
	UpdateGui();
	UpdateFlags();
	SetSizerAndFit(m_sizer);

	m_textCtrl->Bind(wxEVT_TEXT_ENTER, &NumberInput::OnTextChange, this);
	m_textCtrl->Bind(wxEVT_MOUSEWHEEL, &NumberInput::OnMouseWheel, this);
	m_textCtrl->Bind(wxEVT_MOTION, &NumberInput::OnMouseMove, this);
	m_textCtrl->Bind(wxEVT_MIDDLE_DOWN, &NumberInput::OnMouseButton, this);
	m_textCtrl->Bind(wxEVT_MIDDLE_UP, &NumberInput::OnMouseButton, this);
	m_textCtrl->Bind(wxEVT_MOUSE_CAPTURE_LOST, &NumberInput::OnMouseCaptureLost, this);
	m_textCtrl->Bind(wxEVT_KEY_UP, &NumberInput::OnKeyUp, this);
	m_textCtrl->Bind(wxEVT_KEY_DOWN, &NumberInput::OnKeyDown, this);
	m_textCtrl->Bind(wxEVT_KILL_FOCUS, &NumberInput::OnKillFocus, this);
	m_slider->Bind(wxEVT_SLIDER, &NumberInput::OnSlider, this);
}

void NumberInput::SetValueWithEvent(NumberType value, bool sendEvent)
{
	NumberType validValue = ValidateValue(value);
	bool hasChanged = false;
	if (m_value != validValue)
	{
		m_value = validValue;
		hasChanged = true;
	}

	if (hasChanged)
	{
		UpdateGui();

		if (sendEvent)
		{
#ifdef USE_wxDEFINE_EVENT
		wxCommandEvent updateEvent(EVT_NUMBER_INPUT, this->GetId());
		updateEvent.SetEventObject(this);
		this->ProcessEvent(updateEvent);
#endif
		}
	}
}

void NumberInput::UpdateValueFromTextCtrl()
{
	wxString textValue = m_textCtrl->GetValue();
	NumberType value;
	if (TextToValue(textValue, value))
	{
		SetValueWithEvent(value);
	}
	else
	{
		m_textCtrl->SetValue(ValueToText(m_value));
	}
}

void NumberInput::UpdateGui()
{
	wxString textValue = ValueToText(m_value);
	if (m_textCtrl->GetValue() != textValue)
	{
		m_textCtrl->SetValue(textValue);
	}

	if (m_slider->GetPosition() != m_value)
	{
		m_slider->SetPosition(m_value);
	}
}


void NumberInput::UpdateFlags()
{
	// Slider flags
	if (m_flags & FLAG_SLIDER_LINEAR)
	{
		m_slider->Show(true);
	}
	else
	{
		m_slider->Show(false);
	}

	// Size hints need to be updated if content has changed.
	m_sizer->SetSizeHints(this);
}

wxString NumberInput::ValueToText(NumberType value) const
{
	return wxString::Format(wxT("%g"), value);
}

bool NumberInput::TextToValue(const wxString &text, NumberType &value) const
{
	return text.ToCDouble(&value);
}

NumberInput::NumberType NumberInput::ValidateValue(NumberType value) const
{
	NumberType validValue = value;
	if (validValue != validValue) // Test for NaN
	{
		validValue = 0.0; // Use zero value instead of NaN
	}

	// Truncate for the set range
	if (validValue < m_minValue)
	{
		validValue = m_minValue;
	}
	if (validValue > m_maxValue)
	{
		validValue = m_maxValue;
	}

	return validValue;
}

NumberInput::NumberType NumberInput::NewValue(NumberType value, NumberType step) const
{
	NumberType newValue = value + step * m_valueIncrement;
	if (newValue < m_minValue) {
		newValue = m_minValue;
	} else if (newValue > m_maxValue) {
		newValue = m_maxValue;
	}

	return newValue;
}

NumberInput::NumberType NumberInput::NewValueIncrement(NumberType valueIncrement, int change) const
{
	// Value increment is always power of 10;
    double v = log10(valueIncrement);
	int exponent = (valueIncrement > 0) ? log10(valueIncrement) + change : 0;
	if (exponent < std::numeric_limits<NumberType>::min_exponent10)
	{
		exponent = std::numeric_limits<NumberType>::min_exponent10;
	}
	if (exponent > std::numeric_limits<NumberType>::max_exponent10)
	{
		exponent = std::numeric_limits<NumberType>::max_exponent10;
	}
	NumberType newValueIncrement = std::pow(10.0, exponent);

	return newValueIncrement;
}

void NumberInput::ShowIncrementPopup()
{
	ShowPopup(wxString::Format(wxT("Step: %g"), m_valueIncrement));
}

void NumberInput::ShowPopup(const wxString &text)
{
	wxPoint pos = m_textCtrl->ClientToScreen(wxPoint(0,0));
	wxSize sz = m_textCtrl->GetSize();
	pos.x += sz.x;

	m_popup->SetPosition(pos);
	m_popup->SetText(text);
	m_popup->Show();
}

void NumberInput::HidePopup()
{
	m_popup->Hide();
}

void NumberInput::OnTextChange(wxCommandEvent &evt)
{
	bool skip = true;

	if (evt.GetEventObject() == static_cast<wxObject*>(m_textCtrl))
	{
		UpdateValueFromTextCtrl();
	}

	if (skip)
	{
		evt.Skip();
	}
}

void NumberInput::OnMouseWheel(wxMouseEvent &evt)
{
	bool skip = true;

	if (evt.GetEventObject() == static_cast<wxObject*>(m_textCtrl))
	{
		if (!m_isDragging) // Ignore value and step change while dragging
		{
			int wheelRotation = evt.GetWheelRotation();
			if (evt.ShiftDown())
			{
				m_valueIncrement = NewValueIncrement(m_valueIncrement,  (wheelRotation > 0) ? 1 : -1);
				ShowIncrementPopup();
			}
			else
			{
				SetValueWithEvent(NewValue(m_value, (wheelRotation > 0) ? 1 : -1));
			}
		}
		
		skip = false;
	}

	if (skip)
	{
		evt.Skip();
	}
}

void NumberInput::OnMouseMove(wxMouseEvent &evt)
{
	bool skip = true;

	if (evt.GetEventObject() == static_cast<wxObject*>(m_textCtrl))
	{
		if (m_isDragging)
		{
			int distance = evt.GetPosition().x - m_dragStartX;
			NumberType step = (NumberType)distance / 10; // each 10 pixels is one step
			NumberType newValue = NewValue(m_dragStartValue, step);
			SetValueWithEvent(newValue);

			skip = false;
		}
	}

	if (skip)
	{
		evt.Skip();
	}
}

void NumberInput::OnMouseButton(wxMouseEvent &evt)
{
	bool skip = true;

	if (evt.GetEventObject() == static_cast<wxObject*>(m_textCtrl))
	{
		if (evt.MiddleDown())
		{
			// Start middle button drag
			m_textCtrl->CaptureMouse();
			m_textCtrl->SetFocus();
			m_isDragging = true;
			m_dragStartValue = m_value;
			m_dragStartX = evt.GetPosition().x;
		}
		else if (evt.MiddleUp())
		{
			if (m_isDragging)
			{
				if (m_textCtrl->HasCapture())
				{
					m_textCtrl->ReleaseMouse();
				}
				m_isDragging = false;
			}
		}
	}

	if (skip)
	{
		evt.Skip();
	}
}

void NumberInput::OnMouseCaptureLost(wxMouseCaptureLostEvent &evt)
{
	if (evt.GetEventObject() == static_cast<wxObject*>(m_textCtrl))
	{
		if (m_isDragging)
		{
			m_isDragging = false;
		}
	}
}

void NumberInput::OnKeyDown(wxKeyEvent &evt)
{
	bool skip = true;

	if (evt.GetEventObject() == static_cast<wxObject*>(m_textCtrl))
	{
		switch (evt.GetKeyCode())
		{
		case WXK_UP:
		case WXK_NUMPAD_UP:
			if (!m_isDragging) // Ignore value and step change while dragging
			{
				if (evt.ShiftDown())
				{
					m_valueIncrement = NewValueIncrement(m_valueIncrement, 1);
					ShowIncrementPopup();
				}
				else
				{
					SetValueWithEvent(NewValue(m_value, 1));
				}
			}
			skip = false;
			break;
		case WXK_DOWN:
		case WXK_NUMPAD_DOWN:
			if (!m_isDragging) // Ignore value and step change while dragging
			{
				if (evt.ShiftDown())
				{
					m_valueIncrement = NewValueIncrement(m_valueIncrement, -1);
					ShowIncrementPopup();
				}
				else
				{
					SetValueWithEvent(NewValue(m_value, -1));
				}
			}
			skip = false;
			break;
		}
	}

	if (skip)
	{
		evt.Skip();
	}
}

void NumberInput::OnKeyUp(wxKeyEvent &evt)
{
	bool skip = true;

	if (evt.GetEventObject() == static_cast<wxObject*>(m_textCtrl))
	{
		switch (evt.GetKeyCode())
		{
		case WXK_SHIFT:
			HidePopup();
			break;
		case WXK_UP:
		case WXK_NUMPAD_UP:
		case WXK_NUMPAD_DOWN:
		case WXK_DOWN:
			skip = false;
			break;
		}
	}

	if (skip)
	{
		evt.Skip();
	}
}

void NumberInput::OnKillFocus(wxFocusEvent &evt)
{
	if (evt.GetEventObject() == static_cast<wxObject*>(m_textCtrl))
	{
		HidePopup();
		UpdateValueFromTextCtrl();
	}

	evt.Skip();
}

void NumberInput::OnSlider(wxCommandEvent &evt)
{
	bool skip = true;

	if (evt.GetEventObject() == static_cast<wxObject*>(m_slider))
	{
		NumberType value = m_slider->GetPosition();
		SetValueWithEvent(value);
		skip = false;
	}

	if (skip)
	{
		evt.Skip();
	}
}

wxWindowList NumberInput::GetCompositeWindowParts() const
{
	wxWindowList list;
	list.Append(m_staticText);
	list.Append(m_textCtrl);
	list.Append(m_slider);

	return list;
}

wxIMPLEMENT_DYNAMIC_CLASS(NumberInput, wxCompositeWindow<wxWindow>);

////////////////////////////////////////////////////////////////////////////////
/// NumberInputXmlHandler
////////////////////////////////////////////////////////////////////////////////

NumberInputXmlHandler::NumberInputXmlHandler()
{
	AddWindowStyles();
}

wxObject *NumberInputXmlHandler::DoCreateResource()
{
	XRC_MAKE_INSTANCE(control, NumberInput);
	control->Create(GetParentAsWindow(), GetID(), wxEmptyString, 0.0, 0.0, 0.0, -1.0, 0, GetPosition(), GetSize(),GetStyle(), GetName());
	SetupWindow(control);

	return control;
}

bool NumberInputXmlHandler::CanHandle(wxXmlNode *node)
{
	return IsOfClass(node, wxT("NumberInput"));
}

wxIMPLEMENT_DYNAMIC_CLASS(NumberInputXmlHandler, wxXmlResourceHandler);
