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

#include "spcore/coreruntime.h"

#include <wx/mimetype.h>
#include <wx/utils.h>
#include <wx/intl.h>
#include <wx/regex.h>

#include <string>
#include <stdexcept>

#ifdef WIN32
#include <io.h>
#define access(path,mode) _access(path,mode)
#define R_OK    4               /* Test for read permission.  */
#else
#include <unistd.h>
#endif
#include <errno.h>


using namespace spcore;
using namespace std;

namespace mod_puredata {

enum {	// id for sockets
	SERVER_ID = 100, SOCKET_ID
};

BEGIN_EVENT_TABLE(PureDataWrapper, wxEvtHandler)
    EVT_END_PROCESS(wxID_ANY, PureDataWrapper::OnProcessTerm)
	EVT_SOCKET(SOCKET_ID,  PureDataWrapper::OnSocketEvent)
END_EVENT_TABLE()

PureDataWrapper::PureDataWrapper(const PureDataWrapperKey&)
: m_debugGUIMode(false)
, m_entry(false)
, m_tmpError(false)
, m_pdRunning(false)
, m_pid(0)
, m_whichAPI(-1)
, m_status(PureDataWrapper::NOT_RUNNING)
, m_parserStatus(PureDataWrapper::IGNORE_INPUT)
, m_pdConnection(NULL)
, m_logStream(NULL)
, m_listener(NULL)
, m_process(this)
{

	// Uncomment this line to show pd communication messages
	//	m_logStream= stderr;
}

PureDataWrapper::~PureDataWrapper(void)
{
	StopPD ();
//	DisableLog ();
}


void PureDataWrapper::ParseInput (const char* buffer, wxUint32 size)
{
	//
	// TODO: filter pdtk_post messages to avoid messing with expected messages
	//

	static wxRegEx initRegEx (_T(" *pdtk_pd_startup *{"));
	static wxRegEx whichAPIRegEx (_T("[[:space:]]*set pd_whichapi[[:space:]]*"));
	static wxRegEx openPatchRegEx (_T("[[:space:]]*pdtk_canvas_new[[:space:]]+[.period.][[:alnum:]]+[[:space:]]"));
	static wxRegEx closeOkRegEx (_T("[[:space:]]*destroy *[.period.][[:alnum:]]*"));
	static wxRegEx closeErrorRegEx (_T("[[:space:]]*pdtk_post[[:space:]]*{.*:[[:space:]]*no such object"));
	static wxRegEx waitDSPRegEx (_T("pdtk_pd_dsp"));
	static wxRegEx intRegEx (_T("-?[[:digit:]]"));
	static wxRegEx apiListRegEx (_T("{[[:space:]]*{.*}[[:space:]]*}"));
	static wxRegEx apiListItemRegEx (_T("{[^{}]+}"));
	static wxRegEx audioPropIn1RegEx (_T("global *audio_indevlist;"));
	static wxRegEx audioPropIn2RegEx (_T("lappend audio_indevlist"));
	static wxRegEx audioPropOut1RegEx (_T("global *audio_outdevlist;"));
	static wxRegEx audioPropOut2RegEx (_T("lappend audio_outdevlist"));
	static wxRegEx audioPropValuesRegEx (_T("pdtk_audio_dialog *[.period.][[:alnum:]]+[[:space:]]"));

	assert (initRegEx.IsValid());
	assert (whichAPIRegEx.IsValid());
	assert (openPatchRegEx.IsValid());
	assert (closeOkRegEx.IsValid());
	assert (closeErrorRegEx.IsValid());
	assert (waitDSPRegEx.IsValid());
	assert (intRegEx.IsValid());
	assert (apiListRegEx.IsValid());
	assert (apiListItemRegEx.IsValid());
	assert (audioPropIn1RegEx.IsValid());
	assert (audioPropIn2RegEx.IsValid());
	assert (audioPropOut1RegEx.IsValid());
	assert (audioPropOut2RegEx.IsValid());
	assert (audioPropValuesRegEx.IsValid());

	m_parser.NextBuffer (buffer, (unsigned short) size);

	unsigned short lineSize= 0;
	const char* line= m_parser.getLine(&lineSize);
	while (line) {
		wxString wline(line, wxConvLocal, lineSize);
		if (m_status== PureDataWrapper::INIT1) {
			// Try to parse version
			if (initRegEx.Matches(wline)) {
				size_t start, len;
				if (initRegEx.GetMatch(&start, &len)) {
					m_pdVersion= wline.Mid (len + start);
					m_status= PureDataWrapper::INIT2;
				}
			}
		} else if (m_status== PureDataWrapper::INIT2) {
			// Parse audio API devices
            if (apiListRegEx.Matches(wline)) {
				wxString strApiList= apiListRegEx.GetMatch(wline);
				while (apiListItemRegEx.Matches(strApiList)) {
					TApiDesc apiDesc;
					size_t start, len;
					if (apiListItemRegEx.GetMatch(&start, &len)) {
                        wxString strApi= apiListItemRegEx.GetMatch(strApiList);
						strApi.Replace(_T("{"), _T(" "));
						strApi.Replace(_T("}"), _T(""));
						apiDesc.apiName= strApi.AfterFirst(_T('"')).BeforeLast(_T('"'));
						if (apiDesc.apiName.size()) {
							wxString lval= strApi.AfterLast(_T('"')).Trim();
							if (lval.ToLong(&apiDesc.apiNum)) {
								m_apiList.push_back (apiDesc);
								m_status= PureDataWrapper::INIT3;
							}
						}
						else {
							apiDesc.apiName= strApi.AfterFirst(_T(' ')).BeforeLast(_T(' '));
							wxString lval= strApi.AfterLast(_T(' ')).Trim();
							if (lval.ToLong(&apiDesc.apiNum)) {
								m_apiList.push_back (apiDesc);
								m_status= PureDataWrapper::INIT3;
							}
						}

						strApiList= strApiList.Mid(start + len);
					}
				}
			}
		} else if (m_status== PureDataWrapper::INIT3) {
			// Try to parse which API
			if (whichAPIRegEx.Matches(wline))
				if (intRegEx.Matches(wline))
					if (intRegEx.GetMatch(wline).ToLong(&m_whichAPI))
						m_status= PureDataWrapper::READY;
		} else if (m_status== PureDataWrapper::READY) {
			switch (m_parserStatus)	{
			case PureDataWrapper::IGNORE_INPUT:
				break;
			case PureDataWrapper::WAIT_ACK:	// Change state when data received
				if (wline.Length()> 0) m_parserStatus= PureDataWrapper::IGNORE_INPUT;
				break;
			case PureDataWrapper::WAIT_OPEN_PATCH:
				if (openPatchRegEx.Matches(wline)) {
					m_tmpExchange= openPatchRegEx.GetMatch(wline);
					m_tmpExchange.Trim();
					m_tmpExchange.Trim(false);
					m_tmpExchange= m_tmpExchange.AfterFirst(_T(' '));
					m_parserStatus= PureDataWrapper::IGNORE_INPUT;
				}
				break;
			case PureDataWrapper::WAIT_CLOSE_PATCH:
				if (closeOkRegEx.Matches(wline)) {
                    m_tmpError= false;
					m_parserStatus= PureDataWrapper::IGNORE_INPUT;
				}
				else if (closeErrorRegEx.Matches(wline)) {
					m_tmpError= true;
					m_parserStatus= PureDataWrapper::IGNORE_INPUT;
				}
				break;
			case PureDataWrapper::AUDIO_PROP_IN1:
				if (audioPropIn1RegEx.Matches(wline)) {
					m_audioProperties.m_iDevList.clear();
					m_parserStatus= PureDataWrapper::AUDIO_PROP_IN2;
				}
				break;
			case PureDataWrapper::AUDIO_PROP_IN2:
				if (audioPropIn2RegEx.Matches(wline)) {
					size_t start, len;
					if (audioPropIn2RegEx.GetMatch(&start, &len)) {
                        wxString str= wline.Mid (start+len);
						str.Replace(_T("{"), _T(""));
						str.Replace(_T("}"), _T(""));
						str.Trim();
						str.Trim(false);
						m_audioProperties.m_iDevList.push_back (str);
					}
				}
				else {
					// Try to parse output devices
					m_parserStatus= PureDataWrapper::AUDIO_PROP_OUT1;
					continue;
				}
				break;
			case PureDataWrapper::AUDIO_PROP_OUT1:
				if (audioPropOut1RegEx.Matches(wline)) {
					m_audioProperties.m_oDevList.clear();
					m_parserStatus= PureDataWrapper::AUDIO_PROP_OUT2;
				}
				break;
			case PureDataWrapper::AUDIO_PROP_OUT2:
				if (audioPropOut2RegEx.Matches(wline)) {
					size_t start, len;
					if (audioPropOut2RegEx.GetMatch(&start, &len)) {
                        wxString str= wline.Mid (start+len);
						str.Replace(_T("{"), _T(""));
						str.Replace(_T("}"), _T(""));
						str.Trim();
						str.Trim(false);
						m_audioProperties.m_oDevList.push_back (str);
					}
				}
				else {
					// Try to parse output devices
					m_parserStatus= PureDataWrapper::AUDIO_PROP_VALUES;
					continue;
				}
				break;
			case PureDataWrapper::AUDIO_PROP_VALUES:
				if (audioPropValuesRegEx.Matches(wline)) {
					// Dialog name
					m_tmpExchange.Clear();
					m_tmpExchange= audioPropValuesRegEx.GetMatch(wline);
					m_tmpExchange.Trim();
					m_tmpExchange.Trim(false);
					m_tmpExchange= m_tmpExchange.AfterLast(_T(' '));

					// Get value list
					size_t start, len;
					audioPropValuesRegEx.GetMatch(&start, &len);
					int numRead= sscanf(line + start + len, "%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d",
						&m_audioProperties.m_iDev1, &m_audioProperties.m_iDev2, &m_audioProperties.m_iDev3,
						&m_audioProperties.m_iDev4, &m_audioProperties.m_iDev1Channels, &m_audioProperties.m_iDev2Channels,
						&m_audioProperties.m_iDev3Channels, &m_audioProperties.m_iDev4Channels, &m_audioProperties.m_oDev1,
						&m_audioProperties.m_oDev2, &m_audioProperties.m_oDev3, &m_audioProperties.m_oDev4,
						&m_audioProperties.m_oDev1Channels, &m_audioProperties.m_oDev2Channels,
						&m_audioProperties.m_oDev3Channels, &m_audioProperties.m_oDev4Channels,
                        &m_audioProperties.m_sampleRate, &m_audioProperties.m_msDelay, &m_audioProperties.m_canMulti,
						&m_audioProperties.m_flongForm);
					if (numRead!= 20) m_tmpError= true;
					else m_tmpError= false;
					m_parserStatus= PureDataWrapper::IGNORE_INPUT;
				}
				break;
			case PureDataWrapper::WAIT_WHICH_API:
				if (whichAPIRegEx.Matches(wline))
					if (intRegEx.Matches(wline))
						if (intRegEx.GetMatch(wline).ToLong(&m_whichAPI))
							m_parserStatus= PureDataWrapper::IGNORE_INPUT;
				break;
			}
		} else {
			//
		}
		line= m_parser.getLine(&lineSize);
	}
}

void PureDataWrapper::OnSocketEvent(wxSocketEvent& event)
{
	event.Skip(false);

	switch (event.GetSocketEvent())
	{
	case wxSOCKET_LOST:
		assert (m_pdConnection);
		m_pdConnection->Close();
		m_pdConnection->Discard();
		m_pdConnection->Destroy();
		m_pdConnection= NULL;
		if (m_status!= PureDataWrapper::WAIT_TERMINATE && m_status!= PureDataWrapper::NOT_RUNNING) StopPD();
		break;
	case wxSOCKET_INPUT:
		{
		char readBuff[2048];
		assert (m_pdConnection);
		m_pdConnection->Read (readBuff, 2048);
		if (m_pdConnection->Error()) {
			//throw RunTimeException (_("PdWrapper: Error reading from socket."));
			getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, "Error reading from socket.", "pd wrapper");
			StopPD();
			return;
		}

		ParseInput (readBuff, m_pdConnection->LastCount());

		// log
		if (m_logStream) {
			fwrite (readBuff, 1, m_pdConnection->LastCount(), m_logStream);
			fflush (m_logStream);
		}

		}
		break;
	default:
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_FATAL, "Unexpected socket event.", "pd wrapper");
		StopPD();
	}
}

