//
//  MIKMIDIConnectionManager.h
//  MIKMIDI
//
//  Created by Andrew Madsen on 11/5/15.
//  Copyright © 2015 Mixed In Key. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "MIKMIDICompilerCompatibility.h"
#import "MIKMIDISourceEndpoint.h"

@class MIKMIDIDevice;
@class MIKMIDINoteOnCommand;

@protocol MIKMIDIConnectionManagerDelegate;

NS_ASSUME_NONNULL_BEGIN

/**
 *  MIKMIDIConnectionManager can be used to manage a set of connected devices. It can be configured to automatically
 *  connect to devices as they are added, and disconnect from them as they are removed. It also supports saving
 *  the list of connected to NSUserDefaults and restoring them upon relaunch.
 *
 *  The use of MIKMIDIConnectionManager is optional. It is meant to be useful in implementing functionality that
 *  many MIDI device enabled apps need. However, simple connection to devices or MIDI endpoints can be done with
 *  MIKMIDIDeviceManager directly, if desired.
 */
@interface MIKMIDIConnectionManager : NSObject

/**
 *  This method will throw an exception if called. Use -initWithName:delegate:eventHandler: instead.
 *
 *  @return nil
 */
- (instancetype)init NS_UNAVAILABLE;

/**
 *  Initializes an instance of MIKMIDIConnectionManager. The passed in name is used to independently
 *  store and load the connection manager's configuration using NSUserDefaults. The passed in name
 *  should be unique across your application, and the same from launch to launch.
 *
 *  @param name			The name to give the connection manager. Must not be nil or empty.
 *	@param delegate		The delegate of the connection manager
 *  @param eventHandler An MIKMIDIEventHandlerBlock to be called with incoming MIDI messages from any connected device.
 *
 *  @return An initialized MIKMIDIConnectionManager instance.
 */
- (instancetype)initWithName:(NSString *)name
					delegate:(nullable id<MIKMIDIConnectionManagerDelegate>)delegate
				eventHandler:(nullable MIKMIDIEventHandlerBlock)eventHandler NS_DESIGNATED_INITIALIZER;

/**
 *  Initializes an instance of MIKMIDIConnectionManager. The passed in name is used to independently
 *  store and load the connection manager's configuration using NSUserDefaults. The passed in name
 *  should be unique across your application, and the same from launch to launch. This is the same
 *  as calling -initWithName:delegate:eventHandler: with a nil delegate and eventHandler.
 *
 *  @param name			The name to give the connection manager. Must not be nil or empty.
 *
 *  @return An initialized MIKMIDIConnectionManager instance.
 */
- (instancetype)initWithName:(NSString *)name;

/**
 *  Connect to the specified device. When MIDI messages are received, the connection manager's event handler
 *  block will be executed.
 *
 *  @param device	An MIKMIDIDevice instance.
 *  @param error	If an error occurs, upon returns contains an NSError object that describes the problem.
 *  If you are not interested in possible errors, you may pass in NULL.
 *
 *  @return YES if the connection was successful, NO if an error occurred.
 */
- (BOOL)connectToDevice:(MIKMIDIDevice *)device error:(NSError **)error;

/**
 *  Disconnect from a connected device. No further messages from the specified device will be processed.
 *
 *  Note that you don't need to call this method when a previously-connected device was removed from the system.
 *  Disconnection in that situation is handled automatically.
 *
 *  @param device An MIKMIDIDevice instance.
 */
- (void)disconnectFromDevice:(MIKMIDIDevice *)device;

/**
 *  This method can be used to determine if the receiver is connected to a given MIDI device.
 *
 *  @param device An MIKMIDIDevice instance.
 *
 *  @return YES if the receiver is connected to and processing MIDI input from the device, NO otherwise.
 */
- (BOOL)isConnectedToDevice:(MIKMIDIDevice *)device;

/**
 *  If YES (the default), the connection manager will automatically save its configuration at appropriate
 *  times. If this property is NO, -saveConfiguration can still be used to manually trigger saving the
 *  receiver's configuration. Note that -loadConfiguration must always be called manually, e.g. at launch.
 */
@property (nonatomic) BOOL automaticallySavesConfiguration;

/**
 *  Save the receiver's list of connected devices to disk for later restoration.
 */
- (void)saveConfiguration;

/**
 *  Load and reconnect to the devices previously saved to disk by a call to -saveConfiguration. For
 *  this to work, the receiver's name must be the same as it was upon the previous call to -saveConfiguration.
 *
 *  @note: This method will only connect to new devices. It will not disconnect from devices not found in the
 *  saved configuration.
 */
- (void)loadConfiguration;

/**
 *  The name of the receiver. Used for configuration save/load.
 */
@property (nonatomic, copy, readonly) NSString *name;

/**
 *  An MIKMIDIEventHandlerBlock to be called with incoming MIDI messages from any connected device.
 *
 *  If you need to determine which device sent the passed in messages, call source.entity.device on the
 *  passed in MIKMIDISourceEndpoint argument.
 */
@property (nonatomic, copy, null_resettable) MIKMIDIEventHandlerBlock eventHandler;

