/////////////////////////////////////////////////////////////////////////////
// Name:        slider.cpp
// Purpose:
// Author:      Cesar Mauri Loba
// Modified by:
// Created:     13/04/2011 18:37:56
// RCS-ID:
// Copyright:   (C) 2011 Cesar Mauri Loba - CREA Software Systems
// 
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>. 
/////////////////////////////////////////////////////////////////////////////

/*

Sample pattern on how to close to the close event of the parent window and forward it accordingly

bool MyClass::Create( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name )
{
////@begin PureDataConfigPanel creation
    SetExtraStyle(wxWS_EX_BLOCK_EVENTS);
    wxPanel::Create( parent, id, pos, size, style, name );

    CreateControls();
    if (GetSizer())
    {
        GetSizer()->SetSizeHints(this);
    }
    Centre();
////@end PureDataConfigPanel creation

	//
	// Connect parent class close event. This way this panel will receive close events when
	// user clicks on the close box or the application if forced to quit. Note that this
	// events should be managed carefully in the OnCloseWindow method (see below).
	//
	if (parent)
		parent->Connect (wxEVT_CLOSE_WINDOW, wxCloseEventHandler(MyClass::OnCloseWindow), 0, this);

    return true;
}

void MyClass::OnCloseWindow( wxCloseEvent& event )
{
	//
	// We need to tell if the event has been originated from this window
	// or in the parent window.
	//
	if (event.GetEventObject()== this) {
		//
		// If the event has been generated by this object
		// we need to propagate to parent
		//
		if (GetParent()) GetParent()->Close();
		event.Skip(false);
	}
	else {
		// 
		// This branch can be customized to allow close process to complete
		// using event.Skip(true) or blocking it calling event.Skip(false)
		//
		event.Skip();
	}
}

*/

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif

////@begin includes
////@end includes
#include "slider.h"
#include "spcore/conversion.h"
#include <boost/format.hpp>

////@begin XPM images
////@end XPM images

using namespace std;

