Turkmaster (Mturk)

A fork of DonovanM's Turkmaster, now with watcher groups and other new features. A page-monitoring web app for Mturk (Mechanical Turk) designed to make turking more efficient. Easily monitor mturk search pages and requesters and Auto-Accept the HITs you missed.

// ==UserScript==
// @name        Turkmaster (Mturk)
// @namespace   https://greasyfork.org/users/12875
// @author		DonovanM
// @description A fork of DonovanM's Turkmaster, now with watcher groups and other new features. A page-monitoring web app for Mturk (Mechanical Turk) designed to make turking more efficient. Easily monitor mturk search pages and requesters and Auto-Accept the HITs you missed.
// @include     https://www.mturk.com/mturk/*
// @version     1.3.4e
// @require     https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js
// @require 	https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js
// @grant       GM_getValue
// @grant       GM_setValue
// ==/UserScript==

// Incorporates additions by Kevin Schumacher (tubedogg on MTG/GreasyFork):
//    - Basic support for groups
//    - Stop group if one PANDA catches
//    - Start with group (click on any member of the group to start them all)
//    - "Last Found" date (hover over last checked time at bottom-right of each watcher)
//    - Tracks date watcher was added (for watchers added from this point forward - also found by hovering over last 	checked time)
//    - Deselect all (from gear/settings menu)
//    - New option: "Show other page" - load something else where the dashboard normally is (specify URL in                 gear/settings menu)
//    - New option: Force TurkMaster URL: only load if ?turkmaster is added to dashboard URL                                e.g. if you go to http://www.mturk.com/mturk/dashboard it would not load, but if you go to                          http://www.mturk.com/mturk/dashboard?turkmaster it would do so. Useful if you normally keep the dashboard open      in another tab/window/browser so it can auto-refresh, and especially so when paired with the "Show other page"      setting


var settings = (function() {
	var	LOCAL_STORAGE = "turkmaster_settings";
	var pub = {
		sound         : true,
		animation     : true,
		preloadHits   : false,
		volume        : 50,
		notifications : true,
		alertOnly     : false,
		fontSize      : 10,
		typeface      : "Oxygen",
		desktopNotifications : false,
		canHide       : false,
		customURL	  : '',
		forceUniqueURL: false
	};

	_load();

	function _setfontSize(val) {
		if (val >= 5 && val <= 20) {
			pub.fontSize = val;
			$("#dispatcher div").css("font-size", val + "pt");
			$(".notification_panel p").css("font-size", val + "pt");
			$("#settingsDialog, #settingsDialog div, #settingsDialog li, #settingsDialog input, #settingsDialog button").css("font-size", val + "pt");

			_save();
		}
	}

	function _setDesktopNotifications(val, callback) {
		if (val) {
			requestDesktopNotifications(function(isPermitted) {
				callback(isPermitted);
				pub.desktopNotifications = isPermitted;
				_save();
			});

			pub.desktopNotifications = false;
		} else {
			pub.desktopNotifications = false;
		}

		_save();
	}

	function _setVolume(val) {
		if (val >= 0 && val <= 100) {
			Sound.setVolume(val);
			pub.volume = val;
			_save();
		}
	}

	function _setCustomFrameURL(val) {
        var urlregex = new RegExp(
           "^(http|https)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&amp;%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(\:[0-9]+)*(/($|[a-zA-Z0-9\.\,\?\'\\\+&amp;%\$#\=~_\-]+))*$");
        if (urlregex.test(val) && $("#externalFrame").length) {
        	$("#externalFrame").src = val;
			pub.customURL = val;
			_save();
        } else if (urlregex.test(val) || (val === '' && pub.customURL !== '')) {
			// save settings with the new URL (blank or otherwise) and reload the page to add/remove the iframe
			pub.customURL = val;
			_save();
			location.reload();
        } else if (!urlregex.test(val)) {
        	alert('The URL entered is not valid. Please try again. If you are trying to revert to showing the MTurk dashboard, please empty the URL box.');
        }
	}

	function _save() {
		// localStorage.setItem(LOCAL_STORAGE, JSON.stringify(pub));
		GM_setValue(LOCAL_STORAGE, JSON.stringify(pub));
	}

	function _load() {
		var values = GM_getValue(LOCAL_STORAGE);

		if (typeof values === 'undefined')
			values = localStorage.getItem(LOCAL_STORAGE);

		if (values) {
			values = JSON.parse(values);

			for (i in values)
				pub[i] = values[i];
		}
	}

	pub.setfontSize = _setfontSize;
	pub.setVolume   = _setVolume;
	pub.setDesktopNotifications = _setDesktopNotifications;
	pub.setCustomFrameURL = _setCustomFrameURL;
	pub.save = _save;

	return pub;
}());

var pageType = {
	MAIN      : true,	// This is so remote watcher requests don't add new watchers to multiple pages and cause mturk errors.
	DASHBOARD : false,
	HIT       : false,
	REQUESTER : false,
	SEARCH    : false
};

var loadError = false;
var wasViewed = false;
var dispatch;
var notificationPanel;

if(!('contains' in String.prototype)) {
	String.prototype.contains = function(str, startIndex) {
		return -1 !== String.prototype.indexOf.call(this, str, startIndex);
	};
}

$(document).ready(function(){
	console.log('Ready');
	checkPageType();
	loadFonts();

	if (pageType.DASHBOARD) {
		dispatch = new Dispatch();
		DispatchUI.create(dispatch);
		createDetailsPanel();
		IgnoreList.init();

		if (settings.preloadHits)
			loadDefaultWatchers();
		else
			dispatch.load();

		requestMain();
		preloadImages();
		addFormStyle();
	}
	
	if (pageType.HIT || pageType.REQUESTER || pageType.SEARCH)
		addWatchButton();
	
	notificationPanel = new NotificationPanel();
	
	// Listen to messages
	window.addEventListener('storage', onStorageEvent, false);
}); 

$(window).on('beforeunload', function() {
	if (pageType.DASHBOARD && pageType.MAIN) {
		dispatch.save();
	}
});

function loadFonts() {
	WebFont.load({
		google: { families: [ 'Oxygen:400,700:latin', 'Droid Sans Mono' ] }
	});
}

function onStorageEvent(event) {
	if (event.key.substring(0,13) === "notifier_msg_")
		onMessageReceived(event.key.substring(13), JSON.parse(event.newValue).content);
}

function checkPageType() {
	// Dashboard, hit, requester, search
	if (document.URL.search("https://www.mturk.com/mturk/dashboard") > -1 && ((document.URL.search("turkmaster") > -1 && settings.forceUniqueURL) || !settings.forceUniqueURL)) {
		pageType.DASHBOARD = true;
	}
	else if (document.URL.match(/https:\/\/www.mturk.com\/mturk\/(preview|accept).+groupId=.*/) !== null)
	{
		pageType.HIT = true;
	}
	else if (document.URL.match(/requesterId=([A-Z0-9]+)/) !== null)
	{
		pageType.REQUESTER = true;
	}
	else if (document.URL.match(/(searchbar|findhits)/) !== null)
	{
		pageType.SEARCH = true;
	}
}

function requestMain() {
	sendMessage({ header: "request_main" });
}

function preloadImages() {
	var images = [
		'https://i.imgur.com/guRzYEL.png',
		'https://i.imgur.com/5snaSxU.png',
		'https://i.imgur.com/VTHXHI4.png',
		'https://i.imgur.com/peEhuHZ.png'
	];

	$(images).each(function(){
		$('<img>')[0].src = this;
	});
}

var SettingsDialog = function() {
	var DOMElement,
	    TOGGLE = '<button class="on_off"><span>ON</span><span>OFF</span></button>';

	function _show() {
		if (!DOMElement)
			_createDOMElement()

		_getSettings();
		DOMElement.show();
		$(window).on('click', _handleWindowClick);
	}

	function _isVisible() {
		if (DOMElement)
			return DOMElement.is(":visible");
		else
			return false;
	}

	function _getSettings() {
		if (settings.sound) DOMElement.find("#soundSettings > .on_off").addClass("on");
		DOMElement.find("#volume input").val(settings.volume);
		if (settings.notifications) DOMElement.find("#notificationSettings > .on_off").addClass("on");
		if (settings.desktopNotifications) DOMElement.find("#desktopNotifications .on_off").addClass("on");
		if (settings.alertOnly) DOMElement.find("#alertOnly .on_off").addClass("on");
		if (settings.canHide) DOMElement.find("#hideable .on_off").addClass("on");
		DOMElement.find("#fontSize input").val(settings.fontSize);
		DOMElement.find("#typeface input").val(settings.typeface);
		if (settings.customURL) DOMElement.find("#customURL input").val(settings.customURL);
		if (settings.forceUniqueURL) DOMElement.find("#forceUniqueURL .on_off").addClass("on");
		if (settings.stopCaptcha) DOMElement.find("#stopCaptcha .on_off").addClass("on");
	}

	function _save() {

	}

	function _cancel() {
		DOMElement.hide();
	}

	function _createDOMElement() {
		_addStyle();
		DOMElement = $('<div id="settingsDialog"><h2>Settings</h2></div>').append(
			$('<div id="generalSettings"><h3>General</h3>\
					<ul>\
						<li id="stopCaptcha">' + TOGGLE + 'Stop upon encountering captcha</li>\
						<li id="startNonHitWatchers"><button class="more">...</button>Start all non-HIT (PANDA) watchers</li>\
						<li id="stopNonHitWatchers"><button class="more">...</button>Stop all non-HIT (PANDA) watchers</li>\
					</ul>\
			   </div>'),
			$('<div id="soundSettings">' + TOGGLE + '<h3>Sound</h3>\
					<ul><li id="volume">Volume (0 - 100)<input type="text" /></li></ul>\
			   </div>'),
			$('<div id="notificationSettings">' + TOGGLE + '<h3>Notifications</h3>\
					<ul>\
						<li id="desktopNotifications">' + TOGGLE + 'Desktop Notifications</li>\
						<!--li id="alertOnly">' + TOGGLE + 'Alert/Auto only</li-->\
					</ul>\
			   </div>'),
			$('<div id="fontSettings"><h3>Font</h3>\
					<ul>\
						<li id="fontSize"><input type="text" />Size (pt)</li>\
						<!--li id="typeface"><input type="text" />Typeface</li-->\
					</ul>\
			   </div>'),
			$('<div id="uiSettings"><h3>User Interface</h3>\
					<ul>\
						<li id="hideable">' + TOGGLE + 'Hideable</li>\
						<li id="forceUniqueURL">' + TOGGLE + 'Only load when <a href="http://www.mturk.com/turk/dashboard?turkmaster">?turkmaster</a> is appended to URL</li>\
						<li id="customURL"><input type="text" style="width: 150px;"/>Enter custom URL<br />for right side of page</li>\
					</ul>\
			   </div>'),
			$('<div id="export"><h3>Backup</h3>\
					<ul>\
						<li id="export"><button class="more">...</button>Export</li>\
						<li id="import"><button class="more">...</button>Import</li>\
					</ul>\
			   </div>'),
			$('<div id="deselect_all"><h3>Deselect All</h3>\
					<ul>\
						<li id="deselect_all"><button class="more">...</button>Deselect All</li>\
					</ul>\
			   </div>')
		);

		_addHandlers();

		$("body").append(DOMElement);
	}

	function _addHandlers() {
		DOMElement.on('click', function(e) {
			if (e.target.tagName === "BUTTON" || e.target.parentNode.tagName === "BUTTON")
				_handleButtonToggle(e);
		});

		DOMElement.on('change', _handleInputChange);
	}

	function _handleWindowClick(e) {
		var target = e.target;

		if (!DOMElement.is(target) && DOMElement.has(target).length === 0 && $("#settings img").get(0) !== target) {
			_cancel();
			$(window).off('click', _handleWindowClick);
		}
	}

	function _handleInputChange(e) {
		var target = $(e.target),
			value = target.val(),
			id = target.parent().attr('id');

		if (id === "volume")
			settings.setVolume(value);
		else if (id === "fontSize")
			settings.setfontSize(value);
		else if (id === "typeface")
			settings.typeface = value;
		else if (id === "customURL")
			settings.setCustomFrameURL(value);
	}

	function _handleButtonToggle(e) {
		e.preventDefault();

		// Chrome returns the span as the target while FF returns the button
		var target = (e.target.tagName === "BUTTON") ? $(e.target) : $(e.target).parent(),
			value = target.hasClass("on"),
			id = target.parent().attr('id');

		if (id !== "desktopNotifications") {
			if (target.hasClass("on")) {
				target.removeClass("on");
				value = false;
			} else {
				target.addClass("on");
				value = true;
			}
		}

		if (id === "soundSettings") {
			settings.sound = value;
		} else if (id === "notificationSettings") {
			settings.notifications = value;
		} else if (id === "desktopNotifications") {
			if (value)
				target.removeClass("on");

			// Desktop notification requests require user action so we need a callback
			// for when the user responds.
			settings.setDesktopNotifications(!value, function(isPermitted) {
				if (isPermitted) {
					target.addClass("on");
				} else {
					target.removeClass("on");
					console.error("Desktop notifications are blocked.");
				}
			});
		} else if (id === "alertOnly") {
			settings.alertOnly = value;
		} else if (id === "hideable") {
			settings.canHide = value;
			setTimeout(function () { DispatchUI.setHide() }, 50);
		} else if (id === "forceUniqueURL") {
			settings.forceUniqueURL = value;
		} else if (id === "stopCaptcha") {
			settings.stopCaptcha = value;
		}

		settings.save();

		if (id === "export") {
			_showExport();
		} else if (id === "import") {
			_showImport();
		}

		if (id === "deselect_all") {
			_deselectAll();
		}

		if (id === "startNonHitWatchers") {
			_startNonHitWatchers();
		}
		if (id === "stopNonHitWatchers") {
			_stopNonHitWatchers();
		}
	}

	function _showExport() {
		var div = $('<div id="export-box" class="dialog-big"><h2>Export Watchers</h2><h3>Copy the text below. (Triple-click to highlight all)</h3><p>' + dispatch.exportWatchers() + '</p></h2></div>');
		$('<button>Close</button>')
			.click(function() { div.remove(); })
			.appendTo(div);

		div.appendTo($("body"));
	}

	function _showImport() {
		var div = $('<div id="import-box" class="dialog-big"><h2>Import Watchers</h2><h3>Paste the backup text to load watchers</h3><textarea></textarea></h2></div>');
		$('<button>Save</button>')
			.click(function() { dispatch.importWatchers($("#import-box textarea").val()); div.remove() })
			.appendTo(div);
		$('<button>Close</button>')
			.click(function() { div.remove() })
			.appendTo(div);

		div.appendTo($("body"));

		div.find("textarea").focus();
	}

	function _deselectAll() {
		dispatch.deselectAll();
	}

	function _startNonHitWatchers() {
		for (var i = 0, len = dispatch.watchers.length; i < len; i++) {
			if (dispatch.watchers[i].type !== 'hit' && !dispatch.watchers[i].isRunning) {
				dispatch.watchers[i].start();
			}
		}
	}

	function _stopNonHitWatchers() {
		for (var i = 0, len = dispatch.watchers.length; i < len; i++) {
			if (dispatch.watchers[i].type !== 'hit' && dispatch.watchers[i].isRunning) {
				dispatch.watchers[i].stop();
			}
		}
	}

	function _addStyle() {
		addStyle("\
			#settingsDialog {\
				position: fixed;\
				top: 16px;\
				left: 249px;\
				background-color: #fafafa;\
				padding: 10px;\
				width: 300px;\
				font: " + settings.fontSize + "pt 'Oxygen', verdana, sans-serif;\
				border-bottom: 1px solid #DDD;\
				border-right: 1px solid #DDD;\
				border-radius: 0.3em;\
			}\
			#settingsDialog div, #settingsDialog li, #settingsDialog input, #settingsDialog button {\
				font: " + settings.fontSize + "pt 'Oxygen', verdana, sans-serif;\
			}\
			#settingsDialog > div {\
				margin: 0px 0px 0.5em;\
				border: 1px solid #eee;\
				padding: 0.75em;\
				background-color: #fff;\
			}\
			#settingsDialog h2, #settingsDialog h3 {\
				font-weight: 400;\
				margin: 0 0 0.5em;\
			}\
			#settingsDialog h2 {\
				text-align: center;\
				font-size: 140%;\
				color: #333;\
			}\
			#settingsDialog button.on_off {\
				background: none;\
				border: none;\
				padding: 0;\
				outline: none;\
				height: 1.3em;\
				margin-top: 0em;\
			}\
			#settingsDialog .on_off span { color: #333; margin: 1px; font-size: 56%; font-weight: bold; border-radius: 1.6em;  }\
			#settingsDialog .on_off span:nth-child(2) { background-color: #aeaeae; color: #fff; padding: 0.4em 0.8em; }\
			#settingsDialog .on_off.on span:nth-child(1) { background-color: #55b8ea; color: #fff; padding: 0.4em 0.8em; }\
			#settingsDialog .on_off.on span:nth-child(2) { background-color: inherit; color: #333; padding: 0 0.8em 0 0; }\
			#settingsDialog .on_off { margin-top: 6px; }\
			#settingsDialog ul { margin: 0 0 0.2em; padding: 0 0 0 1.9em }\
			#settingsDialog ul li { list-style: none; margin-bottom: 0.5em; }\
			#settingsDialog li input, #settingsDialog li button { float: right; }\
			#settingsDialog li input[type='text'] { width: 3em; font-size: 80%; margin-right: 0.8em; text-align: right; padding-right: 0.5em }\
			#settingsDialog li .more { width: 24px; border: none; color: #808080; font: bold 160% inital; line-height: 0%; transform: rotate(90deg); background-color: transparent; position: relative; top: 8px; cursor: pointer; height: 0.7em; padding: 0 0 0.65em; }\
			#settingsDialog li#typeface input { width: 8em }\
			.dialog-big { position: fixed; top: 2em; left: 50%; width: 860px; margin-left: -430px; background-color: white; padding: 2%; border: 1px solid #ddd; font-family: 'Oxygen'; box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.4); border-radius: 10px; }\
			.dialog-big p { background-color: #f7f7f7; padding: 1.5em; height: 500px; overflow: scroll; }\
			.dialog-big h2, #export-box h3 { font-weight: 400 }\
			.dialog-big h2 { font-size: 170%; margin-top: 0 }\
			.dialog-big button { background-color: #cecece; color: white; padding: 3px 10px; font-family: 'Oxygen'; border: none; border-radius: 3px; font-weight: bold; transition: background-color 0.3s; margin-right: 0.5em }\
			.dialog-big button:hover { background-color: #55B8EA }\
			.dialog-big textarea { display: block; width: 100%; height: 500px; margin: 1em 1em 1em 0; }\
		");
	}

	return {
		show: _show,
		hide: _cancel,
		isVisible: _isVisible
	}
}();