/**
 *  A delegate, used to customize MIKMIDIConnectionManager behavior.
 */
@property (nonatomic, weak, nullable) id<MIKMIDIConnectionManagerDelegate>delegate;

/**
 *  Controls whether the receiver's availableDevices property includes virtual devices (i.e. devices made
 *  up of automatically paired virtual sources and destinations).
 *
 *  If this property is YES (the default), the connection manager will attempt to automtically related
 *  associated virtual sources and destinations and create "virtual" MIKMIDIDevice instances for them.
 *
 *  If this property is NO, the connection manager's availableDevices array will _only_ contain non-virtual
 *  MIKMIDIDevices.
 *
 *  For most applications, this should be left at the default YES, as even many physical MIDI devices present as
 *  "virtual" devices in software.
 *
 *  @note: The caveat here is that this relies on some heuristics to match up source endpoints with destination endpoints.
 *  These heuristics are based on the way certain common MIDI devices behave, but may not be universal, and therefore
 *  may miss, or fail to properly associate endpoints for some devices. If this is a problem for your application,
 *  you should obtain and connect to virtual sources/endpoints using MIKMIDIDeviceManager directly instead.
 */
@property (nonatomic) BOOL includesVirtualDevices; // Default is YES

/**
 *  An array of available MIDI devices.
 *
 *  This property is observable using Key Value Observing.
 */
@property (nonatomic, strong, readonly) MIKArrayOf(MIKMIDIDevice *) *availableDevices;

/**
 *  The set of MIDI devices to which the receiver is connected.
 *
 *  This property is observable using Key Value Observing.
 */
@property (nonatomic, strong, readonly) MIKSetOf(MIKMIDIDevice *) *connectedDevices;

@end


/**
 *  Specifies behavior for connecting to a newly connected device. See
 *  -connectionManager:shouldConnectToNewlyAddedDevice:
 */
typedef NS_ENUM(NSInteger, MIKMIDIAutoConnectBehavior) {
	
	/** Do not connect to the newly added device */
	MIKMIDIAutoConnectBehaviorDoNotConnect,
	
	/** Connect to the newly added device */
	MIKMIDIAutoConnectBehaviorConnect,
	
	/** Connect to the newly added device only if it was previously connected (ie. in the saved configuration data) */
	MIKMIDIAutoConnectBehaviorConnectOnlyIfPreviouslyConnected,
	
	/** Connect to the newly added device if it was previously connected, or is unknown in the configuration data.*/
	MIKMIDIAutoConnectBehaviorConnectIfPreviouslyConnectedOrNew,
};

/**
 *  Protocol containing method(s) to be implemented by delegates of MIKMIDIConnectionManager.
 */
@protocol MIKMIDIConnectionManagerDelegate <NSObject>

@optional

/**
 *  A connection manager's delegate can implement this method to determine whether or not to automatically connect
 *  to a newly added MIDI device. See MIKMIDIAutoConnectBehavior for possible return values.
 *
 *	If this method is not implemented, the default behavior is MIKMIDIAutoConnectBehaviorConnectIfPreviouslyConnectedOrNew.
 *
 *  @param manager An instance of MIKMIDIConnectionManager.
 *  @param device  The newly added MIDI device.
 *
 *  @return One of the values defined in MIKMIDIAutoConnectBehavior.
 */
- (MIKMIDIAutoConnectBehavior)connectionManager:(MIKMIDIConnectionManager *)manager shouldConnectToNewlyAddedDevice:(MIKMIDIDevice *)device;

/**
 *	A connection manager's delegate can implement this method to be notified when a device is connected,
 *	either because -connectToDevice:error: was called, or because the device was plugged, and the manager
 *  automatically connected to it. You can for example use this method to update UI in response to a device being connected.
 *
 *  @param manager  An instance of MIKMIDIConnectionManager.
 *  @param device   The MIKMIDIDevice that was disconnected.
 */
- (void)connectionManager:(MIKMIDIConnectionManager *)manager deviceWasConnected:(MIKMIDIDevice *)device;

/**
 *  A connection manager's delegate can implement this method to be notified when a connected device is disconnected,
 *  either because -disconnectFromDevice: was called, or because the device was unplugged.
 *
 *	If a MIDI device is disconnected between sending a note on message and sending the corresponding note off command,
 *  this can cause a "stuck note" because the note off command will now never be delivered. e.g. a MIDI piano keyboard
 *  that is disconnected with a key held down. This method includes an array of these unterminated note on commands (if any)
 *  so that the receiver can appropriately deal with this situation. For example, corresponding note off commands could
 *  be generated and sent through whatever processing chain is processing incoming MIDI commands to terminate stuck notes.
 *
 *  @param manager  An instance of MIKMIDIConnectionManager.
 *  @param device   The MIKMIDIDevice that was disconnected.
 *  @param commands An array of note on messages for which corresponding note off messages have not yet been received.
 */
- (void)connectionManager:(MIKMIDIConnectionManager *)manager deviceWasDisconnected:(MIKMIDIDevice *)device withUnterminatedNoteOnCommands:(MIKArrayOf(MIKMIDINoteOnCommand *) *)commands;

@end

NS_ASSUME_NONNULL_END

