/////////////////////////////////////////////////////////////////////////////
// Name:       puredataconfig.cpp
// Author:      Cesar Mauri Loba (cesar at crea-si dot com)
// Modified by: 
// Created:     
// Copyright:   (C) 2010 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/>.
/////////////////////////////////////////////////////////////////////////////

// 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 <wx/msgdlg.h>
#include <wx/timer.h>
#include "puredataconfig.h"

//#include "testpd.h"
#include "vumeter.h"

#include "puredatawrapper.h"

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

using namespace spcore;
using namespace std;

namespace mod_puredata {

PureDataConfigComponent::PureDataConfigComponent(const char * name, int argc, const char * argv[])
: CComponentAdapter(name, argc, argv)
, m_pdError(false)
, m_panel(NULL)
, m_oscIn(50003, this)
, m_oscOut(50002)
, m_micInputControl (25,0,100,0)
, m_micInputEnvelope(0)
, m_outputControl (25,0,100,0)
, m_outputEnvelope (0)
{
	// Parse arguments
	for (int iarg= 0; iarg< argc; ++iarg) {
		if (strcmp(argv[iarg], "--data-dir")== 0) {
			if (++iarg< argc) {
				m_patchPath= argv[iarg];
				m_patchPath+= "/";				
			}
			else
				throw std::runtime_error("puredata_config: not enough arguments for --data-dir");
		}
		else {
			string msg("puredata_config: unexpected argument ");
			msg+= argv[iarg];
			throw std::runtime_error(msg);
		}
	}
	m_patchPath+= "testpd.pd";

	// TODO: enable debug mode / console logging as parameters
	//m_chkDebugConsole->SetValue (CPureDataWrapper::getInstance()->IsLogEnabled ());
	//m_chkGUIDebug->SetValue (CPureDataWrapper::getInstance()->getDebugGUIMode());
	//if (event.IsChecked())
	//	CPureDataWrapper::getInstance()->EnableLog();
	//else
	//	CPureDataWrapper::getInstance()->DisableLog();
	//CPureDataWrapper::getInstance()->setDebugGUIMode(true);
}

PureDataConfigComponent::~PureDataConfigComponent()
{
	assert (wxIsMainThread());
	Finish();
	if (m_panel) {
		m_panel->SetComponent(NULL);
		m_panel->Close();
		m_panel= NULL;
	}
}

void PureDataConfigComponent::OnPanelDestroyed ()
{
	assert (wxIsMainThread());
	m_panel= NULL;	
}

wxWindow* PureDataConfigComponent::GetGUI(wxWindow * parent)
{
	assert (wxIsMainThread());	
	if (m_panel) {
		// Already created
		assert (false);
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, "panel alredy open", "puredata_config");
		return NULL;
	}
	else {
		m_panel= new PureDataConfigPanel();
		m_panel->SetComponent(this);
		m_panel->Create(parent);
	}
	return m_panel;
}


	
int PureDataConfigComponent::DoInitialize()
{
	try {
		// Register this patch (which starts pd)
		PureDataController::getInstance()->RegisterPatch(this);

		// Open outbound socket
		m_oscOut.Open();

		// Open inbound socket and starts listening to it
		m_oscIn.Open();
	}
	catch (std::exception& e) {
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, e.what(),  GetTypeName());
		try { PureDataController::getInstance()->UnregisterPatch(this);	} catch(...) {}
		return -1;
	}

	return 0;	
}
 
void PureDataConfigComponent::DoFinish()
{
	// Save settings
	SaveSettings();

	// Close outbound socket
	m_oscOut.Close();

	// Stop listening messages
	m_oscIn.Close();
	
	try {
		PureDataController::getInstance()->UnregisterPatch(this);
	}
	catch (std::exception& e) {
		assert (false);
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, e.what(),  GetTypeName());		
	}
}
		