function addWatchButton() {
	var type = (pageType.HIT) ? 'hit' : (pageType.REQUESTER) ? 'requester' : (pageType.SEARCH) ? 'page' : '';
	var typeCase = (pageType.HIT) ? 'HIT' : type;
	var button = $("<div>").addClass("watcher_button")
		.append($("<a>")
			.text("Watch this " + typeCase)
			.attr('href', "javascript:void(0)")
			.css({
				color: 'black',
				border: '1px solid black'
			})
			.click(addWatcher)
		);

	function addWatcher() {
		// Get current and default values
		var time        = 60,
			auto        = true,
			alert       = false,
			name        = "",
			groupid 	= "",
			dateAdded   = Math.floor(Date.now() / 1000),
			stopOnCatch = true,
			stopGroup 	= true,
			startGroup 	= true;

		// Find the name if available
		if (pageType.REQUESTER) {
			if ($(".title_orange_text_bold").length > 0) {
				name = $(".title_orange_text_bold").text().match(/Created by '(.+)'/);
				name = (typeof name !== 'undefined') ? name[1] : "";
			} else if (document.URL.match(/prevRequester=/)) {
				name = document.URL.match(/prevRequester=([^&]*)/)[1];
			}
		} else if (pageType.SEARCH) {
			name = document.URL.match(/searchWords=([^&]*)/);
			
			if (name !== null) {
				name = name[1].replace('+', ' ');
				name = name.charAt(0).toUpperCase() + name.slice(1);	// Capitalize first letter
			} else {
				name = "";
			}
		} else if (pageType.HIT) {
			name = $(".capsulelink_bold > div:nth-child(1)").text().trim();
		}
		
		// Pull up a Watcher Dialog with default values set
		watcherDialog(
			{	
				name: name,
				groupid: groupid,
				dateAdded: dateAdded,
				time: time * 1000,
				type: type,
				option: {
					auto        : auto,
					alert       : alert,
					stopOnCatch : stopOnCatch,
					stopGroup 	: stopGroup,
					startGroup	: startGroup
				}
			},
			function(values) {
				var id = (document.URL.match(/groupId=([A-Z0-9]+)/) || document.URL.match(/requesterId=([A-Z0-9]+)/) || [,document.URL])[1],
					watcher = {
						id          : id,
						duration    : values.time,
						type        : (type === "page") ? "url" : type,
						name        : values.name,
						groupid		: values.groupid,
						dateAdded   : dateAdded,
						auto        : values.auto,
						alert       : values.alert,
						stopOnCatch : values.stopOnCatch,
						stopGroup 	: values.stopGroup,
						startGroup	: values.startGroup
					};

				sendMessage({
					header    : 'add_watcher',
					content   : watcher,
					timestamp : true
				});
			}
		);
	}

	var location;	// Location to add the watch button
	if (pageType.HIT) {
		if ($(".message.success h6").length)
		{
			location = $(".message.success h6");
		}
		else if ($("#javascriptDependentFunctionality").length)
		{
			if ($("td:contains('Want to see other')").length)
			{
				location = $("#javascriptDependentFunctionality").parents("tr").eq(0).children('td').eq(1);
				location.attr('align', 'center');
				location.parents('td table').eq(0).attr('width', '450');
			}
			else
			{
				location = $("#javascriptDependentFunctionality").parents("tr").eq(0);
				var newCell = ($("<td>")
					.attr('rowspan', '2')
					.attr('align', 'center')
				);
				location.append(newCell);
				location.parents("td table").eq(0).attr('width', '350');
				location = location.children('td').eq(1);
			}
			var nextRowUp = location.parents("table").eq(0).children().eq(0).children().eq(0).children().eq(1);
			nextRowUp.remove("img");
			nextRowUp.text('Want to watch this HIT?').attr('align', 'center').attr('width', '250');
		}
		else if ($("body > form:nth-child(7) > table:nth-child(9) > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(2) > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(2) > td:nth-child(1)").length)
		{
			location = $("body > form:nth-child(7) > table:nth-child(9) > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(2) > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(2) > td:nth-child(1)");
		}
	} else if (pageType.REQUESTER || pageType.SEARCH) {
		if ($(".title_orange_text_bold").length)
			location = $(".title_orange_text_bold");
		else
			location = $(".error_title");
	}
	location.append(button);
	addFormStyle();
}


function addFormStyle() {
	addStyle("\
		#add_watcher_form {\
			position: fixed;\
			width: 600px;\
			top: 50px;\
			left: 50%;\
			margin: 50px -300px;\
			background-color: #fcfcfe;\
			border: 1px solid #aaa;\
			border-radius: 1px;\
			text-align: center;\
			}\
		#add_watcher_form h3 {\
			font: 12pt Verdana;\
			margin: 0 0 15px;\
			background-color: #def;\
			background-color: rgba(230, 230, 230, 1);\
			padding: 3px;\
			color: #111;\
			}\
		#add_watcher_form input[type='text'] {\
			font: 10pt Verdana;\
			margin: 10px 20px 0 0;\
			}\
		#add_watcher_form input[type='button'] {\
			margin-top: 20px;\
			font: 9pt Verdana;\
			color: #444;\
			background-color: #eee;\
			border: 1px solid #999;\
			}\
		#add_watcher_form input[type='button']:hover {\
			background-color: #9df;\
			}\
		#add_watcher_form p {\
			margin: 10px;\
			font: 11pt Verdana;\
			}\
		#add_watcher_form .form_buttons input {\
			margin: 5px;\
		}\
		.watcher_button { display: inline; }\
		.watcher_button a {\
			text-decoration: none;\
			font-weight: normal;\
			background-color: #CECECE;\
			color: white;\
			padding: 3px 10px;\
			border-radius: 8px;\
			font-family: 'Oxygen', verdana, sans-serif;\
			transition: background-color 0.4s;\
		}\
		.watcher_button a:hover { background-color: #55B8EA }\
		.error_title .watcher_button { display: block; margin: 15px }\
	");
}

function addStyle(styleText) {
	var style = '<style type="text/css">' + styleText + '</style>';
	$("head").append(style);
}

function loadDefaultWatchers() {
	// Add a few watchers. Won't be done like this in the future
	dispatch.isLoading = true;
	dispatch.add(new Watcher({
		id: "https://www.mturk.com/mturk/searchbar?selectedSearchType=hitgroups&searchWords=survey&minReward=0.25&qualifiedFor=on&x=13&y=10",
		time: 60000,
		type: 'url',
		name: "Surveys $0.25 and up"})); //$.25 surveys
	dispatch.add(new Watcher({
		id: "https://www.mturk.com/mturk/searchbar?selectedSearchType=hitgroups&searchWords=survey&minReward=0.75&qualifiedFor=on&x=13&y=10",
		time: 60000,
		type: 'url',
		name: "Surveys $0.75 and up"})); //$.75 surveys
	dispatch.add(new Watcher({
		id: "A11L036EBWKONR",
		time: 120000,
		type: 'requester',
		name: "Project Endor",
		option: {alert:true}}));	// Endor
	dispatch.add(new Watcher({
		id: "A6YG5FKV2TAVC",
		time: 300000,
		type: 'requester',
		name: "Agent Agent",
		option: {alert:true}}));	// Agent Agent
	dispatch.add(new Watcher({
		id: "A2SUM2D7EOAK1T",
		time: 120000,
		type: 'requester',
		name: 'Crowdsource'}));
	dispatch.add(new Watcher({
		id: "AKEBQYX32KM19",
		time: 120000,
		type: 'requester',
		name: 'Crowdsurf Support'}));
	dispatch.add(new Watcher({
		id: "https://www.mturk.com/mturk/searchbar?selectedSearchType=hitgroups&searchWords=transcri&minReward=0.00&qualifiedFor=on&x=0&y=0",
		time: 60000,
		type: 'url',
		name: "Transcription HITs"})); // Transcription HITs
	dispatch.isLoading = false;
	dispatch.save();
}

function onMessageReceived(header, message) {
	if (pageType.DASHBOARD && pageType.MAIN) {
		switch(header) {
			case 'notification_viewed' :
				wasViewed = true;
				break;
			case 'add_watcher' : 
				var msg = message;
				dispatch.add(new Watcher({
					id     : msg.id,
					time   : msg.duration,
					type   : msg.type,
					groupid: msg.groupid,
					name   : msg.name,
					option : {
					 	auto: msg.auto,
					 	stopOnCatch: msg.stopOnCatch,
						stopGroup: msg.stopGroup,
						startGroup: msg.startGroup,
					 	alert: msg.alert
					}
				}, true)).start();
				break;
			case 'ignore_requester' :
				console.log("Ignore requester", message);
				IgnoreList.add(IgnoreList.REQUESTER, message.id);
				break;
			case 'mute_hit' :
				var id = message.id;
				IgnoreList.add(IgnoreList.HIT, id);
				break;
			case 'unmute_hit' :
				var id = message.id;
				IgnoreList.remove(IgnoreList.HIT, id);
				break;
			case 'request_main' :
				sendMessage({ header: "request_denied" });
				break;
			case 'request_denied' :
				dispatch.onRequestMainDenied();
				break;
			case 'show_main' :
				alert("Showing the main dashboard. (Close this Mturk page to establish a notifier in a different tab or window)");
				break;
		}
	} else if (!pageType.DASHBOARD || (pageType.DASHBOARD && !pageType.MAIN)) {

		switch(header) {
			case 'new_hits' :
				var hits = message.hits;
				
				// Re-create the hits so their methods can be used
				for(var i = hits.length; i--;) hits[i] = new Hit(hits[i]);

				// Show the hits and let the dashboard know it was seen
				if (document.hasFocus())
					sendMessage({ header: "notification_viewed" });
				notificationPanel.add(new NotificationGroup({ title: message.title, hits: hits, url: message.url }));
				break;

			case 'captcha' :
				if (document.hasFocus())
					alert("Captcha Alert!");
				break;

			case 'turkopticon' :
				// This needs a more elegant solution. If the servers start lagging we might be
				// using addTO() for the wrong group. It won't show the TO for the wrong requester,
				// though, so it's safe to use for now. It's just that some ratings could be missing.
				notificationPanel.notifications[notificationPanel.notifications.length - 1].addTO(message);
				break;
		}
	}
}
function sendMessage(message) {
	var header    = message.header;
	var content   = message.content || new Date().getTime();	// Make the content a timestamp when there's no actual content
	var timestamp = message.timestamp && new Date().getTime();	// If wanted, adds a timestamp to the content so messages with the same content will still trigger the event consecutively
	localStorage.setItem('notifier_msg_' + header, JSON.stringify({ content: content, timestamp: timestamp}));
}

