/**
* @projectDescription	Primary Javascript library for Aggregame.com with
* inspirations from the JQuery library.
*
* @author	Ian Paterson
*/

var defaults = {};
var $ = {};
var doOnce = 0;

/**
 * Moves the tooltip to the specified element and updates the text.
 *
 * @param {Object} Tooltip will be aligned with this element
 * @param {String} HTML to display in the tooltip body
 */
function tooltip(o, msg, opt) {
	o = _(o) || $.tooltip_anchor;
	if (!opt) opt = {};
	var t = _('tooltip');

	clearTimeout($.close_tooltip);

	// Hide the tooltip and unbind the mouseout
	if (opt.off) {
		if ($.tooltip_anchor)
			$.tooltip_anchor.onmouseout = function(){}
		t.toggle(0);
		o.toggle(0);
		$.tooltip_anchor = false;
		return;
	}

	$.tooltip_anchor = o;

	if (opt.src_node)
		t.child('c').innerHTML = opt.src_node.innerHTML;
	else
		t.child('c').innerHTML = msg;
	t.toggle();

	// Position in the horizontal middle of the anchor
	var x = o.realX() - t.offsetWidth / 2 + o.offsetWidth / 2;

	// Position above or below the anchor
	if (opt.above)
		t.pos(x, o.realY() - t.offsetHeight);
	else
		t.pos(x, o.realY() + o.offsetHeight + 3);

	// Close in 10 seconds
	$.close_tooltip = setTimeout(function(){tooltip(o,'',{off:1})}, 10000);

	// Close onhover tooltips on anchor mouseout
	if (opt.hover) {
		$.tooltip_anchor.onmouseout = function () {
			$.close_tooltip = setTimeout(function(){tooltip(o,'',{off:1})}, 500);
		}
	}
	// Close onclick tooltips in 10 seconds unless the mouse is on the tooltip
	else {
		t.onmouseover = function() {
			clearTimeout($.close_tooltip);
			$.close_tooltip = false;
		}
		t.onmouseout = function () {
			$.close_tooltip = setTimeout(function(){tooltip(o,'',{off:1})}, 10000);
		}
	}

	// Close the tooltip after clicking anywhere
	document.onmousedown = function() {
		if ($.close_tooltip) {
			tooltip(o,'',{off:1});
		}
	};

	if (opt.callback)
		opt.callback();
}

/**
 * Moves the submenu to the specified element and updates the text.
 *
 * @param {Object} Submenu will be aligned with this element
 * @param {String} HTML to display in the submenu body
 */
function submenu(o, arr, opt) {
	o = _(o);
	var p = o ? _(o.parentNode.parentNode) : {};
	if (!arr) arr = [];
	if (!opt) opt = {};

	var t = _('navTab');
	var s = _('navSub');

	clearTimeout($.close_submenu);
	$.close_submenu = false;

	// Hide the submenu
	if (opt.off) {
		t.toggle(0);
		s.toggle(0);

		return;
	}

	// Add the submenu heading
	t.child('title').innerHTML = '<a href="' + p.child('title').href + '">' + p.child('title').innerHTML + '</a>';

	t.toggle();
	t.pos(p.realX() - t.offsetWidth / 2 + p.offsetWidth / 2, 60);

	// Add each submenu link
	var c = s.child('c').child('content')
	c.innerHTML = '';
	for (i in arr) {
		c.innerHTML += '<a href="' + arr[i][1] + '">' + arr[i][0] + '</a><br />';
	}

	// Close the submenu on click outside of it or within 10 seconds of mouseout
	// of the tab or submenu
	t.onmouseover = s.onmouseover = function() {
		clearTimeout($.close_submenu);
		$.close_submenu = false;
	}
	t.onmouseout = s.onmouseout = function () {
		$.close_submenu = setTimeout("submenu('',[],{off:1})", 10000);
	}
	document.onmousedown = function() {
		if ($.close_submenu)
			submenu('',[],{off:1});
	};

	s.toggle();

	// Reposition once all content is generated
	s.style.width = 'auto';
	if (s.offsetWidth < t.offsetWidth + 10) {
		s.style.width = t.offsetWidth + 10 + 'px';
	}
	s.pos(t.offsetLeft + t.offsetWidth / 2 - s.offsetWidth / 2 , t.offsetTop + 39);
}

/**
 * Converts a JSON object into a name=value query string.
 *
 * @param {Object} A JSON object with query parameters
 * @return The query string
 */
function jsonToQueryString(json) {
	var s = new Array();

	for (i in json) {
		if (json[i] instanceof Array) {
			for (j in json[i])
				s.push(escape(i) + '=' + escape(json[i][j]));
		}
		else
			s.push(escape(i) + '=' + escape(json[i]));
	}

	return s.join('&');
}

/**
 * Extracts script tags from HTML, adds the remaining HTML to the document, then
 * executes the script
 */
function smartInnerHTML(o, html) {
	o = _(o);
	var js = '';

	while (/<script[^>]*?>(.*?)<\/script>/.test(html)) {
		js += RegExp.$1 + ';';
		html = html.replace(/<script[^>]*?>.*?<\/script>/, '');
	}

	if (o.realHTML != html) {
		o.innerHTML = html;
		o.realHTML = html;
		eval(js);
	}
}

