// ## getSiblingBindingData
// Returns information about the binding read from an attribute node.
// Arguments:
// - node - An attribute node like: `{name, value}`
// - bindingSettings - Optional.  Has {favorViewModel: Boolean}
// Returns an object with:
// - `parent` - {source, name, event, exports, syncSibling}
// - `child` - {source, name, event, exports, syncSibling, setCompute}
// - `bindingAttributeName` - debugging name.
// - `initializeValues` - should parent and child be initialized to their counterpart.
//
// `parent` and `child` properties:
//
// - `source` - where is the value read from: "scope", "attribute", "viewModel".
// - `name` - the name of the property that should be read
// - `event` - an optional event name to listen to
// - `exports` - if the value is exported to its sibling
// - `syncSibling` - if the value is sticky. When this value is updated, should the value be checked after
//   and its sibling be updated immediately.
// - `setCompute` - set the value to a compute.
function getSiblingBindingData(node, bindingSettings) {

	var siblingBindingData,
		attributeName = encoder.decode(node.name),
		attributeValue = node.value || "";

	var result = tokenize(attributeName),
		dataBindingName,
		specialIndex;

	// check if there's a match of a binding name with at least a value before it
	bindingNames.forEach(function(name) {
		if (result.special[name] !== undefined && result.special[name] > 0) {
			dataBindingName = name;
			specialIndex = result.special[name];
			return false;
		}
	});

	if (dataBindingName) {
		var childEventName = getEventName(result);

		var initializeValues = childEventName && dataBindingName !== "bind" ? false : true;
		siblingBindingData = {
			parent: assign({
				source: scopeBindingStr,
				name: result.special.raw ? ('"' + attributeValue + '"') : attributeValue
			}, siblingBindingRules[dataBindingName].parent),
			child: assign({
				source: getChildBindingStr(result.tokens, bindingSettings && bindingSettings.favorViewModel),
				name: result.tokens[specialIndex - 1],
				event: childEventName
			}, siblingBindingRules[dataBindingName].child),
			bindingAttributeName: attributeName,
			initializeValues: initializeValues
		};
		if (attributeValue.trim().charAt(0) === "~") {
			siblingBindingData.child.setCompute = true;
		}
		return siblingBindingData;
	}
}
	event: function(el, data) {
		var eventBindingData;
		// Get the `event` name and if we are listening to the element or viewModel.
		// The attribute name is the name of the event.
		var attributeName = encoder.decode(data.attributeName),
			// the name of the event we are binding
			event,
			// the context to which we bind the event listener
			bindingContext,
			// if the bindingContext is null, then use this observable to watch for changes
			bindingContextObservable;

		// check for `on:event:value:to` type things and call data bindings
		if (attributeName.indexOf(toMatchStr + ":") !== -1 ||
			attributeName.indexOf(fromMatchStr + ":") !== -1 ||
			attributeName.indexOf(bindMatchStr + ":") !== -1
		) {
			return this.data(el, data);
		}

		if (startsWith.call(attributeName, onMatchStr)) {
			eventBindingData = getEventBindingData(attributeName, el, data.scope);
			event = eventBindingData.eventName;
			bindingContext = eventBindingData.bindingContext;
			bindingContextObservable = eventBindingData.bindingContextObservable;
		} else {
			throw new Error("can-stache-bindings - unsupported event bindings " + attributeName);
		}

		// This is the method that the event will initially trigger. It will look up the method by the string name
		// passed in the attribute and call it.
		var handler = function(ev) {
			var attrVal = el.getAttribute(encoder.encode(attributeName));
			if (!attrVal) {
				return;
			}

			var viewModel = canViewModel(el);

			// expression.parse will read the attribute
			// value and parse it identically to how mustache helpers
			// get parsed.
			var expr = expression.parse(attrVal, {
				lookupRule: function() {
					return expression.Lookup;
				},
				methodRule: "call"
			});

			var runScope = makeScopeFromEvent(el, ev, viewModel, arguments, data, bindingContext);

			if (expr instanceof expression.Hashes) {
				var hashExprs = expr.hashExprs;
				var key = Object.keys(hashExprs)[0];
				var value = expr.hashExprs[key].value(runScope);
				var isObservableValue = canReflect.isObservableLike(value) && canReflect.isValueLike(value);
				runScope.set(key, isObservableValue ? canReflect.getValue(value) : value);
			} else if (expr instanceof expression.Call) {
				runEventCallback(el, ev, data, runScope, expr, attributeName, attrVal);
			} else {
				throw new Error("can-stache-bindings: Event bindings must be a call expression. Make sure you have a () in " + data.attributeName + "=" + JSON.stringify(attrVal));
			}
		};

		var attributesDisposal,
			removalDisposal,
			removeObservation,
			currentContext;

		// Unbind the event when the attribute is removed from the DOM
		var attributesHandler = function(ev) {
			var isEventAttribute = ev.attributeName === attributeName;
			var isRemoved = !el.getAttribute(attributeName);
			var isEventAttributeRemoved = isEventAttribute && isRemoved;
			if (isEventAttributeRemoved) {
				unbindEvent();
			}
		};
		var removalHandler = function() {
			var doc = el.ownerDocument;
			var ownerNode = doc.contains ? doc : doc.documentElement;
			if (!ownerNode || !ownerNode.contains(el)) {
				unbindEvent();
			}
		};
		var unbindEvent = function() {
			if (bindingContext) {
				canEventQueue.off.call(bindingContext, event, handler);
			}
			if (attributesDisposal) {
				attributesDisposal();
				attributesDisposal = undefined;
			}
			if (removalDisposal) {
				removalDisposal();
				removalDisposal = undefined;
			}
			if (removeObservation) {
				removeObservation();
				removeObservation = undefined;
			}
		};

		function updateListener(newVal, oldVal) {
			if (oldVal) {
				canEventQueue.off.call(oldVal, event, handler);
			}
			if (newVal) {
				canEventQueue.on.call(newVal, event, handler);
				currentContext = newVal;
			}
		}

		// Bind the handler defined above to the element we're currently processing and the event name provided in this
		// attribute name (can-click="foo")
		attributesDisposal = domMutate.onNodeAttributeChange(el, attributesHandler);
		removalDisposal = domMutate.onNodeRemoval(el, removalHandler);
		if (!bindingContext && bindingContextObservable) {
			// on value changes of the observation, rebind the listener to the new context
			removeObservation = function () {
				if (currentContext) {
					canEventQueue.off.call(currentContext, event, handler);
				}
				canReflect.offValue(bindingContextObservable, updateListener);
			};
			canReflect.onValue(bindingContextObservable, updateListener);
		} else {
			canEventQueue.on.call(bindingContext, event, handler);
		}
	}
