/*=============================================================================
	CAMIDIEndpoints.h
	
	$Log: CAMIDIEndpoints.h,v $
	Revision 1.3  2004/09/04 21:21:21  dwyatt
	add MIDIUniqueID optional argument to EndpointInfo constructor
	
	Revision 1.2  2003/12/04 19:56:13  dwyatt
	fixes:
	- in EndpointInfo copy constructor, null out mDisplayName
	- don't prepend the device name to the entity name if the entity name already starts with the device name (Edirol UM-4)
	
	Revision 1.1  2003/09/24 21:31:11  dwyatt
	initial checkin
	
	created Thu Sep 18 2003, Doug Wyatt
	Copyright (c) 2003 Apple Computer, Inc.  All Rights Reserved

	$NoKeywords: $
=============================================================================*/

#ifndef __CAMIDIEndpoints_h__
#define __CAMIDIEndpoints_h__

#include <CoreMIDI/CoreMIDI.h>
#include <vector>

// ____________________________________________________________________________
// This class manages persistent references to MIDI endpoints,
// and provides user-visible names for them.
class CAMIDIEndpoints {
public:
	// ____________________________________________________________________________
	class EndpointInfo {
	public:
		// when asking for pairs, both source and destination are valid,
		// otherwise only the appropriate MIDIEndpointRef is valid.
		MIDIEndpointRef		mSourceEndpoint;		// driver-owned, can be used for I/O
		MIDIEndpointRef		mDestinationEndpoint;	// driver-owned, can be used for I/O
		CFStringRef			mDisplayName;
		MIDIUniqueID		mUniqueID;			// may refer to a driver or external endpoint.
												// use this as your permanent reference to it.
		
		EndpointInfo(MIDIUniqueID uid=kMIDIInvalidUniqueID) :
			mSourceEndpoint(NULL),
			mDestinationEndpoint(NULL),
			mDisplayName(NULL),
			mUniqueID(uid) { }
		
		EndpointInfo(const EndpointInfo &info) : mDisplayName(NULL) { *this = info; }

		EndpointInfo & operator = (const EndpointInfo &a) {
			ReleaseName();
			this->mSourceEndpoint = a.mSourceEndpoint;
			this->mDestinationEndpoint = a.mDestinationEndpoint;
			this->mDisplayName = a.mDisplayName;
			if (this->mDisplayName) CFRetain(this->mDisplayName);
			this->mUniqueID = a.mUniqueID;
			
			return *this;
		}
		
		~EndpointInfo() { ReleaseName(); }
	private:
		void	ReleaseName() { if (mDisplayName) { CFRelease(mDisplayName); mDisplayName = NULL; } }
	};
	
	struct EndpointInfoList : public std::vector<EndpointInfo *> {
		~EndpointInfoList() { for (iterator it = begin(); it != end(); ++it) delete *it; }
	};

	// ____________________________________________________________________________
	// options
	enum {
		kOptSortByName						= 1,
		kOptIncludeUnconnectedExternalPorts = 2,
		kOptCombineByPort					= 4		// when multiple devices connected to a port,
													// present only a single endpoint
	};
	
	// ____________________________________________________________________________
	// methods
					CAMIDIEndpoints();
	virtual			~CAMIDIEndpoints();
	
	void			UpdateFromCurrentState();
						// call this when you get a notification that the state has changed
	
	// functions that return lists: do not dispose the pointers contained in the lists!
	EndpointInfoList *	GetSources(UInt32 opts = 0) {
							return GetEndpoints(kSources, opts);
						}
	EndpointInfoList *	GetDestinations(UInt32 opts = 0) {
							return GetEndpoints(kDestinations, opts);
						}
	EndpointInfoList *	GetEndpointPairs(UInt32 opts = 0) {
							return GetEndpoints(kPairs, opts);
						}
		// A pair of endpoints: based on being part of the same entity.
		// Considering the possibility of multi-entity USB devices, it's more robust to
		// consider entities/endpoint pairs as things to communicate bidirectionally with,
		// as opposed to devices. But in the UI, you might still treat them as "devices"
		// since most devices have only a single entity.
	
	// look up an endpoint by uniqueID in a EndpointInfo struct, return
	// an updated EndpointInfo. (### options are not currently relevant ###)
	bool				FindSource(EndpointInfo &info, UInt32 opts = 0) {
							return FindEndpoint(kSources, info, opts);
						}
	bool				FindDestination(EndpointInfo &info, UInt32 opts = 0) {
							return FindEndpoint(kDestinations, info, opts);
						}
	bool				FindPair(EndpointInfo &info, UInt32 opts = 0) {
							return FindEndpoint(kPairs, info, opts);
						}
	
protected:
	// ____________________________________________________________________________
	enum EMode { kSources, kDestinations, kPairs };
	
	// ____________________________________________________________________________
	// Corresponds to a CoreMIDI source or destination endpoint, except that
	// in the case of multiple external devices connected to the same driver port,
	// there is a separate instance for each.
	class Endpoint {
	public:
		Endpoint(MIDIEndpointRef ep, CFStringRef name, MIDIObjectRef connectedObj);
		~Endpoint();
		
		MIDIUniqueID		UniqueID() const { return mUniqueID; }
		CFStringRef			Name() const { return mName; } // do not release returned reference
		
		MIDIEndpointRef		IOEndpoint() const { return mIOEndpoint; }
		bool				DriverOwned() const { return mConnectedObj == NULL; }
		bool				EmbeddedOrVirtual() const { return mEmbeddedOrVirtual; }
		MIDIEntityRef		Entity() const { return mEntity; }
		
		void				SetNext(Endpoint *next) { mNext = next; }
		Endpoint *			Next() const { return mNext; }
		void				SetPairMate(Endpoint *e) { mPairMate = e; }
		Endpoint *			PairMate() const { return mPairMate; }
		
		bool				GetEndpointInfo(EMode mode, EndpointInfo &outInfo);
		
	protected:
		MIDIUniqueID		mUniqueID;
		MIDIEndpointRef		mIOEndpoint;		// NULL if unresolved
		CFStringRef			mName;

		MIDIEntityRef		mEntity;
		bool				mEmbeddedOrVirtual;
		
		MIDIObjectRef		mConnectedObj;
		Endpoint *			mNext;				// linked list: driver owned -> connected -> connected ...
		Endpoint *			mPairMate;
	};
	
	typedef std::vector<Endpoint *>		EndpointList;
	
	// ____________________________________________________________________________
	// members
protected:
	void				Clear();
	void				AddEndpoints(MIDIEndpointRef endpoint, EndpointList &eplist);
	EndpointInfoList *	GetEndpoints(EMode mode, UInt32 opts);
	bool				FindEndpoint(EMode mode, EndpointInfo &info, UInt32 opts);

	EndpointList		mSources, mDestinations;
};

#endif // __CAMIDIEndpoints_h__
