// ==UserScript==
// @name			Connotea / box.net mashup
// @namespace		http://www.postgenomic.com/userscripts
// @description		Allows you to upload arbitrary files that are associated with particular bookmarks in your Connotea library
// @include			http://www.connotea.org*
// ==/UserScript==

//
// 1) add upload links to all papers
// 2) add download links to papers with tags of form bn:file=<username>:<file id>
//
// on upload click:
//
// 1) check to see if we have a ticket already
// 2) validate it
// 3) if it doesn't validate or we don't have one, get one, return to (1)
// 4) send user to authenticate if necessary
// 5) bring up an upload form (this could be a problem...)
// 6) upload file to box.net
// 7) add tag to bookmark with API
// 8) reload page so that tag appears
//
// on download:
// 
// 1) check to see if we have a ticket already
// 2) validate it
// 3) if it doesn't validate or we don't have one, get one, return to (1)
// 4) send user to authenticate if necessary
// 5) create download URL
// 6) direct user to download URL
//


// some config stuff.
var debug = 0; // should we print debug alert()s?
var upload_url = "http://www.ghastlyfop.com/boxnet.php";


// check to see if the user is logged in
var connotea_cookie = readCookie("bibliotech");
var bits = connotea_cookie.split("%2C");
var username = bits[1];

// get boxnet authentication ticket, if available
var boxnetAPIKey = "noa01dv9ig7h0t03mtritsu508czd73z";
var boxnetTicket = GM_getValue("ticket");
var parser = new DOMParser();

// check to see if we're in 'username's library
var whichPage = document.location.toString();
var pageTitle = document.title;
var userRegString = username + "'s bookmarks";

var tagsFor = new Array();
var bookmarkObj = new Array();

// is the user in their library page?
if (((username) && (username != "logout")) && (pageTitle.match(userRegString)))
{	
	
	var bookmarks, bookmark;
	bookmarks = document.evaluate("//div[@class='content-mybookmark']", document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);	
	for (var i = 0; i < bookmarks.snapshotLength; i++) {	
		bookmark = bookmarks.snapshotItem(i);
		
		var userPostText = bookmark.innerHTML;
		// get the URI hash of this bookmark.
		var hash = false;
		var matches = Array();
		var uri_regexp = /edit\?uri\=(.+?)\"/i;
		matches = uri_regexp.exec(userPostText);
		if (matches) {
			hash = matches[1];
		}
		
		// get list of all tags from the bookmark....
		var bookmarkTags = Array();
		var hasBoxnet = false;
		var matches = Array();
		var matches = userPostText.match(/<a href="(?:[^"]*?)" title="([^"]*?)" class="postedtag">/ig);
		if (matches) {
			bookmarkTags = new Array(matches.length);
			for (var k=0; k < matches.length; k++) {
				var actualTag = matches[k].match(/title="([^"]*?)"/i);
				if (actualTag.length) {
					var regString = "bn\:file=" + username;
					regString = regString + "-";
					var boxnet_regexp = new RegExp(regString, "i");
					if (boxnet_regexp.test(actualTag[1])) {
						GM_log("Found a box.net tag : " + actualTag[1]);
						hasBoxnet = actualTag[1].substr(8);
						GM_log("ID is " + hasBoxnet);
					}
					bookmarkTags[k] = actualTag[1];
				}
			}
			
			// write to the global tagsFor array.
			tagsFor[hash] = bookmarkTags;
			bookmarkObj[hash] = bookmark;
		}

		// add a 'download file' link, if appropriate (if there's a 'bn:file=<x>' tag)
		if (hasBoxnet) {
			add_remove_link(bookmark, hash);			
			add_download_link(bookmark, hash);
		}		
		
		// and an 'upload file' link
		add_upload_link(bookmark, hash);
	}
	
	// also handle any uploads that have just taken place...
	var messageRegString = "#boxnet:(.*)";
	var messageRegExp = new RegExp(messageRegString, "i");
	if (whichPage.match(messageRegExp)) {
		// yes, we've got a message from the upload script available.
		// deal with it....
		handle_upload_message(unescape(whichPage.substr(whichPage.indexOf("#"))));
	}
}