namespace mod_widgets {

//
// Component methods
//

SliderComponent::SliderComponent(const char * name, int argc, const char * argv[])
: BaseWidgetComponent<SliderPanel,SliderComponent>(name, argc, argv)
, m_sldType(SLD_FLOAT)
, m_sliderMin(0)
, m_sliderMax(1)
, m_fmin(0)
, m_fmax(1.0f)
{
	bool setValue= false;
	float logSpeed= 0;

	// Integer or float?
	if (argc)
		for (int i= 0; i< argc; ++i)
			if (argv[i] && strcmp("-i", argv[i])== 0) {
				m_sldType= SLD_INT;
				break;
			}
	
	// Create pins and internal storage
	if (m_sldType!= SLD_INT) {
		// float slider (either linear or log)
		m_iPin= SmartPtr<InputPinFloat>(new InputPinFloat(*this), false);
		m_oPin= CTypeFloat::CreateOutputPin("value");		
		m_valueFloat= CTypeFloat::CreateInstance();
		// wxSlider uses always integer values. 200 should provide enough resolution
		m_sliderMax= 200;
	}
	else {
		m_iPin= SmartPtr<InputPinInt>(new InputPinInt(*this), false);
		m_oPin= CTypeInt::CreateOutputPin("value");		
		m_valueInt= CTypeInt::CreateInstance();
	}
	RegisterInputPin (*m_iPin);
	RegisterOutputPin (*m_oPin);		
	
	// Process remaining arguments
	if (argc) {
		for (int i= 0; i< argc; ++i) {
			if (argv[i] && strcmp ("--min", argv[i])== 0) {
				// Min
				bool err= false;
				
				++i;
				if (i< argc && argv[i]) {
					if (m_sldType!= SLD_INT) err= !StrToFloat(argv[i], &m_fmin);
					else err= !StrToInt(argv[i], &m_sliderMin);
				}
				else err= true;
				if (err) throw std::runtime_error("widget_slider. Wrong value for option --min");
			}
			else if (argv[i] && strcmp ("--max", argv[i])== 0) {
				// Max
				bool err= false;
				
				++i;
				if (i< argc && argv[i]) {
					if (m_sldType!= SLD_INT) err= !StrToFloat(argv[i], &m_fmax);
					else err= !StrToInt(argv[i], &m_sliderMax);
				}
				else err= true;
				if (err) throw std::runtime_error("widget_slider. Wrong value for option --max");
			}
			else if (argv[i] && strcmp ("-v", argv[i])== 0) {
				// Value
				bool err= false;
				
				++i;
				if (i< argc && argv[i]) {
					if (m_sldType!= SLD_INT) {
						float val= 0;
						err= !StrToFloat(argv[i], &val);
						m_valueFloat->setValue(val);
					}
					else {
						int val= 0;
						err= !StrToInt(argv[i], &val);
						m_valueInt->setValue(val);
					}
				}
				else err= true;
				if (err) throw std::runtime_error("widget_slider. Wrong value for option --v");
				setValue= true;
			}
			else if (argv[i] && strcmp ("--log", argv[i])== 0) {
				// Log slider
				if (m_sldType== SLD_INT) {
					throw std::runtime_error("widget_slider. Integer slider cannot use log scale.");
				}

				m_sldType= SLD_LOG;

				// Provides speed argument?
				if (i+1< argc  && argv[i+1]) {
					if (StrToFloat(argv[i+1], &logSpeed)) ++i;
				}
			}
			else if (argv[i] && strcmp ("-i", argv[i])== 0) {
				// Ignore, handled previously
			}
			else if (argv[i] && strlen(argv[i])) {
				string error_msg("widget_slider. Unknown option:");
				error_msg+= argv[i];
				throw std::runtime_error(error_msg);
			}
		}
	}

	// Set default value when needed
	if (!setValue) {
		if (m_sldType!= SLD_INT) m_valueFloat->setValue(m_fmin);
		else m_valueInt->setValue(m_sliderMin);
	}

	// Check ranges
	if (m_sliderMin>= m_sliderMax || m_fmin>= m_fmax)
		throw std::runtime_error("widget_slider. Wrong numeric range.");
	if (((m_sldType!= SLD_INT) && (m_valueFloat->getValue()< m_fmin || m_valueFloat->getValue()> m_fmax)) ||
	    ((m_sldType== SLD_INT) && (m_valueInt->getValue()< m_sliderMin || m_valueInt->getValue()> m_sliderMax)))
	   throw std::runtime_error("widget_slider. Value out of range.");

	// Set log parameters when needed
	if (m_sldType== SLD_LOG)
		m_linear2exp.SetParams ((float) m_sliderMin,  m_fmin, (float) m_sliderMax, m_fmax, logSpeed);
}

int SliderComponent::DoInitialize()
{
	if (m_sldType!= SLD_INT)
		m_oPin->Send(m_valueFloat);
	else
		m_oPin->Send(m_valueInt);

	return 0;
}

// 
// Called from GUI
//	

void SliderComponent::SetSliderValue (int v)
{
	assert (v>= m_sliderMin && v<= m_sliderMax);

	switch (m_sldType) {
		case SLD_FLOAT: {
			assert (m_sliderMin== 0);

			float newVal= (m_fmax - m_fmin) * ((float) v / (float) m_sliderMax) + m_fmin;
			if (newVal!= m_valueFloat->getValue()) {
				m_valueFloat->setValue(newVal);
				m_oPin->Send(m_valueFloat);
			}
			break;
		}
		case SLD_INT:
			if (v!= m_valueInt->getValue()) {
				m_valueInt->setValue(v);
				m_oPin->Send(m_valueInt);
			}
			break;
		case SLD_LOG: {
			assert (m_sliderMin== 0);

			float newVal= this->m_linear2exp.ToExp((float) v);
			if (newVal!= m_valueFloat->getValue()) {
				m_valueFloat->setValue(newVal);
				m_oPin->Send(m_valueFloat);
			}
			break;
		}
		default: 
			assert(false);
	}
}

int SliderComponent::GetSliderValue() const
{
	switch (m_sldType) {
		case SLD_FLOAT:
			assert (m_sliderMin== 0);
			return (int) (((m_valueFloat->getValue() - m_fmin) / (m_fmax - m_fmin)) * (float) m_sliderMax + 0.5f);
		case SLD_INT:
			return m_valueInt->getValue();
		case SLD_LOG:
			return (int) m_linear2exp.ToLinear(m_valueFloat->getValue());
		default: 
			assert(false);
	}
	return 0;	// Make the compiler happy
}

std::string SliderComponent::GetTextboxValue() const
{
	if (m_sldType!= SLD_INT) {
		return str( boost::format("%.4g") % m_valueFloat->getValue() );
	}
	else {
		return str( boost::format("%d") % m_valueInt->getValue() );
	}
}

// 
// Private methods
//	

void SliderComponent::OnPinValueFloat (const CTypeFloat & msg)
{
	m_valueFloat->setValue(msg.getValue());
	if (m_panel) m_panel->ValueChanged();
}

void SliderComponent::OnPinValueInt (const CTypeInt & msg)
{
	m_valueInt->setValue(msg.getValue());
	if (m_panel) m_panel->ValueChanged();
}

//////////////////////////////////////////////////////////////////////////////

/*!
 * SliderPanel type definition
 */

IMPLEMENT_DYNAMIC_CLASS( SliderPanel, wxPanel )

// New event to comunicate updates to GUI
DECLARE_LOCAL_EVENT_TYPE(wxEVT_SPSLIDER_VALUE_CHANGE, -1)
DEFINE_LOCAL_EVENT_TYPE(wxEVT_SPSLIDER_VALUE_CHANGE)

/*!
 * SliderPanel event table definition
 */

BEGIN_EVENT_TABLE( SliderPanel, wxPanel )

////@begin SliderPanel event table entries
    EVT_SLIDER( ID_SLIDER_CONTROL, SliderPanel::OnSliderControlUpdated )

////@end SliderPanel event table entries

