//---------------------------------------------------------------------
// Begin Utility section
//---------------------------------------------------------------------

//Create YDN Namespace
var YDN = {};

YDN.utils = function() {
	return {
		toggle : function(elem) {
			var rel = elem.getAttribute('rel');
			var relDisplay = $(rel).getStyle('display');
			if ( relDisplay == 'none' || relDisplay === null ) {
					Element.setStyle($(rel), {display: 'block'});
			}
			else {
					Element.setStyle($(rel), {display: 'none'});
			}

		}
	};
}();

//Cookie handler class, taken from http://gorondowtl.sourceforge.net/wiki/Cookie
YDN.utils.Cookie = {
	set: function(name, value, daysToExpire) {

		var expire = '';
		if (daysToExpire != undefined) {
			var d = new Date();
			d.setTime(d.getTime() + (86400000 * parseFloat(daysToExpire)));
			expire = '; expires=' + d.toGMTString();
		}
		return (document.cookie = escape(name) + '=' + escape(value || '') + expire + '; path=/;');
	},
	get: function(name) {
		var cookie = document.cookie.match(new RegExp('(^|;)\\s*' + escape(name) + '=([^;\\s]*)'));
		return (cookie ? unescape(cookie[2]) : null);
	},
	erase: function(name) {
		var cookie = YDN.utils.Cookie.get(name) || true;
		YDN.utils.Cookie.set(name, '', -1);
		return cookie;
	},
	accept: function() {
		if (typeof navigator.cookieEnabled == 'boolean') {
			return navigator.cookieEnabled;
		}
		YDN.utils.Cookie.set('_test', '1');
		return (YDN.utils.Cookie.erase('_test') === '1');
	}
};

//---------------------------------------------------------------------
// End utility section
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// Begin homepage section
//---------------------------------------------------------------------

YDN.HomePage = Class.create({
	Promotion: Class.create({
		initialize : function(id) {
			if ( $(id) ) {
				var elem = $(id);
				elem.observe('mouseover', function() {
					elem.down('p').removeClassName('hidden');
				})
				elem.observe('mouseout', function() {
					elem.down('p').addClassName('hidden');
				})
			}
		}
	}),
	ArchiveBrowser: Class.create({
		initialize : function(id) {
			if ( $(id) ) {
				var elem = $(id);
				var header = elem.select('h2')[0];
				var reset = header.innerHTML;
				elem.select('li').each(function(day) {
					//Switch header to mouseover'd day
					day.observe("mouseover", function() {
						if (day.firstChild.title) {
							header.innerHTML = day.firstChild.title;
						}
					});
					//Reset header to day we're on on mouseout
					day.observe("mouseout", function() {
						header.innerHTML = reset;
					});
				});
			}
		}
	}),
	initialize: function() {
		new this.Promotion('homepagepromotion');
		new this.ArchiveBrowser('archivebrowser');
	}
});

//---------------------------------------------------------------------
// End homepage section
//---------------------------------------------------------------------


//---------------------------------------------------------------------
// Begin Tab Divs section
//---------------------------------------------------------------------

YDN.tabdivs = function() {
	return {
		init : function() {
		$$("div.tab").each (function(tab) {

			var subtabs = Element.childElements(tab);
			var mainbody = Builder.node('div',{className:'mainsubtab'});
			var wrapper = Builder.node('div',{className:'bottomborder'});
			tab.appendChild(mainbody);
			tab.appendChild(wrapper);

			var width = 0;
			var thisdimensions;
			var tabwidth = tab.getWidth();
			var height = 0;
			var width = 0;
			var tmpTabDiv;
			for (i = 0; i < subtabs.length; i++){
						tmpTabDiv = new YDN.tabdivs.switcher(subtabs[i], i);
						thisdimensions = subtabs[i].down('h3.subtabtitle').getDimensions();
						if (tabwidth > width + thisdimensions.width){
							wrapper.appendChild(subtabs[i]);
							subtabs[i].setStyle({
								left: width+'px'
							});
							width += thisdimensions.width;
						}
						else {
							subtabs[i-1].setStyle({
								width: (tabwidth - width + subtabs[i-1].getWidth())+'px'
								});

							width = 0;
							height += 27;
							wrapper = Builder.node('div',{className:'bottomborder'});
							tab.appendChild(wrapper);
							wrapper.appendChild(subtabs[i]);
							subtabs[i].setStyle({
								left: width+'px'
								});
							width += thisdimensions.width;
							$(wrapper).setStyle({
								top: height+'px'
								});
							}
					}
				if (tab.identify() != null){
					tmpTabDiv.getCookie(tab.identify());

				}

				mainbody.style.paddingTop = (height+27)+'px';
				//Make the tab box visible now that it's been assembled
				//We can't use display:none because then dimensions aren't calculated correctly
				tab.style.visibility = 'visible';
			});
		}
	}
}();