void PureDataConfigComponent::SetMicInputControl(int v)
{
	try {
		m_micInputControl.setValue (v); 		
		m_oscOut.SendSimpleMessage ("/micInput", (float) v);
	}
	catch (std::exception& e) {
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, e.what(),  GetTypeName());		
	}
}
	
void PureDataConfigComponent::SetOutputControl(int v)
{
	try {
		m_outputControl.setValue (v); 
		m_oscOut.SendSimpleMessage ("/output", (float) v); 
	}
	catch (std::exception& e) {
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, e.what(),  GetTypeName());		
	}
}
	
void PureDataConfigComponent::SetTestType (EPDTestType tt)
{
	try {
		if (tt== TEST_PLAY_SOUND) 
			m_oscOut.SendSimpleMessage ("/test_play_sound", 0);
		else if (tt== TEST_MICROPHONE) 
			m_oscOut.SendSimpleMessage ("/test_microphone", 0);
	}
	catch (std::exception& e) {
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, e.what(),  GetTypeName());
	}
}

unsigned int PureDataConfigComponent::GetDelay()
{
	try {
		return PureDataController::getInstance()->GetDelay();
	}
	catch (std::exception& e) {
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, e.what(),  GetTypeName());		
	}
	return 0;
}

void PureDataConfigComponent::SetDelay(unsigned int d)
{
	try {
		return PureDataController::getInstance()->SetDelay(d);
	}
	catch (std::exception& e) {
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, e.what(),  GetTypeName());
	}
}

void PureDataConfigComponent::SaveSettings()
{
	try {
		PureDataController::getInstance()->SaveSettings();
	}
	catch (std::exception& e) {
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, e.what(),  GetTypeName());
	}
}

void PureDataConfigComponent::NotifyStatus (PDStatus e)
{
	if (m_panel && e== IPdPatch::PD_STOPED) {
		m_pdError= true;
		m_panel->NotifyComponentUpdate();
	}		
}

const char* PureDataConfigComponent::GetPatchFileName() const
{	
	return m_patchPath.c_str();
}
	


void PureDataConfigComponent::ProcessMessage( const osc::ReceivedMessage& m, const IpEndpointName& )
{
	assert (!wxIsMainThread());
	try {
		if( strcmp( m.AddressPattern(), "/testpd" ) == 0 ) {
			osc::ReceivedMessage::const_iterator arg = m.ArgumentsBegin();
			if (arg->IsInt32())
				m_micInputEnvelope= (float) (arg++)->AsInt32();
			else
				m_micInputEnvelope= (arg++)->AsFloat();

			if (arg->IsInt32())
				m_outputEnvelope = (float) (arg++)->AsInt32();
			else
				m_outputEnvelope = (arg++)->AsFloat();

			m_panel->NotifyComponentUpdate();
		}
		else {
			string msg= string("Unknown message received") + m.AddressPattern();
			getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_DEBUG, msg.c_str(), "puredata_config");
		}
	}
	catch ( std::exception& e ){
        // any parsing errors such as unexpected argument types, or 
        // missing arguments are errors
		string msg= string("Error while parsing message") + m.AddressPattern() + ":" + e.what();
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, msg.c_str(), "puredata_config");
	}
}

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

//#define TIMER_ID 12345

DECLARE_LOCAL_EVENT_TYPE(wxEVT_COMPONENT_UPDATE, -1)
DEFINE_LOCAL_EVENT_TYPE(wxEVT_COMPONENT_UPDATE)


/*!
 * PureDataConfigPanel type definition
 */

IMPLEMENT_DYNAMIC_CLASS( PureDataConfigPanel, wxPanel )


/*!
 * PureDataConfigPanel event table definition
 */

BEGIN_EVENT_TABLE( PureDataConfigPanel, wxPanel )