	EVT_COMMAND  (wxID_ANY, wxEVT_SPSLIDER_VALUE_CHANGE, SliderPanel::OnValueChanged)

END_EVENT_TABLE()


/*!
 * SliderPanel constructors
 */

SliderPanel::SliderPanel()
{
    Init();
}

SliderPanel::SliderPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name )
{
    Init();
    Create(parent, id, pos, size, style, name);
}


/*!
 * Slider creator
 */

bool SliderPanel::Create( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& )
{
////@begin SliderPanel creation
    SetExtraStyle(wxWS_EX_BLOCK_EVENTS);
    wxPanel::Create( parent, id, pos, size, style );

    CreateControls();
    if (GetSizer())
    {
        GetSizer()->SetSizeHints(this);
    }
    Centre();
////@end SliderPanel creation
    return true;
}


/*!
 * SliderPanel destructor
 */

SliderPanel::~SliderPanel()
{
////@begin SliderPanel destruction
////@end SliderPanel destruction
}


/*!
 * Member initialisation
 */

void SliderPanel::Init()
{
////@begin SliderPanel member initialisation
    m_staLabel = NULL;
    m_sldSlider = NULL;
    m_txtNumber = NULL;
////@end SliderPanel member initialisation
}


/*!
 * Control creation for Slider
 */