/**
 * Highlight the background of a DOMNode in yellow and gradually fade out to
 * white
 */
function emphasize(o, args) {
	o = _(o);
	if (!o) return;
	if (!args) args = {};
	if (!o.id) o.id = 'em_' + Math.round(Math.random() * 99999);
	var id = 'emphasize_' + o.id;

	// Set initial rgb colors
	if (args.r == undefined) {
		args.r = 255;
		args.g = 250;
		args.b = 212;
	}

	// Stops any existing emphasize animation on the node
	if ($[id] && args.force) {
		for (i in $[id].timeouts)
			clearTimeout($[id].timeouts[i]);

		delete $[id];
	}

	if (!$[id] && !args.wait) {
		var rgb = {r:1,g:1,b:1};

		$[id] = {};

		for (x in rgb)
			$[id][x] = args[x];

		$[id].timeouts = new Array();

		var c = (args.off) ? 0 : 40;

		// Set up a drop-frame animation to update the background color until we
		// get to white. All timeouts are set at once so that the length of the
		// animation is guaranteed and frames can be dropped.
		while (args.r + args.g + args.b < 765) {
			// Ease out (fade more quickly at first and then more slowly)
			for (x in rgb) {
				args[x] += Math.ceil((255 - args[x]) / 10);
				args[x] = Math.min(args[x], 255);
			}

			$[id].timeouts.push(setTimeout("emphasize('"+o.id+"',{r:"+args.r+",g:"+args.g+",b:"+args.b+"})", 50 * c++));
		}

		$[id].timeouts.push(setTimeout("_('"+o.id+"').style.backgroundColor = 'transparent'; delete $['"+id+"'];", 50 * c));

		for (x in rgb)
			args[x] = $[id][x];
	}

	// Set the background color
	if (args.r)
		o.style.backgroundColor = '#' + args.r.toString(16) + args.g.toString(16) + args.b.toString(16);
}

/**
 * Given a DOMNode, scrolls the page up or down so that the top of the node is
 * at the top of the page. The force arg is required in order to allow the page
 * to scroll up.
 */
function scrollIntoView(o, args) {
	o = _(o);
	if (!o) return;
	if (!args) args = {};
	var id = 'scroll';

	// Store some data about the animation based on the node id
	if (!$[id]) {
		$[id] = {
			target: o.realY(),
			timeouts: new Array()
		};

		// Find the current scroll position
		args.y = window.pageYOffset;
		if (args.y == undefined) args.y = document.body.scrollTop || document.documentElement.scrollTop;

		$[id].dir = (args.y > $[id].target) ? -1 : 1;


		if ($[id].dir == 1 && !args.force) {
			return delete $[id];
		}

		var c = 1;
		// Drop frame animation ensures that we get to the target in a decent
		// amount of time, rather than chaining at each frame
		while (args.y < $[id].target - 1 || args.y > $[id].target + 1) {
			args.y += ($[id].target - args.y) / 7 + $[id].dir;
			$[id].timeouts.push(setTimeout('scrollIntoView("' + o.id + '", {y:' + args.y + '})', 25 * c++));
		}

		$[id].timeouts.push(setTimeout('delete $["' + id + '"]', 25 * c));
		return;
	}
	// Allow scrolling to a specific location if node is unspecified, otherwise
	// clear any active scrolling
	else if (!args.y) {
		for (i in $[id].timeouts) {
			clearTimeout($[id].timeouts[i]);
		}
	}

	clearTimeout($[id].timeouts.shift());

	// Do the scroll
	window.scrollTo(0, Math.round(args.y));
}

/**
 * Moves the tooltip to the specified element and updates the text.
 *
 * @param {Object} Returns
 */
node = _ = function(o) {

	// Is o already a DOMNode?
	try {
		var x = o.style.visibility;
	}
	catch (e) {
		// Is o an ID?
		try {
			var x = document.getElementById(o).style.visibility;
			o = document.getElementById(o);
		}
		catch (e) {
			// Is o a tagName?
			try {
				var x = document.getElementsByTagName(o)[0].style.visibility;
				o = document.getElementsByTagName(o)[0];
			}
			catch (e) { return false; }
		}
	}

	/**
	 * Finds child nodes based on an id, className or tagName string, or by index
	 */
	o.child = function(o, index) {
		if (!index) index = 0;
		var j = 0;
		var r = new RegExp('\\b' + o + '\\b', 'i')
		for (i in this.childNodes) {
			// Check if the child matches by classname, otherwise by id, tag name or index
			if (this.childNodes[i] && this.childNodes[i].id == o || this.childNodes[i] && this.childNodes[i].className && r.test(this.childNodes[i].className) ||
					r.test(this.childNodes[i].tagName) && j++ == index) {
				return _(this.childNodes[i]);
			}
		}
	}

	/**
	 * Toggle the last class on and off by appending an H to it
	 */
	o.toggle = function(state) {
		if (state != undefined) {
			if (state && /H$/.test(this.className) || !(state || /H$/.test(this.className))) {
				return;
			}
		}

		// replace double H
		this.className = (this.className + 'H').replace(/HH$/,'');

		return false;
	}

	/**
	 * Delete the node from the document.
	 */
	o.remove = function() {
		this.parentNode.removeChild(this);
	}

	/**
	 * Calculate the distance in pixels from the top of the page.
	 */
	o.realY = function() {
		if (this.id == 'page') return 0;

		if (this.offsetParent)
			return this.offsetTop + _(this.offsetParent).realY();
	}

	/**
	 * Calculate the distance in pixels from the left side of the page.
	 */
	o.realX = function() {
		if (this.id == 'page') return 0;

		if (this.offsetParent)
			return this.offsetLeft + _(this.offsetParent).realX();
	}

	/**
	 * Set the left and top coordinates of the node, respectively.
	 */
	o.pos = function(x,y) {
		this.style.left = x + 'px';
		this.style.top = y + 'px';
	}

	return o;
}