////@begin PureDataConfigPanel event table entries
    EVT_CLOSE( PureDataConfigPanel::OnCloseWindow )

    EVT_SPINCTRL( ID_SPINCTRL_DELAY, PureDataConfigPanel::OnSpinctrlDelayUpdated )

    EVT_RADIOBUTTON( ID_RADIOBUTTON_PLAYSOUND, PureDataConfigPanel::OnRadiobuttonPlaysoundSelected )

    EVT_RADIOBUTTON( ID_RADIOBUTTON_MIC, PureDataConfigPanel::OnRadiobuttonMicSelected )

    EVT_SLIDER( ID_SLIDER_OUTVOL, PureDataConfigPanel::OnSliderOutvolUpdated )

    EVT_SLIDER( ID_SLIDER_MICVOL, PureDataConfigPanel::OnSliderMicvolUpdated )

    EVT_BUTTON( ID_BUTTON_CLOSE, PureDataConfigPanel::OnButtonCloseClick )

////@end PureDataConfigPanel event table entries

	EVT_COMMAND(wxID_ANY, wxEVT_COMPONENT_UPDATE, PureDataConfigPanel::OnComponentUpdated)

END_EVENT_TABLE()


/*!
 * PureDataConfigPanel constructors
 */

PureDataConfigPanel::PureDataConfigPanel()
//: m_timer(this, TIMER_ID)
{
    Init();
}

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


/*!
 * CPureDataTestW creator
 */

bool PureDataConfigPanel::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 );

    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(PureDataConfigPanel::OnCloseWindow), 0, this);

    return true;
}

/*!
 * wxEVT_CLOSE_WINDOW event handler for ID_CPUREDATATESTW
 */

void PureDataConfigPanel::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();
	}
}


/*!
 * PureDataConfigPanel destructor
 */

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


/*!
 * Member initialisation
 */

void PureDataConfigPanel::Init()
{
////@begin PureDataConfigPanel member initialisation
    m_spinDelay = NULL;
    m_sldOutVol = NULL;
    m_gaugeOutput = NULL;
    m_txtMicVol = NULL;
    m_sldMicVol = NULL;
    m_gaugeMicInput = NULL;
////@end PureDataConfigPanel member initialisation

	m_micTest= false;
	m_component= NULL;
}