void SliderPanel::CreateControls()
{

////@begin SliderPanel content construction
    SliderPanel* itemPanel1 = this;

    wxBoxSizer* itemBoxSizer2 = new wxBoxSizer(wxVERTICAL);
    itemPanel1->SetSizer(itemBoxSizer2);

    m_staLabel = new wxStaticText;
    m_staLabel->Create( itemPanel1, wxID_STATIC_LABEL, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
    itemBoxSizer2->Add(m_staLabel, 0, wxALIGN_LEFT|wxLEFT|wxRIGHT, 5);

    wxBoxSizer* itemBoxSizer4 = new wxBoxSizer(wxHORIZONTAL);
    itemBoxSizer2->Add(itemBoxSizer4, 0, wxGROW, 5);

    m_sldSlider = new wxSlider;
    m_sldSlider->Create( itemPanel1, ID_SLIDER_CONTROL, 0, 0, 0, wxDefaultPosition, wxSize(150, -1), wxSL_HORIZONTAL );
    itemBoxSizer4->Add(m_sldSlider, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);

    m_txtNumber = new wxTextCtrl;
    m_txtNumber->Create( itemPanel1, ID_TEXTCTRL, wxEmptyString, wxDefaultPosition, wxSize(75, -1), wxTE_READONLY|wxTE_RIGHT );
    itemBoxSizer4->Add(m_txtNumber, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);

////@end SliderPanel content construction

	assert (m_component);
/*
    SliderPanel* itemPanel1 = this;

    wxBoxSizer* itemBoxSizer2 = new wxBoxSizer(wxVERTICAL);
    itemPanel1->SetSizer(itemBoxSizer2);

    wxStaticText* itemStaticText3 = NULL;
    if (m_component->GetLabel().size()) {
        itemStaticText3 = new wxStaticText;
        itemStaticText3->Create( itemPanel1, wxID_STATIC_LABEL, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
        itemBoxSizer2->Add(itemStaticText3, 0, wxALIGN_LEFT|wxLEFT|wxRIGHT, 5);
    }

    wxBoxSizer* itemBoxSizer4 = new wxBoxSizer(wxHORIZONTAL);
    itemBoxSizer2->Add(itemBoxSizer4, 0, wxGROW, 5);

    m_sldSlider = new wxSlider;
    m_sldSlider->Create( itemPanel1, ID_SLIDER_CONTROL, 0, 0, 0, wxDefaultPosition, wxSize(150, -1), wxSL_HORIZONTAL );
    itemBoxSizer4->Add(m_sldSlider, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);

    m_txtNumber = new wxTextCtrl;
    m_txtNumber->Create( itemPanel1, ID_TEXTCTRL, wxEmptyString, wxDefaultPosition, wxSize(75, -1), wxTE_READONLY|wxTE_RIGHT );
    itemBoxSizer4->Add(m_txtNumber, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
*/
	// Label
	if (m_component->GetLabel().size()) {
		wxString label(m_component->GetLabel().c_str(), wxConvUTF8);
		m_staLabel->SetLabel(label);
	}
	else
		m_staLabel->Show(false);

    // Fill values
	/*
    if (itemStaticText3) {
        wxString label(m_component->GetLabel().c_str(), wxConvUTF8);
        itemStaticText3->SetLabel(label);
    }*/
	m_sldSlider->SetRange(m_component->GetSliderMin(), m_component->GetSliderMax());
    m_sldSlider->SetValue(m_component->GetSliderValue());
    wxString number(m_component->GetTextboxValue().c_str(), wxConvUTF8);
    m_txtNumber->SetValue(number);
}


/*!
 * Should we show tooltips?
 */

bool SliderPanel::ShowToolTips()
{
    return true;
}

/*!
 * Get bitmap resources
 */

wxBitmap SliderPanel::GetBitmapResource( const wxString& name )
{
    // Bitmap retrieval
////@begin SliderPanel bitmap retrieval
    wxUnusedVar(name);
    return wxNullBitmap;
////@end SliderPanel bitmap retrieval
}

/*!
 * Get icon resources
 */

wxIcon SliderPanel::GetIconResource( const wxString& name )
{
    // Icon retrieval
////@begin SliderPanel icon retrieval
    wxUnusedVar(name);
    return wxNullIcon;
////@end SliderPanel icon retrieval
}


/*!
 * wxEVT_COMMAND_SLIDER_UPDATED event handler for ID_SLIDER1
 */

void SliderPanel::OnSliderControlUpdated( wxCommandEvent& event )
{
	if (m_component) {
		m_component->SetSliderValue(m_sldSlider->GetValue());
		wxString txtBoxVal(m_component->GetTextboxValue().c_str(), wxConvUTF8);
		m_txtNumber->SetValue(txtBoxVal);
	}
	
    event.Skip(false);
}

void SliderPanel::ValueChanged()
{
	wxCommandEvent event(wxEVT_SPSLIDER_VALUE_CHANGE);
	
	if (!wxIsMainThread()) wxPostEvent(this, event);
	else OnValueChanged( event );
}

void SliderPanel::OnValueChanged( wxCommandEvent& )
{
	if (m_component) {
		// TODO: sync
		m_sldSlider->SetValue(m_component->GetSliderValue());
		wxString txtBoxVal(m_component->GetTextboxValue().c_str(), wxConvUTF8);
		m_txtNumber->SetValue(txtBoxVal);
	}
}

};
