//
// RRRadioBinder.m
//
// Created on 2016-01-08.
// Copyright Rogue Research 2016. All rights reserved.
//
// This file is encoded as UTF-8.
//
// $Id: RRRadioBinder.m 31359 2016-01-15 15:45:00Z sean $

#import "RRRadioBinder.h"

// Private Interface
@interface RRRadioBinder()
@property (readwrite, copy, nonatomic) NSArray* radioButtons;
@end


@implementation RRRadioBinder

#pragma mark -

// ---------------------------------------------------------------------------------------------------------------
- (void)awakeFromNib
{
	// Make sure programmer didn't forget to connect required outlets in IB.
	assert([self bindingController]);
	
	// Gather the radio buttons into an array.  Any of them might be nil.
	NSMutableArray* radioButtons = [NSMutableArray array];
	if ([self radioButton0])
	{
		assert([[self radioButton0] target] == self);
		[radioButtons addObject:[self radioButton0]];
	}
	if ([self radioButton1])
	{
		assert([[self radioButton1] target] == self);
		[radioButtons addObject:[self radioButton1]];
	}
	if ([self radioButton2])
	{
		assert([[self radioButton2] target] == self);
		[radioButtons addObject:[self radioButton2]];
	}
	if ([self radioButton3])
	{
		assert([[self radioButton3] target] == self);
		[radioButtons addObject:[self radioButton3]];
	}
	if ([self radioButton4])
	{
		assert([[self radioButton4] target] == self);
		[radioButtons addObject:[self radioButton4]];
	}
	[self setRadioButtons:radioButtons];
	
	// Start KVO.
	[self startObserving];
}

// ---------------------------------------------------------------------------------------------------------------
- (void)dealloc
{
	// Stop KVO.
	[self stopObserving];
}

#pragma mark -

// ---------------------------------------------------------------------------------------------------------------
- (nullable NSButton*)radioButtonWithTag:(NSInteger)inTag
{
	NSButton* button = nil;
	
	// Find the button with the matching tag, if any.
	for (button in [self radioButtons])
	{
		if ([button tag] == inTag)
		{
			break;
		}
	}
	
	return button;
}

#pragma mark - Key value observing

// These global variables are used as contexts for KVO. There is no need to set their values since we are only interested in their addresses, but we set the value to facilitate debugging.
static uint8_t kRRContextIdx_Context = 1;

// ---------------------------------------------------------------------------------------------------------------
- (void)startObserving
{
	NSString* keyPath = [self bindingKeyPath];
	assert([keyPath containsString:@"."]);
	
	NSController* bindingController = [self bindingController];
	assert(bindingController);
	[bindingController addObserver:self
						forKeyPath:keyPath
						   options:NSKeyValueObservingOptionInitial
						   context:&kRRContextIdx_Context];
}

// ---------------------------------------------------------------------------------------------------------------
- (void)stopObserving
{
	NSString* keyPath = [self bindingKeyPath];
	assert([keyPath containsString:@"."]);
	
	NSController* bindingController = [self bindingController];
	assert(bindingController);
	[bindingController removeObserver:self
						   forKeyPath:keyPath
							  context:&kRRContextIdx_Context];
}

// ---------------------------------------------------------------------------------------------------------------
// This is called when the NSController wants to change values
- (void)observeValueForKeyPath:(nullable NSString*)inKeyPath
					  ofObject:(nullable id)inObject
						change:(nullable NSDictionary*)inChange
					   context:(nullable void*)inContext
{
	if (inContext == &kRRContextIdx_Context)
	{
		id newValue = [inObject valueForKeyPath:inKeyPath];
		
		// If the new value is not one of the special markers, try to find the radio button with matching tag (it may not exist).
		NSButton* button = nil;
		if (!NSIsControllerMarker(newValue))
		{
			NSInteger newInt = [(NSNumber*)newValue integerValue];
			button = [self radioButtonWithTag:newInt];
		}
		
		// If exactly one button was found, select it.  Others will be deselected by the OS (assuming they share the same superview).  Otherwise, deselect all of them.
		if (button)
		{
			[button setState:NSOnState];
		}
		else
		{
			for (button in [self radioButtons])
			{
				[button setState:NSOffState];
			}
		}
	}
	else
	{
		[super observeValueForKeyPath:inKeyPath ofObject:inObject change:inChange context:inContext];
	}
}

#pragma mark - Public API

// ---------------------------------------------------------------------------------------------------------------
- (IBAction)handleRadioButton:(id)inSender
{
	assert([inSender isKindOfClass:[NSButton class]]);
	
	NSInteger tag = [(NSButton*)inSender tag];
	
	NSString* keyPath = [self bindingKeyPath];
	assert([keyPath containsString:@"."]);
	
	// Use KVC to set the new value to match the tag of the pressed radio button.
	NSController* bindingController = [self bindingController];
	assert(bindingController);
	[bindingController setValue:@(tag) forKeyPath:keyPath];
}

@end