// after calling the remote upload broker we are returned a message containing either an error or the hash
// of the bookmark to update.
function handle_upload_message(message) {
	var bits = message.split(":");
	
	var status_code = bits[1];
	
	if (status_code) {
		hash = bits[2];
		for_user = bits[3];
		file_id = bits[4];
		if (for_user == username) {
			associate_file_with_bookmark(hash, file_id);
			return;
		}
	} else {
		error = bits[2];
		alert("Sorry, an error occurred:\n" + error);
		return;
	}
}

// check to see if our box.net authentication ticket is still valid for use.
function validate_ticket() {
	// return ticket number if everything is OK, otherwise return false
	return boxnetTicket;
}

// get a new ticket number from box.net
function get_new_ticket() {
	var ticket_url = "http://www.box.net/api/1.0/rest?action=get_ticket&api_key=" + boxnetAPIKey; 
	if (debug) {alert("Getting new box.net ticket...");}
	
	GM_xmlhttpRequest({
		method: 'GET',
		url: ticket_url,
		onload: function(responseDetails) {
			// hopefully everything went swimmingly
			if (responseDetails.status == 200) {
				// we should have a ticket now?
		
				var dom = parser.parseFromString(responseDetails.responseText, "application/xml");		
				var entries = dom.getElementsByTagName('ticket');
				
				if (entries[0]) {
					var ticket = entries[0].textContent;
					boxnetTicket = ticket;
					
					GM_setValue("ticket", ticket);
					
					// now send user off to box.net to authenticate.
					window.location = "http://www.box.net/api/1.0/auth/" + ticket;
				} else {
					alert("Sorry, couldn't see a ticket in the box.net response: " + responseDetails.responseText);
					GM_log("No ticket in response from box.net");
					reset_ticket();
					return false;
				}
			} else {
				// a horrible error occurred.
				alert("Sorry, box.net returned status code " + responseDetails.status);
				reset_ticket();
				return false;
			}
		}
	});
	
	return false;
}

// get an auth token for the box.net ticket
function authenticate_ticket(action, hash) {
	var authenticate_url = "http://www.box.net/api/1.0/rest?action=get_auth_token&api_key=" + boxnetAPIKey + "&ticket=" + boxnetTicket;
	if (debug) {alert("Getting auth token based on ticket " + boxnetTicket);}
	
	GM_xmlhttpRequest({
		method: 'GET',
		url: authenticate_url,
		onload: function(responseDetails) {
			// hopefully everything went swimmingly
			if (responseDetails.status == 200) {
				// did we get an auth token back?
		
				var dom = parser.parseFromString(responseDetails.responseText, "application/xml");		
				var entries = dom.getElementsByTagName('auth_token');
				
				if (entries[0]) {
					var token = entries[0].textContent;
					got_auth_token(token, action, hash);
				} else {
					alert("Sorry, couldn't see an auth token in the box.net response: " + responseDetails.responseText);
					GM_log("No token in response from box.net");
					reset_ticket();
					return false;
				}			
			} else {
				// a horrible error occurred.
				alert("Sorry, box.net returned status code " + responseDetails.status);
				reset_ticket();
				return false;
			}
		}
	});
	
	return false;
}

// box.net is saying that we need to get a new ticket, etc.
function reset_ticket() {
	GM_setValue("ticket", false);
	boxnetTicket = false;
	alert("The authentication ticket we've got for box.net is out of date, or you've logged on to box.net somewhere else.\nWe've deleted it, so try clicking on the upload or download links to try again.");
}

// redirect user to the URI of their uploaded material.
function send_to_download(token, hash) {
	// get file_id from the tags on this bookmark.
	var tags = tagsFor[hash];
	for (i=0; i < tags.length; i++) {
		var bnRegexpString = "bn:file=" + username + "-";
		var bnRegexp = new RegExp(bnRegexpString, "i");
		
		if (tags[i].match(bnRegexp)) {
			var id = tags[i].substr(bnRegexpString.length);
			window.location = "http://box.net/api/1.0/download/" + token + "/" + id;
		}
	}	
}

