Binding:: =
	## ==========================================================================
	## Instance-mutating methods
	## ========================================================================== 
	makePropertyLive: (force)-> if @options.liveProps
		_ = @

		if @type is 'ObjectProp'
			propertyDescriptor = Object.getOwnPropertyDescriptor(@object, @property) or dummyPropertyDescriptor
			shouldWriteLiveProp = force or not @isLiveProp and (propertyDescriptor.configurable or @options.mutateInherited)
			import [browserOnly] Binding;;makePropertyLive.StylingConstructorCheck.coffee
			
			if shouldWriteLiveProp
				@isLiveProp = true
				
				Object.defineProperty @object, @property,
					configurable: true
					enumerable: propertyDescriptor.enumerable
					get: ()-> _.value
					set: if propertyDescriptor.set
							(newValue)-> propertyDescriptor.set(newValue); _.setValue(newValue); return
						else
							(newValue)-> _.setValue(newValue); return
		


		else if @type is 'Array'
			if not @isLiveProp
				@isLiveProp = true

				arrayMutatorMethods.forEach (method)->
					Object.defineProperty _.value, method, 
						configurable: true
						value: ()->
							result = Array::[method].apply _.value, arguments
							_.updateAllDeps(_)
							return result

			
			if @options.trackArrayChildren and not @trackedChildren
				@trackedChildren = [] # Required so we can add new children that are bound to a specific index of this array
				@updateSelf = ()-> _.updateAllDeps(_) # Saved to 'this' so it can be reused when adding new children in .setObject
				opts = updateOnBind:false
				
				@value.forEach (item, index)->
					_.trackedChildren.push(''+index)
					
					SimplyBind(index, opts).of(_.value)
						.to _.updateSelf
		return




	addDep: (dep, bothWays)->
		if dep.isMulti
			@addDep(depItem) for depItem in dep.bindings

		else
			unless @depsMap[1][dep.ID]
				@depsMap[1][dep.ID] = dep
				@deps.push(dep)

			if @placeholder
				@myPholders[dep.ID] = @placeholder
			
			else if @myPholders[dep.ID]
				delete @myPholders[dep.ID]

			
			if dep.placeholder
				@depsPholders[dep.ID] = dep.placeholder


			if bothWays
				@depsMap[2][dep.ID] = dep
		
			else if dep.depsMap[1][@ID] # Check if the passed Object ID has this Object's ID in its dependents and invoke .bothWays()
				dep.addDep(@, true)
				@addDep(dep, true)

		return @



	removeDep: (dep, bothWays)->
		if dep.isMulti
			@removeDep(depItem, bothWays) for depItem in dep.bindings
		
		else
			if @depsMap[1][dep.ID]
				@deps.splice(@deps.indexOf(dep), 1)
				delete @depsMap[1][dep.ID]
				delete @depsPholders[dep.ID]

			if bothWays
				dep.removeDep(@)
				delete @depsMap[2][dep.ID]
		return

	

	removeAllDeps: (bothWays)->
		@removeDep(dep, bothWays) for dep in @deps.slice()
		@destroy() if bothWays or Object.keys(@depsMap[2]).length is 0 # Resets object to initial state (Since it's no longer a dependent or has any dependents)
		return




	destroy: ()->
		delete boundInstances[@ID]
		if @type is 'ObjectProp'
			Object.defineProperty @object, @property, {'value':@value, 'writable':true}
			delete @object._sb_map
			delete @object._sb_ID

		else if @type is 'Event'
			@unRegisterEvent(event, @customEventMethod.remove) for event in @attachedEvents
			delete @object._sb_map

		else if @type is 'Array'
			delete @object._sb_ID
			delete @object[method] for method in arrayMutatorMethods
		
		else if @type is 'Func'
			delete @object._sb_ID

		return







	## ==========================================================================
	## Value-related methods
	## ========================================================================== 
	fetchDirectValue: ()->
		type = @type
		switch
			when type is 'Func' then @object()
			when type is 'Array' then @object
			import [browserOnly] Binding;;fetchDirectValue.DOMTypes.coffee
			else @object[@property]
	



	setValue: (newValue, specificPlaceholder, updater=@, fromSelf)->
		prevValue = if specificPlaceholder then @pholderValues[specificPlaceholder] else @value
		newValue = @selfTransform(newValue) if @selfTransform
		isNewValue = newValue isnt prevValue or @options.updateEvenIfSame
		
		if isNewValue and @type isnt 'Array'
			if specificPlaceholder
				@pholderValues[specificPlaceholder] = newValue
				import [browserOnly] Binding;;setValue.DOMText-placeholders.coffee				
				newValue = applyPlaceholders(@pholderContexts, @pholderValues, @pholderIndexMap)
			

			switch @type
				when 'ObjectProp'
					@object[@property] = newValue unless @isLiveProp import [browserOnly] Binding;;setValue.DOMText.coffee

				when 'Func'
					prevValue = @valuePassed
					newValue = newValue.slice() if updater.type is 'Array' and newValue is updater.value
					@valuePassed = newValue
					newValue = @object(newValue, prevValue)

				when 'Event'
					if not fromSelf
						@isEmitter = true
						@emitEvent(newValue)
						@isEmitter = false
			
				import [browserOnly] Binding;;setValue.DOMTypes.coffee
			
			@value = newValue
			@updateAllDeps(updater)

		return





	updateAllDeps: (updater)-> if @deps.length
		if @throttleRate
			currentTime = +(new Date)
			timePassed = currentTime - @lastUpdate
			
			if timePassed < @throttleRate
				clearTimeout(@throttleTimeout)
				return @throttleTimeout = setTimeout (()=> @updateAllDeps(updater)), @throttleRate-timePassed
			else
				@lastUpdate = currentTime
		
		@updateDep(dep, updater) for dep in @deps
		return



			

	updateDep: (dep, updater)->
		return if (updater is dep) or (updater isnt @ and updater.depsMap[1][dep.ID]) # indicates this is an infinite loop

		myPlaceholder = @myPholders[dep.ID]
		depPlaceholder = @depsPholders[dep.ID]
		currentValue = if myPlaceholder then @pholderValues[myPlaceholder] else @value
		depValue = if depPlaceholder then dep.pholderValues[depPlaceholder] else dep.value

		newValue = if not @hasTransforms then currentValue else @applyTransform(dep, depPlaceholder, currentValue, depValue)
		return if @hasConditions and not @checkCondition(dep, depPlaceholder, currentValue, depValue)

		# Why do we need the 'promiseTransforms' option when we can just check for the existance of .then method?
		# Because tests show that when searching for the .then prop on the object results in a performance slowdown of up to 30%!
		# Checking if the promiseTransforms option is enabled first eliminates unnecessary lookups & slowdowns.
		if @options.promiseTransforms and newValue and checkIf.isFunction(newValue.then)
			newValue.then (newValue)-> dep.setValue(newValue, depPlaceholder, updater); return
		else
			dep.setValue(newValue, depPlaceholder, updater)
		return



















	## ==========================================================================
	## Transforms
	## ==========================================================================
	processTransform: (transformFn, subjects)->
		if not checkIf.isFunction(transformFn)
			throwWarning('fnOnly',2)

		else			
			for prox in subjects
				prox = prox._ or prox # Second is chosen when the passed proxied multi-binding (is a recursive call of this method)

				if prox.isMulti
					@processTransform(transformFn, prox.bindings)
				
				else				
					@addTransform(prox.ID, transformFn)

					if @depsMap[2][prox.ID]
						prox.addTransform(@ID, transformFn)

					@updateDep(prox, @) if @options.updateOnBind or @type is 'Func'

			return true

		


	applyTransform: (dep, placeholder, value, depValue)->
		if @transforms[dep.ID]
			return @transforms[dep.ID](value, depValue)
		else return value
	


	addTransform: (ID, transformFn)->
		@hasTransforms = true
		@transforms[ID] = transformFn
		return










	## ==========================================================================
	## Conditions
	## ========================================================================== 
	processCondition: (conditionFn, subjects)->
		if not checkIf.isFunction(conditionFn)
			throwWarning('fnOnly',2)

		else			
			for prox in subjects
				prox = prox._ or prox # Second is chosen when the passed proxied multi-binding (is a recursive call of this method)

				if prox.isMulti
					@processCondition(conditionFn, prox.bindings)
				
				else				
					@addCondition(prox.ID, conditionFn)
					
					if @depsMap[2][prox.ID]					
						prox.addCondition(@ID, conditionFn)

			return true



	checkCondition: (dep, placeholder, value, depValue)->
		if @conditions[dep.ID]
			return @conditions[dep.ID](value, depValue)
		else return true




	addCondition: (ID, conditionFn)->
		@hasConditions = true
		@conditions[ID] = conditionFn
		return













	## ==========================================================================
	## Placeholders
	## ========================================================================== 
	scanForPholders: ()-> 
		@pholderValues = genObj()
		@pholderIndexMap = genObj()
		@pholderContexts = []

		if checkIf.isString(@valueOriginal)
			@pholderContexts = @valueOriginal.split pholderRegExSplit
			
			index = 0
			@value = @valueOriginal.replace pholderRegEx, (e, pholder)=>
				@pholderIndexMap[index++] = pholder
				@pholderValues[pholder] = pholder
		
		import [browserOnly] Binding;;scanForPholders.DOMText.coffee
		return
	







	## ==========================================================================
	## Polling
	## ========================================================================== 
	addPollInterval: (time)->
		@removePollInterval()
		
		@pollInterval = setInterval ()=>
			polledValue = @fetchDirectValue()

			@setValue polledValue
		, time


	removePollInterval: ()->
		clearInterval(@pollInterval)
		@pollInterval = null










	## ==========================================================================
	## Events
	## ========================================================================== 
	import [browserOnly] Binding;;addUpdateListener.coffee
	import [browserOnly] Binding;;emitChangeEvent.coffee
	
	attachEvents: ()->
		if @eventName
			@registerEvent @eventName, @customEventMethod.in
		import [browserOnly] Binding;;attachEvents.DOM.coffee
		return
	


	registerEvent: (eventName, customInMethod)-> if not arrayIncludes(@attachedEvents, eventName)
		import [browserOnly] Binding;;registerEvent.defaultInMethod-browser.coffee
		import [nodeOnly] Binding;;registerEvent.defaultInMethod-node.coffee

		@attachedEvents.push(eventName)
		attachmentMethod = customInMethod or defaultInMethod
		
		@invokeEventMethod(eventName, attachmentMethod, defaultInMethod)
		return



	unRegisterEvent: (eventName, customMethod)->
		indexOfEvent = @attachedEvents.indexOf eventName
		return if indexOfEvent is -1
		import [browserOnly] Binding;;unRegisterEvent.defaultRemoveMethod-browser.coffee
		import [nodeOnly] Binding;;unRegisterEvent.defaultRemoveMethod-node.coffee

		@attachedEvents.splice(indexOfEvent, 1)
		removalMethod = customMethod or defaultRemoveMethod

		@invokeEventMethod(eventName, removalMethod, defaultRemoveMethod)
		return




	invokeEventMethod: (eventName, eventMethod, backupMethod)->
		subject = @object
		import [browserOnly] Binding;;invokeEventMethod.jQuery.coffee
		eventMethod = backupMethod unless subject[eventMethod]
		@eventHandler = handleUpdateFromEvent.bind(@) unless @eventHandler#exists

		subject[eventMethod]? eventName, @eventHandler
		return



	emitEvent: (extraData)->
		subject = @object
		import [browserOnly] Binding;;emitEvent.defaultOutMethod-browser.coffee
		import [nodeOnly] Binding;;emitEvent.defaultOutMethod-node.coffee
		
		emitMethod = @customEventMethod.out or defaultOutMethod
		import [browserOnly] Binding;;emitEvent.jQuery.coffee
		emitMethod = defaultOutMethod unless subject[emitMethod]#exists

		import [browserOnly] Binding;;emitEvent.dispatchEventObject.coffee
		subject[emitMethod](@eventName, extraData)
		return




handleUpdateFromEvent = ()-> unless @isEmitter
	fetchedValue = if @type is 'Event' then arguments[@property] else @fetchDirectValue()

	@setValue(fetchedValue, null, null, true)
	return





