define(["intern!object", "intern/chai!assert", "./resources/test.utils"],
	function (registerSuite, assert, testutils) {
		"use strict";

		var event,
			ids = {
				TEXTFIELD: 'textField',
				TEXTFIELD2: 'textField2',
				RADIO1: 'radio1',
				RADIO2: 'radio2',
				CHKBOX: 'chkbox',
				ANCHOR: 'anchor',
				PASSWD: 'passwd',
				TXTAREA: 'txtarea',
				BUTTONINP: 'btninp',
				BUTTON: 'btn'
			},
			urlResource = "../../target/test-classes/wcomponents-theme/intern/resources/domEvent.html",
			EVENT = "click",
			called = false,
			dom1called = false,
			testHolder, eventContainer;

		function clickEvent() {
			called = true;
		}

		window.dom1clickEvent = function() {
			dom1called = true;
		};

		function clickEventSelfRemoving($event) {
			called = true;
			var element = $event.currentTarget;
			event.remove(element, EVENT, clickEventSelfRemoving);
		}
		registerSuite({
			name: "wc/dom.event",
			setup: function() {
				var result = new testutils.LamePromisePolyFill();
				testutils.setupHelper(["wc/dom/event"], function(obj) {
					event = obj;
					testHolder = testutils.getTestHolder();
					testutils.setUpExternalHTML(urlResource, testHolder).then(result._resolve);
				});
				return result;
			},
			teardown: function() {
				testHolder.innerHTML = "";
			},
			beforeEach: function() {
				eventContainer = document.getElementById("event_test_container");
				called = dom1called = false;  // belt, braces, string, glue etc
			},
			afterEach: function() {
				var id, element;
				for (id in ids) {
					if ((element = document.getElementById(ids[id]))) {
						event.remove(element, EVENT, clickEvent);
						event.remove(element, EVENT, clickEvent, true);
					}
				}
				called = dom1called = false;
			},

			testEventConstructor: function() {
				if (event.canCapture) {  // at the moment we don't polyfill this, we only rely on it if we are using capture
					assert.strictEqual(window.Event.CAPTURING_PHASE, 1);
					assert.strictEqual(window.Event.AT_TARGET, 2);
					assert.strictEqual(window.Event.BUBBLING_PHASE, 3);
				}
				else {
					assert.isTrue(true);
				}
			},

			testDomLvl1FireEvent: function() {
				var element = document.getElementById(ids.TEXTFIELD);
				if (dom1called) {
					assert.fail("tear down is not cleaning up dom1called as expected");
				}
				event.fire(element, EVENT);
				assert.isTrue(dom1called);

			},

			testAddFireEventTextField: function() {
				var element = document.getElementById(ids.TEXTFIELD2);
				try {
					if (called) {
						assert.fail("tear down is not cleaning up called as expected");
					}
					event.add(element, EVENT, clickEvent);
					event.fire(element, EVENT);
					assert.isTrue(called);
				}
				finally {
					event.remove(element, EVENT, clickEvent);
				}
			},

			testAddFireEventTextArea: function() {
				var element = document.getElementById(ids.TXTAREA);
				try {
					if (called) {
						assert.fail("tear down is not cleaning up called as expected");
					}
					event.add(element, EVENT, clickEvent);
					event.fire(element, EVENT);
					assert.isTrue(called);
				}
				finally {
					event.remove(element, EVENT, clickEvent);
				}

			},

			testAddFireEventButtonInput: function() {
				var element = document.getElementById(ids.BUTTONINP);
				try {
					if (called) {
						assert.fail("tear down is not cleaning up called as expected");
					}
					event.add(element, EVENT, clickEvent);
					event.fire(element, EVENT);
					assert.isTrue(called);
				}
				finally {
					event.remove(element, EVENT, clickEvent);
				}
			},

			testAddFireEventButton: function() {
				var element = document.getElementById(ids.BUTTON);
				try {
					if (called) {
						assert.fail("tear down is not cleaning up called as expected");
					}
					event.add(element, EVENT, clickEvent);
					event.fire(element, EVENT);
					assert.isTrue(called);
				}
				finally {
					event.remove(element, EVENT, clickEvent);
				}

			},

			testAddCaptureFireEventButton: function() {
				var element = document.getElementById(ids.BUTTON);
				try {
					if (called) {
						assert.fail("tear down is not cleaning up called as expected");
					}
					if (event.canCapture) {
						event.add(element, EVENT, clickEvent, null, null, true);
						event.fire(element, EVENT);
						assert.isTrue(called);
					}
					else {
						assert.isTrue(true);  // just so IE does not feel left out.
					}
				}
				finally {
					if (event.capCapture) {
						event.remove(element, EVENT, clickEvent);
					}
				}
			},

			testAddFireEventPassword: function() {
				var element = document.getElementById(ids.PASSWD);
				try {
					if (called) {
						assert.fail("tear down is not cleaning up called as expected");
					}
					event.add(element, EVENT, clickEvent);
					event.fire(element, EVENT);
					assert.isTrue(called);
				}
				finally {
					event.remove(element, EVENT, clickEvent);
				}

			},

			testAddFireEventCheckBox: function() {
				var element = document.getElementById(ids.CHKBOX),
					checked = element.checked ? true : false;
				try {
					if (called) {
						assert.fail("tear down is not cleaning up called as expected");
					}
					event.add(element, EVENT, clickEvent);
					event.fire(element, EVENT);
					assert.strictEqual((element.checked ? true : false), !checked, 'Checkbox state should be toggled');
				}
				finally {
					event.remove(element, EVENT, clickEvent);
				}
			},

			testCanCapture: function() {
				if (window.addEventListener) {
					assert.isTrue(event.canCapture);
				}
				else {
					assert.isFalse(event.canCapture);
				}
			},

			testFireEventCount: function() {
				var count = 0,
					element = document.getElementById(ids.CHKBOX);

				event.add(element, EVENT, counter);
				event.fire(element, EVENT);
				event.remove(element, EVENT, counter);
				event.fire(element, EVENT);
				assert.strictEqual(count, 1, 'Event should be fired once and only once');

				function counter() {
					count++;
				}
			},

			testAddFireEventRadio: function() {
				var element = document.getElementById(ids.RADIO1),
					element2 = document.getElementById(ids.RADIO2),
					checked = element2.checked ? true : false;
				try {
					if (called) {
						assert.fail("tear down is not cleaning up called as expected");
					}
					event.add(element2, EVENT, clickEvent);
					event.fire(element2, EVENT);
					assert.notStrictEqual(element.checked, element2.checked, 'Two radio buttons in same group can not both be checked');
					assert.strictEqual((element2.checked ? true : false), !checked, 'Radio state should be toggled');
				}
				finally {
					event.remove(element2, EVENT, clickEvent);
				}
			},

			// would be nice to check that the page would navigate but not really possible
			testAddFireEventAnchor: function() {
				var element = document.getElementById(ids.ANCHOR);
				try {
					if (called) {
						assert.fail("tear down is not cleaning up called as expected");
					}
					event.add(element, EVENT, clickEvent);
					event.fire(element, EVENT);
					assert.isTrue(called);
				}
				finally {
					event.remove(element, EVENT, clickEvent);
				}

			},

			testAddRemoveEvent: function() {
				var element = document.getElementById(ids.CHKBOX),
					checked = element.checked ? true : false;
				event.add(element, EVENT, clickEvent);
				event.fire(element, EVENT);
				called = false;  // reset called, the test is that removing the event means that handler does not get invoked
				event.remove(element, EVENT, clickEvent);
				checked = element.checked ? true : false;
				event.fire(element, EVENT);
				assert.isFalse(called, 'Event was removed and should not have fired');
				assert.strictEqual((element.checked ? true : false), !checked, 'Checkbox state should be toggled');
			},

			testAddRemoveEventWithCapture: function() {
				var element = document.getElementById(ids.CHKBOX),
					checked = element.checked ? true : false;
				if (event.canCapture) {
					event.add(element, EVENT, clickEvent, null, null, true);
					event.fire(element, EVENT);
					called = false;  // reset called, the test is that removing the event means that handler does not get invoked
					event.remove(element, EVENT, clickEvent, true);
					checked = element.checked ? true : false;
					event.fire(element, EVENT);
					assert.isFalse(called, 'Event was removed and should not have fired');
					assert.strictEqual((element.checked ? true : false), !checked, 'Checkbox state should be toggled');
				}

			},

			testRemoveCaptureIgnoresBubble: function() {
				var element = document.getElementById(ids.CHKBOX);
				try {
					if (called) {
						assert.fail("tear down is not cleaning up called as expected");
					}
					event.add(element, EVENT, clickEvent);
					event.remove(element, EVENT, clickEvent, true);
					event.fire(element, EVENT);
					assert.isTrue(called);
				}
				finally {
					event.remove(element, EVENT, clickEvent);
				}
			},

			testRemoveBubbleIgnoresCapture: function() {
				var element = document.getElementById(ids.CHKBOX);
				try {
					if (called) {
						assert.fail("tear down is not cleaning up called as expected");
					}
					if (event.canCapture) {
						event.add(element, EVENT, clickEvent, null, null, true);
						event.remove(element, EVENT, clickEvent);
						event.fire(element, EVENT);
						assert.isTrue(called);
					}
				}
				finally {
					if (event.canCapture) {
						event.remove(element, EVENT, clickEvent, true);
					}
				}
			},

			testAddRemoveWhileFiringEvent: function() {
				var element = document.getElementById(ids.CHKBOX),
					checked = element.checked ? true : false;
				try {
					event.add(element, EVENT, clickEventSelfRemoving);
					event.fire(element, EVENT);
					called = false;
					checked = element.checked ? true : false;
					event.fire(element, EVENT);
					assert.isFalse(called, 'Event was removed and should not have fired');
					assert.strictEqual((element.checked ? true : false), !checked, 'Checkbox state should be toggled');
				}
				finally {
					event.remove(element, EVENT, clickEventSelfRemoving);  // just in case it fails to remove itself
				}

			},

			testEventPropertiesAndBubbles: function() {
				var element = document.getElementById(ids.CHKBOX),
					ePhase, eTarget, eCurrentTarget, eThis, ePreventDefault, eStopProp;
				if (called) {
					assert.fail("tear down is not cleaning up called as expected");
				}
				event.add(eventContainer, EVENT, clickEventCheckProps);
				event.fire(element, EVENT);
				assert.isTrue(called);
				event.remove(eventContainer, EVENT, clickEventCheckProps);
				assert.strictEqual(eTarget, element, 'target should be the element we clicked on');
				assert.strictEqual(eCurrentTarget, eventContainer, 'currentTarget should be the element we listened on');
				assert.strictEqual(eThis, eCurrentTarget, 'this should be the currentTarget');
				assert.strictEqual(ePreventDefault.constructor, Function, 'The event should have preventDefault method');
				assert.strictEqual(eStopProp.constructor, Function, 'The event should have stopPropagation method');
				if (event.canCapture) {
					// don't expect older browsers to support this - if we polyfill eventPhase we can test everywhere
					assert.strictEqual(ePhase, window.Event.BUBBLING_PHASE, 'eventPhase should be bubbling');
				}


				function clickEventCheckProps($event) {
					eTarget = $event.target;
					eCurrentTarget = $event.currentTarget;
					eThis = this;
					ePreventDefault = $event.preventDefault;
					eStopProp = $event.stopPropagation;
					called = true;
					ePhase = $event.eventPhase;
				}
			},

			testEventPropertiesAndCapture: function() {
				var element = document.getElementById(ids.CHKBOX),
					ePhase, eTarget, eCurrentTarget, eThis, ePreventDefault, eStopProp;
				if (event.canCapture) {
					event.add(eventContainer, EVENT, clickEventCheckProps, null, null, true);
					event.fire(element, EVENT);
					assert.isTrue(called);
					event.remove(eventContainer, EVENT, clickEventCheckProps);
					assert.strictEqual(eTarget, element, 'target should be the element we clicked on');
					assert.strictEqual(eCurrentTarget, eventContainer, 'currentTarget should be the element we listened on');
					assert.strictEqual(eThis, eCurrentTarget, 'this should be the currentTarget');
					assert.strictEqual(ePreventDefault.constructor, Function, 'The event should have preventDefault method');
					assert.strictEqual(eStopProp.constructor, Function, 'The event should have stopPropagation method');
					assert.strictEqual(ePhase, window.Event.CAPTURING_PHASE, 'eventPhase should be capturing');
				}

				function clickEventCheckProps($event) {
					eTarget = $event.target;
					eCurrentTarget = $event.currentTarget;
					eThis = this;
					ePreventDefault = $event.preventDefault;
					eStopProp = $event.stopPropagation;
					called = true;
					ePhase = $event.eventPhase;
				}
			},

			testEventPropertiesAtTarget: function() {
				var element = document.getElementById(ids.CHKBOX),
					ePhase, eTarget, eCurrentTarget, eThis, ePreventDefault, eStopProp;
				if (event.canCapture) {
					event.add(element, EVENT, clickEventCheckProps, null, null, true);
					event.fire(element, EVENT);
					assert.isTrue(called);
					event.remove(element, EVENT, clickEventCheckProps);
					assert.strictEqual(eTarget, element, 'target should be the element we clicked on');
					assert.strictEqual(eCurrentTarget, element, 'currentTarget should be the element we listened on');
					assert.strictEqual(eThis, eCurrentTarget, 'this should be the currentTarget');
					assert.strictEqual(ePreventDefault.constructor, Function, 'The event should have preventDefault method');
					assert.strictEqual(eStopProp.constructor, Function, 'The event should have stopPropagation method');
					assert.strictEqual(ePhase, window.Event.AT_TARGET, 'eventPhase should be capturing');
				}

				function clickEventCheckProps($event) {
					eTarget = $event.target;
					eCurrentTarget = $event.currentTarget;
					eThis = this;
					ePreventDefault = $event.preventDefault;
					eStopProp = $event.stopPropagation;
					called = true;
					ePhase = $event.eventPhase;
				}
			},

			testEventPos: function() {
				var element = document.getElementById(ids.CHKBOX),
					count = 0,
					first, second, third;

				event.add(eventContainer, EVENT, clickEventCheckSecond);  // fire mid
				event.add(eventContainer, EVENT, clickEventCheckThird, 50);  // fire last
				event.add(eventContainer, EVENT, clickEventCheckFirst, -50);  // fire first
				event.fire(element, EVENT);
				event.remove(eventContainer, EVENT, clickEventCheckThird);
				event.remove(eventContainer, EVENT, clickEventCheckFirst);
				event.remove(eventContainer, EVENT, clickEventCheckSecond);
				assert.strictEqual(first, 0);
				assert.strictEqual(second, 1);
				assert.strictEqual(third, 2);


				function clickEventCheckFirst() {
					first = count++;
				}

				function clickEventCheckSecond() {
					second = count++;
				}

				function clickEventCheckThird() {
					third = count++;
				}
			},

			testEventOrderBubbleAndCaptureAtTarget: function() {
				var element = document.getElementById(ids.CHKBOX),
					count = 0,
					first, second, third;

				if (event.canCapture) {
					event.add(element, EVENT, clickEventCheckFirst);  // fire first
					event.add(element, EVENT, clickEventCheckSecond, null, null, true);  // fire mid (attached with capture)
					event.add(element, EVENT, clickEventCheckThird);  // fire last
					event.fire(element, EVENT);
					event.remove(element, EVENT, clickEventCheckFirst);
					event.remove(element, EVENT, clickEventCheckSecond, true);
					event.remove(element, EVENT, clickEventCheckThird);
					assert.strictEqual(first, 0);
					assert.strictEqual(second, 1);
					assert.strictEqual(third, 2);
				}


				function clickEventCheckFirst() {
					first = count++;
				}

				function clickEventCheckSecond() {
					second = count++;
				}

				function clickEventCheckThird() {
					third = count++;
				}
			},

			testEventOrderWithPosAndBubbleAndCaptureAtTarget: function() {
				// note, using the count also checks that these listeners aren't called more than once
				var element = document.getElementById(ids.CHKBOX),
					count = 0,
					first, second, third, fourth;
				if (event.canCapture) {
					event.add(element, EVENT, clickEventCheckSecond, null, null, true);
					event.add(element, EVENT, clickEventCheckThird, 50);
					event.add(element, EVENT, clickEventCheckFourth, 50, null, true);
					event.add(element, EVENT, clickEventCheckFirst, -50);

					event.fire(element, EVENT);
					event.remove(element, EVENT, clickEventCheckThird);
					event.remove(element, EVENT, clickEventCheckFirst);
					event.remove(element, EVENT, clickEventCheckSecond, true);
					event.remove(element, EVENT, clickEventCheckFourth, true);
					assert.strictEqual(first, 0);
					assert.strictEqual(second, 1);
					assert.strictEqual(third, 2);
					assert.strictEqual(fourth, 3);
				}


				function clickEventCheckFirst() {
					first = count++;
				}

				function clickEventCheckSecond() {
					second = count++;
				}

				function clickEventCheckThird() {
					third = count++;
				}

				function clickEventCheckFourth() {
					fourth = count++;
				}
			},

			testCancelEventAndPos: function() {

				var element = document.getElementById(ids.CHKBOX),
					eCancelled;
				event.add(eventContainer, EVENT, clickEventCheckCancel, 50);  // fire AFTER (without the 50 this test will likely fail but we make no guarantee on order unless it is specified)
				event.add(eventContainer, EVENT, clickEventCancels);
				event.fire(element, EVENT);
				event.remove(eventContainer, EVENT, clickEventCheckCancel);
				event.remove(eventContainer, EVENT, clickEventCancels);
				assert.strictEqual(eCancelled, true, 'event should have been cancelled');

				function clickEventCheckCancel($event) {
					eCancelled = $event.defaultPrevented;
				}

				function clickEventCancels($event) {
					$event.preventDefault();
				}
			},

			testCancelByReturnValue: function() {
				var element = document.getElementById(ids.CHKBOX),
					eCancelled;
				event.add(eventContainer, EVENT, clickEventCheckCancel, 50);  // fire AFTER (without the 50 this test will likely fail but we make no guarantee on order unless it is specified)
				event.add(eventContainer, EVENT, clickEventCancels);
				event.fire(element, EVENT);
				event.remove(eventContainer, EVENT, clickEventCheckCancel);
				event.remove(eventContainer, EVENT, clickEventCancels);
				assert.strictEqual(eCancelled, true, 'event should have been cancelled');


				function clickEventCheckCancel($event) {
					eCancelled = $event.defaultPrevented;
				}

				function clickEventCancels() {
					return false;
				}
			},

			testCancelEventPropagates: function() {
				var element = document.getElementById(ids.CHKBOX),
					eCancelled;

				function clickEventCheckCancel($event) {
					eCancelled = $event.defaultPrevented;
				}

				function clickEventCancels($event) {
					// debug('click handled on element');
					$event.preventDefault();
				}

				event.add(eventContainer, EVENT, clickEventCheckCancel);
				event.add(element, EVENT, clickEventCancels);
				event.fire(element, EVENT);
				event.remove(eventContainer, EVENT, clickEventCheckCancel);
				event.remove(element, EVENT, clickEventCancels);
				assert.strictEqual(eCancelled, true, 'event should have been cancelled');


			},

			testStopPropagation: function() {
				var element = document.getElementById(ids.CHKBOX),
					eStopped = true,
					called = false;
				event.add(eventContainer, EVENT, clickEventOuter);
				event.add(element, EVENT, clickEventInner);
				event.fire(element, EVENT);
				event.remove(eventContainer, EVENT, clickEventOuter);
				event.remove(element, EVENT, clickEventInner);
				assert.strictEqual(eStopped, true, 'event should have been stopped');
				assert.strictEqual(called, true, 'event should have been called');


				function clickEventOuter() {
					eStopped = false;
				}

				function clickEventInner($event) {
					$event.stopPropagation();
					called = true;
				}
			},

			testEventSetScope: function() {
				var scope = {
						gremlin: 'gremlin'
					},
					element = document.getElementById(ids.BUTTONINP),
					scopeChecked = false;

				function clickEventScopeCheck() {
					if (this === scope) {
						scopeChecked = true;
					}
				}
				try {
					event.add(element, EVENT, clickEventScopeCheck, null, scope);
					event.fire(element, EVENT);
					assert.isTrue(scopeChecked);
				}
				finally {
					event.remove(element, EVENT, clickEventScopeCheck);
				}

			},

			/**
			 * Tests that the return value of addEvent is what it should be
			 */
			testAddEventReturnValue: function() {
				var element = document.getElementById(ids.BUTTONINP);
				try {
					assert.ok(event.add(element, EVENT, clickEvent), "Should return true(ish) when event add succeeds");
					assert.notOk(event.add(element, EVENT, clickEvent), "Should return false(ish) when event add fails");
				}
				finally {
					event.remove(element, EVENT, clickEvent);
				}
			},

			/**
			 * This checks that our event manager does not think cloned nodes have inherited
			 * any events attached directly to the node they were cloned from.
			 * If this test fails it means the following:
			 * - You have have removed the use of "custom attributes" in the event manager class to store the elid
			 * - You are in Internet Explorer
			 * - You are not quite as clever as you thought.
			 *
			 * Note, this test only tests "cloneNode" but the same problem occurs with copying innerHTML.
			 */
			testClonedWithCloneNode: function() {
				var clone, element = document.getElementById(ids.BUTTONINP);
				try {
					event.add(element, EVENT, clickEvent);
					clone = element.cloneNode(true);
					element.parentNode.replaceChild(clone, element);
					assert.ok("The cloned node should not have any events attached", event.add(clone, EVENT, clickEvent));
				}
				finally {
					event.remove(element, EVENT, clickEvent);
					event.remove(clone, EVENT, clickEvent);
				}
			},

			/**
			 * Truth be told I am writing this test to get the test line coverage increased...
			 * Nevertheless it IS a valid test, so there.
			 */
			testEventToString: function() {
				var before = event.toString(),
					after;
				event.add(document.body, "click", function() {}, null, null, false);
				after = event.toString();
				assert.notStrictEqual(after, before, "adding an event should be reflected in 'toString'");
			}
		});
	});
