/*
	file: CreoleBase.js: "basic" page structure for Creole pages.
	This is normally included in all pages by header.html
	Requires Utils.js and jQuery.
	
	TODO where are global variables stored??
	
	(c) Daniel Winterstein
*/


var DEBUG = (""+window.location).indexOf("debug") != -1;
var NATIVELOG = (""+window.location).indexOf("nativelog") != -1;

/**
 * Hold global context variables: 'user', 'tagSets'.
 * See Widgets.globalVars() and DB2Json for details
 */
var Creole = {	
	/**
	 * The usual user fields, plus:
	 *  - services: all services for which we have a login with password
	 *  
	 *  - xids: map of service:xid 
	 *  
	 *  - version: which version of SoDash to show. Valid values are:
	 *  	"stable" (potentially older code, but safe), 
	 *  	"normal", 
	 *  	"beta" (newer code -- should be OK but may still have some bugs),
	 *  	"dev" (the latest code -- for developers only!)
	 *  	Assume normal if unset. 
	 *  	Tests for special powers should use contains -- we may use this as a place
	 *  	to signal special-cases, e.g. "stable+scotrail" might indicate mostly
	 *  	stable but with some features which have been switched on for ScotRail.
	 */
	user : {},
	
	tagSets : [],
	
	probes : [],
	/**
	 * How often should MessageStreams poll for updates?
	 * This is in milliseconds.
	 */
	pollFreq:1000*120	
};

/**
 * @param ver e.g. "dev"
 * @returns true if the current user matches this version
 */
function isVersion(ver) {
	assert(ver);
	if ( ! Creole.user || ! Creole.user.version) return false;
	return Creole.user.version.indexOf(ver) != -1;
};

/**
* Console replacement, no firebug.
*/
(function() {
	/**
	* Console replacement, no firebug.
	*/
	if (window.console === undefined) {
		window.console = new Object();
	} 

	if (window.console.log === undefined) {
		if(DEBUG) {
			var console = $('<div id="substitute-console"></div>');

			$(function() {
				$(document.body).append(console);
			});

			window.console.log = function() {			
				var args = Array.prototype.slice.call(arguments); // Cast Arguments from "object" to Array

				for(var i = 0; i < args.length; i++) {
					if(args[i] instanceof Object) {				
						args[i] = JSON.stringify(args[i]);
					}
				}

				console.append($("<p>").text(args.join(" ")));

				console[0].scrollTop = console[0].scrollHeight;
			};	
		} else {
			window.console.log = function() {};
		}
	}

	if($.browser.mozilla && $.browser.version.substr(0, 2) == "9." && !DEBUG) {
		window.console.log = function() {};
	}

	/** Upgrade the log to support Android LogCat style tags.
	 * Usage: put log=tag1+tag2 into the url to restrict logging to just those tags.
	 * Call console.log with a tag as the 1st argument.
	 * E.g. console.log('template', "Loading templates...");  */
	if(!NATIVELOG) {
		var nativeLog = window.console.log;

		window.console.log = function() {
			var args = Array.prototype.slice.call(arguments); // Cast Arguments from "object" to Array

			// filter by logcat tag?
			var tags = urlVars["log"];
			if (tags) {
				if(tags.indexOf(args[0]) == -1) {
					return;
				}
			}
			
			if(nativeLog.apply !== undefined) {
				nativeLog.apply(window.console, args);
			} else {
				nativeLog(args.join(" ")); // See previous comment for description.
			}
		};
	}

	/**
	* Alert wrapper. Useful if you need to set a breakpoint here for debugging.
	*/
	nativeAlert = window.alert;

	window.alert = function(message) {
		if(nativeAlert.call !== undefined) {
			nativeAlert.call(window, message);
		} else {
			nativeAlert(message);
		}
	};
})();

/**
 * Sanity check for results from JQuery. Did we get any?
 * @param x jquery array
 * @param num (optional) expected number 
 * @deprecated use assert() instead
 */
function $sanity(x, num, selector) {	
	if (x && x.length) {
		if (!num && x.length!=0) return;
		if (x.length==num) return;
	}
	
	if (x.length) {
		console.log("JQuery failure: wrong number of elements found.");
	} else {
		console.log("JQuery failure: no elements found.");
	}
	
	if (alertedAlready) return;
	
	alert("JQuery selection failure: "+selector);
	alertedAlready = true;
};

var alertedAlready;

function showInfoPop(parentElement, text) {
	var xy = getPosition(parentElement);
	var newDiv = document.createElement("div");
	newDiv.innerHTML = text;
	newDiv.style.position="absolute";
	setPosition(newDiv, xy[0], xy[1]);	  
	document.body.insertAfter(parentElement, newDiv);
};