#define YIELD()	{				\
		if (wxThread::IsMain())	\
            wxSafeYield();		\
        else					\
            wxThread::Yield();	\
	}

bool PureDataWrapper::WaitWhileParserStatusIs (EParserStatus e, int time_cs)
{
	int waitCount= 0;
	while (waitCount< time_cs && m_parserStatus== e) {
		wxMilliSleep(100);
		YIELD();
		waitCount++;
	}
	return (m_parserStatus!= e);
}

bool PureDataWrapper::WaitWhileParserStatusIsNot (EParserStatus e, int time_cs)
{
	int waitCount= 0;
	while (waitCount< time_cs && m_parserStatus!= e) {
		wxMilliSleep(100);
		YIELD();
		waitCount++;
	}
	return (m_parserStatus== e);
}

class SetReset {
public:
	SetReset (bool* var) {	m_val= var; *m_val= true;	}
	~SetReset()	{ *m_val= false; }
private:
	bool* m_val;
};

wxString PureDataWrapper::CorrectFilePath (const wxString& fp) const
{
	wxString retval(fp);
	retval.Replace(_T("\\"), _T("/"));
	retval.Replace(_T(" "), _T("\\ "));
	return retval;
}

void PureDataWrapper::SendMessageToPD (const wxString& msg)
{
	const wxCharBuffer msg_ansi= msg.mb_str(wxConvLocal);
	if (m_logStream) {
		fprintf (m_logStream, "SEND:%s\n", msg_ansi.data());
	}
	m_pdConnection->Write (msg_ansi, (wxUint32) msg.Length());
}