YDN.tabdivs.switcher = Class.create({
	initialize: function(subTab, hidden) {
		if (subTab.hasClassName("subtab"))
		{
			var subtabbody = subTab.down('div.subtabbody');
			subtabbody.ydntext = subtabbody.innerHTML;
			subtabbody.innerHTML = '';
			if (hidden==0){
				Element.addClassName(subTab,"active");
				var mainsubtab = subTab.up('div.tab').down('div.mainsubtab');
				mainsubtab.innerHTML = subtabbody.ydntext;
			}
			var textNode = subTab.down("h3.subtabtitle").firstChild;
			var subTabTitle = textNode.nodeValue;
			var link = document.createElement("a");
			link.setAttribute("href","#");
			textNode.parentNode.replaceChild(link, textNode);
			link.appendChild(textNode);
			Event.observe(link,"click",this.switchTab.bindAsEventListener(this));


			var size = subTab.down('h3.subtabtitle').getWidth();
			return size;
		}
		else{
			return subTab.getWidth;
		}
	},
	setCookie : function(id, value) {
		YDN.utils.Cookie.erase(id);
		YDN.utils.Cookie.set(id,value);
	},
	getCookie : function (id) {
		var value = YDN.utils.Cookie.get(id);

		//If the cookie exists and the element exists too
		this.tab = $(id);

		this.elems = this.tab.select('h3.subtabtitle a');
		this.elems.each (function(elem){

			if (elem.innerHTML == value){

				this.changeCSS(elem);
			}
		}.bind(this));


	},
	changeCSS : function(elem) {
		//Elem is the element that was clicked

		Element.removeClassName(elem.up('div.tab').select('.active')[0], 'active');
		Element.addClassName(elem.up('div.subtab'), 'active');
		elem.up('div.tab').down('div.mainsubtab').innerHTML = elem.up('div.subtab').down('div.subtabbody').ydntext;
	},
	switchTab : function(event) {
		this.clickedTab = Event.element(event);
		//Set the cookie
		this.setCookie(this.clickedTab.up('div.tab').identify(), this.clickedTab.innerHTML);

		//Change the CSS
		this.changeCSS(this.clickedTab);

		//Stop the event from firing
		Event.stop(event);
	}
});


//---------------------------------------------------------------------
// Begin Most Popular section
//---------------------------------------------------------------------

YDN.mostpopular = function() {
	return {
		init : function() {
			$$('div.mostpopular').each ( function(item) {
				new YDN.mostpopular.switcher(item);
			});
		}
	};
}();

YDN.mostpopular.switcher = Class.create({
	setCookie : function(value) {
		YDN.utils.Cookie.erase('TabLastClicked');
		YDN.utils.Cookie.set('TabLastClicked',value);
	},
	getCookie : function () {
		var value = YDN.utils.Cookie.get('TabLastClicked');
		//If the cookie exists and the element exists too
		if (value && $(value) ) {
			this.changeCSS($(value));
		}
	},
	changeCSS : function(elem) {
		//Elem is the element that was clicked
		Element.removeClassName(elem.up('div.mostpopular').select('.mpactive')[0], 'mpactive');
		Element.addClassName(elem.up('div.group'), 'mpactive');
	},
	switchTab : function(event) {
		this.clickedTab = Event.element(event);

		//Set the cookie
		this.setCookie(this.clickedTab.id);

		//Change the CSS
		this.changeCSS(this.clickedTab);

		//Stop the event from firing
		Event.stop(event);
	},
	initialize: function(item) {
		this.item = item;

		// Opera 9 can't handle getElementsBySelector correctly, so I'm
		// forced into this convoluted method to drill down to the
		// 'div.group h3 a' element.
		this.groups = this.item.select('div.group');
		this.groups.each ( function(g) {
			var a = g.select('h3')[0].firstDescendant();
			Event.observe(a, 'click', this.switchTab.bindAsEventListener(this));
		}, this);

		//Get the cookie, and if it's set, switch the tab to the right one
		this.getCookie();
	}
});
//---------------------------------------------------------------------
// End Most Popular section
//---------------------------------------------------------------------


// Comment form
YDN.comments = function() {
	return {
		attachAnonConfirmButtons : function() {
			if ( $('commentFormHiddenControls') ) {
				Event.observe('continueAnonymous', 'click', function(event) {
					Element.toggle('commentFormHiddenControls');
					$('commentFormHidden').removeClassName('hidden');
					Event.stop(event);
				});
				Event.observe('registerAccount', 'click', function(event) {
					window.location.pathname = "/users/register/";
					Event.stop(event);
				});
			}
		},
		init : function() {
			YDN.comments.attachAnonConfirmButtons();
		}
	};
}();



//---------------------------------------------------------------------
// Begin YDN article tools
//---------------------------------------------------------------------

YDN.articles = function() {
	return {
		init : function() {
			//Create new Font Sizer
			new YDN.articles.FontSizer();

			//Create new Share Button
			new YDN.articles.ShareButton();
		}
	};
}();

