// NodeRef.h (Cortex/NodeManager)
//
// * PURPOSE
//   Represents a NodeManager reference to a live Media Kit Node.
//
// * FEATURES UNDER CONSIDERATION +++++
//   - lazy referencing: m_released becomes m_referenced; only reference
//     externally created nodes once they've been grouped or connected.
//     +++++ or disconnected? keep a'thinkin...
//   - expose dormant_node_info
//
// * HISTORY
//   e.moon		11aug99		Added kind().
//   e.moon		9aug99		Moved position & cycle threads into NodeRef;
//											no more 'group transport thread'.
//   e.moon		25jun99		Begun

#ifndef __NodeRef_H__
#define __NodeRef_H__

#include <MediaAddOn.h>
#include <MediaNode.h>
#include <String.h>

#include <vector>

#include "ILockable.h"
#include "MultiInvoker.h"
#include "ObservableHandler.h"
#include "observe.h"

#include "cortex_defs.h"

#if CORTEX_XML
	#include "IPersistent.h"
#endif

__BEGIN_CORTEX_NAMESPACE

class Connection;
class NodeManager;
class NodeGroup;
class NodeSyncThread;

class NodeRef :
	public	ObservableHandler,
	protected	ILockable
#if CORTEX_XML
	, public		IPersistent