void PureDataWrapper::LaunchPD (const wxString& params)
{
	// Get pd binary
	assert (!m_pdRunning);

	wxString pdBinary;

	wxMimeTypesManager mimeManager;
	wxFileType* ft= mimeManager.GetFileTypeFromExtension(_T("pd"));
	if (ft!= NULL) {
		if (!ft->GetOpenCommand(&pdBinary, wxFileType::MessageParameters(wxEmptyString))) {
			delete ft;
			throw std::runtime_error ("PdWrapper: Cannot get the command to start PureData\nIs Pure Data (PD) installed?");
		}
		delete ft;

		pdBinary.Replace (_T("\"\""), _T(""));
		pdBinary.Trim();
	}
	else {
	    // No mime type
#ifdef WIN32
        throw std::runtime_error ("PdWrapper: .pd extension not associated with PureData\nIs Pure Data (PD) installed?");
#else
        // Is pdextended available
        if (access("/usr/bin/pdextended", X_OK)== 0) pdBinary= _T("/usr/bin/pdextended ");
        else if (access("/usr/local/bin/pdextended", X_OK)== 0) pdBinary= _T("/usr/local/bin/pdextended ");
        else if (access("/usr/bin/puredata", X_OK)== 0) pdBinary= _T("/usr/bin/puredata ");
        else if (access("/usr/local/bin/puredata", X_OK)== 0) pdBinary= _T("/usr/local/bin/puredata ");
        else if (access("/usr/bin/pd", X_OK)== 0) pdBinary= _T("/usr/bin/pd ");
        else if (access("/usr/local/bin/pd", X_OK)== 0) pdBinary= _T("/usr/local/bin/pd ");
        else throw std::runtime_error ("PdWrapper: it seems that Pure Data (PD) is not installed");
#endif
	}

	// Set command line parameters
	pdBinary.Append (params);

	// Run pd
	m_pid= wxExecute(pdBinary, wxEXEC_ASYNC, &m_process);
	if (!m_pid)
		throw std::runtime_error ("PdWrapper: Cannot run PureData\nIs Pure Data (PD) properly installed?");
	m_pdRunning= true;
}

