#ifndef __SPIN_SLIDER_DOUBLE_H__
#define __SPIN_SLIDER_DOUBLE_H__

#include <wx/window.h>
#include <wx/windowid.h>
#include <wx/slider.h>
#include <wx/spinctrl.h>
#include <wx/xrc/xmlres.h>
#include "kernel.h"


namespace VRUT
{

	class VRUT_CORE_API SpinCtrlDouble : public wxSpinCtrlDouble
	{
		private:

			const int stepExponentMin;
			const int stepExponentMax;

			void OnMouseWheel(wxMouseEvent& event)
			{
				int wheelRotation = event.GetWheelRotation();
				int lines = wheelRotation / event.GetWheelDelta();

				if (lines != 0)
				{
					if (event.ControlDown())
					{
						double initialStep = GetIncrement();
						int stepExponent = (int)floor(log10(initialStep));
						initialStep = initialStep/pow(10.0, stepExponent);
						stepExponent = clamp(stepExponent, stepExponentMin, stepExponentMax);

						stepExponent += lines;
						stepExponent = clamp(stepExponent, stepExponentMin, stepExponentMax);
						SetIncrement(pow(10.0, stepExponent) * initialStep);

						LOG(wxString::Format("New SpinCtrlDouble increment: %g", GetIncrement()));
					}
					else
					{
						//lines *= event.GetLinesPerAction();
						SetValue(GetValue()+lines*GetIncrement());
					}
				}
			}

			void LocalInit(void)
			{
				this->Connect(wxID_ANY, wxEVT_MOUSEWHEEL, wxMouseEventHandler(SpinCtrlDouble::OnMouseWheel), NULL, this);
			}

		public:

			SpinCtrlDouble() : wxSpinCtrlDouble(), stepExponentMin(-5), stepExponentMax(5)
			{
			}

			SpinCtrlDouble(wxWindow* parent, wxWindowID id = -1, const wxString& value = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxSP_ARROW_KEYS, double min = 0, double max = 100, double initial = 0, double inc = 1, const wxString& name = wxT("SpinCtrlDouble")) : wxSpinCtrlDouble(parent, id, value, pos, size, style, min, max, initial, inc, name), stepExponentMin(-5), stepExponentMax(5)
			{
				LocalInit();
			}

			bool Create(wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& value = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxSP_ARROW_KEYS, double min = 0, double max = 100, double initial = 0, double inc = 1, const wxString& name = "SpinCtrlDouble" )
			{
				if ( !wxSpinCtrlDouble::Create(parent, id, value, pos, size, style, min, max, initial, inc, name) )
					return false;

				LocalInit();

				return true;
			}

	};

	class SpinCtrlDoubleXmlHandler : public wxXmlResourceHandler
	{
		public:
			// Constructor.
			SpinCtrlDoubleXmlHandler()
			{
				XRC_ADD_STYLE(wxSP_HORIZONTAL);
				XRC_ADD_STYLE(wxSP_VERTICAL);
				XRC_ADD_STYLE(wxSP_ARROW_KEYS);
				XRC_ADD_STYLE(wxSP_WRAP);
				XRC_ADD_STYLE(wxALIGN_LEFT);
				XRC_ADD_STYLE(wxALIGN_CENTER);
				XRC_ADD_STYLE(wxALIGN_RIGHT);
				XRC_ADD_STYLE(wxTE_PROCESS_ENTER);

				AddWindowStyles();
			}

			// Creates the control and returns a pointer to it.
			virtual wxObject *DoCreateResource()
			{
				const double DEFAULT_VALUE = 0.0;
				const double DEFAULT_MIN = 0.0;
				const double DEFAULT_MAX = 100.0;
				const double DEFAULT_STEP = 1.0;

				XRC_MAKE_INSTANCE(control, SpinCtrlDouble)

			    control->Create(m_parentAsWindow,
                    GetID(),
                    GetText(wxT("value")),
                    GetPosition(), GetSize(),
                    GetStyle(wxT("style"), wxSP_ARROW_KEYS | wxALIGN_RIGHT),
					GetFloat(wxT("min"), DEFAULT_MIN),
					GetFloat(wxT("max"), DEFAULT_MAX),
					GetFloat(wxT("value"), DEFAULT_VALUE),
					GetFloat(wxT("increment"), DEFAULT_STEP),
                    GetName());

				SetupWindow(control);

				return control;
			}

