Binding:: =
	## ==========================================================================
	## Instance-mutating methods
	## ========================================================================== 
	convertToLive: (force)->
		_ = @
		switch @type
			when 'ObjectProp'
				propertyDescriptor = Object.getOwnPropertyDescriptor(@object, @property) or dummyPropertyDescriptor
				shouldWriteLiveProp = force or propertyDescriptor.configurable
				import [browserOnly] prototype.convertToLive-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
			


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

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



			when 'Proxy'
				originalFn = @original = @value
				context = @object
				@value = result:null, args:null

				if checkIf.isFunction(originalFn)
					slice = [].slice
					
					@object[@property] = ()-> 
						_.value.args = args = slice.call(arguments)
						_.value.result = result = originalFn.apply(context, args)
						_.updateAllSubs(_)
						return result

		return




	addSub: (sub, options=@optionsDefault, updateOnce)->
		if sub.isMulti
			@addSub(subItem, options, updateOnce) for subItem in sub.bindings
		else
			if @subOpts[sub.ID]
				alreadyHadSub = !@myPholders[sub.ID]
			else
				@subs.unshift(sub)
				@subOpts[sub.ID] = cloneObject(options)
				@subOpts[sub.ID].updateEvenIfSame = true if sub.type is 'Func' or sub.type is 'Event'
				sub.pubsMap[@ID] = @

				if updateOnce
					SimplyBind('value', OPTS_NOUPDATE).of(sub).to ()=> @removeSub(sub)
				


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

			
			if sub.placeholder
				@subsPholders[sub.ID] = sub.placeholder		

		return alreadyHadSub



	removeSub: (sub, bothWays)->
		if sub.isMulti
			@removeSub(subItem, bothWays) for subItem in sub.bindings
		else
			if @subOpts[sub.ID]
				@subs.splice(@subs.indexOf(sub), 1)
				delete @subsPholders[sub.ID]
				delete @subOpts[sub.ID]
				delete sub.pubsMap[@ID]

			if bothWays
				sub.removeSub(@)
				delete @pubsMap[sub.ID]

		if @subs.length is 0 and Object.keys(@pubsMap).length is 0
			@destroy() # Since it's no longer a subscriber or has any subscribers
	
		return

	

	removeAllSubs: (bothWays)->
		@removeSub(sub, bothWays) for sub in @subs.slice()
		return




	destroy: ()-> # Resets object to initial state (pre-binding state)
		delete boundInstances[@ID]
		@removePollInterval()
		
		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

		else
			if @type is 'ObjectProp'
				Object.defineProperty @object, @property, {'value':@value, 'writable':true}
			
			delete @object._sb_map
			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] prototype.fetchDirectValue-DOMTypes.coffee
			else @object[@property]
	



	setValue: (newValue, specificPlaceholder, publisher, fromSelf)->
		publisher ||= @
		newValue = @selfTransform(newValue) if @selfTransform
		
		if specificPlaceholder
			prevValue = @pholderValues[specificPlaceholder]
			@pholderValues[specificPlaceholder] = newValue
			import [browserOnly] prototype.setValue-DOMText-placeholders.coffee				
			newValue = applyPlaceholders(@pholderContexts, @pholderValues, @pholderIndexMap)
		

		switch @type
			when 'ObjectProp'
				@object[@property] = newValue if not @isLiveProp and newValue isnt @value import [browserOnly] prototype.setValue-DOMText.coffee

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

			when 'Event'
				if not fromSelf
					@isEmitter = true
					@emitEvent(newValue)
					@isEmitter = false
		
			import [browserOnly] prototype.setValue-DOMTypes.coffee
		
		@value = newValue
		@updateAllSubs(publisher)

		return





	updateAllSubs: (publisher)-> if @subs.length
		if @throttleRate
			currentTime = +(new Date)
			timePassed = currentTime - @lastUpdate
			
			if timePassed < @throttleRate
				clearTimeout(@throttleTimeout)
				return @throttleTimeout = setTimeout (()=> @updateAllSubs(publisher)), @throttleRate-timePassed
			else
				@lastUpdate = currentTime
		

		i = (arr=@subs).length
		@updateSub(arr[i], publisher) while i--
		
		return



			

	updateSub: (sub, publisher)->
		return if (publisher is sub) or (publisher isnt @ and publisher.subOpts[sub.ID]) # indicates this is an infinite loop

		subOptions = @subOpts[sub.ID]
		myPlaceholder = @placeholder and @myPholders[sub.ID] # Check if this binding has a placeholder before performing a placeholder lookup
		subPlaceholder = sub.placeholder and @subsPholders[sub.ID] # Check if the subscriber has a placeholder before performing a placeholder lookup
		currentValue = if myPlaceholder then @pholderValues[myPlaceholder] else @value
		subValue = if subPlaceholder then sub.pholderValues[subPlaceholder] else sub.value

		newValue = if @transforms and @transforms[sub.ID] then @transforms[sub.ID](currentValue, subValue, sub.object) else currentValue

		return if newValue is subValue and not subOptions.updateEvenIfSame or
			@conditions and @conditions[sub.ID] and not @conditions[sub.ID](newValue, subValue, sub.object)

		# 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 subOptions.promiseTransforms and newValue and checkIf.isFunction(newValue.then)
			newValue.then (newValue)-> sub.setValue(newValue, subPlaceholder, publisher); return
		else
			sub.setValue(newValue, subPlaceholder, publisher)
		return



















	## ==========================================================================
	## Transforms & Conditions
	## ==========================================================================
	addModifierFn: (targetCollection, subInterfaces, targetFn, updateOnBind)->
		if not checkIf.isFunction(targetFn)
			throwWarning('fnOnly',2)

		else
			for subInterface in subInterfaces
				subscriber = subInterface._ or subInterface # Second is chosen when the passed subscriber interfaces multi-binding (is a recursive call of this method)

				if subscriber.isMulti
					@addModifierFn(targetCollection, subscriber.bindings, targetFn)
				
				else
					@setModifierFn(subscriber.ID, targetCollection, targetFn)

					if @pubsMap[subscriber.ID]
						subscriber.setModifierFn(@ID, targetCollection, targetFn)

					@updateSub(subscriber, @) if (updateOnBind or @type is 'Func') and targetCollection is 'transforms'

			return true


	setModifierFn: (targetID, targetCollection, targetFn)->
		@[targetCollection] ?= genObj()
		@[targetCollection][targetID] = targetFn
		return


	setSelfTransform: (transformFn, updateOnBind)-> if @type isnt 'Array'
		@selfTransform = transformFn
		@setValue(@value) if updateOnBind
		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] prototype.scanForPholders-DOMText.coffee
		return
	







	## ==========================================================================
	## Polling
	## ========================================================================== 
	addPollInterval: (time)-> if @type isnt 'Event'
		@removePollInterval()
		
		@pollInterval = setInterval ()=>
			polledValue = @fetchDirectValue()

			@setValue polledValue
		, time


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










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


	registerEvent: (eventName, customInMethod)-> if not targetIncludes(@attachedEvents, eventName)
		import [browserOnly] prototype.registerEvent.defaultInMethod-browser.coffee
		import [nodeOnly] prototype.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] prototype.unRegisterEvent.defaultRemoveMethod-browser.coffee
		import [nodeOnly] prototype.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] prototype.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] prototype.emitEvent.defaultOutMethod-browser.coffee
		import [nodeOnly] prototype.emitEvent.defaultOutMethod-node.coffee
		
		emitMethod = @customEventMethod.out or defaultOutMethod
		import [browserOnly] prototype.emitEvent-jQuery.coffee
		emitMethod = defaultOutMethod unless subject[emitMethod]#exists

		import [browserOnly] prototype.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