/** Shorthand for document.getElementById() */
// DEPRECATED: Mostly use $('#'+id) instead, but this is more reliable
function get(id) {
	return document.getElementById(id);
};

/** get a value from local storage. TODO use Html5 storage for preference 
 * (but we do need a robust fallback) */
function ClientGet(key) {
	var gc = getCookie(key);
	if (!gc) return gc;
	return eval(gc);
};

/** put a value into local storage. */
function ClientPut(key,value) {
	setCookie(key,""+value);	
};

var genIdCntr = 0;

/** 
 * Get or generate an ID 
 * 
 * @param elem html element. Can be $()'d or not
 * @param extractNameSelector Optional
 * */
function getId(elem, extractNameSelector) {
	elem = $(elem);
	if ( ! elem.attr('id')) {
		genIdCntr++;		
		var elemName = "";
		if (extractNameSelector) elemName = $(extractNameSelector, doc).text();
		if ( ! elemName) elemName = genIdCntr;
		// get the path but not the query
		var locn = ""+window.location;
		locn = locn.replace(/\?.*$/, "");		
		var id = "genId_"+elemName+"_"+locn;
		id = id.replace(/\W/g, "");		
		elem.attr('id', id);
	}
	return elem.attr('id');
};

/**
* MESSAGE HANDLING FUNCTIONS
*/

/**
 * Handle the standardish return values form a Creole AJAX call
 * Assumes:
 * Message-adding is done by showMessage() and showErrorMessage().
 * Override these to change the default behaviour.
 * TODO how does this interact with Richard's shiny new notifivations queue?
 */
function handleAjaxResponse(response) {
	// Is it a JQuery response (and not the json)?
	if ( ! response.cargo && ! response.messages && response.responseText) {
		try {
			response = $.parseJSON(response.responseText);
		} catch(e) {
			console.log(e, response);
			response = {errors: 
				["Server response was in the wrong format (expected JSON): "+e]};
		}
	}
	var error = response["errors"];
	var msg = response["messages"];
	var callback = response["callback"];
	var related = response["related"];
	if (error) {		
		showErrorMessage(error);
	}
	if (msg) {
		showMessage(msg);
	}
	if (callback) callback();
	// TODO
	if (related) console.log("related", related);	
};

function showErrorMessage(error) {	
	if(window.location.pathname == '/stream') return; // TODO: We need to implement some chain-of-command style pattern for nice error handling.
	
	if(isArray(error)) {
		error = error.map(function(val) { return clean(val); }).join(" ");
	} else {
		error = clean(error);
	}

	console.log("Error: " + error);
	
	var msgsDiv = get('flash-messages');
	
	if ( ! msgsDiv) {
		alert(error);
		return;
	}

	// hack: what is up with the EoFs?
	if (error.toLowerCase().indexOf("eofexception") != -1) {
		console.log("wtf", error);
		return;
	}
	
	// hack: avoid dupes
	console.log("dupe?",msgsDiv.innerHTML, error, msgsDiv.innerHTML.contains(error));
	if (msgsDiv.innerHTML.contains(error)) {
		return;
	}
	
	msgsDiv.innerHTML += "<div class='error'>"+error+"</div>";
};

/**
 *Assumes:
 * There is a div with id=flash-messages for messages
 * Messages are output in div class=message
 * Override to change the default behaviour
 * @param msg
 */
function showMessage(msg) {
	console.log("Message: "+msg);
	
	var msgsDiv = get('flash-messages');
	
	if (!msgsDiv) {
		return;
	}
	
	msgsDiv.innerHTML += "<div class='message'>"+msg+"</div>";
};

/** The ajax call failed: display the error message and (optional) re-enable
the button.*/
function fail(response, form) {
	console.log("ajax fail", response);
	
	var err = ".";
	
	if(response['error']) {
		err = ": "+response['error'];
	} else if(response['errors']) {
		err = ": "+response['errors'];
	}
	
	alert('Sorry, there was a problem' + err);
	
	if(form) $(':submit', form).removeAttr('disabled');

	assert(false, err);
};

/** 
* DATA (where to find it in the DOM)
*/

/**
 * @return the xid from the local DBOScope
 * Assumes: the element is inside a div of class DBOScope with an xid attribute.
 */
function XId(element) {
	var scope = $(element).closest(".DBOScope");
	return scope.attr("xid");
};

/**
 * @param xid
 * @returns the id part of the XId, e.g. "winterstein" from "winterstein@twitter"
 */
