Binding:: =
	## ==========================================================================
	## Subscriber Management
	## ========================================================================== 
	addSub: (sub, options, updateOnce, updateEvenIfSame)->
		if sub.isMulti
			@addSub(subItem, options, updateOnce, updateEvenIfSame) for subItem in sub.bindings
		else
			if metaData=@subsMeta[sub.ID]
				alreadyHadSub = true
			else
				sub.pubsMap[@ID] = @
				@subs.unshift(sub)
				
				metaData = @subsMeta[sub.ID] = genObj()
				metaData.updateOnce = updateOnce
				metaData.opts = cloneObject(options)
				metaData.opts.updateEvenIfSame = true if updateEvenIfSame or @type is 'Event' or @type is 'Proxy' or @type is 'Array'
				metaData.valueRef = if sub.type is 'Func' then 'valuePassed' else 'value'
			
		return alreadyHadSub



	removeSub: (sub, bothWays)->
		if sub.isMulti
			@removeSub(subItem, bothWays) for subItem in sub.bindings
		else
			if @subsMeta[sub.ID]
				@subs.splice(@subs.indexOf(sub), 1)
				delete @subsMeta[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) for event in @attachedEvents
		
		else if @type is 'Func'
			delete @object._sb_ID

		### istanbul ignore next ###
		convertToReg(@, @object) if @isLiveProp and @origDescriptor
		convertToReg(@, @value, true) if @type is 'Array'
		
		if @object._sb_map
			delete @object._sb_map[@selector]
			delete @object._sb_map if Object.keys(@object._sb_map).length is 0


		return






	## ==========================================================================
	## Value set/get
	## ========================================================================== 
	fetchDirectValue: ()->
		type = @type
		switch
			when type is 'Func' then @object()
			
			# simplyimport:if BUNDLE_TARGET = 'browser'
			when type is 'DOMAttr' then @object.getAttribute(@property) or ''

			when @isMultiChoice
				results = []
				for choiceName,choiceEl of @choices
					if choiceEl.object.checked
						if type is 'DOMRadio'
							return choiceName
						else
							results.push choiceName

				return results
			# simplyimport:end
		
			else @object[@property]
	



	setValue: (newValue, publisher, fromSelf, fromChangeEvent)-> # fromSelf===true when called from eventUpdateHandler or property descriptor setter (unless it's an Array binding)
		publisher ||= @
		newValue = @selfTransform(newValue) if @selfTransform
		
		unless fromSelf then switch @type
			when 'ObjectProp'
				if not @isLiveProp
					@object[@property] = newValue if newValue isnt @value
				# simplyimport:if BUNDLE_TARGET = 'browser'
				importInline './prototype.setValue-ObjectProp-DOMValue'
				# simplyimport:end
				else if @origSetter
					@origSetter(newValue)


			when 'Pholder'
				parent = @parentBinding
				parent.pholderValues[@pholder] = newValue
				entireValue = applyPlaceholders(parent.pholderContexts, parent.pholderValues, parent.pholderIndexMap)

				# simplyimport:if BUNDLE_TARGET = 'browser'
				if @textNodes and newValue isnt @value
					for textNode in @textNodes
						textNode[textContent] = newValue
				# simplyimport:end
				
				# simplyimport:if BUNDLE_TARGET = 'browser'
				parent.setValue(entireValue, publisher) unless @property is textContent
				# simplyimport:end
				
				# simplyimport:if BUNDLE_TARGET = 'node'
				parent.setValue(entireValue, publisher)
				# simplyimport:end


			when 'Array'
				if newValue isnt @value
					newValue = Array::concat(newValue) if not checkIf.isArray(newValue)
					convertToReg(@, @value, true)
					convertToLive(@, newValue=newValue.slice(), true)
					@origSetter(newValue) if @origSetter # Will update any other previous non-Array bindings to the same object property


			when 'Func'
				prevValue = @valuePassed
				@valuePassed = newValue
				newValue = @object(newValue, prevValue)

			when 'Event'
				@isEmitter = true
				@emitEvent(newValue)
				@isEmitter = false
		
			# simplyimport:if BUNDLE_TARGET = 'browser'
			importInline './prototype.setValue-DOMTypes'
			# simplyimport:end
		
		@value = newValue
		@updateAllSubs(publisher)

		return





	updateAllSubs: (publisher)-> if i=(arr=@subs).length # Ugly shortcut for index definition in order to limit logic repitiion
		@updateSub(arr[i], publisher) while i--
		return



			

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

		if meta.disallowList and meta.disallowList[publisher.ID]
			return

		if meta.opts.throttle
			currentTime = +(new Date)
			timePassed = currentTime - meta.lastUpdate
			
			if timePassed < meta.opts.throttle
				clearTimeout(meta.updateTimer)
				return meta.updateTimer =
					setTimeout ()=>
						@updateSub(sub, publisher) if @subsMeta[sub.ID]
					, meta.opts.throttle-timePassed
			
			else
				meta.lastUpdate = currentTime

		else if meta.opts.delay and not isDelayedUpdate
			return setTimeout ()=>
				@updateSub(sub, publisher, true) if @subsMeta[sub.ID]
			, meta.opts.delay


		newValue = if @type is 'Array' and meta.opts.sendArrayCopies then @value.slice() else @value
		subValue = sub[meta.valueRef]
		newValue = if transform=meta.transformFn then transform(newValue, subValue, sub.object) else newValue

		return if newValue is subValue and not meta.opts.updateEvenIfSame or
			meta.conditionFn and not meta.conditionFn(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 meta.opts.promiseTransforms and newValue and checkIf.isFunction(newValue.then)
			newValue.then (newValue)-> sub.setValue(newValue, publisher); return
		else
			sub.setValue(newValue, publisher)

		@removeSub(sub) if meta.updateOnce
		return



















	## ==========================================================================
	## Transforms & Conditions
	## ==========================================================================
	addModifierFn: (target, subInterfaces, subjectFn, updateOnBind)->
		if not checkIf.isFunction(subjectFn)
			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(target, subscriber.bindings, subjectFn, updateOnBind)
				
				else
					subMetaData = @subsMeta[subscriber.ID]
					subMetaData[target] = subjectFn
					updateOnBind = updateOnBind and not subMetaData.updateOnce

					if @pubsMap[subscriber.ID]
						subscriber.subsMeta[@ID][target] ||= subjectFn # Will not replace existing modifier function if exists

					@updateSub(subscriber, @) if (updateOnBind or @type is 'Func') and target is 'transformFn'

			return true



	setSelfTransform: (transformFn, updateOnBind)->
		@selfTransform = transformFn
		@setValue(@value) if updateOnBind
		return




	## ==========================================================================
	## Allow/Disallow rules
	## ========================================================================== 
	addDisallowRule: (targetSub, targetDisallow)->
		disallowList = @subsMeta[targetSub.ID].disallowList ?= genObj()
		disallowList[targetDisallow.ID] = 1
		return













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

		if checkIf.isString(@value)
			@pholderContexts = @value.split pholderRegExSplit
			
			index = 0
			@value = @value.replace pholderRegEx, (e, pholder)=>
				@pholderIndexMap[index++] = pholder
				@pholderValues[pholder] = pholder
		
		# simplyimport:if BUNDLE_TARGET = 'browser'
		scanTextNodesPlaceholders(@object, @textNodes=genObj()) if @isDom and @property is textContent
		# simplyimport:end
		return
	







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

			@setValue polledValue, @, true
		, time


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










	## ==========================================================================
	## Events
	## ========================================================================== 
	
	# simplyimport:if BUNDLE_TARGET = 'browser'
	addUpdateListener: (eventName, targetProperty)->
		@object.addEventListener eventName, (event)=>
			unless event._sb
				shouldRedefineValue = @selfTransform and @isDomInput
				@setValue(@object[targetProperty], null, !shouldRedefineValue, true)

			return
		
		, false
		return
	# simplyimport:end
	

	attachEvents: ()->
		if @eventName
			@registerEvent(@eventName)
		
		# simplyimport:if BUNDLE_TARGET = 'browser'
		else if @isDomInput
			@addUpdateListener('input', 'value')
			@addUpdateListener('change', 'value')

		else if not @isMultiChoice and (@type is 'DOMRadio' or @type is 'DOMCheckbox')
			@addUpdateListener('change', 'checked')
		# simplyimport:end

		return
	


	registerEvent: (eventName)->
		@attachedEvents.push(eventName)
		@eventHandler = eventUpdateHandler.bind(@) unless @eventHandler
		
		@object[@eventMethods.listen](eventName, @eventHandler)
		return



	unRegisterEvent: (eventName)->
		@attachedEvents.splice @attachedEvents.indexOf(eventName), 1

		@object[@eventMethods.remove](eventName, @eventHandler)
		return



	emitEvent: (extraData)->
		eventObject = @eventName
		
		# simplyimport:if BUNDLE_TARGET = 'browser'
		if @eventMethods.emit is 'dispatchEvent'
			unless @eventObject
				@eventObject = document.createEvent('Event')
				@eventObject.initEvent(@eventName, true, true)

			@eventObject.bindingData = extraData
			eventObject = @eventObject
		# simplyimport:end

		@object[@eventMethods.emit](eventObject, extraData)
		return




eventUpdateHandler = ()-> unless @isEmitter
	@setValue(arguments[@property], null, true)
	return