function sendDesktopNotification(hits, watcher) {
	// Let's check if the user is okay to get some notification
	if (Notification.permission === "granted" && settings.desktopNotifications) {
		// If the user isn't on a mturk page to receive a rich notification, then send a web notification
		if (!wasViewed) {
			var bodyText = "";
			
			for (var i = 0, len = hits.length; i < len; i++)
				bodyText += "\n" + hits[i].title.substring(0, 40) + ((hits[i].title.length > 40) ? "..." : "") + "\n" + hits[i].reward + "\n";

			var notification = new Notification(
				watcher.name,
				{ 
					body: bodyText,
					icon: "http://halfelf.org/wp-content/uploads/sites/2/2012/06/amazon_icon.png"
				}
			);
			notification.onclick = function() {
				window.focus();					// Focus this window (dashboard)
				this.close();					// Closes the notification
				showDetailsPanel(watcher);		// Opens the details panel for whatever watcher the notification was for
			};
			notification.onshow = function() { setTimeout(function() { notification.close() }, 5000) }; // Need to set a close time for Chrome
		}
	}
}

function requestDesktopNotifications(callback) {
	// Let's check if the browser supports notifications
    if (!("Notification" in window)) {
		alert("This browser does not support desktop notification");
    } else {
		window.Notification.requestPermission(function (permission) {
			// Whatever the user answers, we make sure Chrome stores the information
			if(!('permission' in Notification))
				window.Notification.permission = permission;

			// If the user is okay, let's create a notification
			if (permission === "granted") {
				var notification = new window.Notification("Desktop notifications enabled.");
				notification.onshow = function() { setTimeout(function() { notification.close() }, 5000) };
				callback(true);
			} else {
				callback(false);
			}
		});
	}
}



function Hit(attrs) {
	attrs = attrs || {};
	this.id           = attrs.id;
	this.uid          = attrs.uid;
	this.isAutoAccept = attrs.isAutoAccept || false;
	this.requester    = attrs.requester;
	this.requesterID  = attrs.requesterID;
	this.url          = attrs.url;
	this.title        = attrs.title;
	this.reward       = attrs.reward;
	this.description  = attrs.description;
	this.available    = attrs.available;
	this.time         = attrs.time;
	this.isQualified  = (typeof attrs.isQualified !== 'undefined') ? attrs.isQualified : true;
	this.canPreview   = (typeof attrs.canPreview !== 'undefined') ? attrs.canPreview : true;
}
Hit.prototype.getURL = function(type) {
	switch(type) {
		case 'preview':
			return "https://www.mturk.com/mturk/preview?groupId=" + this.id;
		case 'accept' :
			return (this.isQualified) ? "https://www.mturk.com/mturk/previewandaccept?groupId=" + this.id : null;
		case 'auto'   :
			return "https://www.mturk.com/mturk/previewandaccept?groupId=" + this.id + "&autoAcceptEnabled=true";
		case 'view'   :
			return "https://www.mturk.com/mturk/continue?hitId=" + this.uid;
		case 'return' :
			// This will need to be changed. It's the same as 'view' until more testing is done on AMT's return functionality
			return "https://www.mturk.com/mturk/preview?hitId=" + this.uid;
		default:
			return "";
	}
};
// Returns the position of a hit in a hit array by its ID
Hit.indexOf = function(hitId, hits) {
    for (var i = 0, len = hits.length; i < len; i++) {
        if (hitId === hits[i].id)
            return i;
    }
    return -1;
};
// Returns true if there are multiple hits in the array and all of the hits are from the same requester
Hit.isSameRequester = function(hits) {
	if (hits.length > 1) {
		var compareRequester = hits[0].requester;
		for (var i = 1, len = hits.length; i < len; i++) {
			if (compareRequester !== hits[i].requester)
				return false;
		}
		return true;
	} else {
		return false;
	}
};
// Returns a list of unique requester IDs from an array of hits
Hit.getUniqueReqeusters = function(hits) {
	var ids = [];

	for (var i = 0, len = hits.length; i < len; ++i) {
		var id = hits[i].requesterID;

		if (ids.indexOf(id) === -1)
			ids.push(id);
	}

	return ids;
};

// Message object (Not used)
function Message() {
	/*  Status (changed): Unchanged, Added, Removed, Count
		We should mark each Hit in the message with what has changed. The count change should be sent with this.
		The message will also tell the client whether or not to pop-up the notification.	*/
}

// The details panel for each watcher
function createDetailsPanel() {
	var div = $('<div>').attr('id', 'details_panel').addClass('notification_panel');
	addStyle("#details_panel {\
		background-color: #fff;\
		position: absolute;\
		top: 0px;\
		margin-left: 1px;\
		left: 270px;\
		width: 500px;\
		border: 1px solid #e3e3e3;\
		border-radius: 0 0 3px 0;\
		border-width: 0 1px 1px 0;\
		transition: left 0.5s ease;\
		display: none }\
	#details_panel h4 { display: none }\
	#details_panel.left { left: 30px }");
	
	$(div).mouseleave(function() { $(this).hide() });
		
	$("body").append(div);
}

var lastWatcher = "";
function showDetailsPanel(watcher) {
	var panel = $("#details_panel");
	var group;

	// Only change the panel contents if it's a different watcher or the same one, but updated
	if (watcher !== lastWatcher || (watcher === lastWatcher && watcher.isUpdated)) {
		$("*", panel).remove();
		if (watcher.lastHits.length > 0) {
			group = new NotificationGroup({ hits: watcher.lastHits, isSticky: false, watcher: watcher });
			$(panel).append((group).getDOMElement());

			// This doesn't need a callback since the data will already be cached at this point
			group.addTO(TO.get(Hit.getUniqueReqeusters(watcher.lastHits), _handleTOReceived));
		} else {
			$(panel).append($('<div>').append('<h2>').css('text-align', 'center').html("<br />There are no HITs available.<br /><br />"));
		}
	}
	$(panel).show();

	function _handleTOReceived(data) {
		group.addTO(data);
	}

	lastWatcher = watcher;
}


var IgnoreList = (function() {
	var _time = 60000,
		_hits = [];
		_requesters = [],
		_HIT = 0,
		_REQUESTER = 1;

	function _init() {
		// _clear();
		_load();
		_addListeners();
	}

	function _addListeners() {
		$(window).on('unload', function() { _save(); });
	}

	function _save() {
		localStorage.setItem('notifier_ignore', JSON.stringify(_hits));
		localStorage.setItem('notifier_ignore_requesters', JSON.stringify(_requesters));
		// console.log("Saving ignore list", _hits, _requesters);
	}

	function _load() {
		var storedHits       = localStorage.getItem('notifier_ignore');
		var storedRequesters = localStorage.getItem('notifier_ignore_requesters');

		if (storedHits !== null) {
			try {
				_hits = JSON.parse(storedHits);
			} catch (e) {
				_clear();
				_save();
				console.log("Ignored hits couldn't be loaded correctly.");
			}
		} else {
			console.log("No ignored hits found");
		}

		if (storedRequesters !== null) {
			try {
				_requesters = JSON.parse(storedRequesters);
			} catch (e) {
				_clear();
				_save();
				console.log("Ignored requesters couldn't be loaded correctly.");
			}
		} else {
			console.log("No ignored requesters found");
		}

		// console.log("Ignored requesters", _requesters);
	}

	function _clear() {
		_hits = [];
		_requesters = [];
		localStorage.removeItem('notifier_ignore');
		localStorage.removeItem('notifier_ignore_requesters');
	}

	function _contains(type, item) {
		if (type === _HIT)
			return (_hits.indexOf(item) !== -1);
		else
			return (_requesters.indexOf(item) !== -1);
	}

	function _isIgnored(requester) {
		return (_requesters.indexOf(requester) !== -1);
	}

	function _isMuted(item) {
		return (_hits.indexOf(item) !== -1);
	}

	function _filter(hits) {
		var filteredHits = [];

		for (var i = 0, len = hits.length; i < len; i++) {
			var hit = hits[i];

			if ((_hits.indexOf(hit.id) === -1) && (_requesters.indexOf(hit.requester) === -1))
				filteredHits.push(hit);
		}

		return filteredHits;
	}

	function _add(type, id) {
		if (type === _HIT) {
			if (_hits.indexOf(id) === -1)
				_hits.push(id);
		} else if (type === _REQUESTER) {
			if (_requesters.indexOf(id) === -1)
				_requesters.push(id);
		}

		_save();
	}

	function _remove(type, id) {
		if (type === _HIT) {
			var index = _hits.indexOf(id);

			if (index !== -1)
				_hits.splice(index, 1);

		} else if (type === _REQUESTER) {
			var index = _requesters.indexOf(id);

			if (index !== -1)
				_requesters.splice(index, 1);
		}

		_save();
	}

	return {
		init: _init,
		add: _add,
		remove: _remove,
		filter: _filter,
		isMuted: _isMuted,
		isIgnored: _isIgnored,
		HIT: _HIT,
		REQUESTER: _REQUESTER
	}
})();



function Evt() { /* Nothing */ };
Evt.ADD          = 1;
Evt.REMOVE       = 2;
Evt.START        = 3;
Evt.STOP         = 4;
Evt.CHANGE       = 5;
Evt.UPDATE       = 6;
Evt.HITS_CHANGE  = 7;
Evt.DELETE       = 8;
Evt.VIEW_DETAILS = 9;

Evt.prototype.addListener = function(type, callback) {
	switch(type) {
		case Evt.ADD:
			this.listener.onadd.push(callback);
			break;
		case Evt.REMOVE:
			this.listener.onremove.push(callback);
			break;
		case Evt.START:
			this.listener.onstart.push(callback);
			break;
		case Evt.STOP:
			this.listener.onstop.push(callback);
			break;
		case Evt.CHANGE:
			this.listener.onchange.push(callback);
			break;
		case Evt.UPDATE:
			this.listener.onupdate.push(callback);
			break;
		case Evt.HITS_CHANGE:
			this.listener.onhitschange.push(callback);
			break;
		case Evt.DELETE:
			this.listener.ondelete.push(callback);
			break;
		case Evt.VIEW_DETAILS:
			this.listener.onviewdetails.push(callback);
			break;
		default:
			console.error("Invalid Event type in addListener()");
	}
}

Evt.prototype.notify = function(type, data) {
	switch(type) {
		case Evt.ADD:
			this.callFunctionArray(this.listener.onadd, data);
			break;
		case Evt.REMOVE:
			this.callFunctionArray(this.listener.onremove, data);
			break;
		case Evt.START:
			this.callFunctionArray(this.listener.onstart, data);
			break;
		case Evt.STOP:
			this.callFunctionArray(this.listener.onstop, data);
			break;
		case Evt.CHANGE:
			this.callFunctionArray(this.listener.onchange, data);
			break;
		case Evt.UPDATE:
			this.callFunctionArray(this.listener.onupdate, data);
			break;
		case Evt.HITS_CHANGE:
			this.callFunctionArray(this.listener.onhitschange, data);
			break;
		case Evt.DELETE:
			this.callFunctionArray(this.listener.ondelete, data);
			break;
		case Evt.VIEW_DETAILS:
			this.callFunctionArray(this.listener.onviewdetails, data);
			break;
		default:
			console.error("Unknown event type:", type);
	}
}

Evt.prototype.callFunctionArray = function(functions, data) {
	if (functions.length > 0)
		for (var i = 0, len = functions.length; i < len; i++)
			functions[i](data);
}