/**
 * Just holds whatever information you give it.
 */
function Node(args) {
	for (i in args) this[i] = args[i];
}

/**
 * Placeholder for tags when the full stories script is not loaded.
 */
var Tag = (function() {
	function self() {

	}

	/**
	 * Generate the tag list HTML
	 */
	self.list = function(t){
		var h = '<div class="b tc2">Click for related stories</div><ul class="tabs tc4">';
		t = t.split(/[,;]+/);
		for (i in t) {
			h += '<li><a href="/search/'+t[i]+'">'+t[i]+'</a> &nbsp;</li>';
		}
		h += '</ul>';
		return h;
	}

	return self;
})();

/**
 * Manages the display of absolute and relative datetimes
 */
var Time = (function() {
	function self(o, abs, rel, date_only) {
		Node.call(this, {o: _(o), abs: abs, rel: rel});

		// Add ago for past or not yet for future
		this.rel = (this.rel) ? this.rel + ' ago' : 'not yet';

		// Fix bad grammar
		this.rel = this.rel.replace(/(today|yesterday|tomorrow) ago/, '$1').replace(/ /g,'&nbsp;');

		this.abs = Time.parse(this.abs, date_only).replace(/ /g,'&nbsp;');

		// Any time, when clicked, will toggle all times between abs and rel displays
		this.o.onclick = Time.toggle;
		this.o.style.cursor = 'default';

		/**
		 * Change the node content based on the abs or rel time state
		 */
		this.update = function() {
			if (Time.state)
				this.o.innerHTML = this.abs || this.rel;
			else
				this.o.innerHTML = this.rel || this.abs;
		}

		$.time[this.o.id] = this;
		this.update();
	}

	// Reload time setting via cookies
	self.state = /time_fmt=abs/.test(document.cookie);

	self.now = new Date();

	/**
	 * Change the time state, update all time displays, and set a cookie to allow
	 * the state to persist across requests.
	 */
	self.toggle = function() {
		Time.state = !Time.state;
		for (i in $.time)	$.time[i].update()

		// Doesn't reset all the cookies, just adds the new time_fmt value
		document.cookie = 'time_fmt=' + ((Time.state) ? 'abs' : 'rel') + '; expires=' + new Date(Time.now.getTime()+(9999999999)).toGMTString() + '; path=/';
	}

	/**
	 * Convert absolute date to local timezone
	 */
	self.parse = function(t, date_only) {
		t = new Date(Date.parse((t + ' GMT').replace(/-/g,'/')));
		t = t.toLocaleString()
			// Remove day of week
			.replace(/^.*?,\s+/,'')
			// Extract first 3 letters of month name
			.replace(/(\w{3}).*? /, '$1 ')
			// Remove seconds
			.replace(/:(\d+):\d+/, ':$1');

		if (date_only)
			t = t.replace(/\d+:\d+.*/, '')
		return t;
	}

	/**
	 * Shows a countdown based on the given t seconds. Instead of setting a start
	 * time and counting down every 1000ms, this uses the computer's clock to
	 * show an accurate countdown.
	 */
	self.countdown = function(o, t) {
		o = _(o);

		// Set start milliseconds
		if (t != undefined)
			$['countdown_' + o.id] = new Date().getTime() + t*1000;

		// Calculate milliseconds remaining
		else
			t = Math.floor(($['countdown_' + o.id] - new Date().getTime() + 75) / 1000);

		// Set HTML to mm:ss format
		o.innerHTML = Math.floor(t / 60) + ':' + ('00' + t % 60).replace(/.*(\d\d)$/,'$1');

		if (t <= 0) return;

		// Check time every .25s
		setTimeout(function(){Time.countdown(o)}, 250);
	}
	$.time = {}
	return self;
})();

/**
 * Hide the announcement bar at the top of the page and do an ajac request to
 * prevent it from returning via Db setting and/or cookie,
 */
function hideAnnouncement(o) {
	o = _(o);
	o.parentNode.parentNode.toggle(0);
	ajax.post(o.href);
}