YDN.articles.ShareButton = Class.create({
	switchButton : function(event) {
		var shareListStyle = Element.getStyle(this.list, 'display');
		if ( shareListStyle == 'none' || shareListStyle == null ) {
			Element.setStyle(this.button, {backgroundPosition: '0 -198px'});
		} else {
			Element.setStyle(this.button, {backgroundPosition: '0 -148px'});
		}
		YDN.utils.toggle(this.button);
		Event.stop(event);
	},
	initialize: function() {
		if ($('sharelist') && $('sharebutton')) {
			this.list = $('sharelist');
			this.button = $('sharebutton');

			Event.observe(this.button, 'click', this.switchButton.bindAsEventListener(this));
		}
	}
});

YDN.articles.FontSizer = Class.create({
	initialize: function() {
		//Get and store the default size
		if ( $('storybody') ) {
			//Set max and min values
			this.max = 50;
			this.min = 8;

			this.body = $('storybody');
			this.defaultSize = parseInt(this.body.getStyle('fontSize'),10);
			this.defaultHeight = parseInt(this.body.getStyle('lineHeight'),10);

			//If we have a cookied size, use that
			var cookiedSize = YDN.utils.Cookie.get('fontsize');
			if ( cookiedSize ) {
				this.changeSize(cookiedSize);
			}

			//Attach to buttons
			if ( $('fontsizer') ) {
				Event.observe('font_revert', 'click', this.revert.bindAsEventListener(this));
				Event.observe('font_up', 'click', this.up.bindAsEventListener(this));
				Event.observe('font_down', 'click', this.down.bindAsEventListener(this));
			}
		}
	},
	up : function(event) {
		this.changeSize(this.getSize()+1);
		Event.stop(event);
	},
	down : function(event) {
		this.changeSize(this.getSize()-1);
		Event.stop(event);
	},
	revert: function(event) {
		this.changeSize(this.defaultSize);
		Event.stop(event);
	},
	getSize : function() {
		return parseInt(this.body.getStyle('fontSize'),10);
	},
	isValid : function(value) {
		//Make sure we're between min and max
		return ( value >= this.min && value <= this.max ) ? true : false;
	},
	changeSize : function(newSize) {
		if ( this.isValid(newSize) ) {
			var newLineHeight = parseInt((newSize-this.defaultSize)+this.defaultHeight,10);
			this.body.setStyle({fontSize: newSize + 'px', lineHeight: newLineHeight + 'px'});
			YDN.utils.Cookie.erase('fontsize');
			YDN.utils.Cookie.set('fontsize', newSize, 14);
		}
	}
});

//---------------------------------------------------------------------
// End YDN article tools
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// Begin headline scroller - based on http://digitalhymn.com/argilla/ascroller/
//---------------------------------------------------------------------