void PureDataWrapper::KillPD ()
{
	assert (m_pdRunning);

	// Send SIGTERM and wait
	m_process.Kill (m_pid, wxSIGTERM);
	int waitCounter= 0;
	while (waitCounter< 20 && m_pdRunning) {
		wxMilliSleep(100);
		YIELD();
		waitCounter++;
	}

	if (!m_pdRunning) return;

	// Still running. Send SIGKILL
	m_process.Kill (m_pid, wxSIGKILL);
	waitCounter= 0;
	while (waitCounter< 50 && m_pdRunning) {
		wxMilliSleep(100);
		YIELD();
		waitCounter++;
	}

	// pd should have died
	assert (!m_pdRunning);
}

void PureDataWrapper::OnProcessTerm(wxProcessEvent& event)
{
	// Free resources
//	assert (m_pdRunning);
	m_pdRunning= false;
	m_pid= 0;
	m_pdVersion.Clear();
	m_whichAPI= -1;
	m_apiList.clear();
	EPdWrapperStatus status= m_status;
	m_status= PureDataWrapper::NOT_RUNNING;

	if (status!= PureDataWrapper::WAIT_TERMINATE) {
		getSpCoreRuntime()->LogMessage (ICoreRuntime::LOG_ERROR, "Pure Data process died unexpectedly", "pd wrapper");
		if (m_listener) m_listener->NotifyStatus(PDWrapperListener::PD_STOPED);
	}

	event.Skip(false);
}

void PureDataWrapper::StartPD ()
{
	if (m_debugGUIMode) return;

	if (m_status== PureDataWrapper::READY) return;
	wxSocketServer* serverSocket= NULL;

	try	{
		// Avoid reentrant calls
		if (m_entry) return;
		SetReset sr(&m_entry);
		assert (m_status== PureDataWrapper::NOT_RUNNING);

		//
		// Create a listening socket
		//
		wxIPV4address bindAddress;
		bool bindOk= false;
		if (!bindAddress.AnyAddress())
			throw std::runtime_error ("PdWrapper: Error setting bind address.");
		// Look for a free port
		unsigned short service= 60000;
		while (!bindOk && service> 40000) {
			if (!bindAddress.Service(service))
				throw std::runtime_error ("PdWrapper: Error setting bind port.");

			// Try to create
			serverSocket= new wxSocketServer (bindAddress);
			if (serverSocket->IsOk()) bindOk= true;	// Bind successful
			else {
				delete serverSocket;
				service--;
			}
		}

		if (!bindOk)
			throw std::runtime_error ("PdWrapper: Error creating server socket. Cannot bind to any port.");

		//
		// Launch pd
		//
		//m_status= PureDataWrapper::WAIT_INCOMING_CONNECTION;
		wxString param;
		param.Printf (_T(" -guiport %u"), service);
		LaunchPD (param);

		//
		// Wait for an incomming connection
		//
		assert (m_pdConnection== NULL);
		m_status= PureDataWrapper::WAIT_INCOMING_CONNECTION;
		int waitCount= 0;
		// Wait for incoming connection (8 sec)
		while (waitCount< 80 && m_status== PureDataWrapper::WAIT_INCOMING_CONNECTION) {
			// Note that "WaitForAccept" will call wxYield so further events are processed while waiting
			if (serverSocket->WaitForAccept(0,100)) {
				// Connection received
				//if (!serverSocket->AcceptWith(m_pdConnection))
				m_pdConnection= serverSocket->Accept(false);
				serverSocket->Destroy();
				serverSocket= NULL;
				if (m_pdConnection== NULL)
					throw std::runtime_error ("PdWrapper: Error while trying to stablish connection with Pure Data.");
				m_status= PureDataWrapper::INIT1;
			}
			waitCount++;
		}
		if (m_status== PureDataWrapper::NOT_RUNNING)
			throw  std::runtime_error ("PdWrapper: Pure Data process died unexpectedly while waiting for incoming connection.");
		else if (waitCount>= 80)
			throw  std::runtime_error ("PdWrapper: Timeout while waiting for incoming connection.");

		//
		// Connect socket events
		//
		m_pdConnection->Notify (false);
		m_pdConnection->SetNotify (wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG);
		m_pdConnection->SetEventHandler (*this, SOCKET_ID);
		m_pdConnection->Notify (true);

		//
		// Read initialization strings
		//
		waitCount= 0;
		while (waitCount< 80 && m_status!= PureDataWrapper::READY) {
			wxMilliSleep(100);
			YIELD();
			waitCount++;
		}
		if (m_status!= PureDataWrapper::READY)
			throw std::runtime_error ("PdWrapper: Unexpected error during waiting for PD initialization.");

		//
		// Send pd init message
		//
		m_parserStatus= PureDataWrapper::WAIT_ACK;
		SendMessageToPD (_T("pd init ") + CorrectFilePath(wxGetCwd()) + _T(";"));
		if (!WaitWhileParserStatusIs (PureDataWrapper::WAIT_ACK, 80)) {
			m_status= PureDataWrapper::READY;
			throw std::runtime_error ("PdWrapper: Timeout while waiting for pd init acknowledgment.");
		}

		GetAudioProperties ();

		StartDSP ();
		// Make sure PD is ready
		//wxMilliSleep(500);

		// Notify listener
		if (m_listener) m_listener->NotifyStatus(PDWrapperListener::PD_RUNNING);
	}
	catch(...)
	{
		// Free resources & rethrow
		if (serverSocket) serverSocket->Destroy();
		if (m_pdConnection) m_pdConnection->Destroy();
		m_pdConnection= NULL;
		if (m_status== PureDataWrapper::READY) StopPD();
		else if (m_pdRunning) KillPD();
		throw;
	}
}