XId.id = function(xid) {
	var i = xid.lastIndexOf('@');
	assert(i!=-1, xid);
	return xid.substring(0, i);
};
	
/**
 * @param xid
 * @returns the service part of the XId, e.g. "twitter"
 */
XId.service = function(xid) {
	var i = xid.lastIndexOf('@');
	assert(i!=-1, xid);
	return xid.substring(i+1);
};

/**
 * @param xid
 * @returns the service part of the XId, e.g. "twitter"
 */
XId.prettyName = function(xid) {
	var i = xid.indexOf('@');
	assert(i!=-1, xid);
	return xid.substring(0,i);
};

/**
 * @return the object-type from the local DBOScope, e.g. "Text" or "Person"
 * Assumes: the element is inside a div of class DBOScope with an objType attribute.

 */
function objType(element) {
	var scope = $(element).closest(".DBOScope");
	return scope.attr("objType");
};

/**
 * convenience for "iobj=xid:type", suitable for sticking in a url
 */
function sodashArg(item) {
	return Fields.IOBJECT+"="+escape(item.xid+":"+item.type);
};

/**
 * @param item Only supports DBStream so far!
 * @returns SoDash url
 */
function getLink(item) {
	if (item.type == 'Stream') {
		var clonedParams = Object.clone(item.params);
	
		var servlet = clonedParams.servlet;
		
		delete clonedParams.servlet;
		delete clonedParams.streamId;			
		delete clonedParams.probeId;			
	
		if(item.params.service !== undefined) {
			clonedParams.service = item.params.service.join(" OR ");
		}
	
		return servlet + '?' + $.param(clonedParams);
	}
	console.log(item);
	assert(false, "TODO "+item.type);
}

/**
* "STANDARD" PAGE MANIPULATION
*/

/** Collect functions to be called when adding new html to a page */
console.log("Defining ajaxifyFunctions");

var ajaxifyFunctions = [];

/** Called when new html has been inserted to wire up the ajax widgets */
function ajaxify() {
	console.log("ajaxify");
	
	for(var i=0; i<ajaxifyFunctions.length; i++) {
		var fn = ajaxifyFunctions[i];
		
		console.log("..."+fn.name);
		
		try {
			fn();
		} catch(err) {
			console.log("ERROR "+err);
			console.trace();			
		}
	}				
}

// lengthen urls
if ($.longurlplease) {
	ajaxifyFunctions.push(function (){$.longurlplease();});
}

// nav bar: set the right link to have class "active"
$(function(){
	$('a.navbar').not('active').each(function() {
		var u = $(this).attr('href');
		var pth = ""+window.location;
		if (pth.indexOf(u) != -1) {
			$(this).addClass('active');
		}
	});
});

// Remove subnav element if empty
$(function() {
	if($('#sub-nav').children().length == 0) {
		$('#sub-nav').remove();
	}
});

// User menu functionality
$(function() {
	$('#header .sf-menu a.more').click(function() {
		$(document).click();
	
		if($(this).parent().hasClass("open")) {
			$(this).parent().removeClass("open");		
		} else {
			$(this).parent().addClass("open");
		}

		return false;
	});

	$(document).click(function(event) {				
		if($(event.target).closest('.sf-menu').length == 0) {
			$('#header .sf-menu').removeClass("open");
		}
	});
});

// Detect if user scrolled to bottom of page, fire document:scrolledToBottom event.
$(function() {
	$(window).scroll(function() {	
		if(
			$(document.body).outerHeight() > $(window).height() &&	// Document is higher than window and..
			$(window).scrollTop() + $(window).height() >= $(document.body).outerHeight() // Window scrolltop + window height is greater than or equal to the document height
		) {		
			$(document).trigger("scrolledToBottom");
		}
	});
});

// HACK: highlight certain classes?
if (getUrlVars()["highlight"]) {
	var classes = getUrlVars()["highlight"];
	
	document.write("<style>");
	document.write(classes+" {border:3px solid red;}");	
	document.write("</style>");
}

// Disable caching AJax in IE!!
$.ajaxSetup({'cache' : false});

// Error reporting, via ajax to ErrorServlet
window.onerror = function(errorMsg, url, lineNumber) {
	$.ajax({
		'url' : "/admin-error",
		'data' : {
			'emit' : true,
			'msg' : window.navigator.userAgent + ' | ' +  window.location + ' | ' + url + ' | ' + lineNumber + ': ' + errorMsg + '\n\n' + printStackTrace().join('\n\t')
		}
	});

	return false;
};

// Go!
$(document).ready(function(){
	ajaxify();
});