YDN.HeadlineScroller = Class.create({
	initialize: function(id, speed) {
		//If the id doesn't exist, don't do anything
		if ( !$(id) ) {
			return false;
		}

		//Set the variables that are passed in to be instance variables
		this.id = id;
		this.speed = speed;

		this.scroller = $(this.id);

		//Set styles
		Element.setStyle(this.scroller, {position: 'relative', overflow: 'hidden'});

		//Create inner scrolling div
		this.scroller.innerHTML = "<div id=\"" + id + "_inner\">" + this.scroller.innerHTML + "</div>";

		//Set style of inner
		this.inner = $(id + "_inner");
		this.scrollerDimensions = this.scroller.getDimensions();
		Element.setStyle(this.inner, {position: 'absolute', left: "0px", top: "0px", width: this.scrollerDimensions.width});

		//Calculate the height limit
		this.limit = this.inner.select('div').length * parseInt(this.scrollerDimensions.height,10);

		//Show the scroller
		Element.setStyle(this.scroller, {display: 'block'});

		//We need to wrap this in an anonymous function for scoping reasons
		new PeriodicalExecuter(this.doScroll.bind(this), this.speed);

		//To avoid errors about not returning
		return true;
	},
	doScroll : function() {
		if (parseInt(this.inner.getStyle('top'),10) <= -(this.limit-this.scrollerDimensions.height) ) {
			Element.setStyle(this.inner, {top: this.scrollerDimensions.height + 'px' });
		}
		new Effect.Move (this.inner,{ y: -this.scrollerDimensions.height, mode: 'relative'});
	}
});
//---------------------------------------------------------------------
// End headline scroller
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// Begin scroller widget
//---------------------------------------------------------------------
YDN.ScrollWidgets = function() {
	return {
		widgetArray : new Array,
		init : function () {
			var i = 0;
			$$('div.scrollwidget').each ( function(widget) {
				YDN.ScrollWidgets.widgetArray[i] = new YDN.ScrollWidgets.widget(i, widget);
				i++;
			});
		}
	};
}();
YDN.ScrollWidgets.widget = Class.create({
	// initialize object
	initialize: function(index, widget) {
		this.ready = true;
		this.index = index;
		this.widget = widget;
		this.list = this.widget.select('ul')[0];
		// get total width of visible items
		this.visibleWidth = this.widget.select('div.scrollmask')[0].getStyle('width').sub('px', '');
		// not pretty, but opera reports the length of scrollItems incorrectly if you just use 'ul.scrolllist li a'
		this.scrollItems = this.widget.select('ul.scrolllist')[0].immediateDescendants();
		// set movement step and margin and adjust values
		// -- This should eventually be turned into code that gets the
		//    width of the items and the margin from the DOM.
		this.big = Element.hasClassName(this.widget, 'bigblocks');
		if (this.big) {
			this.step = 100;
			this.margin = 10;
			this.adjust = 0;
		} else {
			this.step = 82;
			this.margin = 7;
			this.adjust = 0;
		}
		this.left = 0;
		// set left and right limits
		this.leftEdge = this.left;
		this.rightEdge = (-1 * ((this.scrollItems.length * this.step) - this.visibleWidth - this.margin));
		//$$('div#masthead p')[0].innerHTML = this.rightEdge + " = -1 * ((" + this.scrollItems.length + " * " + this.step + ") - " + this.visibleWidth + " - " + this.margin + "))";
		// get the height of the items
		this.height = (Element.getHeight(this.widget)) + 'px';
		this.iHeight = (this.height.sub('px', '') - this.adjust) + 'px';
		this.controls = this.widget.select('div.scrollcontrol a');
		// set the height of the controls to match the height of the items
		for (var i=0; i < this.controls.length; i++) {
			this.controls[i].setStyle({height: this.height});
			// controls are invisible by default
			this.controls[i].setStyle({visibility: 'visible'});
			// bind click event to control
			Event.observe(this.controls[i], 'click', this.scroll.bindAsEventListener(this, this.index));
		}
		// set height of all items to match the tallest item
		for (var i=0; i < this.scrollItems.length; i++) {
			this.scrollItems[i].setStyle({height: this.iHeight});
		}
		this.setButtonStatus(this);
	},
	// scroll left or right
	scroll: function(event, i) {
		if (YDN.ScrollWidgets.widgetArray[i].ready) {
			var distance = YDN.ScrollWidgets.widgetArray[i].step;
			// don't let it scroll past the limits
			if (Event.element(event).hasClassName('scrollleft')) {
				if (YDN.ScrollWidgets.widgetArray[i].left + distance > YDN.ScrollWidgets.widgetArray[i].leftEdge) {
					distance = 0;
				}
			} else {
				distance = -1 * distance;
				if (YDN.ScrollWidgets.widgetArray[i].left + distance < YDN.ScrollWidgets.widgetArray[i].rightEdge) {
					distance = 0;
				}
			}
			YDN.ScrollWidgets.widgetArray[i].left = YDN.ScrollWidgets.widgetArray[i].left + distance;
			YDN.ScrollWidgets.widgetArray[i].setButtonStatus(YDN.ScrollWidgets.widgetArray[i]);
			function beginMoving(obj) {
				YDN.ScrollWidgets.widgetArray[i].ready = false;
			}
			function finishMoving(obj) {
				YDN.ScrollWidgets.widgetArray[i].ready = true;
			}
			new Effect.Move(YDN.ScrollWidgets.widgetArray[i].list, {transition: Effect.Transitions.sinoidal, duration: 0.25, x: distance, y: 0, mode: 'relative', beforeStart: beginMoving, afterFinish: finishMoving});
			//YDN.ScrollWidgets.widgetArray[i].list.setStyle({marginLeft: YDN.ScrollWidgets.widgetArray[i].left + 'px'});
		}
		Event.stop(event);
	},
	// enable/disable scroll buttons
	setButtonStatus: function(widget) {
		if (widget.left >= widget.leftEdge) {
			widget.controls[0].setStyle({opacity: 0.25});
		} else {
			widget.controls[0].setStyle({opacity: 1});
		}
		// opera thiks widget.controls also includes the scrolllist and all its child elements
		if (Prototype.Browser.Opera) {
			var j = widget.controls.length - 1;
		} else {
			var j = 1;
		}
		if (widget.left <= widget.rightEdge) {
			widget.controls[j].setStyle({opacity: 0.25});
		} else {
			widget.controls[j].setStyle({opacity: 1});
		}
	}
});
//---------------------------------------------------------------------
// End scroller widget
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// Begin video thumbs
//---------------------------------------------------------------------
YDN.media = YDN.media || {};
YDN.media.VideoHeadlines = function() {
	return {
		currentThumb : null,
		aVids : null,
		init : function() {
		if ($('videolist')) {
			// create array of videothumbs
			YDN.media.VideoHeadlines.aVids = new Array;
			for (var i=0; i < $('videolist').select('a').length; i++) {
				YDN.media.VideoHeadlines.aVids[i] = new YDN.media.VideoHeadlines.Thumb(i+1);
			}
			// turn them all off by default
			YDN.media.VideoHeadlines.aVids.each( function(thumb) {
				thumb.unhover();
			});
			// turn on the current one
			YDN.media.VideoHeadlines.aVids[YDN.media.VideoHeadlines.currentThumb - 1].hover();
		}
		}
	};
}();
//Create VideoHeadlines.Thumb class with new Prototype 1.6 syntax!
//No more directly modifying the object's prototype!
YDN.media.VideoHeadlines.Thumb = Class.create({
	// initialize object
	initialize: function(index) {
		this.index = index;
		this.item = $('video' + index);
		this.href = this.item.href;
		this.headline = this.item.select('span')[0];
		this.description = this.item.select('span')[2];
		this.video = this.item.getAttribute('rel');
		Element.hide(this.headline);
		Event.observe(this.item, 'mouseover', this.activate.bindAsEventListener(this));
		Event.observe(this.item, 'mouseout', this.deactivate.bindAsEventListener(this));
		Event.observe(this.item, 'click', this.swap.bindAsEventListener(this));
		this.current = (this.item.classNames() == 'current');
		if (this.current) {
			YDN.media.VideoHeadlines.currentThumb = index;
		}
	},
	// unhover all thumbs then hover the one you're over
	activate: function() {
		YDN.media.VideoHeadlines.aVids.each( function(thumb) {
			thumb.unhover();
		});
		this.hover();
	},
	// unhover if not current then hover current
	deactivate: function() {
		if (!this.current) {
			this.unhover();
			YDN.media.VideoHeadlines.aVids[YDN.media.VideoHeadlines.currentThumb - 1].hover();
		}
	},
	// show description and highlight thumbnail
	hover: function() {
		Element.show(this.headline);
		Element.setStyle(this.item.select('img')[0], {opacity: 1});
	},
	// hide description and subdue thumbnail
	unhover: function() {
		Element.hide(this.headline);
		Element.setStyle(this.item.select('img')[0], {opacity: 0.5});
	},
	// replace existing video with new one
	swap: function(evt) {
		$('bigmovie').innerHTML = '<object width="182" height="151"><param name="movie" value="' + this.video + '"></param><param name="wmode" value="transparent"></param><embed src="' + this.video + '" type="application/x-shockwave-flash" wmode="transparent" width="182" height="151"></embed></object>';
		$('vidheadline').innerHTML = this.headline.innerHTML;
		$('viddescription').innerHTML = this.description.innerHTML;
		$('viddescription').up('a').href = this.href;
		YDN.media.VideoHeadlines.currentThumb = this.index;
		YDN.media.VideoHeadlines.aVids.each( function(thumb) {
			thumb.current = false;
		});
		this.current = true;
		Event.stop(evt);
	}
});

