suite "Data Binding", ()->
	suiteSetup(restartSandbox)
	import ./placeholders
	import ./throttling
	import ./delaying

	suite "Binding-Type Specific", ()->
		import ./type-DOMValue
		import ./type-DOMCheckbox
		import ./type-DOMRadio
		import ./type-DOMAttr
		import ./type-Function
		import ./type-Array
		import ./type-Event
		import ./type-Proxy
		import ./type-ObjectProp

	suite "Group Bindings", ()->
		import ./group-basic
		import ./group-transforms
		import ./group-conditions
		import ./group-misc
		import ./group-unbinding



	test "Infinite loops should not occur in looping update chains", ()->
		SimplyBind.defaultOptions.updateEvenIfSame = true

		# ==== Objects =================================================================================
		SimplyBind('prop').of(objectA)
			.to('prop').of(objectB).bothWays()
				.chainTo('prop').of(objectC).bothWays()
					.chainTo('prop').of(objectA).bothWays()

		objectA.prop = 'from objectA'
		expect(objectA.prop).to.equal 'from objectA'
		expect(objectB.prop).to.equal 'from objectA'
		expect(objectC.prop).to.equal 'from objectA'

		objectB.prop = 'from objectB'
		expect(objectA.prop).to.equal 'from objectB'
		expect(objectB.prop).to.equal 'from objectB'
		expect(objectC.prop).to.equal 'from objectB'

		

		# ==== Arrays =================================================================================
		SimplyBind('array:list').of(objectA)
			.to('array:list').of(objectB).bothWays()
				.chainTo('array:list').of(objectC).bothWays()
					.chainTo('array:list').of(objectA).bothWays()

		expect(objectA.list.length).to.equal 10
		expect(objectB.list.length).to.equal 10
		expect(objectC.list.length).to.equal 10
		expect(objectA.list).not.to.equal(objectB.list)
		expect(objectA.list).to.eql(objectB.list)
		expect(objectB.list).to.eql(objectC.list)

		objectA.list.pop()
		expect(objectA.list.length).to.equal 9
		expect(objectB.list.length).to.equal 9
		expect(objectC.list.length).to.equal 9
		expect(objectA.list).not.to.equal(objectB.list)
		expect(objectA.list).to.eql(objectB.list)
		expect(objectB.list).to.eql(objectC.list)

		objectB.list.pop()
		expect(objectA.list.length).to.equal 8
		expect(objectB.list.length).to.equal 8
		expect(objectC.list.length).to.equal 8
		expect(objectA.list).not.to.equal(objectB.list)
		expect(objectA.list).to.eql(objectB.list)
		expect(objectB.list).to.eql(objectC.list)

		objectC.list.push(1)
		expect(objectA.list.length).to.equal 9
		expect(objectB.list.length).to.equal 9
		expect(objectC.list.length).to.equal 9
		expect(objectA.list).not.to.equal(objectB.list)
		expect(objectA.list).to.eql(objectB.list)
		expect(objectB.list).to.eql(objectC.list)
		


		if isBrowser
			# ==== DOMAttr =================================================================================
			SimplyBind('attr:someattr').of(regA)
				.to('attr:someattr').of(regB).bothWays()
					.chainTo('attr:someattr').of(regC).bothWays()
						.chainTo('attr:someattr').of(regA).bothWays()

			# regA.setAttribute 'someattr', 'from regElementA'
			SimplyBind('attr:someattr').of(regA).set('from regElementA')
			expect(regA.getAttribute 'someattr').to.equal 'from regElementA'
			expect(regB.getAttribute 'someattr').to.equal 'from regElementA'
			expect(regC.getAttribute 'someattr').to.equal 'from regElementA'

			# regB.setAttribute 'someattr', 'from regElementB'
			SimplyBind('attr:someattr').of(regB).set('from regElementB')
			expect(regA.getAttribute 'someattr').to.equal 'from regElementB'
			expect(regB.getAttribute 'someattr').to.equal 'from regElementB'
			expect(regC.getAttribute 'someattr').to.equal 'from regElementB'
			


			# ==== DOMText =================================================================================
			SimplyBind('textContent').of(regA)
				.to('textContent').of(regB).bothWays()
					.chainTo('textContent').of(regC).bothWays()
						.chainTo('textContent').of(regA).bothWays()

			SimplyBind('textContent').of(regA).set 'from regElementA'
			expect(regA.textContent).to.equal 'from regElementA'
			expect(regB.textContent).to.equal 'from regElementA'
			expect(regC.textContent).to.equal 'from regElementA'

			SimplyBind('textContent').of(regB).set 'from regElementB'
			expect(regA.textContent).to.equal 'from regElementB'
			expect(regB.textContent).to.equal 'from regElementB'
			expect(regC.textContent).to.equal 'from regElementB'
			


			# ==== DOMValue =================================================================================
			SimplyBind('value').of(inputA)
				.to('value').of(inputB).bothWays()
					.chainTo('value').of(inputC).bothWays()
						.chainTo('value').of(inputA).bothWays()

			inputA.value = 'from inputElementA'
			inputA.emit 'change'
			expect(inputA.value).to.equal 'from inputElementA'
			expect(inputB.value).to.equal 'from inputElementA'
			expect(inputC.value).to.equal 'from inputElementA'

			inputB.value = 'from inputElementB'
			inputB.emit 'change'
			expect(inputA.value).to.equal 'from inputElementB'
			expect(inputB.value).to.equal 'from inputElementB'
			expect(inputC.value).to.equal 'from inputElementB'



		# ==== Events =================================================================================			
		SimplyBind('event:eventA').of(eventEmitterA)
			.to('event:eventB').of(eventEmitterA).bothWays()
		
		SimplyBind('event:eventB').of(eventEmitterA)
			.to('event:eventC').of(eventEmitterA).bothWays()
		
		SimplyBind('event:eventC').of(eventEmitterA)
			.to('event:eventA').of(eventEmitterA).bothWays()

		invokeCountA = invokeCountB = invokeCountC = 0
		eventEmitterA.on 'eventA', ()-> invokeCountA++
		eventEmitterA.on 'eventB', ()-> invokeCountB++
		eventEmitterA.on 'eventC', ()-> invokeCountC++
		
		eventEmitterA.emit 'eventA'
		eventEmitterA.emit 'eventB'
		eventEmitterA.emit 'eventC'

		expect(invokeCountA).to.equal 3
		expect(invokeCountB).to.equal 3
		expect(invokeCountC).to.equal 3
		SimplyBind.defaultOptions.updateEvenIfSame = false
		restartSandbox()




	test "Infinite loops should not occur for colliding two-way bindings", ()->
		SimplyBind.defaultOptions.updateEvenIfSame = true
		dispatcher = 'prop': 0
		objectA.prop1 = 0
		objectB.prop1 = 0
		objectC.prop1 = 0
		SimplyBind('prop').of(dispatcher)
			.to('prop1').of(objectA).bothWays().transform (v, ov)-> v+ov
			.and.to('prop1').of(objectB).bothWays().transform (v, ov)-> v+ov
			.and.to('prop1').of(objectC).bothWays().transform (v, ov)-> v+ov

		expect(dispatcher.prop).to.equal 0
		expect(objectA.prop1).to.equal 0
		expect(objectB.prop1).to.equal 0
		expect(objectC.prop1).to.equal 0
		
		dispatcher.prop = 1
		expect(dispatcher.prop).to.equal 1
		expect(objectA.prop1).to.equal 1
		expect(objectB.prop1).to.equal 1
		expect(objectC.prop1).to.equal 1

		objectA.prop1 = 2
		expect(dispatcher.prop).to.equal 3
		expect(objectA.prop1).to.equal 2
		expect(objectB.prop1).to.equal 4 # not 3 because dispatcher.prop gets a 2 from objectA.prop1 but then it transforms it to 3 and passes to this binding.
		expect(objectC.prop1).to.equal 4 # not 3 because dispatcher.prop gets a 2 from objectA.prop1 but then it transforms it to 3 and passes to this binding.

		objectC.prop1 = 3
		expect(dispatcher.prop).to.equal 6
		expect(objectA.prop1).to.equal 8
		expect(objectB.prop1).to.equal 10
		expect(objectC.prop1).to.equal 3


		SimplyBind.defaultOptions.updateEvenIfSame = false
		restartSandbox()	



	test "Infinite loops should occur only when a value is bound to a function and that function updates the value", ()->
		invokeCount = 0

		SimplyBind('prop1').of(objectA)
			.to ()-> unless invokeCount is 15
				objectA.prop1 = ++invokeCount

		expect(invokeCount).to.equal(objectA.prop1)
		expect(invokeCount).to.equal(15)
		restartSandbox()




	test "Update subscribers", ()->
		SimplyBind.defaultOptions.updateOnBind = false
		invokeCount =
			'object': 0
			'array': 0
			'function': 0
			'proxy': 0
			'domAttr': 0
			'domText': 0
			'domValue': 0
			'domCheckboxSingle': 0
			'domCheckbox': 0
			'domRadioSingle': 0
			'domRadio': 0
			'event': 0
		

		# ==== Objects =================================================================================
		SimplyBind('prop').of(objectA).to ()-> invokeCount.object++
		objectA.prop = true
		expect(invokeCount.object).to.equal(1)


		# ==== Arrays =================================================================================
		SimplyBind('array:list').of(objectA).to ()-> invokeCount.array++
		objectA.list.push(1)
		objectA.list.unshift(1)
		objectA.list.pop()
		objectA.list.shift()
		objectA.list.splice(0,1)
		expect(invokeCount.array).to.equal(5)


		# ==== Functions =================================================================================
		SimplyBind(()->true).to ()-> invokeCount.function++
		expect(invokeCount.function).to.equal(1)


		# ==== Proxy functions =================================================================================
		obj = base:10, test:(a,b)-> (a+b)*@base
		SimplyBind('func:test').of(obj).to (value)->
			expect(value.result).to.equal 50
			expect(value.args).to.eql [2,3]
			invokeCount.proxy++

		obj.test(2,3)
		expect(invokeCount.proxy).to.equal(1)


		if isBrowser
			# ==== DOMAttr =================================================================================
			SimplyBind('attr:someattr').of(regA).to ()-> invokeCount.domAttr++
			regA.setAttribute('someattr', 10)
			SimplyBind('attr:someattr').of(regA).set(true)
			expect(invokeCount.domAttr).to.equal(1)


			# ==== DOMText =================================================================================
			SimplyBind('textContent').of(regA).to ()-> invokeCount.domText++
			SimplyBind('textContent').of(regA).set 'true'
			expect(invokeCount.domText).to.equal(1)


			# ==== DOMValue =================================================================================
			SimplyBind('value').of(inputA).to ()-> invokeCount.domValue++
			inputA.value = 'true'
			inputA.emit 'change'
			expect(invokeCount.domValue).to.equal(1)


			# ==== DOMCheckbox Single =================================================================================
			SimplyBind('checked').of(checkboxA).to ()-> invokeCount.domCheckboxSingle++
			checkboxA.checked = true
			checkboxA.emit 'change'
			expect(invokeCount.domCheckboxSingle).to.equal(1)


			# ==== DOMCheckbox =================================================================================
			SimplyBind('checked').of(checkboxFields).to ()-> invokeCount.domCheckbox++
			checkboxB.checked = true
			checkboxB.emit 'change'
			expect(invokeCount.domCheckbox).to.equal(1)


			# ==== DOMRadio Single =================================================================================
			SimplyBind('checked').of(radioA).to ()-> invokeCount.domRadioSingle++
			radioA.checked = true
			radioA.emit 'change'
			expect(invokeCount.domRadioSingle).to.equal(1)


			# ==== DOMRadio =================================================================================
			SimplyBind('checked').of(radioFields).to ()-> invokeCount.domRadio++
			radioB.checked = true
			radioB.emit 'change'
			expect(invokeCount.domRadio).to.equal(1)


		# ==== Event =================================================================================
		SimplyBind.defaultOptions.updateEvenIfSame = true
		SimplyBind('event:someEvent').of(eventEmitterA).to ()-> invokeCount.event++
		eventEmitterA.emit 'someEvent'
		

		expect(invokeCount.event).to.equal(1)

		SimplyBind.defaultOptions.updateEvenIfSame = false
		SimplyBind.defaultOptions.updateOnBind = true
		restartSandbox()







	test "Receive a value as a dependent", ()->
		SimplyBind.defaultOptions.updateOnBind = false
		invokeCount =
			'object': 0
			'array': 0
			'proxy': 0
			'function': 0
			'domAttr': 0
			'domText': 0
			'domValue': 0
			'domCheckboxSingle': 0
			'domCheckbox': 0
			'domRadio': 0
			'event': 0
		

		# ==== Objects =================================================================================
		SimplyBind('prop').of(objectA).to ()-> invokeCount.object++
		SimplyBind(()-> true).to('prop').of(objectA)

		expect(objectA.prop).to.equal(true)
		expect(invokeCount.object).to.equal(1)


		# ==== Arrays =================================================================================
		expect(objectA.list.length).to.equal(10)
		SimplyBind('array:list').of(objectA).to ()-> invokeCount.array++
		SimplyBind(()-> [1,2]).to('array:list').of(objectA)

		expect(objectA.list.length).to.equal(2)
		expect(invokeCount.array).to.equal(1)


		# ==== Proxy functions =================================================================================
		obj = base:10, test:(a,b)-> (a+b)*@base
		proxyInterface = SimplyBind('func:test').of(obj).to ()-> invokeCount.proxy++
		SimplyBind(()-> true).to(proxyInterface)
		expect(invokeCount.proxy).to.equal(1)



		if isBrowser
			# ==== DOMAttr =================================================================================
			SimplyBind('attr:someattr').of(regA).to ()-> invokeCount.domAttr++
			SimplyBind(()-> 'true').to('attr:someattr').of(regA)

			expect(regA.getAttribute('someattr')).to.equal('true')
			expect(invokeCount.domAttr).to.equal(1)


			# ==== DOMText =================================================================================
			SimplyBind('textContent').of(regA).to ()-> invokeCount.domText++
			SimplyBind(()-> 'true').to('textContent').of(regA)

			expect(regA.textContent).to.equal('true')
			expect(invokeCount.domText).to.equal(1)


			# ==== DOMValue =================================================================================
			SimplyBind('value').of(inputA).to ()-> invokeCount.domValue++
			SimplyBind(()-> 'true').to('value').of(inputA)
			
			expect(inputA.value).to.equal('true')
			expect(invokeCount.domValue).to.equal(1)


			# ==== DOMCheckbox Single =================================================================================
			expect(checkboxA.checked).to.be.false
			SimplyBind('checked').of(checkboxA).to ()-> invokeCount.domCheckboxSingle++
			SimplyBind(()-> 'truthy').to('checked').of(checkboxA)
			
			expect(checkboxA.checked).to.be.true
			expect(invokeCount.domCheckboxSingle).to.equal(1)


			# ==== DOMCheckbox =================================================================================
			SimplyBind('checked').of($checkboxFields).to ()-> invokeCount.domCheckbox++
			SimplyBind(()-> ['checkboxB', 'checkboxC']).to('checked').of($checkboxFields)
			
			expect(checkboxA.checked).to.be.false
			expect(checkboxB.checked).to.be.true
			expect(checkboxC.checked).to.be.true
			expect(invokeCount.domCheckbox).to.equal(1)
			
			SimplyBind(()-> 'checkboxA').to('checked').of($checkboxFields)
			
			expect(checkboxA.checked).to.be.true
			expect(checkboxB.checked).to.be.false
			expect(checkboxC.checked).to.be.false
			expect(invokeCount.domCheckbox).to.equal(2)


			# ==== DOMRadio =================================================================================
			SimplyBind('checked').of($radioFields).to ()-> invokeCount.domRadio++
			SimplyBind(()-> 'radioC').to('checked').of($radioFields)
			
			expect(radioA.checked).to.be.false
			expect(radioB.checked).to.be.false
			expect(radioC.checked).to.be.true
			expect(invokeCount.domRadio).to.equal(1)


		# ==== Event =================================================================================
		SimplyBind('event:someEvent').of(eventEmitterA).to ()-> invokeCount.event++
		SimplyBind(()-> true).to('event:someEvent').of(eventEmitterA)

		expect(invokeCount.event).to.equal(1)
		

		SimplyBind.defaultOptions.updateOnBind = true
		restartSandbox()



