/*=============================================================================
	CAMIDIEndpointMenu2.mm
	
	$Log: CAMIDIEndpointMenu2.mm,v $
	Revision 1.1  2004/10/04 23:11:11  dwyatt
	initial checkin
	
	created 10/3/04, Doug Wyatt
	Copyright (c) 2004 Apple Computer, Inc.  All Rights Reserved

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

#import "CAMIDIEndpointMenu2.h"
#include <vector>

class MIDIEndpointInfoMgr {
public:
	struct EndpointInfo {
		MIDIUniqueID	mUniqueID;
		NSString *		mName;
		MIDIEndpointRef	mEndpoint;
	};
	
	typedef std::vector<EndpointInfo> EndpointInfoList;

	MIDIEndpointInfoMgr() { UpdateFromCurrentState(); }
	
	void	Clear() {
		EndpointInfoList::iterator eit;
		for (eit = mSources.begin(); eit != mSources.end(); ++eit)
			[(*eit).mName release];
		for (eit = mDestinations.begin(); eit != mDestinations.end(); ++eit)
			[(*eit).mName release];
		mSources.clear();
		mDestinations.clear();
	}

	void	UpdateFromCurrentState() {
		Clear();
		
		int i, n;
		MIDIEndpointRef e;
		
		n = MIDIGetNumberOfDestinations();
		for (i = 0; i < n; ++i) {
			e = MIDIGetDestination(i);
			if (e == NULL) continue;
			
			EndpointInfo ei;
			ei.mEndpoint = e;
			if (MIDIObjectGetIntegerProperty(e, kMIDIPropertyUniqueID, &ei.mUniqueID)) continue;
			if (MIDIObjectGetStringProperty(e, kMIDIPropertyDisplayName, (CFStringRef *)&ei.mName)) continue;
			mDestinations.push_back(ei);
		}
		
		n = MIDIGetNumberOfSources();
		for (i = 0; i < n; ++i) {
			e = MIDIGetSource(i);
			if (e == NULL) continue;
			
			EndpointInfo ei;
			ei.mEndpoint = e;
			if (MIDIObjectGetIntegerProperty(e, kMIDIPropertyUniqueID, &ei.mUniqueID)) continue;
			if (MIDIObjectGetStringProperty(e, kMIDIPropertyDisplayName, (CFStringRef *)&ei.mName)) continue;
			mSources.push_back(ei);
		}
	}
	
	EndpointInfoList &	Sources() { return mSources; }
	EndpointInfoList &	Destinations() { return mDestinations; }
	
private:
	EndpointInfoList	mSources;
	EndpointInfoList	mDestinations;
};

static MIDIEndpointInfoMgr *gMIDIEndpoints = NULL;
static NSMutableSet *		gInstances = NULL;
static MIDIClientRef		gClient = NULL;

// CoreMIDI callback for when endpoints change -- rebuilds all menu instances
static void NotifyProc(const MIDINotification *message, void *refCon)
{
	if (message->messageID == kMIDIMsgSetupChanged) {
		gMIDIEndpoints->UpdateFromCurrentState();
		
		NSEnumerator *e = [gInstances objectEnumerator];
		CAMIDIEndpointMenu *menu;
		while ((menu = [e nextObject]) != nil)
			[menu rebuildMenu];
	}
}

@implementation CAMIDIEndpointMenu

- (void)_init
{
	if (gInstances == NULL)
		gInstances = [[NSMutableSet alloc] init];
	[gInstances addObject: self];
	mInited = YES;
	mType = -1;
	mOptions = 0;
	mSelectedUniqueID = 0;
}

- (id)initWithFrame: (NSRect)frame
{
    self = [super initWithFrame: frame];
    if (self)
		[self _init];
    return self;
}

- (void)dealloc
{
	[gInstances removeObject: self];
	if ([gInstances count] == 0) {
		delete gMIDIEndpoints;	gMIDIEndpoints = NULL;
		[gInstances release];	gInstances = nil;
		if (gClient) {
			MIDIClientDispose(gClient);
			gClient = NULL;
		}
	}
	[super dealloc];
}

- (void)buildMenu: (int)type opts: (int)opts
{
	if (!mInited)
		[self _init];
	
	if (gClient == NULL)
		MIDIClientCreate(CFSTR(""), NotifyProc, NULL, &gClient);
	if (gMIDIEndpoints == NULL)
		gMIDIEndpoints = new MIDIEndpointInfoMgr;
	
	mType = type;
	mOptions = opts;
	[self rebuildMenu];
}

- (void)syncSelectedName
{
}

static NSString *UniqueTitle(NSString *name, NSMutableDictionary *previousTitles)
{
	NSString *newItemTitle = name;
	int suffix = 0;
	while (true) {
		if ([previousTitles objectForKey: newItemTitle] == nil)
			break;
		if (suffix == 0) suffix = 2; else ++suffix;
		newItemTitle = [NSString stringWithFormat: @"%@ #%d", name, suffix];
	}
	[previousTitles setObject: newItemTitle forKey: newItemTitle];
	return newItemTitle;
}

- (void)rebuildMenu
{
	int itemsToKeep = (mOptions & kMIDIEndpointMenuOpt_CanSelectNone) ? 1 : 0;
	
	while ([self numberOfItems] > itemsToKeep)
		[self removeItemAtIndex: itemsToKeep];

	MIDIEndpointInfoMgr::EndpointInfoList &eil = (mType == kMIDIEndpointMenuSources) ? gMIDIEndpoints->Sources() : gMIDIEndpoints->Destinations();
	
	NSMutableDictionary *previousTitles = [[NSMutableDictionary alloc] init];
	int n = eil.size();
	bool foundSelection = false;
	for (int i = 0; i < n; ++i) {
		MIDIEndpointInfoMgr::EndpointInfo *ei = &eil[i];
		NSString *name = ei->mName;
		NSString *newItemTitle = UniqueTitle(name, previousTitles);
		// see if that collides with any previous item -- base class requires unique titles
		
		[self addItemWithTitle: newItemTitle]; // cast from CFString
		if (ei->mUniqueID == mSelectedUniqueID) {
			[self selectItemAtIndex: itemsToKeep + i];
			[self syncSelectedName];
			foundSelection = true;
		}
	}
	if (!foundSelection)
		[self selectItemAtIndex: 0];
	[previousTitles release];
}

- (MIDIEndpointRef)selectedEndpoint
{
	int itemsToIgnore = (mOptions & kMIDIEndpointMenuOpt_CanSelectNone) ? 1 : 0;
	int i = [self indexOfSelectedItem];
	if (i >= itemsToIgnore) {
		MIDIEndpointInfoMgr::EndpointInfoList &eil = (mType == kMIDIEndpointMenuSources) ? gMIDIEndpoints->Sources() : gMIDIEndpoints->Destinations();
	
		MIDIEndpointInfoMgr::EndpointInfo *ei = &eil[i - itemsToIgnore];
		mSelectedUniqueID = ei->mUniqueID;
		[self syncSelectedName];
		return ei->mEndpoint;
	}
	return NULL;
}

- (MIDIUniqueID)selectedUniqueID
{
	[self syncSelectedName];
	int itemsToIgnore = (mOptions & kMIDIEndpointMenuOpt_CanSelectNone) ? 1 : 0;
	int i = [self indexOfSelectedItem];
	MIDIEndpointInfoMgr::EndpointInfoList &eil = (mType == kMIDIEndpointMenuSources) ? gMIDIEndpoints->Sources() : gMIDIEndpoints->Destinations();
	MIDIUniqueID uid = (i >= itemsToIgnore) ? eil[i - itemsToIgnore].mUniqueID : kMIDIInvalidUniqueID;
	mSelectedUniqueID = uid;
	return uid;
}

- (BOOL)selectUniqueID: (MIDIUniqueID)uniqueID
{
	mSelectedUniqueID = uniqueID;
	int itemsToIgnore = (mOptions & kMIDIEndpointMenuOpt_CanSelectNone) ? 1 : 0;
	if (uniqueID == kMIDIInvalidUniqueID && itemsToIgnore == 1) {
		[self selectItemAtIndex: 0];
		[self syncSelectedName];
		return YES;
	}
	MIDIEndpointInfoMgr::EndpointInfoList &eil = (mType == kMIDIEndpointMenuSources) ? gMIDIEndpoints->Sources() : gMIDIEndpoints->Destinations();

	int n = eil.size();
	for (int i = 0; i < n; ++i) {
		MIDIEndpointInfoMgr::EndpointInfo *ei = &eil[i];
		if (ei->mUniqueID == uniqueID) {
			[self selectItemAtIndex: itemsToIgnore + i];
			[self syncSelectedName];
			return YES;
		}
	}
	return NO;
}

@end