//---------------------------------------------------------------------
// End video thumbs
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// Begin close popup button links
//---------------------------------------------------------------------
YDN.popups = function() {
	return {
		popupLinks : function() {
			var toggleLinks = Element.select(document, '.togglePopup');
			toggleLinks.each(function(thisLink) {
					Event.observe(thisLink, 'click', function(event) {
						YDN.utils.toggle(thisLink);
						Event.stop(event);
					});
			});
		},
		closeWindowLinks : function() {
			//If we're in a popup window
			if ( $('popup') ) {
				Event.observe('closeWindow', 'click', function(event) {
					window.close();
					Event.stop(event);
				});
			}
		},
		init : function() {
			YDN.popups.popupLinks();
			YDN.popups.closeWindowLinks();
		}
	};
}();

//---------------------------------------------------------------------
// End close popup button links
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// Begin User Login/Registration Stuff
//---------------------------------------------------------------------

YDN.users = function() {
	function sha1(msg) {
		// constants [4.2.1]
		var K = [0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6];

		// PREPROCESSING

		msg += String.fromCharCode(0x80); // add trailing '1' bit to string [5.1.1]

		// convert string msg into 512-bit/16-integer blocks arrays of ints [5.2.1]
		var l = Math.ceil(msg.length/4) + 2;  // long enough to contain msg plus 2-word length
		var N = Math.ceil(l/16);              // in N 16-int blocks
		var M = new Array(N);
		for (var i=0; i<N; i++) {
			M[i] = new Array(16);
			for (var j=0; j<16; j++) {  // encode 4 chars per integer, big-endian encoding
				M[i][j] = (msg.charCodeAt(i*64+j*4)<<24) | (msg.charCodeAt(i*64+j*4+1)<<16) |
						  (msg.charCodeAt(i*64+j*4+2)<<8) | (msg.charCodeAt(i*64+j*4+3));
			}
		}
		// add length (in bits) into final pair of 32-bit integers (big-endian) [5.1.1]
		M[N-1][14] = ((msg.length-1) >>> 30) * 8;
		M[N-1][15] = ((msg.length-1)*8) & 0xffffffff;

		// set initial hash value [5.3.1]
		var H0 = 0x67452301;
		var H1 = 0xefcdab89;
		var H2 = 0x98badcfe;
		var H3 = 0x10325476;
		var H4 = 0xc3d2e1f0;

		// HASH COMPUTATION [6.1.2]

		var W = new Array(80); var a, b, c, d, e;
		for (var i=0; i<N; i++) {

			// 1 - prepare message schedule 'W'
			for (var t=0;  t<16; t++) W[t] = M[i][t];
			for (var t=16; t<80; t++) W[t] = sha1helper2(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1);

			// 2 - initialise five working variables a, b, c, d, e with previous hash value
			a = H0; b = H1; c = H2; d = H3; e = H4;

			// 3 - main loop
			for (var t=0; t<80; t++) {
				var s = Math.floor(t/20); // seq for blocks of 'f' functions and 'K' constants
				var T = (sha1helper2(a,5) + sha1helper1(s,b,c,d) + e + K[s] + W[t]) & 0xffffffff;
				e = d;
				d = c;
				c = sha1helper2(b, 30);
				b = a;
				a = T;
			}

			// 4 - compute the new intermediate hash value
			H0 = (H0+a) & 0xffffffff;  // note 'addition modulo 2^32'
			H1 = (H1+b) & 0xffffffff;
			H2 = (H2+c) & 0xffffffff;
			H3 = (H3+d) & 0xffffffff;
			H4 = (H4+e) & 0xffffffff;
		}

		return toHexStr(H0) + toHexStr(H1) + toHexStr(H2) + toHexStr(H3) + toHexStr(H4);
	}
	function toHexStr(num) {
		var s="", v;
		for (var i=7; i>=0; i--) { v = (num>>>(i*4)) & 0xf; s += v.toString(16); }
		return s;
	}
	function sha1helper1(s, x, y, z) {
		switch (s) {
			case 0: return (x & y) ^ (~x & z);
			case 1: return x ^ y ^ z;
			case 2: return (x & y) ^ (x & z) ^ (y & z);
			case 3: return x ^ y ^ z;
		}
	}
	function sha1helper2(x,n) {
		return (x<<n) | (x>>>(32-n));
	}
	function shakePasswords() {
		Element.setStyle($('passwordsError'), {display: 'block'});
		Effect.Shake('register_password');
		Effect.Shake('password_confirmation');
	}
	return {
		randomString : function(len) {
			var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
			var randomstring = '';
			for (var i=0; i<len; i++) {
				var rnum = Math.floor(Math.random() * chars.length);
				randomstring += chars.substring(rnum,rnum+1);
			}
			return randomstring;
		},
		encrypt : function(string) {
			return sha1(string);
		},
		attachToSubmit : function(event) {
			var theForm = Event.element(event);
			var formName = theForm.id;

			//Disable the submit buttons
			var buttons = theForm.select('[type="submit"]');
			buttons.each(function(theButton) {
				Form.Element.disable(theButton);
			});

			if ( formName == 'loginForm' ) {
				var password = $('login_password').value;
				var hash = this.encrypt(this.encrypt(password));
				var fake_pass = this.randomString(password.length);
				$('login_hashed_pw').value = hash;
				$('login_password').value = fake_pass;
			}
			else if ( formName == 'registerForm' || formName == 'editUserForm' ) {
				//Check for blankness only on registerForm, it's ok on editUserForm
				if ( formName == 'registerForm' && $('register_password').value == '' ) {
					shakePasswords();
					Event.stop(event);
				}
				else if ( $('register_password').value != $('password_confirmation').value ) {
					shakePasswords();
					Event.stop(event);
				}
				else {
					$('register_password_real').value = this.encrypt($('register_password').value);
					$('register_password').value = this.randomString($('register_password').value.length);
					$('password_confirmation').value = this.randomString($('password_confirmation').value.length);
				}
			}
		},
		countrySelect : function(event) {
			var selectedValue = Event.element(event).value;
			if ( selectedValue == 'USA' || selectedValue == '' ) {
				Element.show('zipcoderow');
				$('zipcode').value='';
			}
			else {
				Element.hide('zipcoderow');
				$('zipcode').value='06520';
			}
		},
		init : function() {
			if ( $('loginForm') ) {
				Event.observe('loginForm', 'submit', YDN.users.attachToSubmit.bindAsEventListener(YDN.users));
			}
			if ( $('registerForm') ) {
				Event.observe('registerForm', 'submit', YDN.users.attachToSubmit.bindAsEventListener(YDN.users));
			}
			if ( $('editUserForm') ) {
				Event.observe('editUserForm', 'submit', YDN.users.attachToSubmit.bindAsEventListener(YDN.users));
			}
			if ( $('UserCountry') && $('zipcode') ) {
				Event.observe('UserCountry', 'click', YDN.users.countrySelect.bindAsEventListener(YDN.users));
			}
		}
	};
}();