void PureDataWrapper::StopPD ()
{
	// Code to avoid reentrant calls from the same thread
	assert (!m_entry);
	if (m_entry) return;
	SetReset sr(&m_entry);

	if (m_status== PureDataWrapper::NOT_RUNNING ||
		m_status== PureDataWrapper::WAIT_TERMINATE) return;

	assert (m_status!= PureDataWrapper::WAIT_INCOMING_CONNECTION);
	if (m_status== PureDataWrapper::WAIT_INCOMING_CONNECTION) {
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_FATAL, "Inconsistent process status while stopping", "pd wrapper");
		return;
	}

	int waitCounter= 0;

	m_status= PureDataWrapper::WAIT_TERMINATE;

	if (!m_debugGUIMode) {
		// Try to gently stop pd sending a "pd quit;" message
		if (m_pdConnection && m_pdConnection->IsConnected()) {
			static const char* buff= "pd quit;";
			m_pdConnection->Write (buff, (wxUint32) strlen (buff));

			while (waitCounter< 50 && m_status!= PureDataWrapper::NOT_RUNNING) {
				wxMilliSleep(100);
				YIELD();
				waitCounter++;
			}

			if (m_pdConnection) m_pdConnection->Destroy();
			m_pdConnection= NULL;
			if (m_status== PureDataWrapper::NOT_RUNNING) {
				if (m_listener) m_listener->NotifyStatus(PDWrapperListener::PD_STOPED);
				return;
			}
		}
	}

	KillPD();

	if (m_listener) m_listener->NotifyStatus(PDWrapperListener::PD_STOPED);
/*
	// If the previous method did not work send a SIGTERM and wait again
	m_process.Kill (m_pid, wxSIGTERM);
	waitCounter= 0;
	while (waitCounter< 20 && m_status!= PureDataWrapper::NOT_RUNNING) {
		wxMilliSleep(100);
		YIELD();
		waitCounter++;
	}

	if (m_status== PureDataWrapper::NOT_RUNNING) {
		if (m_listener) m_listener->NotifyStatus(PDWrapperListener::PD_STOPED);
		return;
	}

	// Finally kill the process
	m_process.Kill (m_pid, wxSIGKILL);
	waitCounter= 0;
	while (waitCounter< 50 && m_status!= PureDataWrapper::NOT_RUNNING) {
		wxMilliSleep(100);
		YIELD();
		waitCounter++;
	}

	if (m_status== PureDataWrapper::NOT_RUNNING) {
		if (m_listener) m_listener->NotifyStatus(PDWrapperListener::PD_STOPED);
		return;
	}

	// The code should never reach here. If so we only assert because
	// StopPD may have been called from the destructor
	assert (false);
	*/
}


wxString PureDataWrapper::OpenPatch (const wxString& file)
{
	// Check if file exists
	//if (!::wxFileExists(file))
	//	throw std::runtime_error ("PdWrapper: File not found: " + string(file.mb_str()));

	if (!m_debugGUIMode) {
		// Code to avoid reentrant calls from the same thread
		assert (!m_entry);
		if (m_entry) throw std::runtime_error ("PdWrapper: reentrant call");
		SetReset sr(&m_entry);
		if (m_status!= PureDataWrapper::READY) throw std::runtime_error ("PdWrapper: PD not running");

		m_parserStatus= PureDataWrapper::WAIT_OPEN_PATCH;

		wxString name= wxFileNameFromPath(file);
		wxString path= wxPathOnly(file);
		if (path.size()== 0) path= _T(".");
		SendMessageToPD (_T("pd open ") + name + _T(" ") + CorrectFilePath(path) + _T(";"));
		if (!WaitWhileParserStatusIs (PureDataWrapper::WAIT_OPEN_PATCH)) {
			m_parserStatus= PureDataWrapper::IGNORE_INPUT;
			throw std::runtime_error ("PdWrapper: Timeout opening patch.");
		}

		return m_tmpExchange;
	}
	else {
		// GUI Debug mode
		StopPD();	// Stop pd in case it was running

		LaunchPD (_T(" \"") + file + _T("\""));

		m_status= PureDataWrapper::READY;

		return _T("");
	}
}