var makeDataBinding = function(node, bindingContext, bindingSettings) {
	// Get information about the binding.
	var siblingBindingData = getSiblingBindingData( node, bindingSettings );
	if (!siblingBindingData) {
		return;
	}

	// Get computes for the parent and child binding
	var parentObservable = getObservableFrom[siblingBindingData.parent.source](
		siblingBindingData.parent,
		bindingContext, bindingSettings
	),
	childObservable = getObservableFrom[siblingBindingData.child.source](
		siblingBindingData.child,
		bindingContext, bindingSettings,
		parentObservable
	);

	var childToParent = !!siblingBindingData.child.exports;
	var parentToChild = !!siblingBindingData.parent.exports;

	// Check for child:bind="~parent" (it’s not supported because it’s unclear
	// what the “right” behavior should be)

	//!steal-remove-start
	if (process.env.NODE_ENV !== 'production') {
		if (siblingBindingData.child.setCompute && childToParent && parentToChild) {
			dev.warn("Two-way binding computes is not supported.");
		}
	}
	//!steal-remove-end

	var bindingOptions = {
		child: childObservable,
		childToParent: childToParent,
		// allow cycles if one directional
		cycles: childToParent === true && parentToChild === true ? 0 : 100,
		onInitDoNotUpdateChild: bindingSettings.alreadyUpdatedChild || siblingBindingData.initializeValues === false,
		onInitDoNotUpdateParent: siblingBindingData.initializeValues === false,
		onInitSetUndefinedParentIfChildIsDefined: true,
		parent: parentObservable,
		parentToChild: parentToChild,
		priority: bindingContext.parentNodeList ? bindingContext.parentNodeList.nesting + 1 : undefined,
		queue: "domUI",
		sticky: siblingBindingData.parent.syncSibling ? "childSticksToParent" : undefined
	};

	//!steal-remove-start
	if (process.env.NODE_ENV !== 'production') {
		var nodeHTML = encoder.decode(node.name)+"="+JSON.stringify(node.value);
		var tagStart = "<"+bindingContext.element.nodeName.toLowerCase(),
			tag = tagStart+">";

		var makeUpdateName = function(child, childName) {

			if(child === "viewModel") {
				return tag+"."+childName;
			}
			else if(child === "scope") {
				return "{{"+childName+"}}";
			}
			else {
				return ""+child+"."+childName;
			}
		};
		bindingOptions.updateChildName = tagStart+" "+nodeHTML+"> updates "+
			makeUpdateName(siblingBindingData.child.source, siblingBindingData.child.name)+
			" from "+makeUpdateName(siblingBindingData.parent.source, siblingBindingData.parent.name);

		bindingOptions.updateParentName = tagStart+" "+nodeHTML+"> updates "+
			makeUpdateName(siblingBindingData.parent.source, siblingBindingData.parent.name)+
			" from "+makeUpdateName(siblingBindingData.child.source, siblingBindingData.child.name);
	}
	//!steal-remove-end

	// Create the binding
	var canBinding = new Bind(bindingOptions);

	return {
		siblingBindingData: siblingBindingData,
		binding: canBinding
	};
};