//---------------------------------------------------------------------
// End User Login/Registration Stuff
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// Begin Albums
//---------------------------------------------------------------------

YDN.albums = {};
YDN.albums.albumpreview = function() {
	return {
		attachToLinks : function () {
			var albumPreviewLinks = Element.select(document, '.albumpreviewlink');
			albumPreviewLinks.each(function(thisLink) {
					Event.observe(thisLink, 'click', YDN.albums.albumpreview.openPopup.bindAsEventListener(YDN.albums.albumpreview));
			});
		},
		openPopup : function (event) {
			var theLink = Event.element(event).up(0);
			window.open(theLink.href, '_blank', 'width=700, height=600, scrollbars=yes, resizable=yes');
			Event.stop(event);
		},
		init : function () {
			if ( $('albumpreview') ) {
				YDN.albums.albumpreview.attachToLinks();
			}
		}
	}
}();

//---------------------------------------------------------------------
// End Albums
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// Begin crime map
//---------------------------------------------------------------------

YDN.CrimeMap = Class.create({
	initialize: function(mapId) {
		 //If the map element doesn't exist or the browser isn't compatatible, return!
		if ( !$(mapId) ) {
			return;
		}
		else {
			if ( !GBrowserIsCompatible() ) {
				return;
			}
			else {
				//Setup the map
				this.map = new GMap2($(mapId)); //Reference to the Google Map object
				this.map.addControl(new GSmallMapControl()); //Add zoom controls
				this.map.addControl(new GMapTypeControl()); //Add type (satellite, map, hybrid) controls
				this.map.setCenter(new GLatLng('41.310908', '-72.925966'), 15); //Set map center to New Haven, zoom level 16 is good
				this.manager = new MarkerManager(this.map); //Create the marker manager
				
				//Message display
				this.message = $('crimesMessage');
				
				//Table container div
				this.tableContainer = $('crimesTableDiv');
	
				//Notable events to observe
				document.observe("dom:loaded", this.bootstrap.bindAsEventListener(this)); //Load crimes on DOM load
				Event.observe('crimeMapForm', 'submit', this.bootstrap.bindAsEventListener(this)); //Load crimes when form is submitted
				Event.observe(window, 'unload', GUnload);
			}
		}
	},
	//This function starts up the Ajax request and calls callbacks
	bootstrap: function(event) {
		$('crimeMapForm').request({
			onCreate: this.onCreate.bindAsEventListener(this),
			onComplete: this.onComplete.bindAsEventListener(this)
		});

		//Stop the event unless it's the dom:loaded event
		if ( event.eventName != 'dom:loaded') {
			Event.stop(event);
		} 
	},
	onCreate: function() {
		this.markers = []; //Clear out marker array
		this.manager.clearMarkers(); //Clear the markers from the MarkerManager
		this.map.clearOverlays(); //Clear out the map of existing markers
		
		//Remove the table and add it back
		//Setting this.crimesTbody.innerHTML = '' causes the table to jump down about 10px on every new request
		//So we're forced to this convoluted method
		if ( this.crimesTbody ) {
			this.crimesTbody.parentNode.remove();
		}
		this.crimesTbody = Builder.node('tbody');

		//Message is loading
		this.message.innerHTML = 'Loading...';
	},
	onComplete: function(transport) {
		
		//Set the returned data to an instance variable
		this.retData = transport.responseText.evalJSON();

		//Update message
		this.message.innerHTML = 'Showing markers on map...';
		
		//Show the markers now
		this.showMarkers();
		
		//Update message
		this.message.innerHTML = 'Building results table...';
		
		//Show the crimes table
		this.showCrimesTable();
		
		//Update the count returned
		var s = (this.retData.count == 1) ? '' : 's'
		this.message.innerHTML = '<a href="#crimesTableDiv">' + this.retData.count + ' result' + s + ' returned.</a>';
		//If it's over 100, explain
		if ( parseInt(this.retData.count,10) > 100 ) {
			this.message.innerHTML += '<br /><div id="crimesPerformance">For performance reasons, we only show 100 results on the map and in the table.</div>';
		}
	},
	showMarkers: function() {
		//Get count for the for loop, so it's done once and cached
		var count = this.retData.points.length;
		for(var i = 0; i<count; i++) {
			//Add marker to marker array
			this.markers[i] = new GMarker(new GLatLng(this.retData.points[i].Crime.latitude, this.retData.points[i].Crime.longitude));
			this.markers[i].YDNINFO = this.retData.points[i].Crime;
			GEvent.bind(this.markers[i], "click", this.markers[i], this.showMarkerInfo);
		}
		
		//If we have a building, show that marker
		if ( this.retData.building ) {
			//Create blue icon
			this.blueIcon = this.blueIcon || new GIcon(G_DEFAULT_ICON);
			this.blueIcon.image = "/img/crimemap_building_icon.png";
			
			this.buildingMarker = new GMarker(new GLatLng(this.retData.building.latitude, this.retData.building.longitude), {icon:this.blueIcon});
			this.buildingMarker.YDNBUILDINGINFO = this.retData.building;
			GEvent.bind(this.buildingMarker, "click", this.buildingMarker, this.showMarkerInfo);
			
			//Add building marker
			this.markers.push(this.buildingMarker);
			
			//If the only marker is the building, we have no other markers
			//But we still want to zoom to the building marker
			if ( this.markers.length == 1 ) {
				this.retData.latmax = this.retData.building.latitude;
				this.retData.latmin = this.retData.building.latitude;
				this.retData.longmax = this.retData.building.longitude;
				this.retData.longmin = this.retData.building.longitude;
			}
		}

		if(this.markers.length > 0) {
			// Zoom map so that all points are showing
			// and pan to center
			var max = new GLatLng(this.retData["latmax"],this.retData["longmax"]);
			var min = new GLatLng(this.retData["latmin"],this.retData["longmin"]);
			var bound = new GLatLngBounds(min,max);
			this.map.setZoom(this.map.getBoundsZoomLevel(bound));
			this.map.panTo(bound.getCenter());
		}

		//Create the markers, add them, and refresh the map
		this.manager.addMarkers(this.markers,0);
		this.manager.refresh();
	},
	showCrimesTable: function() {
		var count = this.retData.points.length;
		if ( count > 0 ) {
			for(var i = 0; i<count; i++) {
				//Build the casenumber separately so we can attach a GEvent to it
				var casenumber = Builder.node('td',Builder.node('a',{'href': '#crimeMap'},this.retData.points[i].Crime.casenumber));
				if ( this.markers[i] ) {
					GEvent.bindDom(casenumber, "click", this.markers[i], this.showMarkerInfo);
				}
				

				//Build the row for this crime
				var tr = Builder.node('tr',[
					casenumber,
					Builder.node('td',this.retData.points[i].Crime.crime),
					Builder.node('td',this.retData.points[i].Crime.datetoshow),
					Builder.node('td',this.retData.points[i].Crime.location),
					Builder.node('td',this.retData.points[i].Crime.status)
				]);
				this.crimesTbody.appendChild(tr);
			}
		}
		else {
			var tr = Builder.node('tr',[
				Builder.node('td',{'colSpan': '5'},'No results found.') //colSpan (capital S) capitalization is important for IE
			]);
			this.crimesTbody.appendChild(tr);
		}
			
		//Create the table element
		//Don't use Builder.node - it creates an extra empty <tbody> in IE
		var table = document.createElement('table');
		table.id = 'crimesTable';
		table.className = 'sortable';
		
		//Create header row
		var thead = Builder.node('thead');
		var tr = Builder.node('tr',[
			Builder.node('th','Case Number'),
			Builder.node('th','Type'),
			Builder.node('th',{'className':'sortfirstdesc'},'Date'),
			Builder.node('th','Location'),
			Builder.node('th','Status')
		]);
		thead.appendChild(tr);
		table.appendChild(thead);
		
		table.appendChild(this.crimesTbody);
		this.tableContainer.appendChild(table);
		
		//Make the table sortable
		TableKit.Sortable.init(this.crimesTbody.parentNode);
	},
	//Shows the popup window for a marker when it's clicked
	showMarkerInfo: function() {
		var text = '<div class="crimeBubble">';
		
		if ( this.YDNBUILDINGINFO ) {
			//It's a building marker
			text += '<strong>' + this.YDNBUILDINGINFO.name + '</strong><br />';
			text += this.YDNBUILDINGINFO.address;
		}
		else if (this.YDNINFO) {
			//It's a crime marker
			text += '<strong>Case Number: </strong> ' + this.YDNINFO.casenumber + '<br />';
			text += '<strong>Type: </strong> ' + this.YDNINFO.crime + '<br />';
			text += '<strong>Date: </strong> ' + this.YDNINFO.datetoshow + '<br />';
			text += '<strong>Location: </strong> ' + this.YDNINFO.location + '<br />';
			text += '<strong>Status: </strong> ' + this.YDNINFO.status + '<br />';
		}
		text += '</div>';
		this.openInfoWindow(text);
	}
});

//---------------------------------------------------------------------
// End crime map
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// Begin Initialization Function
//---------------------------------------------------------------------

YDN.init = function init() {

	//Headline scroller
	new YDN.HeadlineScroller("headlinescroller", 4);

	//Homepage promotion
	new YDN.HomePage();

	//Article stuff
	YDN.articles.init();

	//User stuff
	YDN.users.init();

	//Album previews
	YDN.albums.albumpreview.init();

	//Popups and windows
	YDN.popups.init();

	//Tab divs init
	YDN.tabdivs.init();

	//Most popular box
	YDN.mostpopular.init();

	// Comments form
	YDN.comments.init();

	//Video headlines
	YDN.media.VideoHeadlines.init();

	//Scroller widgets
	YDN.ScrollWidgets.init();
	
	//Crime map
	new YDN.CrimeMap('crimeGoogleMap');

}();

//---------------------------------------------------------------------
// End Initialization Function
//---------------------------------------------------------------------