/*!
 * Control creation for CPureDataTestW
 */

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

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

    wxStaticText* itemStaticText3 = new wxStaticText;
    itemStaticText3->Create( itemPanel1, wxID_STATIC, _("Set \"Delay\" to the minimum\npossible value before you\nget sound defects."), wxDefaultPosition, wxDefaultSize, 0 );
    itemBoxSizer2->Add(itemStaticText3, 0, wxALIGN_LEFT|wxALL, 5);

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

    wxStaticText* itemStaticText5 = new wxStaticText;
    itemStaticText5->Create( itemPanel1, wxID_STATIC, _("Delay"), wxDefaultPosition, wxDefaultSize, 0 );
    itemBoxSizer4->Add(itemStaticText5, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);

    m_spinDelay = new wxSpinCtrl;
    m_spinDelay->Create( itemPanel1, ID_SPINCTRL_DELAY, _T("1"), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 1, 100, 1 );
    m_spinDelay->Enable(false);
    itemBoxSizer4->Add(m_spinDelay, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);

    wxStaticBox* itemStaticBoxSizer7Static = new wxStaticBox(itemPanel1, wxID_ANY, _("Test type"));
    wxStaticBoxSizer* itemStaticBoxSizer7 = new wxStaticBoxSizer(itemStaticBoxSizer7Static, wxVERTICAL);
    itemBoxSizer2->Add(itemStaticBoxSizer7, 0, wxGROW|wxALL, 5);

    wxRadioButton* itemRadioButton8 = new wxRadioButton;
    itemRadioButton8->Create( itemPanel1, ID_RADIOBUTTON_PLAYSOUND, _("Play sound"), wxDefaultPosition, wxDefaultSize, 0 );
    itemRadioButton8->SetValue(true);
    itemStaticBoxSizer7->Add(itemRadioButton8, 0, wxALIGN_LEFT|wxALL, 5);

    wxRadioButton* itemRadioButton9 = new wxRadioButton;
    itemRadioButton9->Create( itemPanel1, ID_RADIOBUTTON_MIC, _("Microphone"), wxDefaultPosition, wxDefaultSize, 0 );
    itemRadioButton9->SetValue(false);
    itemStaticBoxSizer7->Add(itemRadioButton9, 0, wxALIGN_LEFT|wxALL, 5);

    wxStaticText* itemStaticText10 = new wxStaticText;
    itemStaticText10->Create( itemPanel1, wxID_STATIC, _("Out. vol."), wxDefaultPosition, wxDefaultSize, 0 );
    itemBoxSizer2->Add(itemStaticText10, 0, wxALIGN_LEFT|wxALL, 5);

    m_sldOutVol = new wxSlider;
    m_sldOutVol->Create( itemPanel1, ID_SLIDER_OUTVOL, 0, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL );
    itemBoxSizer2->Add(m_sldOutVol, 0, wxGROW|wxALL, 5);

    m_gaugeOutput = new wxGauge;
    m_gaugeOutput->Create( itemPanel1, ID_GAUGE_OUTVOL, 100, wxDefaultPosition, wxSize(-1, 15), wxGA_HORIZONTAL );
    m_gaugeOutput->SetValue(1);
    itemBoxSizer2->Add(m_gaugeOutput, 0, wxGROW|wxALL, 5);

    m_txtMicVol = new wxStaticText;
    m_txtMicVol->Create( itemPanel1, wxID_STATIC, _("Mic. vol."), wxDefaultPosition, wxDefaultSize, 0 );
    m_txtMicVol->Enable(false);
    itemBoxSizer2->Add(m_txtMicVol, 0, wxALIGN_LEFT|wxALL, 5);

    m_sldMicVol = new wxSlider;
    m_sldMicVol->Create( itemPanel1, ID_SLIDER_MICVOL, 0, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL );
    m_sldMicVol->Enable(false);
    itemBoxSizer2->Add(m_sldMicVol, 0, wxGROW|wxALL, 5);

    m_gaugeMicInput = new wxGauge;
    m_gaugeMicInput->Create( itemPanel1, ID_GAUGE_MICINPUT, 100, wxDefaultPosition, wxSize(-1, 15), wxGA_HORIZONTAL );
    m_gaugeMicInput->SetValue(1);
    itemBoxSizer2->Add(m_gaugeMicInput, 0, wxGROW|wxALL, 5);

    wxBoxSizer* itemBoxSizer16 = new wxBoxSizer(wxHORIZONTAL);
    itemBoxSizer2->Add(itemBoxSizer16, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5);

    wxButton* itemButton17 = new wxButton;
    itemButton17->Create( itemPanel1, ID_BUTTON_CLOSE, _("Close"), wxDefaultPosition, wxDefaultSize, 0 );
    itemBoxSizer16->Add(itemButton17, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);

////@end PureDataConfigPanel content construction

	assert (m_component);

	// 
	// Initial GUI update
	//
	m_sldMicVol->SetRange (m_component->GetMicInputControl().getMin(), m_component->GetMicInputControl().getMax());
	m_sldMicVol->SetValue (m_component->GetMicInputControl().getValue());
//	wxCommandEvent evt (wxEVT_COMMAND_SLIDER_UPDATED, ID_SLIDER_MICVOL);
//	GetEventHandler()->ProcessEvent( evt );
	m_gaugeMicInput->SetRange (100);

	m_sldOutVol->SetRange (m_component->GetOutputControl().getMin(), m_component->GetOutputControl().getMax());
	m_sldOutVol->SetValue (m_component->GetOutputControl().getValue());
//	wxCommandEvent evt2 (wxEVT_COMMAND_SLIDER_UPDATED, ID_SLIDER_OUTVOL);
//	GetEventHandler()->ProcessEvent( evt );
	m_gaugeOutput->SetRange (100);
}


