/////////////////////////////////////////////////////////////////////////////
// Name:       puredatawrapper.h
// 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/>.
/////////////////////////////////////////////////////////////////////////////

//
// The PureDataWrapper need a complete rewrite to make it run from a different
// thread, get rid off the awful "yield" calls and better model its status
//

#ifndef PUREDATAWRAPPER_H
#define PUREDATAWRAPPER_H

#include <wx/string.h>
#include <wx/event.h>
#include <wx/process.h>
#include <wx/socket.h>

#include <vector>

#include <boost/utility.hpp>

namespace mod_puredata {

// Help to convert read buffer in lines
class CSimpleParser {
public:
	CSimpleParser()	{ Begin(); }
	void Begin () {	// Reset internal state
		m_inBufferSize= m_inBufferIndex= m_lineBufferSize= 0;
		m_inBufferPtr= NULL;
		m_lineBuffer[0]= 0;		
	}
	// Change buffer
	void NextBuffer (const char* buff, unsigned short size)
	{
		assert (buff);
		assert (m_inBufferSize== m_inBufferIndex);	// Ensure last buffer was fully processed
        m_inBufferPtr= buff;
		m_inBufferSize= size;
		m_inBufferIndex= 0;
	}
	// Get the next line if avaible, or NULL if no further lines available
    const char* getLine(unsigned short* lineSize= NULL)
	{
		if (m_inBufferSize== m_inBufferIndex) return NULL;	

		// Append to current line or start a new one?
		if (m_lineBuffer[m_lineBufferSize]== 0)	m_lineBufferSize= 0;	// New line

		// Copy line until new line, inBuffer end or lineBuffer full
		bool eolFound= false;
		while (m_lineBufferSize< MAXLINE-1 && m_inBufferIndex< m_inBufferSize && !eolFound)	{
			if (m_inBufferPtr[m_inBufferIndex]== '\n') {
				m_lineBuffer[m_lineBufferSize]= 0;	// EOL
				eolFound= true;				
			}
			else {
				m_lineBuffer[m_lineBufferSize]= m_inBufferPtr[m_inBufferIndex];
				m_lineBufferSize++;
			}
			m_inBufferIndex++;
		}
		if (eolFound) {
			if (lineSize) *lineSize= m_lineBufferSize;
			return m_lineBuffer;
		}
		if (m_lineBufferSize== MAXLINE-1) {
			// Not enough size to hold the entire line
			m_lineBuffer[m_lineBufferSize]= 0;
			m_lineBufferSize++;
			if (lineSize) *lineSize= m_lineBufferSize;
			return m_lineBuffer;
		}			
		return NULL;
	}
private:
	enum { MAXLINE= 512 };	
	unsigned short m_inBufferSize;
	unsigned short m_inBufferIndex;
	const char* m_inBufferPtr;
	unsigned short m_lineBufferSize;
	char m_lineBuffer[MAXLINE];	
};

class CAudioProperties
{	
public:
	int GetDelay() { return m_msDelay; }
private:
	CAudioProperties ()	{
		m_iDev1= m_iDev2= m_iDev3= m_iDev4= 0;
		m_iDev1Channels= m_iDev2Channels= m_iDev3Channels= m_iDev4Channels= 0;
		m_oDev1= m_oDev2= m_oDev3= m_oDev4= 0;
		m_oDev1Channels= m_oDev2Channels= m_oDev3Channels= m_oDev4Channels= 0;
		m_sampleRate= m_msDelay= m_canMulti= m_flongForm= 0;
	}

	std::vector<wxString> m_iDevList;
	std::vector<wxString> m_oDevList;

	// Choosen input devices
	int m_iDev1, m_iDev2, m_iDev3, m_iDev4;
	// Number of channels for each input (negative to disable input)
	int m_iDev1Channels, m_iDev2Channels, m_iDev3Channels, m_iDev4Channels;

	// Choosen output devices
	int m_oDev1, m_oDev2, m_oDev3, m_oDev4;
	// Number of channels for each output (negative to disable output)
	int m_oDev1Channels, m_oDev2Channels, m_oDev3Channels, m_oDev4Channels;

	int m_sampleRate;
	int m_msDelay;
	int m_canMulti;	// Number of max simultanious devices
	int m_flongForm;	// Undocumented

	friend class PureDataWrapper;

};

// Listener interface to be notified of status changes
class PDWrapperListener 
{
public:
	enum PDStatus { PD_RUNNING, PD_STOPED };

	virtual void NotifyStatus (PDStatus e) = 0;
};


// "Key" to allow only PureDataController to construct PureDataWrapper objects
class PureDataWrapperKey : boost::noncopyable
{
	friend class PureDataController;
	PureDataWrapperKey() {}
};

class PureDataWrapper : public wxEvtHandler
{
public:
	// Construction (only controller allowed)
	PureDataWrapper(const PureDataWrapperKey&);
	~PureDataWrapper();

	void SetNotifyCallback(PDWrapperListener* l) { m_listener= l; }

	void StartPD ();
	void StopPD ();