#endif
{

	typedef	ObservableHandler _inherited;

	friend NodeManager;
	friend NodeGroup;

public:				// *** messages
	enum message_t {
		// OUTBOUND
		//  nodeID: int32
		//  target: BMessenger
		M_OBSERVER_ADDED			=NodeRef_message_base,
		M_OBSERVER_REMOVED,
		M_RELEASED,
		
		// OUTBOUND
		// nodeID: int32
		// groupID: int32
		M_GROUP_CHANGED,		
		
		// nodeID: int32
		// runMode: int32
		M_RUN_MODE_CHANGED,

		// +++++
		M_INPUTS_CHANGED, //nyi
		M_OUTPUTS_CHANGED,  //nyi
		
		// OUTBOUND
		//  * only sent to position listeners
		//  nodeID:	         int32
		//  when:      		   int64
		//  position:        int64
		M_POSITION,
		
		// INBOUND
		// runMode: int32
		// delay: int64 (bigtime_t; applies only to B_RECORDING mode)
		M_SET_RUN_MODE,
		
		// INBOUND
		M_PREROLL,
		
		// INBOUND
		// "cycling"; bool (OR "be:value"; int32)
		M_SET_CYCLING,
		
		// OUTBOUND
		// "cycling"; bool
		M_CYCLING_CHANGED
	};

public:				// *** flags
	enum flag_t {
		// node-level transport restrictions
		NO_START_STOP						= 1<<1,
		NO_SEEK									= 1<<2,
		NO_PREROLL							= 1<<3,
		
		// [e.moon 28sep99] useful for misbehaved nodes
		NO_STOP									= 1<<4,
		
		// [e.moon 11oct99]
		// Disables media-roster connection (which currently
		// only makes use of B_MEDIA_NODE_STOPPED.)
		// This flag may become deprecated as the Media Kit
		// evolves (ie. more node-level notifications come
		// along.)
		NO_ROSTER_WATCH					= 1<<5,
		
		// [e.moon 14oct99]
		// Disables position reporting (via BMediaRoster::SyncToNode().)
		// Some system-provided nodes tend to explode when SyncToNode() is
		// called on them (like the video-file player in BeOS R4.5.2).
		NO_POSITION_REPORTING		= 1<<6
	};

public:				// *** dtor

	// free the node (this call will result in the eventual
	// deletion of the object.)
	// returns B_OK on success; B_NOT_ALLOWED if release() has
	// already been called; other error codes if the Media Roster
	// call fails.
	
	status_t release();

	// call release() rather than deleting NodeRef objects
	virtual ~NodeRef();

public:				// *** const accessors
	// [e.moon 13oct99] moved method definitions here to keep inline
	// in the face of a balky PPC compiler
	inline const media_node& node() const { return m_info.node; }
	inline uint32 kind() const { return m_info.node.kind; }
	inline const live_node_info& nodeInfo() const { return m_info; }
	inline const char* name() const { return m_info.name; }
	inline media_node_id id() const { return m_info.node.node; }

public:				// *** member access

	// turn cycle mode (looping) on or off	
	void setCycling(
		bool												cycle);
	bool isCycling() const;
	
	// is the node running?
	bool isRunning() const;
	
	// was the node created via NodeManager::instantiate()?
	bool isInternal() const;

	// fetch the group, or 0 if the node is ungrouped.
	NodeGroup* group() const;
	
	// flag access
	uint32 flags() const;
	void setFlags(
		uint32											flags);
	
	// [e.moon 29sep99]
	// access addon-hint info
	// - returns B_BAD_VALUE if not an add-on node created by this NodeManager

	status_t getDormantNodeInfo(
		dormant_node_info*					outInfo); //nyi

	// [e.moon 29sep99]
	// access file being played
	// - returns B_BAD_VALUE if not an add-on node created by this NodeManager,
	//   or if the node has no associated file

	status_t getFile(
		const entry_ref*						outFile); //nyi 

//		now implemented by ObservableHandler [20aug99]
//	// has this reference been released?
//	bool isReleased() const;

public:				// *** run-mode operations
	void setRunMode(
		uint32											runMode,
		bigtime_t										delay=0LL);
	uint32 runMode() const;
	
	// calculates the minimum amount of delay needed for
	// B_RECORDING mode
	// +++++ 15sep99: returns biggest_output_buffer_duration * 2
	// +++++ 28sep99: adds downstream latency
	bigtime_t calculateRecordingModeDelay(); //nyi

public:				// *** connection access

	// connection access: vector versions
	
	status_t getInputConnections(
		vector<Connection>&		ioConnections,
		media_type									filterType=B_MEDIA_UNKNOWN_TYPE) const;

	status_t getOutputConnections(
		vector<Connection>&		ioConnections,
		media_type									filterType=B_MEDIA_UNKNOWN_TYPE) const;
	
	// connection access: flat array versions
	
	status_t getInputConnections(
		Connection*									outConnections,
		int32												maxConnections,
		int32*											outNumConnections,
		media_type									filterType=B_MEDIA_UNKNOWN_TYPE) const;

	status_t getOutputConnections(
		Connection*									outConnections,
		int32												maxConnections,
		int32*											outNumConnections,
		media_type									filterType=B_MEDIA_UNKNOWN_TYPE) const;

	// +++++ connection matching by source/destination +++++

public:				// *** position reporting/listening

	bool positionReportsEnabled() const;
	
	void enablePositionReports();
	void disablePositionReports();
	
	// Fetch the approximate current position:
	//   Returns the last reported position, and the
	//   performance time at which that position was reached.  If the
	//   transport has never been started, the start position and
	//   a performance time of 0 will be returned.  If position updating
	//   isn't currently enabled, returns B_NOT_ALLOWED.
	
	status_t getLastPosition(
		bigtime_t*									outPosition,
		bigtime_t*									outPerfTime) const;

	// Subscribe to regular position reports:
	//   Position reporting isn't rolled into the regular observer
	//   interface because a large number of messages are generated
	//   (the frequency can be changed; see below.)
	
	status_t addPositionObserver(
		BHandler*										handler);
		
	status_t removePositionObserver(
		BHandler*										handler);
	
	// Set how often position updates will be sent:
	//   Realistically, period should be > 10000 or so.
	
	status_t setPositionUpdatePeriod(
		bigtime_t										period);
		
	bigtime_t positionUpdatePeriod() const;

public:				// *** BMediaRoster wrappers & convenience methods

	// release the media node
	// (if allowed, will trigger the release/deletion of this object)
	status_t releaseNode();

	// calculate total (internal + downstream) latency for this node
	
	status_t totalLatency(
		bigtime_t*									outLatency) const;


	// retrieve input/output matching the given destination/source.
	// returns B_MEDIA_BAD_[SOURCE | DESTINATION] if the destination
	// or source don't correspond to this node.
	
	status_t findInput(
		const media_destination&		forDestination,
		media_input*								outInput) const;
		
	status_t findOutput(
		const media_source&					forSource,
		media_output*								outOutput) const;


	// endpoint matching (given name and/or format as 'hints').
	// If no hints are given, returns the first free endpoint, if
	// any exist.
	// returns B_ERROR if no matching endpoint is found.
	
	status_t findFreeInput(
		media_input*								outInput,
		media_type									type=B_MEDIA_UNKNOWN_TYPE,
		const char*									name=0) const;

	status_t findFreeInput(
		media_input*								outInput,
		const media_format*					format,
		const char*									name=0) const;

	status_t findFreeOutput(
		media_output*								outOutput,
		media_type									type=B_MEDIA_UNKNOWN_TYPE,
		const char*									name=0) const;

	status_t findFreeOutput(
		media_output*								outOutput,
		const media_format*					format,
		const char*									name=0) const;

	// node endpoint access: vector versions (wrappers for BMediaRoster
	// calls.)
	
	status_t getFreeInputs(
		vector<media_input>&		ioInputs,
		media_type									filterType=B_MEDIA_UNKNOWN_TYPE) const;

	status_t getConnectedInputs(
		vector<media_input>&		ioInputs,
		media_type									filterType=B_MEDIA_UNKNOWN_TYPE) const;

	status_t getFreeOutputs(
		vector<media_output>&	ioOutputs,
		media_type									filterType=B_MEDIA_UNKNOWN_TYPE) const;

	status_t getConnectedOutputs(
		vector<media_output>&	ioOutputs,
		media_type									filterType=B_MEDIA_UNKNOWN_TYPE) const;
		
		
	// node endpoint access: array versions (wrappers for BMediaRoster
	// calls.)
	
	status_t getFreeInputs(
		media_input*								outInputs,
		int32												maxInputs,
		int32*											outNumInputs,
		media_type									filterType=B_MEDIA_UNKNOWN_TYPE) const;
	
	status_t getConnectedInputs(
		media_input*								outInputs,
		int32												maxInputs,
		int32*											outNumInputs) const;
	
	status_t getFreeOutputs(
		media_output*								outOutputs,
		int32												maxOutputs,
		int32*											outNumOutputs,
		media_type									filterType=B_MEDIA_UNKNOWN_TYPE) const;
	
	status_t getConnectedOutputs(
		media_output*								outOutputs,
		int32												maxOutputs,
		int32*											outNumOutputs) const;
	
#if CORTEX_XML
public:													// *** IPersistent
	
	// [e.moon 04oct99: begun]

	// EXPORT:
	// implement this method to write a start tag via
	// context.startElement().  You can also write comments or
	// processing instructions directly to the stream.
	
	virtual void xmlExportBegin(
		ExportContext&							context) const; //nyi
		
	// EXPORT:
	// implement this method to write all the attributes needed
	// to represent your object, via context.writeAttribute().
	// (If subclassing an IPersistent implementation, don't forget
	// to call up to its version of this method.)
	
	virtual void xmlExportAttributes(
		ExportContext&							context) const; //nyi
		
	// EXPORT: optional
	// implement this method to write any child objects, using
	// context.writeObject().  You can also write text content
	// directly to the stream.
	// (If subclassing an IPersistent implementation, don't forget
	// to call up to its version of this method.)
	
	virtual void xmlExportContent(
		ExportContext&							context) const; //nyi
	
	// EXPORT:
	// implement this method to write an end tag via
	// context.endElement().
	
	virtual void xmlExportEnd(
		ExportContext&							context) const; //nyi
	
	// IMPORT:
	// called when the start tag of the element corresponding to
	// this object is encountered, immediately after this object
	// is constructed.  no action is required.
	//
	// context.element() will return the element name that produced
	// this object.
	
	virtual void xmlImportBegin(
		ImportContext&							context); //nyi
	
	// IMPORT:
	// called for each attribute/value pair found in
	// the element corresponding to this object.
	
	virtual void xmlImportAttribute(
		const char*									key,
		const char*									value,
		ImportContext&							context); //nyi
		
	// IMPORT:
	// called when character data is encountered for the
	// element corresponding to this object.  data is not
	// 0-terminated; this method may be called several times
	// (if, for example, the character data is broken up
	//  by child elements.)
	
	virtual void xmlImportContent(
		const char*									data,
		uint32											length,
		ImportContext&							context); //nyi
		
	// IMPORT:
	// called when an object has been successfully
	// constructed 'beneath' this one.
	//
	// context.element() will return the element name corresponding
	// to the child object.
	
	virtual void xmlImportChild(
		IPersistent*								child,
		ImportContext&							context); //nyi
		
	// IMPORT:
	// called when close tag is encountered for the element
	// corresponding to this object.  a good place to do
	// validation.
	//
	// context.element() will return the element name that produced
	// this object.
	
	virtual void xmlImportComplete(
		ImportContext&							context); //nyi
	
#endif /*CORTEX_XML*/

public:													// *** BHandler:
	virtual void MessageReceived(
		BMessage*								message);
		
public:				// *** IObservable:		[20aug99]
	virtual void observerAdded(
		const BMessenger&				observer);
		
	virtual void observerRemoved(
		const BMessenger&				observer);

	virtual void notifyRelease();

	virtual void releaseComplete();

protected:		// *** ILockable
							//     Only WRITE locking is allowed!

	bool lock(
		lock_t type=WRITE,
		bigtime_t timeout=B_INFINITE_TIMEOUT);	
	bool unlock(
		lock_t type=WRITE);	
	bool isLocked(
		lock_t type=WRITE) const;

protected:			// *** ctor (accessible to NodeManager)
	// throws runtime_error
	NodeRef(
		const media_node&		node,
		NodeManager*				manager,
		uint32							userFlags,
		uint32							implFlags);

protected:			// *** endpoint-fixing operations (no lock required)

	// 'fix' (fill in node if needed) sets of inputs/outputs
	void _fixInputs(
		media_input*									inputs,
		int32													count) const;
	void _fixInputs(
		vector<media_input>&			inputs) const;

	void _fixOutputs(
		media_output*									outputs,
		int32													count) const;
	void _fixOutputs(
		vector<media_output>&		outputs) const;

protected:			// *** internal/NodeManager operations (LOCK REQUIRED)

	// call after instantiation to register info used to select and
	// create this add-on node

	void _setAddonHint(
		const dormant_node_info*			info,
		const entry_ref*							file=0);
		
	// call to set a new group; if 0, the node must have no
	// connections
	void _setGroup(
		NodeGroup*										group);
		
	// *** NodeGroup API ***
	//     9aug99: moved from NodeGroup
	//     [e.moon 13oct99] removed inlines for the sake of PPC sanity
		
	// initialize the given node's transport-state members
	// (this may be called from the transport thread or from
	//  an API-implementation method.)
	
	status_t _initTransportState();
		
	status_t _setTimeSource(
		media_node_id					timeSourceID);
		
	status_t _setRunMode(
		const uint32					runMode,
		bigtime_t							delay=0LL);
		
	status_t _setRunModeAuto(
		const uint32					runMode);
	
	// seek and preroll the given node.
	// *** this method should not be called from the transport thread
	// (since preroll operations can block for a relatively long time.)
	//
	// returns B_NOT_ALLOWED if the node is running, or if its NO_PREROLL
	// flag is set; otherwise, returns B_OK on success or a Media Roster
	// error.
	
	status_t _preroll(
		bigtime_t							position);
		
	// seek the given node if possible
	// (this may be called from the transport thread or from
	//  an API-implementation method.)

	status_t _seek(
		bigtime_t							position,
		bigtime_t							when);

	// seek the given (stopped) node
	// (this may be called from the transport thread or from
	//  an API-implementation method.)

	status_t _seekStopped(
		bigtime_t							position);

	// start the given node, if possible & necessary, at
	// the given time
	// (this may be called from the transport thread or from
	//  an API-implementation method.)
		
	status_t _start(
		bigtime_t							when);

	// stop the given node (which may or may not still be
	// a member of this group.)
	// (this may be called from the transport thread or from
	//  an API-implementation method.)

	status_t _stop();
	
	// roll the given node, if possible.
	// (this may be called from the transport thread or from
	//  an API-implementation method.)

	status_t _roll(
		bigtime_t							start,
		bigtime_t							stop,
		bigtime_t							position);
	
	// refresh the node's current latency; if I reference
	// a B_RECORDING node, update its 'producer delay'.
	
	status_t _updateLatency();
	
	// +++++ this method may not be needed 10aug99 +++++
	// Figure the earliest time at which the given node can be started.
	// Also calculates the position at which it should start from to
	// play in sync with other nodes in the group, if the transport is
	// running; if stopped, *outPosition will be set to the current
	// start position.
	// Pass the estimated amount of time needed to prepare the
	// node for playback (ie. preroll & a little fudge factor) in
	// startDelay.
	//
	// (this may be called from the transport thread or from
	//  an API-implementation method.)

	status_t _calcStartTime(
		bigtime_t							startDelay,
		bigtime_t*						outTime,
		bigtime_t*						outPosition); //nyi

	// *** Position reporting ***
	
	// callers: _start(), _roll(), enablePositionReports()
	status_t _startPositionThread();
	
	// callers: _stop(), disablePositionReports(), dtor
	status_t _stopPositionThread();
	
	// [e.moon 14oct99] handle a report
	status_t _handlePositionUpdate(
		bigtime_t							perfTime,
		bigtime_t							position);
				
	// callers: _handlePositionUpdate
	// (schedules a position update for the given time and position;
	//  if the position overlaps a cycle, adjusts time & position
	//  so that the notification is sent at the cycle point.)
	status_t _schedulePositionUpdate(
		bigtime_t							when,
		bigtime_t							position);
		
	// callers: _handlePositionUpdate, _initPositionThread()
	// Send a message to all position observers
	status_t _notifyPosition(
		bigtime_t							when,
		bigtime_t							position);

private:										// *** members

	// the node manager
	NodeManager*							m_manager;
	
	// owning (parent) group; may be 0 if node is not connected.
	// A connected node always has a group, since groups allow transport
	// operations.  New groups are created as necessary.
	NodeGroup*								m_group;

	// the node's state
	live_node_info						m_info;

	// user-definable transport behavior
	uint32										m_flags;
	
	// private/implementation flags
	
	enum impl_flag_t {
		// the node was created by NodeManager
		_INTERNAL					= 1<<1,
		// the node should NOT be released when this instance is destroyed
		_NO_RELEASE				= 1<<2,
		// notification of the node's instantiation has been received
		// [e.moon 11oct99]
		_CREATE_NOTIFIED	= 1<<3
	};
	
	uint32										m_implFlags;
	
	// takes BMediaNode::run_mode values or 0 (wildcard:
	// group run mode used instead)
	// May not be B_OFFLINE; that must be specified at the group level.
	
	uint32										m_runMode;

	// only valid if m_runMode is BMediaNode::B_RECORDING
	bigtime_t									m_recordingDelay; 

	// Media Roster connection [e.moon 11oct99]
	bool											m_watching;

	// hint information: this info is serialized with the object
	// to provide 'hints' towards properly finding the same add-on
	// node later on.  If no hint is provided, the node is assumed
	// to be external to (not created by) the NodeManager.
	
	struct addon_hint;
	addon_hint*								m_addonHint;
	
	// * position listening:
	//   - moved from NodeGroup 9aug99

	bool											m_positionReportsEnabled;
	bool											m_positionReportsStarted;
	bigtime_t									m_positionUpdatePeriod;
	static const bigtime_t		s_defaultPositionUpdatePeriod			= 50000LL;
	
	MultiInvoker							m_positionInvoker;
	
	bigtime_t									m_tpLastPositionUpdate;
	bigtime_t									m_lastPosition;
	
	// * synchronization threads
	
	// only active if position listening has been enabled
	NodeSyncThread*						m_positionThread;
	
//	// only active if this node is cycling (looping)
//	CycleSyncThread*				m_cycleSyncThread;
//	+++++ 10aug99: moved back to NodeGroup

private:										// *** transport state members

	// is this node running?
	bool											m_running;
	
	// has the media_node (NOT this object) been released?
	bool											m_nodeReleased;

	// is this node supposed to loop?
	bool											m_cycle;

	// has the node been prepared to start?
	bool											m_prerolled;
	
	// when was the node started?
	bigtime_t									m_tpStart;
	
	// if the node has been prerolled, m_tpLastSeek will be 0 but
	// m_lastSeekPos will reflect the node's prerolled position
	
	bigtime_t									m_tpLastSeek;
	bigtime_t									m_lastSeekPos;

	// has a stop event been queued? [e.moon 11oct99]
	bigtime_t									m_stopQueued;
	
	// last known latency for this node
	bigtime_t									m_latency;
};

__END_CORTEX_NAMESPACE
#endif /*__NodeRef_H__*/