var DispatchUI = {
	create: function(dispatch) {
		DispatchUI.dispatch = dispatch;
		DispatchUI.init();
		DispatchUI.addStyle();
		DispatchUI.addActions();
		DispatchUI.addListeners();
		DispatchUI.addDragAndDrop();
		return DispatchUI.div;
	},

	init: function() {
		var div = DispatchUI.div = $("<div>").attr('id', "dispatcher")
			.append($("<div>").attr('id', "controller"))
			.append($("<div>").attr('id', "watcher_container"));

		DispatchUI.watchers = [];

		// Move dashboard contents to the right and put the dispatch panel on the left
		var pageElements = $("body > *");
		$("body").html("");
		$("body").append(
			$("<div>")
				.attr('id', "content_container")
		);
		if (settings.customURL) {
			$("#content_container").append($("<iframe id='externalsite' src='" + settings.customURL + "' style='width: 100%; height: 99%; border: 0px solid' />"));
			$("#content_container").css('height', '99%');
		} else {
			$("#content_container").append($(pageElements));
		}


		$("body").css('margin', "0").prepend(div);

		var ctrl = DispatchUI.ctrl = $("#controller", div);
		var settingsBtn = $("<a>")
				.attr('id', "settings")
				.attr('href', "javascript:void(0)")
				.attr('title', "Settings")
				.html('<img />')
				.click(function() {
					if (!SettingsDialog.isVisible())
						SettingsDialog.show();
					else
						SettingsDialog.hide();
				});

		ctrl.append(
			settingsBtn,
			'<div class="play_container">\
				<div class="play_all" title="Start All"></div>\
				<div class="play selected" title="Start Selected"></div>\
				<div class="pause" title="Pause All"></div>\
			</div>',
			"Turkmaster"
		);

		// Adding the data URL inline wouldn't work for some reason, so I'm doing it this way.
		// Image from http://latierrasenosestrecha.org/wp-content/themes/purity/img/icons/settings.png
		$("img", settingsBtn)[0].src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2RpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpFRUQ3N0Q2NkUyQjJFMDExOTM4OUZBRkY5RUM4NjkxMiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpFQjNFQjA2OEIyRTYxMUUwOUZDRUUxRERBNzIzQkY1NyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpFQjNFQjA2N0IyRTYxMUUwOUZDRUUxRERBNzIzQkY1NyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpFRkQ3N0Q2NkUyQjJFMDExOTM4OUZBRkY5RUM4NjkxMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpFRUQ3N0Q2NkUyQjJFMDExOTM4OUZBRkY5RUM4NjkxMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PiP+YRgAAALDSURBVHja7FdPZB1BGJ9tawmt8HinrUQpjdBqqFaq5BChJBI5xCvVQ1mPRq89hGqkeusxpLpKDtFTqpEorUZKKdFcWo2WR7REwyOE8niUeP19/Jbxmd3NbjXv0Aw/szO733z/ft/MrNdqtUw72zHT5tZ2A04UEYqiaBTdPFAC9oDb1Wp15TAjMEflhv3cP4kAPC2j6wK24OEv61WgPg2U3El054BtyO2m6fCSqgCLDKBbBjqBHWAQ+A3cAmYcItPAAp/XgDOAGD0GI97nMgDKRel3K8yGuRbP/BSHxMCGkhMjemBEPQ8HetUica79jJT6Djlx5nxeEn4GagnvNiSswCl45UnP8UbC99vAehEOSA4/AmUrvPeBF8A9YJgEFQVvgMdABXhgRUoIeBWGbhUtw07rOWRkvgB3qNywrwKfGIVJJb9fdB+oWJ6Ih8LkRRLR1WT+JfAO+GBxonLgMkTYx9E9cxBpBLgO3OW4xt1vHTJDlIkj8oQGPFdrSBWFkFlKi8CsQ7khicat8YQolwf0qyrso4yWcVTRbFYKAofgPpTsWWRsYrzJiL2yKiNuZZLP1YIsA1ybxXEoKlmLdmB8kd6PcK7f+n7XMtZkra/PgpAHS5eaFwUrZL+0RRgRMtfD6jASwg4k7AeTefaBGda0NMnzTeBrincx0XpYDdc49xCRmi5ShpHkm89D9PRGSn5lfoJkjZU3uU6hfcDnwRK3p0zFBS5at/Iacb5fMb2Rsm8kG4Dwd6B7q8ItBj3iES0p6eNZ0Mfxa773VUWs8XTNdSERlp9NeHeZO6IYepBLT0CZ1Twp+EZCaYI1M5Q1HXJyH9jMlQJev8YoLO0HcAXoBqYS1pri+0v83lg3onru45ghLvFqVcMiDWu+5TDaUxzq/as7YcZl9afaVneg6PRhXstDVYZh0Wu5d/Rv+N8b8EeAAQBg+uBx8hdn9gAAAABJRU5ErkJggg==";
	},

	addActions: function() {
		var dispatch = DispatchUI.dispatch,
			ctrl = DispatchUI.div.find("#controller");
		$("div.play_all", ctrl).mousedown(function() {
			dispatch.start(true);
		});
		$("div.play", ctrl).mousedown(function() {
			dispatch.start();
		});
		$("div.pause", ctrl).mousedown(function() {
			dispatch.stop();
		});
		$("div.deselect_all", ctrl).mousedown(function() {
			dispatch.deselectAll();
		});
	},

	addListeners: function() {
		var dispatch = DispatchUI.dispatch;
		var div = DispatchUI.div;

		dispatch.addListener(Evt.ADD, function(watcher) {
			// This could be done on one line, but then we would lose access to the WatcherUI's internal Watcher object and functionality
			var watcher = WatcherUI.create(watcher);
			$("#watcher_container", DispatchUI.div).append(watcher.element);
			DispatchUI.watchers.push(watcher);
			// watchers.push(WatcherUI.create(watcher).appendTo($("#watcher_container", div)));
		});

		dispatch.addListener(Evt.REMOVE, function(watcher) {
			// Remove watcher from array
			var index = -1,
				watchers = DispatchUI.watchers;

			for (var i = 0, len = watchers.length; i < len; i++) {
				if (watchers[i].watcher === watcher) {
					index = i;
					break;
				}
			}

			if (index !== -1)
				DispatchUI.watchers.splice(index, 1);
		});

		DispatchUI.setHide = function() {
			if (settings.canHide) {
				$(window).on('click', handleWindowClick);
			} else {
				$(window).off('click', handleWindowClick);
			}
		}

		DispatchUI.setHide();

		function handleWindowClick(e) {
			if (!div.is(e.target) && div.has(e.target).length === 0 && $(".notification_panel").has(e.target).length === 0 && !$("#settingsDialog").is(e.target) && $("#settingsDialog").has(e.target).length === 0) {
				hide();
				$(window).off('click', handleWindowClick);
				$(div).on('click', handleDivClick);
			}
		}

		function handleDivClick(e) {
			show();
			$(div).off('click', handleDivClick);

			if (settings.canHide)
				$(window).on('click', handleWindowClick);
		}

		function hide() {
			div.addClass("tm-hidden");
			$("#content_container").addClass("full");
			$("#details_panel").addClass("left");
		}

		function show() {
			div.removeClass("tm-hidden");
			$("#content_container").removeClass("full");
			$("#details_panel").removeClass("left");
		}
	},

	addStyle: function() {
		addStyle("#dispatcher { background-color: #f5f5f5; position: fixed; top: 0px; float: left; left: 0; height: 100%;  width: 270px; font-size: 8pt;  margin-left: 0px; transition: left 0.5s ease; }\
			#dispatcher.tm-hidden { left: -240px }\
			#content_container { position: absolute; left: 270px; top: 0; right: 0; border-left: 2px solid #dadada; transition: left 0.5s ease; }\
			#content_container.full { left: 30px }\
			#dispatcher #controller { text-align: center; font: 160% Candara, sans-serif; color: #585858; position: relative; padding: 3px 5px; }\
			#dispatcher #controller .on_off { margin: 6px 5px 0 0 }\
			#dispatcher #controller .on_off a { font-size: 80% }\
			#dispatcher #controller #settings { top: 2px; position: absolute; right: 5px; }\
			#dispatcher #controller #settings img { width: 1.5em }\
			#dispatcher #controller .play_container { position: absolute; left: 5px }\
			#dispatcher #watcher_container { position: absolute; top: 30px; bottom: 0; overflow-y:auto; width: 100%;}\
			#dispatcher #watcher_container p { margin: 30px 0px }\
			#dispatcher #watcher_container .error_button a { text-decoration: none; color: #555; background-color: #fff; padding: 3px 10px; margin: 5px; border: 1px solid #aaa; border-radius: 2px }\
			#dispatcher #watcher_container .error_button a:hover { background-color: #def; border-color: #aaa }\
			#dispatcher div { font-size: 7pt }\
			#dispatcher .watcher {\
				box-sizing: border-box;\
				margin: 3px 3px 0;\
				background-color: #fff;\
				position: relative;\
				border-bottom: 1px solid #ddd;\
				border-right: 1px solid #ddd;\
				top: 0;\
				transition: background-color 0.5s, top 0.1s;\
				-moz-user-select: none;\
				-webkit-touch-callout: none;\
				-webkit-user-select: none;\
				-khtml-user-select: none;\
			}\
			#dispatcher .watcher.dragging { cursor: grabbing; z-index: 100; opacity: 0.8; transition: background-color 0.5s, top 0s }\
			#dispatcher .watcher div { font: " + settings.fontSize + "pt 'Oxygen', verdana, sans-serif }\
			#dispatcher .watcher.running .details { background-color: #C3ECFC; background-color: rgba(218, 240, 251, 1); }\
			#dispatcher .watcher.updated { background-color: #e8f5fc; background-color: rgba(218, 240, 251, 1) }\
			#dispatcher .watcher .details { width: 25px; text-align: center; float: right; background-color: rgba(234, 234, 234, 1); position: absolute; top: 0; bottom: 0; right: 0; font-size: 90%; color: #fff; transition: background-color 0.5s }\
			#dispatcher .watcher .details.updated { background-color: rgba(218, 240, 251, 1); background-color: #e8f5fc; background-color: rgba(220, 255, 228, 1) }\
			#dispatcher .watcher .name { font-size 130%; color: black; text-decoration: none; display: inline-block; margin-top: -3px}\
			#dispatcher .watcher .name:hover { text-decoration: underline }\
			#dispatcher .watcher.dragging .name:hover { text-decoration: none }\
			#dispatcher .watcher .time { display: block; float: left; font-size: 80% }\
			.on_off { float: right; cursor: pointer }\
			.on_off a { color: #333; margin: 1px; font-size: 56%; font-weight: bold }\
			.on_off a:nth-child(2) { background-color: #aeaeae; color: #fff; border-radius: 12px; padding: 3px 6px; }\
			.on_off.on a:nth-child(1) { background-color: #55b8ea; color: #fff; border-radius: 12px; padding: 3px 6px; }\
			.on_off.on a:nth-child(2) { background-color: inherit; color: #333; border-radius: inherit; padding: inherit; }\
			.watcher .on_off {  }\
			#dispatcher .watcher > .content { margin-right: 25px; padding: 5px 5px 5px 33px;}\
			#dispatcher .watcher .bottom { margin: 0 0 -5px; color: #aaa }\
			#dispatcher .watcher .bottom a:link { color: black; }\
			#dispatcher .watcher .bottom a:hover { color: #cef; }\
			#dispatcher .watcher .details { font-size: 150%; font-weight: bold }\
			#dispatcher .watcher .last_updated { position: absolute; right: 30px; bottom: 4px; font-size: 80% }\
			#dispatcher .watcher .icons { visibility: hidden; margin-left: 10px; bottom: 5px }\
			#dispatcher .watcher:hover .icons { visibility: visible }\
			#dispatcher .watcher .icons img { opacity: 0.2; height: 0.9em }\
			#dispatcher .watcher .icons img:hover { opacity: 1 }\
			#dispatcher .watcher .color_code { position: absolute; left: 0; top: 0; bottom: 0; width: 9px; cursor: grab;}\
			#dispatcher .watcher .color_code div { position: absolute; left: 0; top: 0; bottom: 0; width: 5px; transition: width 0.15s; }\
			#dispatcher .watcher.dragging .color_code { cursor: grabbing; }\
			#dispatcher .watcher .color_code:hover div { width: 9px }\
			#dispatcher .watcher .color_code.hit div       { background-color: rgba(234, 111, 111, .7); }\
			#dispatcher .watcher .color_code.requester div { background-color: rgba(51, 147, 255, .7); }\
			#dispatcher .watcher .color_code.url div       { background-color: rgba(57, 221, 122, .7); }\
			.watcher .play_container {\
				padding: 0px 0px 0px 12px;\
				float: left;\
				cursor: default;\
			}\
			.play, .pause, .play_all, .deselect_all {\
				width:20px;\
				height: 20px;\
				position: relative;\
				display: block;\
			}\
			#controller .play, #controller .pause, #controller .play_all { float: left; }\
			.play:before, .play_all:before {\
				width: 0;\
				height: 0;\
				border-width: 8px 11px;\
				border-style: solid;\
				border-color: transparent transparent transparent #747474;\
				position: absolute;\
				content: '';\
				top: 3px;\
				left: 0px;\
			}\
			.play.selected:after {\
				width: 6px;\
				height: 6px;\
				border-radius: 1px;\
				position: absolute;\
				content: '';\
				background-color: #999;\
				top: 13px;\
				right: 9px;\
			}\
			.play_all:after {\
				width: 0;\
				height: 0;\
				border-width: 8px 11px;\
				border-style: solid;\
				border-color: transparent transparent transparent #999;\
				position: absolute;\
				content: '';\
				top: 3px;\
				left: 5px;\
			}\
			.watcher.running .play:before, .watcher.running .play:after, .pause:before, .pause:after {\
				width: 4px;\
				height: 15px;\
				background: #747474;\
				position: absolute;\
				content: '';\
				top: 3px;\
			}\
			.watcher.running .play:before, .pause:before {\
				left: 0px;\
				border: none;\
			}\
			.watcher.running .play:after, .pause:after {\
				left: 6px;\
			}\
			.play_select {\
				width: 6px;\
				height: 6px;\
				border: 2px solid #cecece;\
				border-radius: 2px;\
				margin-top: 2px;\
			}\
			.watcher.selected .play_select { background-color: #55b8ea; border-color: #b4e6ff; }\
			");
	},

	addDragAndDrop: function() {
		// Drag watchers
		var startY, currentBaseY, max, min, height,
			dragDiv, nextDiv, prevDiv, startPos, endPos, isDragging,
			slop = 7, currentWatcher, watchers = DispatchUI.watchers;

		DispatchUI.div.on("mousedown", ".watcher", function(e) {
			isDragging = false;

			// Get the position of the watcher in the listing
			startPos = endPos = $("#watcher_container .watcher").index(e.currentTarget);

			// Get reference to the selected watcher
			currentWatcher = watchers[startPos];
			dragDiv = currentWatcher.element;
			nextDiv = dragDiv.next();
			prevDiv = dragDiv.prev();

			// TODO Check target to prevent dragging from a component inside the watcher (i.e. buttons, links, etc.)
			height = dragDiv.outerHeight(true);

			startY = e.clientY;
			currentBaseY = dragDiv.offset().top;

			// max = Math.min($("#watcher_container").outerHeight(true), height * (DispatchUI.dispatch.watchers.length + .75)) - height;
			min = watchers[0].element.offset().top;
			max = Math.min($("#watcher_container").outerHeight(true), height * (watchers.length - 1) + watchers[0].element.offset().top);

			$(window).on("mousemove", move);
			$(window).on("mouseup", up);
		});

		function move(e) {
			var offsetY = e.clientY - startY;

			if (!isDragging && (Math.abs(offsetY) > slop)) {
				// Start dragging
				isDragging = true;

				dragDiv.addClass("dragging");
			}

			if (isDragging) {
				dragDiv.css('top', offsetY);

				var diffY = dragDiv.offset().top - currentBaseY;

				if (dragDiv.offset().top > max) {
					dragDiv.offset({ top: max });
					diffY = 0;
				} else if (dragDiv.offset().top < min) {
					dragDiv.offset({ top: min });
					diffY = 0;
				}

				if (diffY > height / 2) {
					// Move down one spot
					nextDiv.offset({ 'top': nextDiv.offset().top - height });
					nextDiv = nextDiv.nextAll(":not(.dragging)").first();
					prevDiv = (prevDiv.length) ? prevDiv.nextAll(":not(.dragging)").first() : (dragDiv !== watchers[0].element) ? watchers[0].element : watchers[1].element;

					currentBaseY += height;
					endPos++;
				} else if (-diffY > height / 2) {
					// Move up one spot
					prevDiv.offset({ 'top': prevDiv.offset().top + height });
					prevDiv = prevDiv.prevAll(":not(.dragging)").first();
					nextDiv = (nextDiv.length) ? nextDiv.prevAll(":not(.dragging)").first() : (dragDiv !== watchers[watchers.length - 1].element) ? watchers[watchers.length - 1].element : watchers[watchers.length - 2].element;

					currentBaseY -= height;
					endPos--;
				}
			}
		}

		function up(e) {
			$(window).off("mousemove", move);
			$(window).off("mouseup", up);

			if (isDragging) {
				e.preventDefault();
				dragDiv.removeClass("dragging");
				isDragging = false;

				// $("div", colorCode).css('width', '');
				dragDiv.css('cursor', '');
				dragDiv.css('z-index', '');
				dragDiv.css('opacity', '');
				$(".name", dragDiv).removeClass("no_hover");

				// Reset all watcher offsets
				$("#watcher_container .watcher").css('transition', "background-color 0.5s, top 0s");
				$("#watcher_container .watcher").css('top', '');

				setTimeout(function() { $("#watcher_container .watcher").css('transition', ''); }, 600);

				if (startPos !== endPos) {
					if (endPos > startPos)
						dragDiv.insertAfter($("#watcher_container .watcher")[endPos]);
					else
						dragDiv.insertBefore($("#watcher_container .watcher")[endPos]);

					DispatchUI.dispatch.moveWatcher(startPos, endPos);

					// Re-arrange our watchers array
					watchers.splice(startPos, 1);
					watchers.splice(endPos, 0, currentWatcher);
				}
			}
		}
	}
}

