/////////////////////////////////////////////////////////////////////////////
// Name:        choice.cpp
// Purpose:     
// Author:      César Mauri Loba
// Modified by: 
// Created:     26/05/2011 21:44:47
// 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/>.    
/////////////////////////////////////////////////////////////////////////////

////@begin includes
////@end includes

#include "choice.h"
#include "spcore/conversion.h"

#include <wx/sizer.h>
#include <wx/bitmap.h>

#include <boost/tokenizer.hpp>
#include <boost/foreach.hpp>

////@begin XPM images
////@end XPM images
using namespace spcore;
using namespace boost;
using namespace std;

namespace mod_widgets {

ChoiceComponent::ChoiceComponent(const char * name, int argc, const char * argv[])
: BaseWidgetComponent<ChoicePanel,ChoiceComponent>(name, argc, argv)
, m_selection(-1)
{
	//
	// Create pins
	//
	RegisterInputPin(*SmartPtr<IInputPin>(new InputPinOptions(*this), false));
	RegisterInputPin(*SmartPtr<IInputPin>(new InputPinSelect(*this), false));

	m_oPinSelection= CTypeInt::CreateOutputPin("selection");
	m_oPinSelectionString= CTypeString::CreateOutputPin("selection_string");

	RegisterOutputPin (*m_oPinSelection);
	RegisterOutputPin (*m_oPinSelectionString);

	//
	// Process arguments
	//
	if (argc) {
		int selected= -1;

		for (int i= 0; i< argc; ++i) {
			if (argv[i] && strcmp ("-o", argv[i])== 0) {
				// Options
				if (m_options.size())
					throw std::runtime_error("widget_choice. Option -o can only be set once.");

				bool err= false;
				
				++i;
				if (i< argc && argv[i]) {
					escaped_list_separator<char> sep("\\", "|", "\"");
					string options(argv[i]);
					tokenizer<escaped_list_separator<char> > tok(options, sep);
					BOOST_FOREACH(string t, tok) {
						m_options.push_back (t);
					}

					if (m_options.size()== 0) err= true;
				}
				else err= true;
				if (err) throw std::runtime_error("widget_choice. Wrong value for option -o");
			}
			else if (argv[i] && strcmp ("-v", argv[i])== 0) {
				// Selected option
				++i;
				if (i>= argc || !argv[i] || !StrToInt(argv[i], &selected) || selected< 0)
					throw std::runtime_error("widget_choice. Wrong value for option -v");
			}
			else if (argv[i] && strlen(argv[i])) {
				string error_msg("widget_choice. Unknown option:");
				error_msg+= argv[i];
				throw std::runtime_error(error_msg);
			}
		}

		// Check that selected option is correct
		if (selected== -1 && m_options.size()) selected= 0;
		if (selected>= (int) m_options.size())
			std::runtime_error("widget_choice. Value for option -v out of range");

		m_selection= selected;
	}
}

int ChoiceComponent::DoInitialize()
{
	// No sync needed here, by definition Initialize is called before
	// any data is flowing through the graph
	if (m_options.size()) {
		assert (m_selection>= 0);
		
		SmartPtr<CTypeInt> selection= CTypeInt::CreateInstance();
		selection->setValue(m_selection);
		m_oPinSelection->Send(selection);

		SmartPtr<CTypeString> selectionString= CTypeString::CreateInstance();
		if (m_selection>= 0) selectionString->set(m_options[m_selection].c_str());
		m_oPinSelectionString->Send(selectionString);
	}
	return 0;
}

// 
// Private methods
//	

void ChoiceComponent::OnPinOptions (const spcore::CTypeAny & msg)
{
	// Make sure no one else is touching internal data
	m_mutex.lock();

	// Clear previous options
	m_options.clear();

	// Get new options
	SmartPtr<IIterator<CTypeAny*> > cit= msg.QueryChildren();
	if (cit.get()) {
		int str_type_id= CTypeString::getTypeID();
		for (; !cit->IsDone(); cit->Next()) {
			if (cit->CurrentItem()->GetTypeID()== str_type_id)
				m_options.push_back(sptype_static_cast<const CTypeString>(cit->CurrentItem())->get());
			else
				getSpCoreRuntime()->LogMessage(
					ICoreRuntime::LOG_WARNING, "Unexpected value on list of options", "widget_choice");
		}
	}

	if (m_options.size()) {
		// If options added then notify listeners
		m_selection= 0;
	
		SmartPtr<CTypeInt> selection= CTypeInt::CreateInstance();
		selection->setValue(m_selection);		
		SmartPtr<CTypeString> selectionString= CTypeString::CreateInstance();
		selectionString->set(m_options[m_selection].c_str());

		m_mutex.unlock();

		m_oPinSelection->Send(selection);
		m_oPinSelectionString->Send(selectionString);
	}
	else {
		m_selection= -1;
		m_mutex.unlock();
	}

	// Notify GUI
	if (m_panel) m_panel->ValueChanged();
}

void ChoiceComponent::OnPinSelect (const spcore::CTypeInt & msg)
{
	int newSel= msg.getValue();

	if (newSel< 0) {
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_WARNING, "Selection index out of range.", "widget_choice");
		return;
	}