void PureDataWrapper::ClosePatch (const wxString& patchId)
{
	if (m_debugGUIMode) {
		StopPD();	// Debug mode
		return;
	}

	//assert (!m_entry);
	if (m_entry) return; //throw std::runtime_error ("PdWrapper: reentrant call");
	SetReset sr(&m_entry);
	if (m_status!= PureDataWrapper::READY) throw std::runtime_error ("PdWrapper: PD not running");

	m_parserStatus= PureDataWrapper::WAIT_CLOSE_PATCH;
	m_tmpError= false;
	SendMessageToPD (patchId + _T(" menuclose 0 ;"));
	if (!WaitWhileParserStatusIs (PureDataWrapper::WAIT_CLOSE_PATCH)) {
		m_parserStatus= PureDataWrapper::IGNORE_INPUT;
		throw std::runtime_error ("PdWrapper: Timeout closing patch.");
	}

	if (m_tmpError)
		throw std::runtime_error ("PdWrapper: Closing patch. No such object: " + string(patchId.mb_str()));
}

void PureDataWrapper::StartDSP ()
{
	if (m_debugGUIMode) return;

	//assert (!m_entry);
//	if (m_entry) return; //throw std::runtime_error ("PdWrapper: reentrant call");
//	SetReset sr(&m_entry);
//	if (m_status!= PureDataWrapper::RUNNING) throw std::runtime_error ("PdWrapper: PD not running");

	// Don't wait for ACK. To avoid error when the DSP was already enabled
	SendMessageToPD (_T("pd dsp 1 ;"));
}

void PureDataWrapper::StopDSP ()
{
	if (m_debugGUIMode) return;

	//assert (!m_entry);
//	if (m_entry) return; //throw std::runtime_error ("PdWrapper: reentrant call");
//	SetReset sr(&m_entry);
//	if (m_status!= PureDataWrapper::RUNNING) throw std::runtime_error ("PdWrapper: PD not running");

	SendMessageToPD (_T("pd dsp 0 ;"));
}

void PureDataWrapper::ManageAudioOptionsDialog (const wxString& msg)
{
	m_parserStatus= PureDataWrapper::AUDIO_PROP_IN1;
	SendMessageToPD (msg);
	if (!WaitWhileParserStatusIsNot (PureDataWrapper::IGNORE_INPUT)) {
		m_parserStatus= PureDataWrapper::IGNORE_INPUT;
		throw std::runtime_error ("PdWrapper: Timeout reading audio properties.");
	}

	bool errorFlag= m_tmpError;
	// Close audio dialogue
	m_parserStatus= PureDataWrapper::WAIT_CLOSE_PATCH;
	SendMessageToPD (m_tmpExchange + _T(" cancel ;"));
	if (!WaitWhileParserStatusIs (PureDataWrapper::WAIT_CLOSE_PATCH)) {
		m_parserStatus= PureDataWrapper::IGNORE_INPUT;
		throw std::runtime_error ("PdWrapper: Timeout while closing audio properties dialogue.");
	}

	if (errorFlag)
		throw std::runtime_error ("PdWrapper: Unexpected error while parsing audio properties.");

	if (m_tmpError)
		throw std::runtime_error ("PdWrapper: Unexpected error while closing audio properties dialogue.");
}

void PureDataWrapper::GetAudioProperties ()
{
	if (m_debugGUIMode) return;

	assert (m_status== PureDataWrapper::READY);

	ManageAudioOptionsDialog (_T("pd audio-properties ;"));

//	return new CAudioProperties(m_audioProperties);
}


void PureDataWrapper::SetAudioProperties (bool savePreferences)
{
	if (m_debugGUIMode) return;

	/*
	assert (!m_entry);
	if (m_entry) throw std::runtime_error ("PdWrapper: reentrant call");
	SetReset sr(&m_entry);
	if (m_status!= PureDataWrapper::RUNNING) throw std::runtime_error ("PdWrapper: PD not running");
	*/
	assert (m_status== PureDataWrapper::READY);

	wxString msg;
	msg.Printf (_T("pd audio-dialog %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d ;"),
		m_audioProperties.m_iDev1, m_audioProperties.m_iDev2, m_audioProperties.m_iDev3, m_audioProperties.m_iDev4,
		m_audioProperties.m_iDev1Channels, m_audioProperties.m_iDev2Channels, m_audioProperties.m_iDev3Channels,
		m_audioProperties.m_iDev4Channels, m_audioProperties.m_oDev1, m_audioProperties.m_oDev2,
		m_audioProperties.m_oDev3, m_audioProperties.m_oDev4, m_audioProperties.m_oDev1Channels,
		m_audioProperties.m_oDev2Channels, m_audioProperties.m_oDev3Channels, m_audioProperties.m_oDev4Channels,
        m_audioProperties.m_sampleRate, m_audioProperties.m_msDelay, m_audioProperties.m_flongForm);
	if (savePreferences) msg.Append (_T(" pd save-preferences ;"));

	m_parserStatus= PureDataWrapper::WAIT_WHICH_API;
	SendMessageToPD (msg);
	if (!WaitWhileParserStatusIs (PureDataWrapper::WAIT_WHICH_API)) {
		m_parserStatus= PureDataWrapper::IGNORE_INPUT;
		throw std::runtime_error ("PdWrapper: Timeout while setting audio properties.");
	}
}