/** Dispatch object. Controls all of the watchers.

**/
function Dispatch() {
	this.watchers = new Array();
	this.isLoading = false;

	// Listeners
	this.listener = {
		onadd:		[],
		onremove:	[],
		onstart:	[],
		onstop:		[],
		onchange:	[]
	};
}
Dispatch.prototype = new Evt();
Dispatch.prototype.start = function(startAll) {
	if (this.watchers.length > 0) {
		var count = 0;
		for (var i = 0, len = this.watchers.length; i < len; i++) {
			// Don't start them all at the same time. There is a 2 second delay
			// between each start. It had to be done in a self-executing function
			// in order for the setTimeout to work properly.
			if (this.watchers[i].state.isSelected || startAll) {
				(function (watcher, x){
						watcher.timer = setTimeout(function() { watcher.start(); }, x * 0000); // Let's try 0ms
				})(this.watchers[i], count++);
			}
		}
	}
	this.notify(Evt.START, null);
}
Dispatch.prototype.stop = function() {
	// Stop all Watchers
	if (this.watchers.length > 0) {
		for (var i = 0, len = this.watchers.length; i < len; i++)
			this.watchers[i].stop();
	}
	this.interruptStart = true;
	this.notify(Evt.STOP, null);
}
Dispatch.prototype.deselectAll = function() {
	if (this.watchers.length > 0) {
		for (var i = 0, len = this.watchers.length; i < len; i++)
			this.watchers[i].toggleSelected(true);
	}
}
Dispatch.prototype.add = function(watcher) {
	var self = this; 	 	
		 	
	watcher.addListener(Evt.CHANGE, function() { 	 	
		self.save(); 	 	
	});

	this.watchers.push(watcher);

	if (!this.isLoading) {
		this.save();
	}

	this.notify(Evt.ADD, watcher);

	// TODO Add a listener to save the watcher list after a watcher has been changed
	return watcher;
}
Dispatch.prototype.save = function() {
    if (!loadError) {
        // localStorage.setItem('notifier_watchers', JSON.stringify(dispatch.watchers, Watcher.replacerArray));
        GM_setValue('notifier_watchers', JSON.stringify(dispatch.watchers, Watcher.replacerArray));

        var lastChecked = getLastChecked(dispatch.watchers);

        if (lastChecked > 0)
	        localStorage.setItem('notifier_watchers_lastChecked', JSON.stringify(lastChecked));
    }
	
	var test = GM_getValue('notifier_watchers');

    function getLastChecked(watchers) {
    	var lastChecked = (watchers[0].date) ? watchers[0].date.getTime() : 0;

    	for (var i = 1, len = watchers.length; i < len; i++) {
    		if (watchers[i].isRunning)
    			return new Date.getTime();

    		if ((watchers[i].date) && (watchers[i].date.getTime() > lastChecked))
    			lastChecked = watchers[i].date.getTime();
    	}

    	return lastChecked;
    }
}
Dispatch.prototype.load = function() {
	this.isLoading = true;

	var data;
	var watchers,
		lastChecked = localStorage.getItem('notifier_watchers_lastChecked');

	data = GM_getValue('notifier_watchers');

	if (typeof data === 'undefined')
		data = localStorage.getItem('notifier_watchers');

	if (data !== null) {
		try {
			watchers = JSON.parse(data);

			try {
				lastChecked = JSON.parse(lastChecked);
			} catch(e) {
				lastChecked = null;
			}

			var now = new Date().getTime(),
				expTime = 180000,	// 3 minutes
				expired = (lastChecked !== null) ? now - lastChecked > expTime : false; // Expired if most recent watcher update happened more than x minutes before page was loaded

			// Add the watchers. Clear last hits if past the expiration time
			for(var i = 0; i < watchers.length; i++) {
				if (expired)
					watchers[i].lastHits = [];

				// for upgrading users, these variables won't be in the existing data, and TM will refuse to load
				// unless they are instantiated
				if (typeof watchers[i].lastFound === "undefined") {
					watchers[i].lastFound = 0;
				} else if (watchers[i].lastFound === 0) {
					watchers[i].lastFound = -1;
				}
				if (typeof watchers[i].groupid === "undefined") {
					watchers[i].groupid = '';
				}
				if (typeof watchers[i].option.startGroup === "undefined") {
					watchers[i].option.startGroup = false;
				}
				if (typeof watchers[i].option.stopGroup === "undefined") {
					watchers[i].option.stopGroup = false;
				}
				if (typeof watchers[i].dateAdded === "undefined") {
					watchers[i].dateAdded = -1;
				} else if (watchers[i].dateAdded === 0) {
					watchers[i].dateAdded = -1;
				}

				this.add(new Watcher(watchers[i]));
			}

		} catch(e) {
			loadError = true;
			console.log("Error loading saved list", e);
        }
	} else {
		loadDefaultWatchers();
	}

	this.isLoading = false;
}
Dispatch.prototype.remove = function(watcher) {
	var index = this.watchers.indexOf(watcher);

	if (index !== -1)
		this.watchers.splice(index, 1);

	watcher.delete();
	this.save();
	this.notify(Evt.REMOVE, watcher);
}
Dispatch.prototype.moveWatcher = function(from, to) {
	if ((to >= 0 && to < this.watchers.length) && (from >= 0 && from < this.watchers.length)) {
		var watcher = this.watchers.splice(from, 1);
		this.watchers.splice(to, 0, watcher[0]);
		this.save();
	}
}
Dispatch.prototype.getWatcherByProperty = function(name, value) {
	if (this.watchers.length > 0) {
		for (var i = 0, len = this.watchers.length; i < len; i++) {
			if (this.watchers[i][name] === value)
				return this.watchers[i];
		}
	}
	return null;
}
Dispatch.prototype.getWatcherIndex = function(watcher) {
	return this.watchers.indexOf(watcher);
}
Dispatch.prototype.getWatcher = function(index) {
	return this.watchers[index];
}
Dispatch.prototype.getWatcherCount = function() {
	return this.watchers.length;
}
Dispatch.prototype.hideWatchers = function() {
	$("#controller a").css('display', "none");
	$("#watcher_container").html("");
	$("#watcher_container")
		.css('background-color', "#f9f9f9")
		.css('color', "#ff6b6b")
		.css('text-align', "center").append(
			$("<p>").text("There is already a notifier running on a different page."),
			$("<p>").addClass("error_button").append(
				$("<a>")
					.html("Close")
					.attr('href', "javascript:void(0)")
					.click(function() {
						$("#dispatcher").css('display', "none");
						$("#content_container").css('left', "0px");
					}),
				$("<a>")
					.html("Show")
					.attr('href', "javascript:void(0)")
					.click(function() {
						sendMessage({ header: 'show_main' });
					})
			));
}
Dispatch.prototype.onRequestMainDenied = function() {
	pageType.MAIN = false;
	this.hideWatchers();
}
Dispatch.prototype.exportWatchers = function() {
	var watcherAttrs = ["id", "time", "type", "name", "groupid", "option", "auto", "alert", "stopOnCatch", "stopGroup", "startGroup", "state", "isSelected", "url", "dateAdded"];

	return JSON.stringify(this.watchers, watcherAttrs);
}
Dispatch.prototype.importWatchers = function(data) {
	try {
		data = JSON.parse(data);
		dispatch.isLoading = true;

		for (var i = 0, len = data.length; i < len; i++) {
			var watcher = new Watcher(data[i]);

			if (!this.getWatcherByProperty('id', watcher.id) && !this.getWatcherByProperty('name', watcher.name))
				this.add(watcher);
		}

		dispatch.isLoading = false;
		dispatch.save();

		console.log("Watchers imported", dispatch.watchers);
	} catch(e) {
		console.error("Error importing watchers", e, data);
		alert("Invalid input. Try disabling word wrap on your text editor and re-copy.");
	}
}


function watcherDialog(watcher, callback) {
	var dialog = $("<div>").attr('id', 'add_watcher_form').append(
	$("<h3>").text("Add a watcher"),
	$("<p>").append(
		$("<label>").text("Name ").append(
			$("<input>").attr('id', "watcherName").attr('type', "text").val(watcher.name)),
		$("<label>").text(" Time ").append(
			$("<input>").attr('id', "watcherDuration").attr('type', "text").val(watcher.time / 1000))
		),
	$("<p>").append(
		$("<label>").text("Group Name ").append(
			$("<input>").attr('type', "text").attr('id', 'groupid').val(watcher.groupid))
		),
		(watcher.type === "hit") ?
			$("<p>").append(
				$("<input>").attr('type', "checkbox").attr('id', "autoaccept").prop('checked', watcher.option.auto),
				$("<label>").attr('for', "autoaccept").text("Auto-accept")
				)
			: "",
		(watcher.type === "hit") ?
			$("<p>").append(
				$("<input>").attr('type', "checkbox").attr('id', "stopaccept").prop('checked', watcher.option.stopOnCatch),
				$("<label>").attr('for', "stopaccept").text("Stop on accept")
				)
			: "",
		(watcher.type === "hit") ?
			$("<p>").append(
				$("<input>").attr('type', "checkbox").attr('id', "stopgroup").prop('checked', watcher.option.stopGroup),
				$("<label>").attr('for', 'stopgroup').text("Stop group on accept")
				)
			: "",
		$("<p>").append(
			$("<input>").attr('type', "checkbox").attr('id', "startgroup").prop('checked', watcher.option.startGroup),
			$("<label>").attr('for', 'startgroup').text("Start/stop with group")
			),
		$("<p>").append(
			$("<input>").attr('type', "checkbox").attr('id', "alert").prop('checked', watcher.option.alert),
			$("<label>").attr('for', "alert").text("Alert")
			),
		$("<p>").addClass("form_buttons").append(
			$("<input>").attr('type', "button").attr('value', "Save"),
			$("<input>").attr('type', "button").attr('value', "Cancel")
			)
	);

	function save() {
		callback({
			name		: $("#watcherName", dialog).val(),
			groupid		: $("#groupid", dialog).val(),
			time		: parseInt($("#watcherDuration", dialog).val(), 10) * 1000,
			alert		: $("#alert", dialog).prop('checked'),
			auto		: $("#autoaccept", dialog).prop('checked'),
			stopOnCatch	: $("#stopaccept", dialog).prop('checked'),			
			stopGroup   : $("#stopgroup", dialog).prop('checked'),
			startGroup	: $("#startgroup", dialog).prop('checked')
		})

		hide();
	};

	function hide() {
		dialog.hide();
		dialog.remove();
		dialog.empty();
	}

	$("input[value='Save']", dialog).click(save);

	$("input[type='button']", dialog).click(hide);

	$(dialog).keydown(function(e) {
		switch(e.keyCode) {
			case 13:
				save();
				break;
			case 27:
				hide();
				break;
		}
	});

	$("body").append(dialog);

	if ($("#watcherName", dialog).val() === "")
		$("#watcherName", dialog).focus().select();
	else
		$("#watcherDuration", dialog).focus().select();
}


function WatcherUI() { /* Nothing */ };
WatcherUI.create = function(watcher) {
	// Create jQuery Element...
	var div = $("<div>").addClass("watcher")
		.html('<div class="details"> > </div>\
		<div class="play_container"><div class="play"></div><div class="play_select"></div></div>\
		<div class="content">\
			<a class="name" href="' + watcher.getURL() + '" target="_blank">' + ((watcher.groupid !== '') ? '(' + watcher.groupid + ') ' : '') + ((typeof watcher.name !== 'undefined') ? watcher.name : watcher.id) + '</a>\
			<div class="bottom">\
	            <span class="time">' + (watcher.time / 1000) + ' seconds </span>\
	            <span class="icons">\
	                <a class="edit" href="javascript:void(0)"><img src="https://i.imgur.com/peEhuHZ.png" /></a>\
	                <a class="delete" href="javascript:void(0)"><img src="https://i.imgur.com/5snaSxU.png" /></a>\
	            </span>\
				<div class="last_updated" title="Checked: ' + watcher.getFormattedDateTime(0) + '  |  Found: ' + watcher.getFormattedDateTime(watcher.lastFound)  + '  |  Added: ' + watcher.getFormattedDateTime(watcher.dateAdded) + '">' + ((typeof watcher.date !== 'undefined') ? watcher.getFormattedTime(0) : "n/a") + '</div>\
			</div>\
			<div class="color_code"><div></div></div>\
		</div>');

	if (watcher.state.isSelected)
		div.addClass("selected");

	// Add listeners
	watcher.addListener(Evt.START, function() {
		div.addClass("running");
	});

	watcher.addListener(Evt.STOP, function() {
		div.removeClass("running");
	});

	watcher.addListener(Evt.UPDATE, function(e) {
		$(".last_updated", div).text(watcher.getFormattedTime(0)).attr('title', "Checked: " + watcher.getFormattedDateTime(0) + "  |  Found: " + watcher.getFormattedDateTime(watcher.lastFound) + "  |  Added: " + watcher.getFormattedDateTime(watcher.dateAdded));
		div.addClass("updated");
		setTimeout(function() { div.removeClass("updated") }, 1000);
	});

	watcher.addListener(Evt.CHANGE, function() {
		$(".name", div).text(((watcher.groupid !== '') ? '(' + watcher.groupid + ') ' : '') + watcher.name).attr('href', watcher.url);
		$(".time", div).text(watcher.time / 1000 + " seconds");

		if (watcher.state.isSelected)
			$(div).addClass("selected");
		else
			$(div).removeClass("selected");
	});

	watcher.addListener(Evt.HITS_CHANGE, function() {
		$(".details", div).addClass("updated");
	});

	watcher.addListener(Evt.DELETE, function() {
		div.remove();
	});

	watcher.addListener(Evt.VIEW_DETAILS, function() {
		$(".details", div).removeClass("updated");
	});


	// Set actions
	$(".edit", div).click(showWatcherDialog);
	div.dblclick(showWatcherDialog);

	function showWatcherDialog() {
		watcherDialog(watcher, function(values) {
			watcher.setValues({
				name        : values.name,
				groupid     : values.groupid,
				time        : values.time,
				alert       : values.alert,
				auto        : values.auto,
				stopOnCatch : values.stopOnCatch,
				stopGroup   : values.stopGroup,
				startGroup	: values.startGroup
			});
		});
	}

	$(".delete", div).click(function() {
		dispatch.remove(watcher);
	});

	$(".details", div).mouseover(function () {
		showDetailsPanel(watcher);
		$(this).removeClass("updated");
	});

	$("div.play_select", div).mousedown(function() {
		watcher.toggleSelected();
	});

	$("div.play", div).mousedown(function() {
		if (watcher.state.isRunning) {
			watcher.stop();
			var toDo = 'stop';
		} else {
			watcher.start();
			var toDo = 'start';
		}
		if (watcher.groupid !== '' && watcher.option.startGroup) {
			var filteredWatchers = watcher.getWatchersByProperties({'name1': 'groupid', 'subgroup1': false, 'value1': watcher.groupid, 'name2': 'option', 'subgroup2': 'startGroup', 'value2': true});
			if (filteredWatchers !== null) {
				for (var i = 0, len = filteredWatchers.length; i < len; i++) {
					if (dispatch.watchers[filteredWatchers[i]].state.isRunning === false && toDo === 'start') {
						dispatch.watchers[filteredWatchers[i]].start();
					} else if (dispatch.watchers[filteredWatchers[i]].state.isRunning && toDo === 'stop') {
						dispatch.watchers[filteredWatchers[i]].stop();
					}
				}
			}
		}
	});


	// Add colors for watcher type
	var colorCode = $(".color_code", div);
	if (watcher.type === 'hit') {
		colorCode.addClass("hit");
		colorCode.attr('title', "HIT Watcher");
	} else if (watcher.type === 'requester') {
		colorCode.addClass("requester");
		colorCode.attr('title', "Requester Watcher");
	} else if (watcher.type === 'url') {
		colorCode.addClass("url");
		colorCode.attr('title', "URL Watcher");
	}
	colorCode.attr('title', colorCode.attr('title') + "\nClick and drag to re-order");


	$(".delete img", div).hover(function() { $(this).attr('src', "https://i.imgur.com/guRzYEL.png")}, function() {$(this).attr('src', "https://i.imgur.com/5snaSxU.png")});
	$(".edit img", div).hover(function() { $(this).attr('src', "https://i.imgur.com/VTHXHI4.png")}, function() {$(this).attr('src', "https://i.imgur.com/peEhuHZ.png")});

	return { element: div, watcher: watcher };
}