	if (SetSelection(newSel))
		// Notify GUI
		if (m_panel) m_panel->ValueChanged();
}

// 
// Called from GUI
//	

bool ChoiceComponent::SetSelection(int s)
{
	assert (s>= 0);

	m_mutex.lock();
	
	// Could happen that an incorrect selection value arrive, just ignore
	// If not change, don't do anything
	if (s< (int) m_options.size() && s!= m_selection) {		
		m_selection= s;

		// Don't take risks and sends full data copies
		SmartPtr<CTypeInt> selection= CTypeInt::CreateInstance();
		selection->setValue(s);
		
		SmartPtr<CTypeString> selectionString= CTypeString::CreateInstance();
		selectionString->set(m_options[s].c_str());
			
		m_mutex.unlock();

		m_oPinSelection->Send(selection);
		m_oPinSelectionString->Send(selectionString);

		return true;
	}	
	else 
		m_mutex.unlock();

	return false;
}

void ChoiceComponent::GetOptionsAndSelection(vector<string>& op, int& selection) const
{
	m_mutex.lock();

	selection= m_selection;
	op= m_options;
	
	m_mutex.unlock();
}

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

/*!
 * ChoicePanel type definition
 */

IMPLEMENT_DYNAMIC_CLASS( ChoicePanel, wxPanel )

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

/*!
 * ChoicePanel event table definition
 */

BEGIN_EVENT_TABLE( ChoicePanel, wxPanel )

////@begin ChoicePanel event table entries
    EVT_CHOICE( ID_CHOICE, ChoicePanel::OnChoiceSelected )

////@end ChoicePanel event table entries

	EVT_COMMAND  (wxID_ANY, wxEVT_SPCHOICE_VALUE_CHANGE, ChoicePanel::OnValueChanged)

END_EVENT_TABLE()


/*!
 * ChoicePanel constructors
 */

ChoicePanel::ChoicePanel()
{
    Init();
}

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


/*!
 * ChoicePanel creator
 */

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

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


/*!
 * ChoicePanel destructor
 */

ChoicePanel::~ChoicePanel()
{
	if (m_component!= NULL) {
		m_component->OnPanelDestroyed();
		m_component= NULL;
	}
////@begin ChoicePanel destruction
////@end ChoicePanel destruction
}


/*!
 * Member initialisation
 */

void ChoicePanel::Init()
{
	m_component= NULL;
////@begin ChoicePanel member initialisation
    m_staLabel = NULL;
    m_theChoice = NULL;
////@end ChoicePanel member initialisation
}


/*!
 * Control creation for ChoicePanel
 */

void ChoicePanel::CreateControls()
{    
////@begin ChoicePanel content construction
    ChoicePanel* itemPanel1 = this;

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

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

    wxArrayString m_theChoiceStrings;
    m_theChoice = new wxChoice;
    m_theChoice->Create( itemPanel1, ID_CHOICE, wxDefaultPosition, wxDefaultSize, m_theChoiceStrings, 0 );
    itemBoxSizer2->Add(m_theChoice, 0, wxALIGN_LEFT|wxALL, 5);

////@end ChoicePanel content construction

	assert (m_component);

	// Label
	if (m_component->GetLabel().size()) {
		wxString label(m_component->GetLabel().c_str(), wxConvUTF8);
		m_staLabel->SetLabel(label);
	}
	else
		m_staLabel->Show(false);

	ValueChanged();
}


/*!
 * Should we show tooltips?
 */

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

/*!
 * Get bitmap resources
 */

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

/*!
 * Get icon resources
 */

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

/*!
 * wxEVT_COMMAND_CHOICE_SELECTED event handler for ID_CHOICE
 */

void ChoicePanel::OnChoiceSelected( wxCommandEvent& event )
{
	if (m_component)
		m_component->SetSelection(event.GetSelection());
	
    event.Skip(false);
}

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

void ChoicePanel::OnValueChanged( wxCommandEvent& )
{
	if (m_component) {
		// Clear control		
		m_theChoice->Clear();

		// Get options and selection
		vector<string> options;
		int selection;
		m_component->GetOptionsAndSelection(options, selection);
		assert (selection== -1 || selection< (int) options.size());

		// Populate control with options
		vector<string>::const_iterator it= options.begin();
		for (; it!= options.end(); ++it)
			m_theChoice->Append (wxString((*it).c_str(), wxConvUTF8));

		// Select item
		if (selection== -1) m_theChoice->SetSelection(wxNOT_FOUND);
		else m_theChoice->SetSelection(selection);
	}
}

}; // namespace end



