// NodeManager.h (Cortex)
//
// * PURPOSE
//   Provides a Media Kit application with a straightforward
//   way to keep track of media nodes and the connections
//   between them.  Nodes are collected into sets via the
//   NodeGroup class; these sets can be controlled in tandem.
//
// * GROUPING NOTES
//   A new group is created with the following information:
//   - time source (defaults to the DAC time source)
//   - a user-provided name
//
//   New nodes can be added to a group via NodeGroup methods.  When a
//   node is added to a group, it will automatically be assigned the
//   group's time source.  Unless the node has a run mode set, it will
//   also be assigned the group's run mode.  (If the group is in B_OFFLINE
//   mode, this will be assigned to all nodes even if they specify something
//   else.)  If a node is added to a group whose transport is running, it
//   will automatically be seeked and started (unless one or both of those
//   operations has been disabled.)
//   
// * SYNCHRONIZATION NOTES
//   Each NodeManager object, including all the NodeGroup and NodeRef
//   objects in its care, is synchronized by a single semaphore.
//   Most operations in these three classes require that the object
//   be locked.
//
// * UI HOOKS
//   NodeManager, NodeGroup, and NodeRef all implement the
//   Listenable interface.  (pedantic note: there's no 'I' prefix
//   because Listenable isn't a 'pure' interface; it includes
//   a bit of functionality, but relies on an ILockable
//   implementation.)
//
//   Anyway, user-interface objects (or any other BHandler that's
//   interested in what NodeManager and friends are up to) can
//   subscribe to the NodeManager, or a particular NodeGroup or
//   NodeRef, by calling addListener().  Before the object to which
//   they've subscribed is deleted, they'll be informed via a 
//   synchronous message (Listenable::M_DELETED) which must be
//   replied to in order for the object to be deleted.
//
//   The messages sent by NodeManager/Group/Ref are defined at the
//   head of each class.
//
//   NodeManager resends any Media Roster messages to all listeners
//   *after* processing them: the NodeRef corresponding to a newly-
//   created node, for example, must exist by the time that a
//   NodeManager listener receives B_MEDIA_NODE_CREATED.
//
//
// * HISTORY
//   e.moon		11aug99		Expanded findConnection() methods.
//   e.moon		6jul99		Begun

#ifndef __NodeManager_H__
#define __NodeManager_H__

#include "ILockable.h"
#include "ObservableLooper.h"
#include "observe.h"

#include <Looper.h>
#include <MediaDefs.h>
#include <MediaNode.h>

#include <vector>
#include <map>

class BMediaRoster;

#include "cortex_defs.h"
__BEGIN_CORTEX_NAMESPACE

class Connection;
class NodeGroup;
class NodeRef;