/**	The Watcher object. This is what controls the pages that are monitored and how often

	Events:
		onStart      - The watcher has started to check the desired page with a time interval
		onStop       - The time interval has stopped
		onUpdate     - The watcher has just checked the page for hits
		onChange     - Attributes of the watcher changed, like name, interval time, etc.
		onDelete     - When a watcher has been deleted
		onHitsChange - The watcher updated and found a different set of hits from last time
		onCaptcha?   - The watcher encounters a captcha. Not sure if this should be handled by the Watcher or Loader (maybe both)

**/
function Watcher(attrs, firstLoad) {
	firstLoad = firstLoad || false;
	var DEFAULT_TIME = 60000;
	this.interval    = null;		// For continuous interval
	this.timer       = null; 			// For initial setTimeout
	this.newHits     = [];

	attrs = attrs || {};

	// Default states
	this.state = {};
	state = attrs.state || {};
	this.state.isRunning  = (typeof state.isRunning !== 'undefined') ? state.isRunning : false;
	this.state.isSelected = (typeof state.isSelected !== 'undefined') ? state.isSelected : false;
	this.state.isUpdated  = (typeof state.isUpdated !== 'undefined') ? state.isUpdated : false;

	// TODO Erase these state overwrites once we implement resuming state after a page load
	// Currently if a watcher is on when dispatch saves the watcher list, it'll still be marked as running even
	// though it wouldn't be running on page load.
	this.state.isRunning = false;
	this.state.isUpdated = false;
	
	// Required attributes
	this.id   = attrs.id;
	this.time = attrs.time || DEFAULT_TIME;
	this.type = attrs.type;
	this.name = attrs.name || this.id;
	this.lastHits = attrs.lastHits || [];
	this.lastFound = attrs.lastFound || ((attrs.lastFound === -1 && this.lastHits.length > 0) ? Math.floor(Date.now() / 1000) : -1);
	this.groupid = attrs.groupid || "";
	if (firstLoad)
		this.dateAdded = Math.floor(Date.now() / 1000);
	else
		this.dateAdded = attrs.dateAdded || -1;
	
	// Options
	this.option = {};
	option 	= attrs.option || {};
	this.option.auto        = (typeof option.auto !== 'undefined') ? option.auto : false;
	this.option.alert       = (typeof option.alert !== 'undefined') ? option.alert : false;
	this.option.stopOnCatch = (typeof option.stopOnCatch !== 'undefined') ? option.stopOnCatch : true;
	this.option.stopGroup	= (typeof option.stopGroup !== 'undefined') ? option.stopGroup : true;
	this.option.startGroup  = (typeof option.startGroup !== 'undefined') ? option.startGroup : true;

	// Figure out the URL
	this.url = attrs.url;

	if (typeof this.url === 'undefined')
		this.setUrl();

	// Listeners
	this.listener = {
		onstart       : [],
		onstop        : [],
		onupdate      : [],
		onchange      : [],
		onhitschange  : [],
		ondelete      : [],
		onviewdetails : []
	};

	return this;
}
Watcher.prototype = new Evt();
Watcher.prototype.toString = function() {
	return this.name;
}
Watcher.prototype.getHTML = function() {
	this.DOMElement = $("<div>");
	return $("<div>");
}
Watcher.prototype.getURL = function() {
	return this.url;
}
Watcher.prototype.setUrl = function() {
	switch(this.type) {
		case 'hit':
			this.url = "https://www.mturk.com/mturk/preview" + (this.option.auto ? "andaccept" : "") + "?groupId=" + this.id;
			break;
		case 'requester':
			this.url = "https://www.mturk.com/mturk/searchbar?selectedSearchType=hitgroups&requesterId=" + this.id;
			break;
		case 'url':
			if (typeof this.url === 'undefined')
				this.url = this.id;
			
			// URL watchers get a random id because of id requirements for CSS
			this.id = "A" + Math.floor(Math.random() * 100000000);
			break;
	}
}
Watcher.prototype.setAuto = function(isAuto) {
	this.option.auto = isAuto;
	this.setUrl();
}
Watcher.prototype.isNewHit = function (hit) {
	return (this.newHits.indexOf(hit) !== -1);
}
Watcher.prototype.onChanged = function(newHits) {
	Messenger.sendHits(this, newHits);
	this.isUpdated = true;
	this.lastFound = Math.floor(Date.now() / 1000);
	this.notify(Evt.HITS_CHANGE, newHits);
}
Watcher.prototype.start = function() {
	if (!this.state.isRunning) {
		var _this = this;

		// Set the interval and start right away
		this.interval = setInterval(function(){ _this.getData() }, this.time);
		this.getData();
		
		this.state.isRunning = true;

		this.notify(Evt.START, null);
	}

	return this;
}
Watcher.prototype.stop = function() {
	// Stop the interval object and the timer object
	clearInterval(this.interval);
	clearTimeout(this.timer);
	this.state.isRunning = false;

	this.notify(Evt.STOP, null);
}
Watcher.prototype.delete = function() {
	this.notify(Evt.DELETE, this);

	this.stop();
	this.listener = null;
	this.newHits  = null;
	this.lastHits = null;
}
Watcher.prototype.filterMessages = function(newHits) {
	// Determine which hits, if any, the user should be notified of
	// For now just showing new hits
	var filteredHits;

	if (typeof this.lastHits !== 'undefined' && this.lastHits.length > 0) {
		filteredHits = [];

		for (var i = 0, len = newHits.length; i < len; i++) {
			for (var j = 0, len2 = this.lastHits.length; j < len2; j++) {
				if (newHits[i].id === this.lastHits[j].id)
					break;
				
				// If we reach the end with no matches, add it to the changed hits array
				if (j === len2 - 1 )
					filteredHits.push(newHits[i]);
			}
		}
	} else {
		// If "last hits" doesn't exist, then all of the new hits should be considered new
		filteredHits = newHits;
	}
	
	this.lastHits = newHits;
	return filteredHits;
}
Watcher.prototype.toggleSelected = function(deselectAll) {
	deselectAll = deselectAll || false;
	if (this.state.isSelected)
		this.state.isSelected = false;
	else if (deselectAll === false)
		this.state.isSelected = true;

	this.notify(Evt.CHANGE, null);
}
Watcher.prototype.markViewed = function () {
	if (this.isUpdated) {
		isUpdated = false;
		this.notify(Evt.VIEW_DETAILS, null);
	}
}
Watcher.prototype.updateWatcherPanel = function() {
	this.date = new Date();
	this.notify(Evt.UPDATE, null);
}
Watcher.prototype.setValues = function(values) {
	var val = values || {};
	this.name = val.name || this.name;
	this.groupid = val.groupid;
	this.setAuto(val.auto);
	this.option.stopOnCatch = val.stopOnCatch;
	this.option.stopGroup = val.stopGroup;
	this.option.startGroup = val.startGroup;
	this.option.alert = val.alert;

	if (typeof val.time !== 'undefined' && this.time !== val.time) {
		this.time = val.time;

		if (this.state.isRunning) {
			this.stop();
			this.start();
		}
	}

	this.notify(Evt.CHANGE, null);
}
Watcher.prototype.getFormattedDate = function(useTimestamp) {
	if ((useTimestamp === 0 && typeof this.date !== 'undefined') || useTimestamp > 1) {
		var date = (useTimestamp > 0 ? new Date(useTimestamp * 1000) : this.date);
		var str = "";

		str += (date.getMonth() + 1) + '/' + date.getDate() + '/'
			+ date.getFullYear();
		return str;
	} else {
		return "n/a";
	}
}
Watcher.prototype.getFormattedTime = function(useTimestamp) {
	if ((useTimestamp === 0 && typeof this.date !== 'undefined') || useTimestamp > 0) {
		var time = (useTimestamp > 0 ? new Date(useTimestamp * 1000) : this.date);
		var str = "";
		var hours = time.getHours();
		var ampm = "am";
		
		if (hours >= 12) {
			if (hours > 12)
				hours -= 12;
			ampm = "pm";
		} else if (hours === 0) {
			hours = 12;
		}
			
		str += hours + ":" 
			+ ((time.getMinutes() < 10) ? "0" : "") + time.getMinutes() + ":"
			+ ((time.getSeconds() < 10) ? "0" : "") + time.getSeconds()
			+ ampm;
			
		return str;
	} else {
		return "n/a";
	}
}
Watcher.prototype.getFormattedDateTime = function(useTimestamp) {
	var dateString = this.getFormattedDate(useTimestamp);
	if (dateString == "n/a") {
		return dateString;
	} else {
		return dateString + ' ' + this.getFormattedTime(useTimestamp);
	}
}
Watcher.prototype.setHits = function(hits) {
	if (typeof hits !== 'undefined') {
		if (Object.prototype.toString.call(hits) !== '[object Array]')
			hits = new Array(hits);
		this.sendHits(hits);
	}
	this.updateWatcherPanel();
}
Watcher.prototype.sendHits = function(hits) {
	// Only send the hits if there is actually something to send
	// In the near future this will have to be changed to show when HITs go away completely
	if (typeof hits !== 'undefined' && hits.length > 0) {
		var newHits = this.newHits = this.filterMessages(hits);

		if (newHits.length) {
			this.onChanged(newHits);
		} else if (this.option.auto && !this.option.stopOnCatch && !this.option.stopGroup) {
			this.onChanged(newHits); // Might add a different method for this case, but using onChanged for now
		}
	}
}
Watcher.prototype.getData = function() {
	var _this = this;
	Loader.load(this, this.url, function(data) { _this.onDataReceived($(data)); });
}
Watcher.prototype.onDataReceived = function(data) {
	var error = $(".error_title", data);
	if (error.length > 0) {
		if (error.text().contains("You have exceeded")) {
			console.error("Exceeded the maximum rate!");
			return;
		}
	}

	if (this.type === 'hit')
		this.setHits(this.parseHitPage(data));
	else
		this.setHits(this.parseListing(data));
}
Watcher.prototype.parseListing = function(data) {
	var hitCount = $("table:nth-child(3) > tbody:nth-child(1) > tr", data).length;
	var hits = new Array();
	var	qryUrl       = "td:nth-child(3) > span:nth-child(1) > a",
		qryTitle     = "td:nth-child(1) > a:nth-child(1)",
		qryRequester = "td:nth-child(1) > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(2) > a",
		qryReward    = "td:nth-child(3) > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(2) > span:nth-child(1)",
		qryAvailable = "td:nth-child(3) > table > tbody > tr:nth-child(2) > td:nth-child(2)",
		qryTime      = "td:nth-child(2) > table > tbody > tr:nth-child(2) > td:nth-child(2)";

	for (var i = 0; i < hitCount; i++) {
		// Get nearby ancestors so jQuery won't have to do a full search for each element (faster)
		var base    = $("table:nth-child(3) > tbody:nth-child(1) > tr:nth-child(" + (i+1) + ") > td:nth-child(1) > table:nth-child(1) > tbody:nth-child(1)", data),
			topRow  = $("tr:nth-child(2) > td:nth-child(1) > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(1)", base),
			content = $("tr:nth-child(3) > td:nth-child(3) > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(1)", base);

		var hit = new Hit();
		hit.requester   = $(qryRequester, content).text();
		hit.requesterID = $(qryRequester, content).attr("href").match(/requesterId=([A-Z0-9]+)/)[1];
		hit.title       = $(qryTitle, topRow).text().trim();
		hit.reward      = $(qryReward, content).text().trim();
		hit.available   = $(qryAvailable, content).text().trim();
		hit.time        = $(qryTime, content).text().trim();
		
		var urlData = $(qryUrl, topRow);
		hit.url = urlData.attr("href");

		var idMatch = hit.url.match(/(group|notqualified\?hit|requestqualification\?qualification)Id=([A-Z0-9]+)/);

		if (idMatch !== null) {
			hit.id = idMatch[2];
		}
		
		hit.canPreview = false;
		
		// Check each link to see if user is qualified or can preview the HIT, etc.
		urlData.each(function() {
			if (typeof this.href !== 'undefined') {
				if (this.href.contains("qual"))
					hit.isQualified = false;
				else if (this.href.contains("preview"))
					hit.canPreview = true;
			}
		});
		hits[i] = hit;
	}

	return hits;
}
Watcher.prototype.getWatchersByProperties = function(checkArray) {
	var filteredWatchers = [];
	var lenCheck = Object.keys(checkArray).length / 3;
	if (dispatch.watchers.length > 0) {
		for (var i = 0, len = dispatch.watchers.length; i < len; i++) {
			var matches = 0;
			for (var j = 1; j <= lenCheck; j++) {
				currentSubgroup = 'subgroup' + j;
				currentName = 'name' + j;
				currentValue = 'value' + j;
				if (checkArray[currentSubgroup] !== false) {
					var checkString = dispatch.watchers[i][checkArray[currentName]][checkArray[currentSubgroup]];
				} else {
					var checkString = dispatch.watchers[i][checkArray[currentName]];
				}
				if (checkString == checkArray[currentValue]) {
					matches++;
				} else {
					// if there's no match, we can break, because it's all or nothing
					break;
				}
			}
			if (matches == lenCheck) {
				filteredWatchers.push(i);
			}
		}
	}
	return (filteredWatchers.length > 0) ? filteredWatchers : null;
}
Watcher.prototype.parseHitPage = function(data) {
	var msgbox = $("#alertboxHeader", data);
	var hasCaptcha = ($(data).length > 0) ? ($(data).text()).contains("In order to accept your next HIT") : false;
	
	if ($(msgbox).length > 0 && ($(msgbox).text()).contains("There are no more available HITs in this group.")) {
		// If there aren't any more available, keep checking. If they were just previously available
		// then we should alert the user that it's gone.
	} else if ($(msgbox).length > 0 && ($(msgbox).text()).contains("You are not qualified to accept this HIT. You either do not have the required Qualification or")) {
		// not qualified
	} else {
		// If it's newly available, alert the user and start auto-stacking if that's desired.
		//TODO We need to test for "You are not qualified to accept this HIT."
		
		if (hasCaptcha) {
			console.log("Has captcha");
			sendMessage({header: 'captcha'});
		}

		var uid = $("input[name='hitId']", data).attr("value");
		var hit = new Hit({id: this.id, uid: uid, isAutoAccept: this.option.auto});
		hit.requester = $("form:nth-child(7) > div:nth-child(9) > div:nth-child(1) > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(3) > td:nth-child(3) > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(2)", data).text().trim();
		hit.title     = $(".capsulelink_bold > div:nth-child(1)", data).text().trim();
		hit.reward    = $("td.capsule_field_text:nth-child(5) > span:nth-child(1)", data).text().trim();
		hit.available = $("td.capsule_field_text:nth-child(8)", data).text().trim();
		hit.time      = $("td.capsule_field_text:nth-child(11)", data).text().trim();
		
		if (this.option.auto && (this.option.stopOnCatch || this.option.stopGroup || (settings.stopCaptcha && hasCaptcha)) && this.state.isRunning) {
			// We should probably toggle off all auto-accept hits when we encounter a captcha. Maybe send a special message to all mturk windows while we're at it.
			// The special message could be some kind of banner that says that no more hits can be accepted in the background until the captcha is entered. (It would
			// be pretty cool if we could pull up the captcha image in the background and just show it and the form to enter it from another page).
			this.stop();
			if (this.option.stopGroup && this.groupid !== '') {
				var watchersToStop = this.getWatchersByProperties({'name1': 'groupid', 'subgroup1': false, 'value1': this.groupid, 'name2': 'option', 'subgroup2': 'stopGroup', 'value2': true});
				if (watchersToStop !== null) {
					for (var i = 0, len = watchersToStop.length; i < len; i++) {
						if (dispatch.watchers[watchersToStop[i]].state.isRunning) {
							dispatch.watchers[watchersToStop[i]].stop();
						}
					}
				}
			}
		}
		
		return new Array(hit);
	}
}
Watcher.replacerArray = ["id", "time", "type", "name", "groupid", "option", "auto", "alert", "stopOnCatch", "stopGroup", "startGroup", "state", "isRunning", "isSelected", "isUpdated", "url", "lastHits", "lastFound", "dateAdded"];