void PureDataWrapper::setCurrentAPI(long apiNum)
{
	if (m_debugGUIMode) return;

	assert (m_status== PureDataWrapper::READY);

	// Ensure apiNum is correct
	bool found= false;
	for (unsigned int i= 0; i< m_apiList.size(); i++)
		if (m_apiList[i].apiNum== apiNum) {
			found= true;
			break;
		}

	if (!found) throw std::runtime_error ("PdWrapper: API id not available.");

	wxString msg;
	msg.Printf (_T("pd audio-setapi %d ;"), apiNum);
	ManageAudioOptionsDialog (msg);

	m_whichAPI= apiNum;
}

unsigned int PureDataWrapper::GetDelay()
{
	if (m_debugGUIMode) return 0;

//	assert (!m_entry);
//	if (m_entry) throw std::runtime_error ("PdWrapper: reentrant call");
//	SetReset sr(&m_entry);
	if (m_status!= PureDataWrapper::READY) throw std::runtime_error ("PdWrapper: PD not running");

	return m_audioProperties.m_msDelay;
}

void PureDataWrapper::SetDelay(unsigned int delay)
{
	if (m_debugGUIMode) return;

//	assert (!m_entry);
	if (m_entry) return; //throw std::runtime_error ("PdWrapper: reentrant call");
	SetReset sr(&m_entry);
	if (m_status!= PureDataWrapper::READY) throw std::runtime_error ("PdWrapper: PD not running");

	m_audioProperties.m_msDelay= delay;

	SetAudioProperties ();
}

void PureDataWrapper::SetIntelligentASIOConfig (int delay, bool savePreferences)
{
	if (m_debugGUIMode) return;

	assert (!m_entry);
	if (m_entry) throw std::runtime_error ("PdWrapper: reentrant call");
	SetReset sr(&m_entry);
	if (m_status!= PureDataWrapper::READY) throw std::runtime_error ("PdWrapper: PD not running");

	// Search ASIO api number
	unsigned int i;
	for (i= 0; i< m_apiList.size(); i++) {
		wxString name= m_apiList[i].apiName;
		name.MakeLower();
		if (name.Contains(_T("asio"))) break;	// Found
	}

	if (i== m_apiList.size())
		throw std::runtime_error ("Can not detect ASIO drivers. Are they installed?");

	// Switch to ASIO API
	setCurrentAPI(m_apiList[i].apiNum);

	/*
	// Read configuration
	CAudioProperties* ap= getAudioProperties();

	if (!ap)
		throw std::runtime_error ("Can not get audio properties.");

	try {
	*/
		//
		// Configure properties
		//

		// Input device
		unsigned int iDevNum;
		for (iDevNum= 0; iDevNum< m_audioProperties.m_iDevList.size(); iDevNum++)
			if (m_audioProperties.m_iDevList[iDevNum].Lower().Contains(_T("asio"))) break;	// Found
		if (iDevNum== m_audioProperties.m_iDevList.size())
			throw std::runtime_error ("Can not detect ASIO drivers. Are they installed?");

		// Output device
		unsigned int oDevNum;
		for (oDevNum= 0; oDevNum< m_audioProperties.m_oDevList.size(); oDevNum++)
			if (m_audioProperties.m_oDevList[oDevNum].Lower().Contains(_T("asio"))) break;	// Found
		if (oDevNum== m_audioProperties.m_oDevList.size())
			throw std::runtime_error ("Can not detect ASIO drivers. Are they installed?");

		// Set audio parameters
		m_audioProperties.m_iDev1= iDevNum;
		m_audioProperties.m_iDev2= m_audioProperties.m_iDev3= m_audioProperties.m_iDev4= 0;
		m_audioProperties.m_iDev1Channels= 2;		// Assumes two audio channels
		m_audioProperties.m_iDev2Channels= m_audioProperties.m_iDev3Channels= m_audioProperties.m_iDev4Channels= 0;
		m_audioProperties.m_oDev1= oDevNum;
		m_audioProperties.m_oDev2= m_audioProperties.m_oDev3= m_audioProperties.m_oDev4= 0;
		m_audioProperties.m_oDev1Channels=2;		// Assumes two audio channels
		m_audioProperties.m_oDev2Channels= m_audioProperties.m_oDev3Channels= m_audioProperties.m_oDev4Channels= 0;
		if (delay!= -1) m_audioProperties.m_msDelay= delay;

		SetAudioProperties (savePreferences);
/*
		delete ap;
	}
	catch (...)
	{
		if (ap) delete ap;
		throw;
	}
	*/
}