// called when we're all authenticated up on box.net
function got_auth_token(token, action, hash) {
	// we have a valid ticket and a token...
	if (action == "download") {
		send_to_download(token, hash);
	} else if (action == "upload") {
		// show the upload form
		bookmark = bookmarkObj[hash];
		add_upload_form(bookmark, hash, token);
	} else if (action == "upload_file") {
		// the action is 'upload file'
		do_upload(hash, token);
	}
}


// called when the user clicks on a download link.
function handle_authentication(hash, action) {
	var tags = tagsFor[hash];
	var authenticated = false;
	
	boxnetTicket = validate_ticket();
	
	if (!boxnetTicket) {
		boxnetTicket = get_new_ticket();
		// calls authenticate_ticket() when done.
	} else {
		authenticate_ticket(action, hash);
	}
}


// add an upload form to a bookmark
function add_upload_form(bookmark, hash, token) {
	newform = document.createElement("div");
	newform.innerHTML = " \
<div style='border: 2px solid #DEDEDE; padding: 5px;' id='upload_form_" + hash + "'> \
<form enctype='multipart/form-data' method='POST' action='" + upload_url + "'> \
<input type='hidden' name='MAX_FILE_SIZE' value='1500000' /> \
<input type='hidden' name='token' value='" + token + "' /> \
<input type='hidden' name='hash' value='" + hash + "' /> \
<input type='hidden' name='username' value='" + username + "' /> \
<p>Choose a file to attach to this bookmark: <input id='file_for_" + hash + "' name='uploadedfile' type='file' /> \
<p><input type='submit' value='Upload' id='upload_to_" + hash + "' /> \
<input type='button' value='Cancel' id='cancel_form_" + hash + "'/> \
</form> \
</div> \
";	
	
	bookmark.parentNode.insertBefore(newform, bookmark.nextSibling);
}

// check for clicks on upload or download links and form buttons.
document.addEventListener('click', function(event) {
    // event.target is the element that was clicked
	matches = event.target.id.match(/boxnet_upload_(\w+)/i);
	if (matches) {
		var hash = event.target.id.substr(14);		
		handle_authentication(hash, "upload");
	}
	matches = event.target.id.match(/boxnet_download_(\w+)/i);
	if (matches) {
		var hash = event.target.id.substr(16);
		handle_authentication(hash, "download");
	}
	matches = event.target.id.match(/boxnet_remove_(\w+)/i);
	if (matches) {
		var hash = event.target.id.substr(14);
		associate_file_with_bookmark(hash, false);
		event.stopPropagation();
		event.preventDefault();
		return;
	}
	matches = event.target.id.match(/cancel_form_(\w+)/i);
	if (matches) {
		var hash = event.target.id.substr(12);
		// remvove the relevant upload form.
		var uploadForm = document.getElementById("upload_form_" + hash);
		uploadForm.parentNode.removeChild(uploadForm);
	}
	matches = event.target.id.match(/upload_to_(\w+)/i);
	if (matches) {
		// the user has clicked on the 'upload' button.
		var hash = event.target.id.substr(10);

		// do some sanity checking.
		var filenameBox = document.getElementById("file_for_" + hash);
		var filename = filenameBox.value;
		
		if (!filename) {
			alert("You haven't said which file you want to attach to this bookmark. Use the 'browse' button in the upload form.");
			event.stopPropagation();
			event.preventDefault();
			return;			
		}
	}	
	
}, true);

// the user has clicked on the 'upload' button underneath bookmark with hash 'hash'
function do_upload(hash, token) {
	// is there anything in the filename box?
	var uploadForm = document.getElementById("form_name_" + hash);
	var submitButton = document.getElementById("submit_for_" + hash);

	
	if (!filename) {

		return;
	}

	if (!token) {
		// make sure that we're properly authenticated, etc.
		handle_authentication(hash, "upload_file");
		return;
	}
}