var Messenger = function() {
	var SEND_HITS = "new_hits";
	var SEND_TO = "turkopticon";
	var notificationGroup;

	function _sendHits(watcher, hits) {
		// Pass through ignore filters
		hits = IgnoreList.filter(hits);

		if (hits.length) {
			var toData = TO.get(Hit.getUniqueReqeusters(hits), _handleTOReceived);

			if (settings.notifications) {
				// Set wasViewed to false to check if any receiving windows were focused when this was sent.
				wasViewed = false;

				// Send Hits
				sendMessage({ header: SEND_HITS, content: { 'title': watcher.name, 'hits': hits, 'url': watcher.url } });

				// Get TO and send it
				if (toData)
					sendMessage({ header: SEND_TO, content: toData });

				// Attempt to send a browser notification after a brief period of time. If another mturk
				// page was visible when it received the hits, this will cancel out.
				if (!document.hasFocus())
					setTimeout(function() { sendDesktopNotification(hits, watcher); }, 200);
			}

			// Show notification on dashboard, too
			notificationGroup = notificationPanel.add(new NotificationGroup({ title: watcher.name, hits: hits, url: watcher.url }));
			notificationGroup.addTO(toData);

			// Sound alert for auto-accept HIT watchers and watchers that have the alert set on
			if (watcher.option.auto || watcher.option.alert)
				Sound.alert(watcher);

		} else {
			if (watcher.option.auto && !watcher.option.stopOnCatch && !watcher.option.stopGroup)
				Sound.alert(watcher);
		}
	}

	function _handleTOReceived(data) {
		// console.log("TurkOpticon data", JSON.stringify(JSON.parse(data), null, 4));
		sendMessage({ header: SEND_TO, content: data });

		if (data) 
			notificationGroup.addTO(data);
	}

	return {
		sendHits: _sendHits
	}

}();


/** Watcher Stack and Queue
	Stack - Grab as many as possible right away
		Limit - The number of HITs to stack at once
			Stop - Stop after the limit is reached
			Queue - Start queing after the limit is reached
			
	Queue - Grab one at a time, paced about as fast as they can be done
**/


// Loader. This is what loads pages in the background. Page requests get added to a queue
// so we can load pages in moderation to avoid exceeding the maximum request rate.
// 
// Public methods:
// - load(watcher, url, callback) is the only "public" method. The callback receives the data from
//	 the requested page.

var Loader = function() {
	var queue        = [],
		pauseTime    = 2000,	// The amount of time to pause (in milliseconds)
		intervalTime = 200,	// The amount of time between page loads
		count        = 0,
		paused       = true,
		maxLoad      = 6;	// The max number of pages to load without pausing

	function _load(watcher, url, callback) {
		if (!_isQueued(watcher)) {
			queue.push({url: url, callback: callback, watcher: watcher});

			// If queue length is now 1 and was paused, it means we should resume loading
			if (queue.length === 1 && paused) {
				paused = false;
				_next();
			}
		}
	}
	
	// Checks to see if the watcher is already queued
	function _isQueued(watcher) {
		if (queue.length > 0) {
			for (var i = 0, len = queue.length; i < len; i++)
				if (queue[i].watcher === watcher)
					return true;
		}
		return false;
	}

	// GETs thet next URL in the queue
	function _next() {
		if (queue.length > 0) {
			var info = queue.shift();
			_getData(info.url, info.callback);
		} else {
			paused = true;
		}
	}

	function _getData(url, callback) {
		$.get(url, function(data) {
			callback(data);

			if (++count < maxLoad) {
				setTimeout(_next, intervalTime);
			} else {
				paused = true;
				count = 0;
				setTimeout(function() {
					if (paused)
						_next();
				}, pauseTime);
			}
		})
	}

	return {
		load: _load
	}
}();

var TO = function() {
	var URL_PREFIX = "https://turkopticon.ucsd.edu/api/multi-attrs.php?ids=",
		cache = {};

	setInterval(function() { cache = {}; }, 3600000); // Clear cache once per hour

	function _get(ids, callback) {
		var results = _getFromCache(ids);

		// If not all requesters found in storage, fetch from server
		if (results.missing.length > 0)
			_fetchFromServer(URL_PREFIX + results.missing.join(','), callback);

		return JSON.stringify(results.found);
	}

	function _getFromCache(ids) {
		var sorted = { found: {}, missing: [] };

		for (var i = 0, len = ids.length; i < len; i++) {
			if (cache[ids[i]])
				sorted.found[ids[i]] = cache[ids[i]];
			else
				sorted.missing.push(ids[i]);
		}

		return sorted;
	}

	function _fetchFromServer(url, callback) {
		$.get(url, function(data) {
			_cache(data);

			if (typeof callback === 'function')
				callback(data);
		})
	}

	function _getCount(obj) {
		var count = 0;

		for (key in obj)
			count++;
	}

	function _cache(data) {
		var ratings = JSON.parse(data);

		for (id in ratings) {
			cache[id] = ratings[id];
		}
	}

	return {
		get: _get
	}
}();

/** The NotificationPanel object. This holds and manipulates incoming notification groups

**/
function NotificationPanel() {
	this.isHidden      = true;
	this.notifications = new Array();
	this.createPanel();
	this.isHovered     = false;
	this.timeout       = null;
}
NotificationPanel.prototype.add = function(notification) {
	var _this = this;
	
	// Get rid of the leftover notification if there's one there
	if (this.notifications.length > 0 && this.notifications[0].hasTimedOut && !this.notifications[0].isHovered) {
		var oldNotification = this.notifications[0];
		setTimeout(function() { _this.onTimeoutListener(oldNotification);}, 1500);
	}

	// Cancel delayed timeout from mouseout (so panel won't close right after a new
	// notification comes in)
	clearTimeout(this.timeout);

	notification.onTimeout = function() { _this.onTimeoutListener(notification) };
	this.notifications.push(notification);
	this.addToPanel(notification);

	if (this.isHidden) {
		this.show();
	}

	return notification;
}

NotificationPanel.prototype.remove = function(notification) {
	// Don't remove the notification if the user has their mouse hovering over it.
	// The notification will trigger onTimeout later on mouseout which will call
	// this method again for removal.
	if (!notification.isHovered) {
		this.removeFromPanel(notification);

		var newArray = new Array();
		for (var i = 0, len = this.notifications.length; i < len; i++)
			if (this.notifications[i] !== notification)
				newArray.push(this.notifications[i]);
				
		this.notifications = newArray;
	}
}
NotificationPanel.prototype.show = function() {
	if (this.isHidden) {
		this.getDOMElement().removeClass("tm-hidden");
		this.isHidden = false;
	}
}
NotificationPanel.prototype.hide = function() {
	if (!this.isHidden && !this.isHovered) {
		this.getDOMElement().addClass("tm-hidden");
		this.isHidden = true;
	}
}
NotificationPanel.prototype.createPanel = function() {
	var _this = this;
	var panel =	$('<div class="notification_panel tm-hidden" id="receiver"></div>')
			.hover(
				function() {
					clearTimeout(this.timeout);
					_this.isHovered = true;
					_this.show();
				},
				function(){ 
					_this.isHovered = false;
					this.timeout = setTimeout(function() { _this.hide() }, 1500); // Delay hiding the panel
				}
			);

	$("body").append(panel);
	
	this.DOMElement = panel;

	addStyle("\
		.notification_panel div, .notification_panel p { font: " + settings.fontSize + "pt 'Oxygen', verdana, sans-serif; }\
		#receiver.notification_panel { \
			position      : fixed;\
			width         : 400px;\
			bottom        : 0px;\
			right         : 0px;\
			background    : rgba(255, 255, 255, 1);\
			padding       : 5px;\
			border        : 1px solid #d5d5d5;\
			border-size   : 1px 0 0 1px;\
			overflow      : auto;\
			border-radius :  5px 0 0 0;\
			border-right  : 0;\
			transition    : right 0.2s;\
		}\
		#receiver.notification_panel.tm-hidden {\
			right: -395px;\
		}\
		#receiver .notification_group {\
			background : #fdfdfd;\
			border     : 1px solid #eaeaea;\
			padding    : 5px;\
			margin     : 10px 0;\
			opacity    : 1;\
			overflow   : hidden;\
			transition : opacity 0.7s, max-height 0.2s ease-in-out 0.7s, margin 0.2s linear 0.7s, padding 0.2s linear 0.7s;\
			border-right-color  : #dedede;\
			border-bottom-color : #dedede;\
		}\
		#receiver .notification_group.removed {\
			opacity    : 0;\
			max-height : 0;\
			padding    : 0;\
			margin     : 0;\
		}\
		#receiver .notification_group h3 { margin: 3px; font-weight: normal }\
		#receiver .notification_group h3 a:link, #receiver .notification_group h3 a:visited { color: #333 }\
		#receiver .notification_group h4 a:link,\
		#receiver .notification_group h4 a:visited { margin: 2px 0 0 4px; color: #222; }\
		.notification_panel h2, #details_panel h2 { font-size: 100%; font-weight: normal; margin: 0px }\
		.notification {\
			padding          : 5px 3px 0 5px;\
			background-color : #fff;\
			border-bottom    : 1px solid #e9e9e9;\
			position         : relative;\
			margin-left      : 5px;\
		}\
		.notification:last-child { border: none; padding-bottom: 3px }\
		.notification .mute {\
			position  : absolute;\
			bottom    : 6px;\
			right     : 5px;\
			color     : #999;\
			cursor    : pointer;\
			font-size : 76%;\
		}\
		.notification a.requester:link, .notification a.requester:visited {\
			margin-top  : 2px;\
			color       : black;\
			font-size   : 80%;\
			font-weight : bold;\
		}\
		#details_panel .notification.ignored {\
			opacity: 0.4;\
		}\
		#receiver .notification.ignored {\
			display: none;\
		}\
		.notification .ignore {\
			font-size: 60%;\
			color: #999;\
			visibility: hidden;\
			cursor: pointer;\
		}\
		.notification:hover .ignore {\
			visibility: visible;\
		}\
		.notification .extra_info {\
			font-style : italic;\
			font-size  : 80%;\
			color      : #505050;\
			cursor     : default;\
		}\
		.notification_panel a:link, .notification_panel a:visited {\
			text-decoration : none;\
			color           : #6bf;\
		}\
		.notification_panel a.title:link, .notification_panel a.title:visited {\
			display       : block;\
			white-space   : nowrap;\
			overflow      : hidden;\
			text-overflow : ellipsis;\
			font-size     : 102%;\
		}\
		.notification_panel .links {\
			position : absolute;\
			bottom   : 6px;\
			right    : 35px;\
		}\
		.notification_panel a.hit_link {\
			font-size     : 70%;\
			color         : #fff;\
			background    : none repeat scroll 0% 0% #55B8EA;\
			border-radius : 12px;\
			display       : inline;\
			margin        : 10px 5px 0px 0px;\
			padding       : 3px 9px;\
			font-weight   : bold;\
			transition    : background-color 0.25s;\
		}\
		.notification_panel a.hit_link:visited { background-color: #9df; }\
		.notification_panel a.hit_link:hover { background: #8df; }\
		.notification_panel p {	margin: 3px 0 6px 0; font-size: 80%; cursor: default }\
		.notification_panel .autoaccept {\
			background-color : rgba(148, 236, 255, .3);\
			background-color : rgba(214, 255, 91, 1);\
			background-color : rgba(252, 255, 143, 1);\
		}\
		.notification.not_qualified { background-color: rgba(245, 244, 229, 1) }\
		.notification_panel .new { background-color: rgba(220, 255, 228, 1); }\
		.notification_panel .ratings-button {\
			float: left;\
			margin-right: 0.3em;\
			height: 0.7em;\
			width: 0.7em;\
			background-color: #93C9FF;\
			border-radius: 3px;\
			font-size: 80%;\
			position: relative;\
			top: 0.6em;\
		}\
		.notification_panel .ratings.no-TO .ratings-button {\
			background-color: #ccc;\
		}\
		.notification_panel .ratings-button > .ratings-chart {\
			position: absolute;\
			bottom: -3em;\
			left: 0.4em;\
			background-color: rgb(255, 255, 255);\
			color: #444;\
			visibility: hidden;\
			padding: 0.3em;\
			border: 1px solid #f0f0f0;\
			z-index: 100;\
		}\
		.notification_panel .ratings-button:hover > .ratings-chart { visibility: visible; }\
		.notification_panel .ratings.no-TO .ratings-button > .ratings-chart { bottom: -1em; }\
		.notification_panel .ratings-chart table { border-collapse: collapse; }\
		.notification_panel .ratings-chart td { font-family: 'Oxygen',verdana,sans-serif; font-size: 70%; color: #444; padding: 0 2em 0 0; cursor: default; vertical-align: center }\
		.notification_panel .ratings-chart td:nth-child(3) { font-family: 'Droid Sans Mono',fixed-width; font-size: 60%; white-space: nowrap }\
		.notification_panel .ratings-chart p { font-size: 80%; padding: 0 2em 0 0; margin: 0.5em 0 0; white-space: nowrap }\
		.notification_panel .ratings-chart .light { opacity: 0.6 }\
		.notification_panel .ratings.no-TO .ratings-chart { padding: 0.5em }\
		.notification_panel .ratings-chart .rating { padding: 0.3em 0 0 }\
		.notification_panel .ratings-chart .rating > div { background-color: #eee; height: 0.5em; width: 13em; margin: 0 0.5em 0 0; border-radius: 4px }\
		.notification_panel .ratings-chart .rating > div > div { background-color: #55B8EA; height: 100%; border-radius: 4px }\
		");
}
NotificationPanel.prototype.getDOMElement = function() {
	return this.DOMElement;
}
NotificationPanel.prototype.addToPanel = function(notification) {
	$(this.getDOMElement()).prepend(notification.getDOMElement());
}
NotificationPanel.prototype.removeFromPanel = function(notification) {
	$(notification.getDOMElement()).remove();
}
NotificationPanel.prototype.onTimeoutListener = function(notification) {
	if (this.notifications.length > 1) {
		var _this = this;
		if (document.hasFocus() && settings.animation) {
			notification.fadeOut();
			setTimeout(function() { _this.remove(notification) }, 905);
		} else {
			setTimeout(function() { _this.remove(notification) }, 705);
		}
	} else {
		this.hide();
	}
}


