//
//  OxBinder.m
//  Flash2
//
//  Copyright 2008 Nicholas Matsakis. 
//  The code in thie file is released to the public under the
//  terms of the MIT License as described here:
//  http://www.opensource.org/licenses/mit-license.php
//

#import "OxBinder.h"
#import "Ox.h"

#define OxBindingKey @"OxBindingKey"

@implementation OxBinder

- initWithObject:(id)object {
    [super init];
    m_activeBindings = [[NSMutableDictionary alloc] init];
	m_object = object; // NOTE: Intentionally weak reference.
    return self;
}

- initWithCoder:(NSCoder *)coder {
	if ((self = [self init])) { // note: NOT super!
		// Reestablish bindings:
		NSDictionary *activeBindings = [coder decodeObjectForKey:@"OxActiveBindings"];
		for (NSString *binding in [activeBindings allKeys]) {
			NSDictionary *activeBinding = [activeBindings objectForKey:binding];
			[self forwardedBind:binding 
					   toObject:[activeBinding objectForKey:NSObservedObjectKey] 
					withKeyPath:[activeBinding objectForKey:NSObservedKeyPathKey]
						options:[activeBinding objectForKey:NSOptionsKey]];
		}
	}
	return self;
}

- (void) dealloc
{
	for (NSString *binding in [m_activeBindings allKeys])
		[self forwardedUnbind:binding];
	
	[m_activeBindings release];
	[super dealloc];
}

- (void) encodeWithCoder:(NSCoder *)coder
{
	[coder encodeObject:m_activeBindings forKey:@"OxActiveBindings"];
}

- (NSDictionary*) forwardedInfoForBinding:(NSString*)binding
{
	return [m_activeBindings objectForKey:binding];
}

- (void)forwardedBind:(NSString *)binding 
			 toObject:(id)observableController 
		  withKeyPath:(NSString *)keyPath 
			  options:(NSDictionary *)options 
{
	if (options == nil) 
		options = [NSDictionary dictionary];
	//NSLog(@"forwardedBind:%@ toObject:%@ withKeyPath:%@ options:%@", binding, observableController, keyPath, options);
	NSDictionary *activeBinding = OxDict([[binding copy] autorelease] FOR OxBindingKey,
										 observableController FOR NSObservedObjectKey,
										 [[keyPath copy] autorelease] FOR NSObservedKeyPathKey,
										 [[options copy] autorelease] FOR NSOptionsKey);

	[m_activeBindings setObject:activeBinding forKey:binding];
	
	[m_object addObserver:self
			   forKeyPath:binding
				  options:0      // NSKeyValueObservingOptions
				  context:NULL];
    
    [observableController addObserver:self
						   forKeyPath:keyPath
							  options:0
							  context:activeBinding]; // m_activeBindings keeps the ref
	
	id initialValue = [observableController valueForKeyPath:keyPath];
    [m_object setValue:initialValue forKey:binding];
	
	//NSLog(@"  initialValue: %@", initialValue);
}

- (void) forwardedUnbind:(NSString*)binding
{
	NSDictionary *activeBinding = [m_activeBindings objectForKey:binding];
	if (!activeBinding)
		return;
	
	id observableObject = [activeBinding objectForKey:NSObservedObjectKey];
	NSString *observableKeyPath = [activeBinding objectForKey:NSObservedKeyPathKey];
	[m_object removeObserver:self forKeyPath:binding];
	[observableObject removeObserver:self forKeyPath:observableKeyPath];
	[m_activeBindings removeObjectForKey:binding];
}

- (void) observeValueForKeyPath:(NSString *)keyPath 
                       ofObject:(id)object 
                         change:(NSDictionary *)change 
                        context:(void *)context
{
	id sourceObject;
	NSString *sourceKeyPath;
	id targetObject;
	NSString *targetKeyPath;
	
	if (context == NULL) {
		// This is a change in m_object
		NSDictionary *activeBinding = [m_activeBindings objectForKey:keyPath];
		sourceObject = m_object;
		sourceKeyPath = keyPath;
		targetObject = [activeBinding objectForKey:NSObservedObjectKey];
		targetKeyPath = [activeBinding objectForKey:NSObservedKeyPathKey];
	} else {
		// A change in the observed object
		NSDictionary *activeBinding = (id)context;
		sourceObject = [activeBinding objectForKey:NSObservedObjectKey];
		sourceKeyPath = [activeBinding objectForKey:NSObservedKeyPathKey];
		targetObject = m_object;
		targetKeyPath = [activeBinding objectForKey:OxBindingKey];
	}
		
	id sourceValue = [sourceObject valueForKeyPath:sourceKeyPath];
	id targetValue = [targetObject valueForKeyPath:targetKeyPath];
	
	if (sourceValue != targetValue && ![targetValue isEqual:sourceValue]) {
		[targetObject setValue:sourceValue forKeyPath:targetKeyPath];
	}
}

@end

@implementation OxBindableObject

- init
{
	if ((self = [super init])) {
		m_binder = [[OxBinder alloc] initWithObject:self];
	}
	return self;
}

- initWithCoder:(NSCoder*)coder
{
	if ((self = [super init])) {
		m_binder = [[coder decodeObjectForKey:@"OxBinder"] retain];
	}
	return self;
}

- (void) dealloc
{
	[m_binder release];
	[super dealloc];
}

- (void) encodeWithCoder:(NSCoder*)coder
{
	[coder encodeObject:m_binder forKey:@"OxBinder"];
}

- (void) bind:(NSString *)binding toObject:(id)observable withKeyPath:(NSString *)keyPath options:(NSDictionary *)options
{
	if ([[self exposedBindings] containsObject:binding]) {
		[m_binder forwardedBind:binding toObject:observable withKeyPath:keyPath options:options];
		return;
	}
	[super bind:binding toObject:observable withKeyPath:keyPath options:options];
}

- (void) unbind:(NSString *)binding
{
	if ([[self exposedBindings] containsObject:binding]) {
		[m_binder forwardedUnbind:binding];
		return;
	}
	[super unbind:binding];
}

- (NSDictionary*) infoForBinding:(NSString*)binding
{
	if ([[self exposedBindings] containsObject:binding]) {
		return [m_binder infoForBinding:binding];
	}
	return [super infoForBinding:binding];
}

@end