class NodeManager :
	public	ObservableLooper,
	public	ILockable {

	// primary parent class:
	typedef	ObservableLooper _inherited;
	
	friend NodeGroup;
	friend NodeRef;
	friend Connection;

public:				// *** messages
	// [13aug99]
	//   NodeManager retransmits Media Roster messages to its listeners,
	//   after processing each message.
	//
	///  B_MEDIA_CONNECTION_BROKEN
	//     This message, as sent by the Media Roster, contains only
	//     source/destination information.  NodeManager adds these fields
	//     to the message:
	//     __connection_id:        (uint32) id of the Connection; 0 if no
	//                             matching Connection was found.
	//     __source_node_id:			 media_node_id of the node corresponding to
	//                        		 the source; 0 if no matching Connection was
	//                        		 found.
	//     __destination_node_id:	 media_node_id of the node corresponding to
	//                         		 the source; 0 if no matching Connection was
	//                         		 found.

	enum outbound_message_t {
		M_OBSERVER_ADDED						=NodeManager_message_base,
		M_OBSERVER_REMOVED,
		M_RELEASED,

		// groupID: int32
		M_GROUP_CREATED,
		M_GROUP_DELETED,
		
		// groupID: int32 x2 (the first is the original)
		M_GROUP_SPLIT,
		
		// groupID: int32 x2
		M_GROUPS_MERGED
	};

public:				// *** default group names

	static const char* const			s_defaultGroupPrefix;
	static const char* const			s_timeSourceGroup;
	static const char* const			s_audioInputGroup;
	static const char* const			s_videoInputGroup;
	static const char* const			s_audioMixerGroup;
	static const char* const			s_videoOutputGroup;

public:				// *** ctor/dtor

	NodeManager();

	// don't directly delete NodeManager;
	// use IObservable::release()
	virtual ~NodeManager();

public:				// *** const members

	// cached roster pointer
	::BMediaRoster*	const						roster;
	
public:				// *** operations

	// * ACCESS

	// fetches NodeRef corresponding to a given ID; returns
	// B_BAD_VALUE if no matching entry was found (and writes
	// a 0 into the provided pointer.)

	status_t getNodeRef(
		media_node_id									id,
		NodeRef**											outRef) const;
	
	// [13aug99]	
	// fetches Connection corresponding to a given source/destination
	// on a given node.  Returns an invalid connection and B_BAD_VALUE
	// if no matching connection was found.
	
	status_t findConnection(
		media_node_id									node,
		const media_source&						source,
		Connection*										outConnection) const;
	
	status_t findConnection(
		media_node_id									node,
		const media_destination&			destination,
		Connection*										outConnection) const;
		
	// [e.moon 28sep99]
	// fetches a Connection matching the given source and destination
	// nodes.  Returns an invalid connection and B_BAD_VALUE if
	// no matching connection was found
	
	status_t findConnection(
		media_node_id									sourceNode,
		media_node_id									destinationNode,
		Connection*										outConnection) const;
		
	// [e.moon 28sep99]
	// tries to find a route from 'nodeA' to 'nodeB'; returns
	// true if one exists, false if not.  If nodeA and nodeB
	// are the same node, only returns true if it's actually
	// connected to itself.
	
	bool findRoute(
		media_node_id									nodeA,
		media_node_id									nodeB);

private:	
	// implementation of above
	class _find_route_state;
	bool _find_route_recurse(
		NodeRef*													origin,
		media_node_id											target,
		_find_route_state*								state);
	
public:	
	// fetches Connection corresponding to a given source or
	// destination; Returns an invalid connection and B_BAD_VALUE if
	// none found.
	// *   Note: this is the slowest possible way to look up a
	//     connection.  Since the low-level source/destination
	//     structures don't include a node ID, and a destination
	//     port can differ from its node's control port, a linear
	//     search of all known connections is performed.  Only
	//     use these methods if you have no clue what node the
	//     connection corresponds to.
	
	status_t findConnection(
		const media_source&						source,
		Connection*										outConnection) const;
		
	status_t findConnection(
		const media_destination&			destination,
		Connection*										outConnection) const;
	
	// fetch NodeRefs for system nodes (if a particular node doesn't
	// exist, these methods return 0)
	
	NodeRef* audioInputNode() const;
	NodeRef* videoInputNode() const;
	NodeRef* audioMixerNode() const;
	NodeRef* audioOutputNode() const;
	NodeRef* videoOutputNode() const;
	
	// fetch groups by index
	// - you can write-lock the manager during sets of calls to these methods;
	//   this ensures that the group set won't change.  The methods do lock
	//   the group internally, so locking isn't explicitly required.
	
	uint32 countGroups() const;
	NodeGroup* groupAt(
		uint32												index) const;

	// look up a group by unique ID; returns B_BAD_VALUE if no
	// matching group was found
	
	status_t findGroup(
		uint32												id,
		NodeGroup**										outGroup) const;

	// look up a group by name; returns B_NAME_NOT_FOUND if
	// no group matching the name was found.

	status_t findGroup(
		const char*										name,
		NodeGroup**										outGroup) const;
		
	// merge the given source group to the given destination;
	// empties and releases the source group
	
	status_t mergeGroups(
		NodeGroup*										sourceGroup,
		NodeGroup*										destinationGroup);
	
	// [e.moon 28sep99]
	// split group: given two nodes currently in the same group
	// that are not connected (directly OR indirectly),
	// this method removes outsideNode, and all nodes connected
	// to outsideNode, from the common group.  These nodes are
	// then added to a new group, returned in 'outGroup'.  The
	// new group has " split" appended to the end of the original
	// group's name.
	//
	// Returns B_NOT_ALLOWED if any of the above conditions aren't
	// met (ie. the nodes are in different groups or an indirect
	// route exists from one to the other), or B_OK if the group
	// was split successfully.
	
	status_t splitGroup(
		NodeRef*											insideNode,
		NodeRef*											outsideNode,
		NodeGroup**										outGroup); //nyi

	// * INSTANTIATION & CONNECTION
	//   Use these calls rather than the associated BMediaRoster()
	//   methods to assure that the nodes and connections you set up
	//   can be properly serialized & reconstituted.

	// basic BMediaRoster::InstantiateDormantNode() wrapper
	// - writes a 0 into *outRef if the instantiation fails

	status_t instantiate(
		const dormant_node_info&			info,
		NodeRef**											outRef,
		uint32												nodeFlags=0);
		
	// SniffRef/Instantiate.../SetRefFor: a one-call interface
	// to create a node capable of playing a given media file.
	// - writes a 0 into *outRef if the instantiation fails; on the
	//   other hand, if instantiation succeeds, but SetRefFor() fails,
	//   a NodeRef will still be returned.
	
	status_t instantiate(
		const entry_ref&							file,
		uint64												requireNodeKinds,
		NodeRef**											outRef,
		uint32												nodeFlags=0,
		bigtime_t*										outDuration=0);

	// use this method to reference nodes created within your
	// application.  These nodes can't be automatically reconstituted
	// by the cortex serializer yet.

	status_t reference(
		BMediaNode*										node,
		NodeRef**											outRef,
		uint32												nodeFlags=0);

	// the most flexible form of connect(): set the template
	// format as you would for BMediaRoster::Connect().
	
	status_t connect(
		const media_output&						output,
		const media_input&						input,
		const media_format&						templateFormat,
		Connection*										outConnection=0);
		
	// format-guessing form of connect(): tries to find
	// a common format between output & input before connection;
	// returns B_MEDIA_BAD_FORMAT if no common format type found.
	//
	// NOTE: the specifics of the input and output formats are ignored;
	//       this method only looks at the format type, and properly
	//       handles wildcards at that level (B_MEDIA_NO_TYPE).
	
	status_t connect(
		const media_output&						output,
		const media_input&						input,
		Connection*										outConnection=0);

	// disconnects the connection represented by the provided
	// Connection object.  if successful, returns B_OK.
	
	status_t disconnect(
		const Connection&							connection);

	// * GROUP CREATION

	NodeGroup* createGroup(
		const char*										name,
		BMediaNode::run_mode					runMode=BMediaNode::B_INCREASE_LATENCY);

public:				// *** node/connection iteration
							// *** MUST BE LOCKED for any of these calls
	
	// usage:
	//   For the first call, pass 'cookie' a pointer to a void* set to 0.
	//   Returns B_BAD_INDEX when the set of nodes has been exhausted (and
	//   invalidates the cookie, so don't try to use it after this point.)
	
	status_t getNextRef(
		NodeRef**											outRef,
		void**												cookie);
		
	// if you want to stop iterating, call this method to avoid leaking
	// memory
	void disposeRefCookie(
		void**												cookie);

	status_t getNextConnection(
		Connection*										outConnection,
		void**												cookie);

	void disposeConnectionCookie(
		void**												cookie);
		
public:				// *** BHandler impl
	void MessageReceived(BMessage*	message); //nyi

public:				// *** IObservable hooks
	virtual void observerAdded(
		const BMessenger&				observer);
		
	virtual void observerRemoved(
		const BMessenger&				observer);

	virtual void notifyRelease();

	virtual void releaseComplete();


public:				// *** ILockable impl.
	virtual bool lock(
		lock_t												type=WRITE,
		bigtime_t											timeout=B_INFINITE_TIMEOUT);
	
	virtual bool unlock(
		lock_t												type=WRITE);
	
	virtual bool isLocked(
		lock_t												type=WRITE) const;
		

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


	void _initCommonNodes();
	
	void _addRef(
		NodeRef*											ref);
	void _removeRef(
		media_node_id									id);
		
	// create, add, return a NodeRef for the given external node;
	// must not already exist
	NodeRef* _addRefFor(
		const media_node&							node,
		uint32												nodeFlags,
		uint32												nodeImplFlags=0);
		
	void _addConnection(
		Connection*										connection);
	void _removeConnection(
		const Connection&							connection);
		
	void _addGroup(
		NodeGroup*										group);
	void _removeGroup(
		NodeGroup*										group);

// [e.moon 13oct99] moved to an external template function to
//                  please the PPC compiler.
//	// [e.moon 28sep99] graph crawling
//	//
//	// - does NOT apply operation to origin node, just the nodes
//	//   connected to it.
//	// - if lockRef is true, locks target node before applying operation.
//	//   Be careful with this -- a node lock should not be multiply
//	//   acquired!
//	// - if inGroup is non-0, visits (& applies operation to) only the
//	//   nodes in that group.
//
//	class _for_each_state;	
//	template<class Op>
//	void _do_for_each_connected(
//		NodeRef*													origin,
//		NodeGroup*												inGroup,
//		bool															recurse,
//		bool															lockRef,
//		Op																operation,
//		_for_each_state*									state);
	
	// dtor helpers
	inline void _clearGroup(
		NodeGroup*										group);

	inline void _freeConnection(
		Connection*										connection);
		
	// message handlers
	
	// now returns B_OK iff the message should be relayed to observers
	// [e.moon 11oct99]
	inline status_t _handleNodesCreated(
		BMessage*											message);

	inline void _handleNodesDeleted(
		BMessage*											message);

	inline void _handleConnectionMade(
		BMessage*											message);

	inline void _handleConnectionBroken(
		BMessage*											message);	//nyi-13aug99
		
	// return flags appropriate for an external
	// node with the given 'kind'

	inline uint32 _userFlagsForKind(
		uint32												kind);

	inline uint32 _implFlagsForKind(
		uint32												kind);
	
	// [e.moon 28sep99] latency updating
	// These methods must set the recording-mode delay for
	// any B_RECORDING nodes they handle.
	
	// +++++ abstract to 'for each' and 'for each from'
	//       methods (template or callback?)

	
	// refresh cached latency for every node in the given group
	// (or all nodes if no group given.)
	
	inline void _updateLatencies(
		NodeGroup*										group =0);
	
	// refresh cached latency for every node attached to
	// AND INCLUDING the given origin node.
	// if 'recurse' is true, affects indirectly attached
	// nodes as well.
	
	inline void _updateLatenciesFrom(
		NodeRef*											origin,
		bool													recurse =false);
		
	// a bit of unpleasantness [e.moon 13oct99]
	inline void _lockAllGroups(); //nyi
	inline void _unlockAllGroups(); //nyi
		
private:				// *** internal messages
	enum int_message_t {
		//  groupID: int32
		_M_GROUP_RELEASED					= NodeManager_int_message_base
	};
	
private:				// *** members
	// the main NodeRef store
	typedef map<media_node_id, NodeRef*> node_ref_map;
	node_ref_map					m_nodeRefMap;
	
	// the Connection stores (connections are indexed by both
	// source and destination node ID)
	typedef multimap<media_node_id, Connection*> con_map;
	con_map								m_conSourceMap;
	con_map								m_conDestinationMap;
	
	// the NodeGroup store
	typedef vector<NodeGroup*> node_group_set;
	node_group_set				m_nodeGroupSet;
	
	// common system nodes
	NodeRef*							m_audioInputNode;
	NodeRef*							m_videoInputNode;
	NodeRef*							m_audioMixerNode;
	NodeRef*							m_audioOutputNode;
	NodeRef*							m_videoOutputNode;
	
	// next unique connection ID
	uint32								m_nextConID;

	// false until the first 'nodes created' message is
	// received from the Media Roster, and pre-existing connection
	// info is filled in:
	bool									m_existingNodesInit;
};

__END_CORTEX_NAMESPACE
#endif /*__NodeManager_H__*/