void PureDataWrapper::SaveSettings()
{
	if (m_debugGUIMode) return;

	//assert (!m_entry);
	if (m_entry) return; //throw std::runtime_error ("PdWrapper: reentrant call");
	SetReset sr(&m_entry);
	if (m_status!= PureDataWrapper::READY) throw std::runtime_error ("PdWrapper: PD not running");

	SetAudioProperties (true);

	/*
	CAudioProperties* ap= getAudioProperties ();
	if (!ap)
		throw std::runtime_error ("Can not get audio properties.");
	try {
		setAudioProperties (*ap, true);
	}
	catch(...)
	{
		delete ap;
		throw;
	}
	delete ap;
	*/
}
/*
// Provides a basic debug mode in which only one patch can be loaded
// at given time but with the full PD GUI open.
void PureDataWrapper::setDebugGUIMode (bool enable)
{
	if (m_debugGUIMode== enable) return;

	// Stop pd in case it was running
	StopPD();

	m_debugGUIMode= enable;
}

void PureDataWrapper::EnableLog (FILE *stream)
{
	if (stream== NULL) {
		m_logStream= stdout;
	} else {
		m_logStream= stream;
	}
}

void PureDataWrapper::DisableLog ()
{
	m_logStream= NULL;
}*/

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

PureDataController* PureDataController::g_single= NULL;

PureDataController* PureDataController::getInstance()
{
	if (g_single== NULL)
		g_single= new PureDataController();
	return g_single;
}

void PureDataController::destroyInstance()
{
	if (g_single!= NULL) {
		delete g_single;
		g_single= NULL;
	}
}

PureDataController::PureDataController()
: m_usageCount(0)
, m_wrapper(PureDataWrapperKey())
{
}


PureDataController::~PureDataController()
{
	m_wrapper.StopPD();
}

unsigned int PureDataController::GetDelay()
{
	assert (m_usageCount);
	if (!m_usageCount)
		throw std::runtime_error ("pure data not running");

	return m_wrapper.GetDelay();
}

void PureDataController::SetDelay(unsigned int delay)
{
	assert (m_usageCount);
	if (!m_usageCount)
		throw std::runtime_error ("pure data not running");

	m_wrapper.SetDelay(delay);
}

void PureDataController::SaveSettings()
{
	assert (m_usageCount);
	if (!m_usageCount)
		throw std::runtime_error ("pure data not running");

	m_wrapper.SaveSettings();
}

void PureDataController::IncUsageCount()
{
	if (m_usageCount== 0) {
		m_wrapper.StartPD();
		//m_wrapper.StartDSP();
		//m_wrapper.SetIntelligentASIOConfig();
	}

	++m_usageCount;
}

void PureDataController::DecUsageCount()
{
	assert (m_usageCount>= 0);

	if (m_usageCount== 0) return;

	--m_usageCount;
	if (m_usageCount== 0) m_wrapper.StopPD();
}

void PureDataController::RegisterPatch (IPdPatch* p)
{
	// Already registered?
	vector<pair<IPdPatch*, wxString> >::const_iterator it= m_patchs.begin();
	for (; it!= m_patchs.end(); ++it)
		if (it->first== p) break;

	if (it!= m_patchs.end())
		throw std::runtime_error ("Patch already registered");

	// File exists? Check before sending request to pd just to provide
	// better diagnostic when there is any problem. Note that access
	// syscall introduces a race condition within the time this call
	// is performed and the file is actually opened.
	if (access(p->GetPatchFileName(), R_OK)!= 0) {
		string msg("Cannot open patch.");
		switch(errno) {
		case EACCES:
			msg+= " Permision denied: ";
			msg+= p->GetPatchFileName();
			throw std::runtime_error (msg);
		case ENOENT:
			msg+= " File does not exist: ";
			msg+= p->GetPatchFileName();
			throw std::runtime_error (msg);
		default:
			msg+= " Unknown error: ";
			msg+= p->GetPatchFileName();
			throw std::runtime_error (msg);
		}
	}

	// Need to start pd?
	IncUsageCount();

	// Try to open patch
	wxString patchId;
	try {
		patchId= m_wrapper.OpenPatch (wxString(p->GetPatchFileName(), wxConvUTF8));
		// Add to registered open patchs
		m_patchs.push_back ( make_pair(p, patchId) );
	}
	catch (...) {
		DecUsageCount();
		throw;
	}
}

void PureDataController::UnregisterPatch (IPdPatch* p)
{
	// Registered?
	vector<pair<IPdPatch*, wxString> >::iterator it= m_patchs.begin();
	for (; it!= m_patchs.end(); ++it)
		if (it->first== p) break;

	if (it== m_patchs.end())
		throw std::runtime_error ("Patch not fount when unregistring");

	// Close patch
	m_wrapper.ClosePatch(it->second);

	m_patchs.erase (it);

	DecUsageCount();
}

void PureDataController::NotifyStatus (PDStatus e)
{
	//
	// Notify in reverse order and using and index instead of an iterator
	// to handle properly unregistration which can be triggered by this notification
	//
	for (int i= m_patchs.size() - 1; i>= 0; --i)
		m_patchs[i].first->NotifyStatus(e);

	if (e== PD_STOPED) {
		m_usageCount= 0;
	}
}

};