			// Returns true if we know how to create a control for the given node.
			virtual bool CanHandle(wxXmlNode *node)
			{
				// this function tells XRC system that this handler can parse
				// the <object class="MyControl"> tags
				return IsOfClass(node, wxT("SpinCtrlDouble"));
			}

		// Register with wxWidgets' dynamic class subsystem.
		DECLARE_DYNAMIC_CLASS(SpinCtrlDoubleXmlHandler)
	};

	class VRUT_CORE_API SliderDouble : public wxSlider
	{
		public:

			typedef enum _SliderMode {
				EE_SLIDER_LOGARITHMIC,		// positive values greater than zero 
				EE_SLIDER_LINEAR,			// any value
				EE_SLIDER_SCALE				// any value, sqrt
			} SliderMode;

			SliderDouble(wxWindow *parent, wxWindowID id, double value, double minValue, double maxValue, SliderMode mode = EE_SLIDER_LINEAR, unsigned int resolution=100, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxSL_HORIZONTAL, const wxValidator &validator=wxDefaultValidator, const wxString &name=wxSliderNameStr);

			using wxSlider::SetRange;
			bool SetRange(double low, double high, unsigned int resolution);
			bool SetPosition(double position);
			double GetPosition() const;

		private:

			double       m_minValue;		// min value
			double       m_maxValue;		// max value	
			unsigned int m_resolution;		// wxSlider resolution
			SliderMode   m_mode;            // logarithmic or linear
	};

	class VRUT_CORE_API SpinSliderDouble : public wxWindow
	{
		private:

			SpinCtrlDouble   *m_spinCtrl;
			SliderDouble     *m_slider;
			wxString          m_textValue;
			double            m_value;
			double            m_minValue;
			double            m_maxValue;
			double            m_valueInc;
			SliderDouble::SliderMode m_mode;

			void OnSpinUpdated(wxSpinDoubleEvent &evt);
			void OnSpinTextEntered(wxCommandEvent &evt);
			void OnSlider(wxCommandEvent &evt);

		public:

			SpinSliderDouble(wxWindow *parent, wxWindowID id, const wxString &title, double value, double minValue, double maxValue, SliderDouble::SliderMode mode, double valueIncrement = 0.1);

			double GetValue()
			{
				return m_value;
			}

			wxString GetTextValue()
			{
				return m_textValue;
			}

			void SetValue(double value);
	};

	class VRUT_CORE_API SpinSlider : public wxWindow
	{
		wxSpinCtrl *spin;
		wxSlider   *slider;
		int      value;
		wxString textValue;

		void OnSpin(wxCommandEvent &evt);
		void OnSlider(wxCommandEvent &evt);

	public:

		int GetValue()
		{
			return value;
		}

		wxString GetTextValue()
		{
			return textValue;
		}

		void SetValue(int val)
		{
			value = val;
			textValue = wxString::Format(wxT("%i"), val);

			spin->SetValue(value);
			slider->SetValue(value);
		}

		SpinSlider(wxWindow *parent, wxWindowID id, const wxString &title, int value, int minValue, int maxValue) : wxWindow(parent, id), value(value)
		{
			spin = new wxSpinCtrl(this, wxID_ANY, wxString::Format(wxT("%i"), value), wxDefaultPosition, wxSize(62, 20), 4608L, minValue, maxValue, value);
			slider = new wxSlider(this, wxID_ANY, value, minValue, maxValue);
			SetValue(value);

			wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);

			sizer->Add(new wxStaticText(this, wxID_ANY, title));
			sizer->Add(spin);
			sizer->Add(slider);

			SetSizerAndFit(sizer);

			spin->Connect(wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(SpinSlider::OnSpin), NULL, this);
			slider->Connect(wxID_ANY, wxEVT_COMMAND_SLIDER_UPDATED, wxCommandEventHandler(SpinSlider::OnSlider), NULL, this);
		}
	};

}

#ifdef USE_wxDEFINE_EVENT
wxDECLARE_EXPORTED_EVENT(VRUT_CORE_API, wxEVT_COMMAND_SPINSLIDER_UPDATED, wxCommandEvent);
#endif

#endif /* __SPIN_SLIDER_DOUBLE_H__ */