// add an 'remove file' link to a bookmark
function add_remove_link(bookmark, hash) {
	newanchor = document.createElement("a");
	newanchor.setAttribute("id", "boxnet_remove_" + hash);
	newanchor.setAttribute("title","Remove file");
	newanchor.setAttribute("style", "text-decoration: none; cursor: pointer;");			
	newanchor.innerHTML = " remove file ";			
	img = document.createElement("img");
	img.setAttribute("alt","Remove file associated with this bookmark");
	img.setAttribute("src","http://www.ghastlyfop.com/cancel.png");
	img.setAttribute("border","0");
	newanchor.appendChild(img);
	bookmark.parentNode.insertBefore(newanchor, bookmark.nextSibling);	
}

// add an 'upload file' link to a bookmark
function add_upload_link(bookmark, hash) {
	newanchor = document.createElement("a");
	newanchor.setAttribute("id", "boxnet_upload_" + hash);
	newanchor.setAttribute("title","Upload file");
	newanchor.setAttribute("style", "text-decoration: none; cursor: pointer;");			
	newanchor.innerHTML = " upload file ";			
	img = document.createElement("img");
	img.setAttribute("alt","Upload file to box.net");
	img.setAttribute("src","http://www.ghastlyfop.com/page_white_get.png");
	img.setAttribute("border","0");
	newanchor.appendChild(img);
	bookmark.parentNode.insertBefore(newanchor, bookmark.nextSibling);	
}

// add a 'download file' link to a bookmark
function add_download_link(bookmark, hash) {
	newanchor = document.createElement("a");
	newanchor.setAttribute("id", "boxnet_download_" + hash);
	newanchor.setAttribute("title","Download file");
	newanchor.setAttribute("style", "text-decoration: none; cursor: pointer;");			
	newanchor.innerHTML = " download file ";			
	img = document.createElement("img");
	img.setAttribute("alt","Download file from box.net");
	img.setAttribute("src","http://www.ghastlyfop.com/page_white_put.png");
	img.setAttribute("border","0");
	newanchor.appendChild(img);
	bookmark.parentNode.insertBefore(newanchor, bookmark.nextSibling);	
}

// use the API to edit a bookmark. Note that any changes won't be visible until after we reload the page.
function associate_file_with_bookmark(hash, file) {
	var tags = tagsFor[hash];
	var tag_to_add = "bn:file=" + username + "-" + file;
	var new_tags = new Array();
	// go through the tags, remove any bn:file ones, add the new bn:file=username:file tag.
	for (i=0; i < tags.length; i++) {
		var tag = tags[i];

		if (tag.match(tag_to_add)) {
			alert("Bookmark already has the right tag.");
			return;
		}
		
		if (tag.match("bn:file")) {
			// we'll be removing this tag.
		} else {
			new_tags.push(tag);
		}
	}
	
	// file is optional - if it's false then just remove any existing bn:file tags.
	if (file) {
		new_tags.push(tag_to_add);
	}
	
	tags = new_tags;
	
	var args = "uri=" + hash + "&tags=" + tags.join(",");
	
	if (debug) {alert("Calling API to edit " + hash + " with tags " + tags + '\n' + args);}
	
	var editUrl = 'http://' + username + '@www.connotea.org/data/edit';
	
	GM_xmlhttpRequest({
		method: 'POST',
		url: editUrl,
		headers: {'Content-type': 'application/x-www-form-urlencoded'},
		data: args,
		onload: function(responseDetails) {
			// hopefully everything went swimmingly
			if (responseDetails.status == 201) {
				GM_log("Updated OK");
				
				var index = whichPage.indexOf("#");
				var redirectTo = whichPage;
				
				if (index >= 1) {
					redirectTo = whichPage.substr(0, index);
				}
				
				// reload the page.
				window.location = redirectTo;
			} else {
				alert(responseDetails.responseText + "\nvars were " + args);
			}
		}
	});	
}

// helpful cookie function
function readCookie(name) {
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for(var i=0;i < ca.length;i++) {
		var c = ca[i];
		while (c.charAt(0)==' ') c = c.substring(1,c.length);
		if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
	}
	return null;
}