/** The NotificationGroup object. This holds groups of Notifications and interacts 
	directly with the NotificationPanel
**/
function NotificationGroup(obj) { // title, hits, isSticky, watcher, url
	this.title       = obj.title || null;
	this.hits        = obj.hits;
	this.isSticky    = (typeof obj.isSticky !== 'undefined') ? obj.isSticky : this.hasAutoAccept();
	this.url         = obj.url;
	this.timeout     = (this.isSticky) ? 15000 : 6000;
	this.hasTimedOut = false;
	this.isHovered   = false;

	if (typeof obj.watcher !== 'undefined') this.watcher = obj.watcher;
	
	var _this = this;
	setTimeout(function() {
		if (typeof _this.onTimeout !== 'undefined' && _this.onTimeout !== null) {
			_this.hasTimedOut = true;

			if (!_this.isHovered)
				_this.onTimeout(_this);
		}
	}, this.timeout);

	if (typeof this.hits[0] === 'undefined')
		console.error("Error, no hits for notification", document.URL, obj);

	this.createDOMElement();
}
NotificationGroup.prototype.addTO = function(data) {
	if (data !== "{}") {
		var ratings = JSON.parse(data);
		var group = this.getDOMElement();

		var notifications = group.find(".notification"),
			singleRequester = group.find("h4 .requester");

		if (singleRequester.length > 0) {
			var id;
			for (var i in ratings)
				id = i;

			this.appendRatings({ notification: singleRequester.parent(), id: id, ratings: ratings[id] });
		}

		for (var id in ratings) {
			currentNotification = notifications.filter(function() { return $(this).data("requesterID") === id });
			this.appendRatings({ notification: currentNotification, id: id, ratings: ratings[id] });
		}
	}
}
NotificationGroup.prototype.appendRatings = function(obj) {
	var notification = obj.notification,
		requesterID  = obj.id,
		ratings      = obj.ratings,
		attrs        = ratings.attrs,
		requesterEl  = notification.find(".requester");

	// Would be nice to have a chart-looking icon
	var element = $('<div class="ratings"><div class="ratings-button" style="float: left"><div class="ratings-chart"></div></div></div>');

	if (ratings) {
		var html = '\
				<table><tbody>\
					<tr><td>Communicativity</td><td class="rating">' + bar(attrs.comm) + '</td><td class="light">' + attrs.comm + ' / 5</td></tr>\
					<tr><td>Pay</td>            <td class="rating">' + bar(attrs.pay ) + '</td><td class="light">' + attrs.pay  + ' / 5</td></tr>\
					<tr><td>Fairness</td>       <td class="rating">' + bar(attrs.fair) + '</td><td class="light">' + attrs.fair + ' / 5</td></tr>\
					<tr><td>Quickness</td>      <td class="rating">' + bar(attrs.fast) + '</td><td class="light">' + attrs.fast + ' / 5</td></tr>\
				</tbody></table>';

		var count = ratings.reviews;

		html +=	'<p>Scores based on <a href="http://turkopticon.ucsd.edu/' + requesterID + '" target="_blank">' + count + ' review' + ((count !== 1) ? "s" : "") + '</a>';
		html += ' <span class="light">(' + ratings.tos_flags + ' TOS violation' + ((ratings.tos_flags !== 1) ? "s" : "") + ')</light> - <a href="http://turkopticon.ucsd.edu/report?requester[amzn_id]=' + requesterID + '&requester[amzn_name]=' + ratings.name +'" target="_blank">Add review</a></p>';

		element.find(".ratings-chart").append(html);
		element.find(".ratings-button").css('background-color', getHsl(avg(attrs) / 5 * 100));
	} else {
		var html = '<p>No ratings available.</p>';
		html += '<p>Be the first to <a href="http://turkopticon.ucsd.edu/report?requester[amzn_id]=' + requesterID + '&requester[amzn_name]=' + ratings.name +'" target="_blank">review this requester</a>.';

		element.find(".ratings-chart").append(html);
		element.addClass("no-TO");
	}

	function bar(rating) {
		var percent = rating / 5 * 100,
			color = getHsl(percent);

		return '<div><div style="width: ' + percent + '%; background-color: ' + color + '">&nbsp;</div></div>';
	}

	function getHsl(percent) {
		var hue = ((percent / 100 * 5) - 1) / 4  * 100; // Max hue = 100 (green)
		return 'hsl(' + hue + ', 78%, 50%)';
	}

	function avg(attrs) {
		var count = 0,
			sum   = 0,
			comm  = parseFloat(attrs.comm, 10),
			pay   = parseFloat(attrs.pay, 10),
			fast  = parseFloat(attrs.fast, 10),
			fair  = parseFloat(attrs.fair, 10);

		if (comm !== 0) { sum += comm; count ++; }
		if (pay  !== 0) { sum += pay;  count ++; }
		if (fast !== 0) { sum += fast; count ++; }
		if (fair !== 0) { sum += fair; count ++; }

		return sum / count;
	}
	
	requesterEl.before(element);
}
NotificationGroup.prototype.createDOMElement = function() {
	var _this = this,
		REQUESTER_PREFIX = "https://www.mturk.com/mturk/searchbar?selectedSearchType=hitgroups&requesterId=",
		hit = this.hits[0],
		isSameReq = Hit.isSameRequester(this.hits),
		self = this;

	var div = $('<div>').addClass("notification_group")
		.append((this.title !== null) ? $('<h3><a href="' + this.url + '" target="_blank">' + this.title + '</a></h3>') : "")
		.append((isSameReq) ? $('<h4><a href="' + REQUESTER_PREFIX + hit.requesterID + '" target="_blank" class="requester">' + hit.requester + '</a></h4>') : "")
		.hover(
			function() { _this.isHovered = true },
			function() {
				_this.isHovered = false;

				if (_this.hasTimedOut && typeof _this.onTimeout === 'function')
					_this.onTimeout(_this);
			}
		);

	// Sort the notifications (ignored go to the bottom)
	if (pageType.DASHBOARD && pageType.MAIN)
		this.hits.sort(function(a, b) { return (IgnoreList.isIgnored(a.requester)) ? 1 : 0 });
	
	// Add the notifications
	for (var i = 0, len = this.hits.length; i < len; i++) {
		var notification = new NotificationHit(this.hits[i], isSameReq, (typeof this.watcher !== 'undefined') ? this.watcher : null);

		notification.onIgnore = function(requesterID) {
			// Remove all notifications within the group that match the requester ID
			var notifications = self.DOMElement.find(".notification");
			notifications.filter(function() { return $(this).data("requesterID") === requesterID }).addClass("ignored");
		};

		notification.onUnIgnore = function(requesterID) {
			// Remove all notifications within the group that match the requester ID
			var notifications = self.DOMElement.find(".notification");
			notifications.filter(function() { return $(this).data("requesterID") === requesterID }).removeClass("ignored");
		};

		$(div).append(notification.getDOMElement());
	}
	
	if (this.hits[0].isAutoAccept)
		div.addClass("autoaccept");

	this.DOMElement = div;

	// This is required to get the shrinking effect when notifications are removed
	setTimeout(function() { div.css('max-height', div.height()); }, 1000);
}
NotificationGroup.prototype.getDOMElement = function() {
	return this.DOMElement;
}
NotificationGroup.prototype.hasAutoAccept = function() {
	var hasAutoAccept = false;
	for (var i = 0, len = this.hits.length; i < len; i++)
		if (this.hits[i].isAutoAccept) hasAutoAccept = true;
	return hasAutoAccept;
}
NotificationGroup.prototype.fadeOut = function(duration) {
	this.getDOMElement().addClass("removed");
}

/** The Notification object. This holds the notification data for individual hits

**/
function NotificationHit(hit, isSameReq, watcher) {
	this.hit = hit;
	this.isSameReq = isSameReq;
	if (typeof watcher !== 'undefined') this.watcher = watcher;
	
	this.createDOMElement();
}
NotificationHit.prototype.createDOMElement = function() {
	var URL_PREFIX = "https://www.mturk.com/mturk/searchbar?selectedSearchType=hitgroups&requesterId=";

	// Create notification
	var hit = this.hit;
	var notification = $('<div>').addClass("notification").append(
		'<a class="title" target="_blank" href="' + hit.getURL('preview') + '" title="' + hit.title + '">' + hit.title + '</a>',
		(!this.isSameReq) ? $('<a class="requester" href="' + URL_PREFIX + hit.requesterID + '" target="_blank">' + hit.requester + '</a> <a class="ignore">ignore</a>') : "",
		'<p>' + hit.reward + " - " + hit.available + " rem. - " + hit.time.replace("minutes", "mins") + '</p>\
		 <div class="links"></div>\
		 <div><a class="mute"></a></div>'
	).data("requesterID", hit.requesterID);

	// Add links
	if (typeof hit.id !== 'undefined' && hit.id !== "undefined" && hit.isQualified) {
		if (this.hit.isAutoAccept) {
			$(".links", notification).append('<a class="hit_link" href="' + hit.getURL('view') + '" target="_blank">VIEW</a>');
		} else {
			$(".links", notification).append('\
				<a class="hit_link" target="_blank" href="' + hit.getURL('preview') + '">PREVIEW</a>\
				<a class="hit_link" target="_blank" href="' + hit.getURL('accept') + '">ACCEPT</a>\
				<a class="hit_link" target="_blank" href="' + hit.getURL('auto') + '">+AUTO</a>');
		}
	} else {
		$(notification).addClass("not_qualified");
		$(".links", notification).append(
			(hit.canPreview) ? '<a class="hit_link" href="' + hit.getURL('preview') + '" target="_blank">PREVIEW</a>' : "",
			'<span class="extra_info">Not Qualified&nbsp;&nbsp;</span>');
	}

	if (IgnoreList.isIgnored(hit.requester))
		notification.addClass("ignored");
	
	
	var id = hit.id;
	var muteButton = $('a.mute', notification);
	
	$(muteButton).text((typeof IgnoreList !== 'undefined' && IgnoreList.isMuted(id)) ? "muted" : "mute");
	$(muteButton).click(function () {
		if (!pageType.DASHBOARD || (pageType.DASHBOARD && !pageType.MAIN)) {
			if ($(this).text() === "mute")
				sendMessage({ header: "mute_hit", content: { id: id }, timestamp: true });
			else
				sendMessage({ header: "unmute_hit", content: { id: id }, timestamp: true });
		} else {
			if (!IgnoreList.isMuted(id))
				IgnoreList.add(IgnoreList.HIT, id);
			else
				IgnoreList.remove(IgnoreList.HIT, id);
		}

		if ($(this).text() === "mute")
			$(this).text("muted");
		else
			$(this).text("mute");
	});

	var ignoreButton = $("a.ignore", notification);

	var self = this;
	ignoreButton.click(function() {
		if (!pageType.DASHBOARD || (pageType.DASHBOARD && !pageType.MAIN)) {
			sendMessage({ header: "ignore_requester", content: { id: hit.requester } });
		} else {
			if (!notification.hasClass("ignored"))
				IgnoreList.add(IgnoreList.REQUESTER, hit.requester);
			else
				IgnoreList.remove(IgnoreList.REQUESTER, hit.requester);
		}

		if (!notification.hasClass("ignored")) {
			if (self.onIgnore && typeof self.onIgnore === 'function')
				self.onIgnore(hit.requesterID);
		} else {
			if (self.onUnIgnore && typeof self.onUnIgnore === 'function')
				self.onUnIgnore(hit.requesterID);
		}
	});

	
	if (hit.isAutoAccept)
		notification.addClass("autoaccept");

	if  (typeof this.watcher !== 'undefined' && this.watcher !== null && this.watcher.isNewHit(hit))
		$(notification).addClass("new");

	$(notification).append(muteButton);
	
	this.DOMElement = notification;
}
NotificationHit.prototype.getDOMElement = function() {
	return this.DOMElement;
}

var Sound = function() {
	var sound = new Audio(),
		altSound = new Audio();
	
	if (sound.canPlayType('audio/ogg;codecs="vorbis"')) {
		// Sound from http://rpg.hamsterrepublic.com/wiki-images/3/3e/Heal8-Bit.ogg
		sound.src = "data:audio/ogg;base64," + "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";
		sound.volume = settings.volume / 100;
	}

	if (altSound.canPlayType('audio/mp3')) {
		// Sound from http://www.freesfx.co.uk (Multimedia System Alert 003)
		altSound.src = "data:audio/mp3;base64," + "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VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuOf/7WGT/j/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABDdVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuOf/7WGT/j/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABDdVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7WGT/j/AAAGkAAAAIAAANIAAAAQAAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVQ==";
		altSound.volume = settings.volume / 100;
	}

	function _alert(context) {
		if (settings.sound) {
			if (context instanceof Watcher) {	// It's written this way in case we want sounds for other things in the future
				var option = context.option;
				if (!option.stopOnCatch && !option.stopGroup && option.auto) {
					altSound.currentTime = 0;
					altSound.play();
				} else if (option.auto || option.alert) {
					sound.currentTime = 0;
					sound.play();
				}
			}
		}
	}

	function _setVolume(volume) {
		sound.volume = altSound.volume = volume / 100;
	}

	return {
		alert     : _alert,
		setVolume : _setVolume
	}
}();