	void SetDelay(unsigned int delay);
	unsigned int GetDelay();
	
	// Save settings internally to PD
	void SaveSettings();

	// Opens a patch and returns the pathId or raises an exception if failed
	wxString OpenPatch (const wxString& file);
	void ClosePatch (const wxString& patchId);	

private:
	// Tries to automatically provide a suitable configuration unsing ASIO drivers
	// on Windows (TODO: Linux). Throws an exception if an error happens
	void SetIntelligentASIOConfig (int delay= -1, bool savePreferences= false);

	
	void StartDSP ();
	void StopDSP ();

	void KillPD();
	/*
	bool getDebugGUIMode () const { return m_debugGUIMode; }
	void setDebugGUIMode (bool enable);

	void EnableLog (FILE *stream= NULL);
	void DisableLog ();
	bool IsLogEnabled () { return m_logStream!= NULL; }
*/
	// Enums
	enum EPdWrapperStatus {NOT_RUNNING= 0, WAIT_INCOMING_CONNECTION, INIT1, INIT2, INIT3, READY, WAIT_TERMINATE};
	enum EParserStatus { IGNORE_INPUT= 0, WAIT_ACK, WAIT_OPEN_PATCH, WAIT_CLOSE_PATCH, AUDIO_PROP_IN1, AUDIO_PROP_IN2,
						 AUDIO_PROP_OUT1, AUDIO_PROP_OUT2, AUDIO_PROP_VALUES, WAIT_WHICH_API };
	
	// Auxiliary
    void ParseInput (const char* buffer, wxUint32 size);
	wxString CorrectFilePath (const wxString& fp) const;
	// True is state changed to e, false if timeout
	bool WaitWhileParserStatusIs (EParserStatus e, int time_cs= 50);
	// True is state, false if timeout
	bool WaitWhileParserStatusIsNot (EParserStatus e, int time_cs= 50);
	void SendMessageToPD (const wxString& msg);
	void ManageAudioOptionsDialog (const wxString& msg);

	void GetAudioProperties ();
	void SetAudioProperties (bool savePreferences= false);

	//long getCurrentAPI() const { return m_whichAPI; }
	void setCurrentAPI(long apiNum);

	//const wxString& getVersion() { return m_pdVersion; }

	// Tries to start pd
	void LaunchPD (const wxString& params);
	
	// Check whether the call is not reentrant and PD is running, used by
	// almost all public methods. Returns false if is not possible to run
	// the method now (i.e. the caller should return)
//	bool CommonChecks ();
	
	// Events
	void OnProcessTerm(wxProcessEvent& event);
	void OnSocketEvent(wxSocketEvent& event);

	//
	// Attributes
	//
	bool m_debugGUIMode;
	bool m_entry;
	bool m_tmpError;
	bool m_pdRunning;

	long m_pid;
	long m_whichAPI;

	EPdWrapperStatus m_status;
	EParserStatus m_parserStatus;

	wxSocketBase* m_pdConnection;
	FILE* m_logStream;
	PDWrapperListener* m_listener;

	wxString m_patchName;
	wxString m_pdVersion;	
	wxString m_tmpExchange;
	wxProcess m_process;

	typedef struct { wxString apiName; long apiNum; } TApiDesc;
	std::vector<TApiDesc> m_apiList;

	CAudioProperties m_audioProperties;
	CSimpleParser m_parser;
	//long m_msAudioBuffer;

	// any class wishing to process wxWidgets events must use this macro
	DECLARE_EVENT_TABLE()
};

// Interface for patch objects
class IPdPatch : public PDWrapperListener
{
public:
	virtual const char* GetPatchFileName() const = 0;
};

// Auxiliary class to manage automatic PD start/stop when opening/closing patchs
class PureDataController : public PDWrapperListener
{
public:
	static PureDataController* getInstance();


	// Register a new patch to the controller which automatically
	// starts pd (when needed) and opens it. Throws exception when error
	void RegisterPatch (IPdPatch* p);

	// Unregister a patch from the controller which automatically
	// closes the patch and stops pd (if it is the last one). 
	// Throws exception when error
	void UnregisterPatch (IPdPatch* p);

	//
	// These methods are intended for the PureDataConfigComponent class
	//

	unsigned int GetDelay();
	void SetDelay(unsigned int delay);

	void SaveSettings();

private:
	PureDataController();
	~PureDataController();

	// Callback implementation
	virtual void NotifyStatus (PDStatus e);

	// 
	// Internal methods
	//

	friend class PureDataConfigComponent;

	// Increment usage count starting PD when necessary
	void IncUsageCount();

	// Decrement usage count stoping PD when necessary
	// Don't throw
	void DecUsageCount();

	//
	// Singleton
	//
	friend class PureDataModule;
	static void destroyInstance();
	static PureDataController* g_single;

	//
	// Attributes
	//
	unsigned int m_usageCount;
	std::vector<std::pair<IPdPatch*, wxString> > m_patchs;	
	PureDataWrapper m_wrapper;
};

};

#endif