/*!
 * Should we show tooltips?
 */

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

/*!
 * Get bitmap resources
 */

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

/*!
 * Get icon resources
 */

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

void PureDataConfigPanel::NotifyComponentUpdate()
{
	wxCommandEvent event(wxEVT_COMPONENT_UPDATE);
	wxPostEvent(this, event);
}

void PureDataConfigPanel::OnComponentUpdated( wxCommandEvent& event )
{
	assert (m_component);
	if (m_component) {
		if (m_component->GetError()) {
			wxMessageDialog dlg(this, _(
				"An error ocurred and Pure Data cannot be started or died unexpectedly.\nSee console for details."), _("Error"));
			dlg.ShowModal();

			Close();

			return;
		}

		// Delay (only first time)
		if (!m_spinDelay->IsEnabled()) {
			m_spinDelay->SetValue(m_component->GetDelay());
			m_spinDelay->Enable(true);
		}
		
		if (m_micTest)
			m_gaugeMicInput->SetValue ((int) Envelope2Meter(m_component->GetMicInputEnvelope()));		
		else
			m_gaugeMicInput->SetValue (0);

		m_gaugeOutput->SetValue ((int) Envelope2Meter(m_component->GetOutputEnvelope()));
	}

	event.Skip(false);
}

/*!
 * wxEVT_COMMAND_SPINCTRL_UPDATED event handler for ID_SPINCTRL_DELAY
 */

void PureDataConfigPanel::OnSpinctrlDelayUpdated( wxSpinEvent& event )
{	
	m_component->SetDelay (event.GetPosition());
	//CPureDataWrapper::getInstance()->SetIntelligentASIOConfig (event.GetPosition());
    event.Skip(false);
}


/*!
 * wxEVT_COMMAND_RADIOBUTTON_SELECTED event handler for ID_RADIOBUTTON_PLAYSOUND
 */

void PureDataConfigPanel::OnRadiobuttonPlaysoundSelected( wxCommandEvent& event )
{
	if (m_micTest) {
        m_sldMicVol->Enable(false);
        m_gaugeMicInput->Enable(false);
        m_txtMicVol->Enable(false);
		m_gaugeMicInput->SetValue (0);
		m_gaugeOutput->SetValue (0);
		m_micTest= false;
		m_component->SetTestType (PureDataConfigComponent::TEST_PLAY_SOUND);
	}
    event.Skip(false);
}


/*!
 * wxEVT_COMMAND_RADIOBUTTON_SELECTED event handler for ID_RADIOBUTTON_MIC
 */

void PureDataConfigPanel::OnRadiobuttonMicSelected( wxCommandEvent& event )
{
	if (!m_micTest) {
        m_sldMicVol->Enable();
        m_gaugeMicInput->Enable();
        m_txtMicVol->Enable();
		m_micTest= true;
		m_component->SetTestType (PureDataConfigComponent::TEST_MICROPHONE);
	}
    event.Skip(false);
}

/*!
 * wxEVT_COMMAND_BUTTON_CLICKED event handler for ID_BUTTON_OK
 */

void PureDataConfigPanel::OnButtonCloseClick( wxCommandEvent& event )
{
	Close();
	
    event.Skip(false);
}

/*!
 * wxEVT_COMMAND_SLIDER_UPDATED event handler for ID_SLIDER_MICVOL
 */

void PureDataConfigPanel::OnSliderMicvolUpdated( wxCommandEvent& event )
{
	m_component->SetMicInputControl (m_sldMicVol->GetValue());
    event.Skip (false); 
}


/*!
 * wxEVT_COMMAND_SLIDER_UPDATED event handler for ID_SLIDER_OUTVOL
 */

void PureDataConfigPanel::OnSliderOutvolUpdated( wxCommandEvent& event )
{
	m_component->SetOutputControl (m_sldOutVol->GetValue());
	event.Skip (false); 
}

}; // namespace end

