// Gmail Signature Float
// version 0.8.1 BETA!
// 2006-10-29
// Copyright (c) 2006, Tim Jarrett
// Contact: tim |at| tim-jarrett |dot| com
// Released under the GPL license
// http://www.gnu.org/copyleft/gpl.html
//
// --------------------------------------------------------------------
//
// This is a Greasemonkey user script.
//
// To install, you need Greasemonkey: http://greasemonkey.mozdev.org/
// Then restart Firefox and revisit this script.
// Under Tools, there will be a new menu item to "Install User Script".
// Accept the default configuration and install.
//
// IF YOU ARE UPGRADING FROM A PREVIOUS VERSION OF GMAIL SIGNATURE 
// FLOAT, go to Tools/Manage User Scripts and manually uninstall the 
// previous version before installing this one.  Sorry, this is a 
// limitation of Greasemonkey.
// 
// To uninstall, go to Tools/Manage User Scripts,
// select "Gmail Signature Float", and click Uninstall.
//
// --------------------------------------------------------------------
//
// WHAT IT DOES:
//
// mail.google.com (Gmail):
// - Adds your signature above the quoted text, rather than below it
// - Updates the Settings page text to reflect this change in 
//   functionality
//
// --------------------------------------------------------------------
//
// PROPS TO:
//
//	Robson Braga Araujo (http://www.userscripts.org/people/868) and
//  his "Gmail: Random Signature" script.  I lifted his method for 
//  searching through Gmail's functions.  I'm not sure if it helped...
//  but it is being used in this update.
//
// --------------------------------------------------------------------
//
// CHANGELOG:
// 0.8.1
//	  - Fixed 'Settings' to work for other languages
//    - Slightly changed way that settings are injected into the DOM to be a bit
//		more friendly
//
// 0.8
//	  - Added ability to turn of Signature Floating
//    - Added a link to the UserScripts page for this script for update checking
//
// 0.7.1
//	  - Fixed bug in function body extracting causing GSF not to work sometimes - 
//       GSF should now work on Gmail and Gmail Domains
//
// 0.7
//	  - Did some house cleaning on function names to try to keep them consistent
//    - Added option to use HTML in the signature
//
// 0.6:
//	  - Fixed "Compose" functionality, wasn't properly rewriting the function
//       before
//    - Added "Remove Dashes" functionality - see Settings page in GMail 
//
// --------------------------------------------------------------------

/* Gmail Signature Float
   Copyright 2006 Tim Jarrett
   See also: <http://www.tim-jarrett.com/greasemonkey/gmailsignaturefloat.user.js>
	
   This software is licensed under the CC-GNU LGPL:
   <http://creativecommons.org/licenses/LGPL/2.1/>
*/

// ==UserScript==
// @name          Gmail Signature Float
// @namespace     http://www.tim-jarrett.com
// @description   Moves your signature in Gmail to the top of the message rather than the bottom
// @include       http://mail.google.com/*
// @include       https://mail.google.com/*
// @include		  https://mail.google.com/hosted*
// ==/UserScript==
(function()
{

var VERSION_NUMBER = "0.8.1";
var arr_other_funcs = new Array();

//If loading a javascript page
if ( window.location.href.match(/name=js/) ) {
	//It's a JS document - lets see if we can find our signature creation function
	var sig_func_name = "";
	
	var js = unsafeWindow.top.document.getElementsByName('js')[0].contentWindow;
	if (js.wrappedJSObject) js = js.wrappedJSObject;
	
	for ( propName in js ) {	
		var propValue = js[propName];
		
		if ( typeof propValue != "function" ) {
			continue;
			
		}
		
		var functionBody = propValue.toString();
		
		if ( functionBody.indexOf('<br clear=all><br>-- <br>') != -1 ) {
			sig_func_name = propName;
			GM_log("Found " + propName + " main functions.");
		
		} else {
			arr_other_funcs[propName] = functionBody;
			
		}
		
	}//for
	
	//If we got the right file and we found the right function!!
	if ( sig_func_name != '' ) {
		//alert("Now hunting for complimentary functions");
		var regex_reply = "\\+\\s*" + escapeRegExChars(sig_func_name) + "\\s*\\(";
		var regex_comp  = "\\+\\=\\s*" + escapeRegExChars(sig_func_name) + "\\s*\\(";
		var func_reply  = "";
		var func_compose = "";
		
		for ( propName in arr_other_funcs ) {
			if ( func_reply != "" && func_compose != "" ) {
				break;	
			}
			//GM_log("Analyzing reply function: " + propName);
			functionBody = arr_other_funcs[propName];
			if ( functionBody.search(regex_reply) != -1 ) {
				func_reply = propName;
				GM_log("Found reply function: " + propName);
				
			} else if ( functionBody.search(regex_comp) != -1 ) {
				func_compose = propName;
				GM_log("Found compose function: " + propName);
				
			}
			
		}//for
		
		if ( func_reply != "" && func_compose != "" ) {
			var gsf_temp_window = js;
			
			js[func_reply] = update_func_reply(func_reply);
			js[func_compose] = update_func_compose(func_compose);	
			
		} else {
			GM_log("GSF could not initialize.", 2);
			//alert("GSF could not initialize.");	
		}
			
	}
	
}

/**
 * Handles the onclick event for the input that indicates whether the signature should be "floated" above quoted response
 */
function update_sig_above_quote(event)
{
	var elem = event ? event.target : this;
	GM_setValue('sig_above_quote', elem.checked);
	update_signature_byline();
	
}//end update_sig_above_quote

/**
 * Handles the onclick event for the input that indicates whether dash removing should be used
 */
function update_remove_dashes(event)
{
	var elem = event ? event.target : this;
	GM_setValue('clear_dashes', elem.checked);
	update_signature_byline();
	
}//end update_remove_dashes

/**
 * Handles the onclick even for the input that indicates whether dash removing should be sued
 */
function update_allow_html(event)
{
	var elem = event ? event.target : this;
	GM_setValue('allow_html', elem.checked);
	update_signature_byline();
	
}//end update_allow_html

/**
 * Updates the text under "Signature" on the settings page
 */
function update_signature_byline()
{
	gsf_elem_span.style.display = "block";
	gsf_elem_span.innerHTML = "<a href='http://userscripts.org/scripts/show/3067' target='_blank'>Gmail Signature Float " + VERSION_NUMBER + "</a>";
	gsf_elem_span.innerHTML += "<ul style='padding-top: 0; margin-top: 0;'>";
	gsf_elem_span.innerHTML += "<li style='margin-left: 5px; list-style-position: outside;'>" + (( GM_getValue('sig_above_quote', true) ) ? "inserted before all quoted messages" : " appended at the end of all outgoing messages" ) + "</li>";
	if ( GM_getValue('clear_dashes', false) ) {
		gsf_elem_span.innerHTML += "<li style='margin-left: 5px; list-style-position: outside;'>clearing dashes from above signature</li>";		
	}
	
	if ( GM_getValue('allow_html', false) ) {
		gsf_elem_span.innerHTML += "<li style='margin-left: 5px; list-style-position: outside;'>allowing HTML in signature</li>";	
	}

	gsf_elem_span.innerHTML += "</ul>";
	
	
}//end update_signature_byline

//Need to update the mail settings page, I don't see a clear "name=??" for it
//so sniff for the radio button with id "sx_sg_1"
if ( window.document.getElementById("sx_sg_1") ) {
	//This sucks...but find sx_sg_1, go up the DOM tree to the main TR
	//Then comes down the DOM tree to find the appropriate TD and SPAN that we need to modify...so fragile!
	var shared_parent = window.document.getElementById("sx_sg_1").parentNode.parentNode.parentNode.parentNode.parentNode.parentNode;
	var arr_spans = shared_parent.cells[0].getElementsByTagName('SPAN');
	var gsf_elem_span = arr_spans[0];
	
	//Um...update the stuff beneath "Signature:"
	update_signature_byline();
	
	//Add in the "clear dashes from signature" option
	var elem_ta = window.document.getElementById('sx_sg_1val');
	var elem_tbody = elem_ta.parentNode.parentNode.parentNode;
	
	//Add in the gsf_sig_above_quote options
	var elem_tr = window.document.createElement('TR');
	elem_tbody.appendChild(elem_tr);
	elem_tr.innerHTML += '<td><input id="gsf_sig_above_quote" type="checkbox" /></td><td style="padding-top: 2px;"><label style="font-weight: bold" for="gsf_sig_above_quote">Place Signature Above Quoted Response</label></td>';
	
	//Add in the gsf_clear_dashes option
	var elem_tr = window.document.createElement('TR');
	elem_tbody.appendChild(elem_tr);
	elem_tbody.innerHTML += '<td><input id="gsf_clear_dashes" type="checkbox" /></td><td style="padding-top: 2px;"><label style="font-weight: bold" for="gsf_clear_dashes">Clear Dashes From Signature</label></td>';
	
	//Addin the gsf_allow_html option
	var elem_tr = window.document.createElement('TR');
	elem_tbody.appendChild(elem_tr);
	elem_tbody.innerHTML += '<td><input id="gsf_allow_html" type="checkbox" /></td><td style="padding-top: 2px;"><label style="font-weight: bold" for="gsf_allow_html">Allow HTML in Signature</label></td>';
	
	var elem_input = window.document.getElementById('gsf_sig_above_quote');
	elem_input.checked = GM_getValue('sig_above_quote', true);
	elem_input.addEventListener('click', update_sig_above_quote, true);	//see http://dunck.us/collab/GreaseMonkeyUserScripts
	
	var elem_input = window.document.getElementById('gsf_clear_dashes');
	elem_input.checked = GM_getValue('clear_dashes', false);
	elem_input.addEventListener('click', update_remove_dashes, true);	//see http://dunck.us/collab/GreaseMonkeyUserScripts
	
	//Add in the "allow html in signature" option	
	var elem_input = window.document.getElementById('gsf_allow_html');
	elem_input.checked = GM_getValue('allow_html', false);
	elem_input.addEventListener('click', update_allow_html, true);	//see http://dunck.us/collab/GreaseMonkeyUserScripts	
}

/**
 * Rewrite the reply function
 */
function update_func_reply(func_name)
{
	//Turn the function into a string
	var str_func      		= js[func_name].toString();
	
	//Extract the function definition (ie function something(whatever, whatever))
	var str_func_def   		= gsf_get_func_def(func_name, str_func);
	
	//Extract the function body (everything between the first { after the definition and the last })
	var str_func_body  		= gsf_get_func_body(str_func);
	
	//make a copy of the body
	var str_func_new_body 	= str_func_body;
	
	//Was having problems calling some original GMail functions from inside of my rewritten 
	//functions, so I store all gmail functions and call them locally - this redeclares all 
	//the necessary functions
	str_func_new_body  		= gsf_escape_functions(str_func_new_body);
	
	//We slice and dice this one...
	var pos_sigcall 		= str_func_new_body.lastIndexOf('gsf_temp_window.' + sig_func_name);
	var pos_lastsemi 		= str_func_new_body.lastIndexOf(';');
	var pos_lastreturn 		= str_func_new_body.lastIndexOf('return ');
	var pos_lastplus 		= str_func_new_body.lastIndexOf('+');
	
	//Figure out the signature function call portion
	var str_sigcall = str_func_new_body.substring(pos_sigcall, pos_lastsemi);
	str_sigcall = "signature_filter(" + str_sigcall + ")";		
	
	//Add in the part we don't need to worry about
	var new_reply_func_body = str_func_new_body.substring(0, pos_lastreturn);
	
	//Put in the logic, directly into the function, to determine if we should float or not
	new_reply_func_body += 'if ( GM_getValue("sig_above_quote", true) ) return ' + str_sigcall + " + " + str_func_new_body.substring(pos_lastreturn+7, pos_sigcall-2) + ";\n";
	
	//Otherwise, don't change the order, but still apply the signature filter
	new_reply_func_body += 'else return ' + str_func_new_body.substring(pos_lastreturn+7, pos_sigcall-2) + "+ " + str_sigcall + ";\n";
	
	//Wrap the function definition around the body, assign it to a variable, change it from a string to an actual function and return it
	var str_func_final = str_func_def + "\n " + new_reply_func_body + "}//end func \n var testme = " + func_name + ";";
	eval(str_func_final);
	
	return testme;
	
}//end update_func_reply

/**
 * Rewrite the compose function
 */
function update_func_compose(func_name)
{   
	//Turn the function into a string
	var str_func      		= js[func_name].toString();
	
	//Extract the function definition (ie function something(whatever, whatever))
	var str_func_def   		= gsf_get_func_def(func_name, str_func);
	
	//Extract the function body (everything between the first { after the definition and the last })
	var str_func_body  		= gsf_get_func_body(str_func);
	
	//make a copy of the body
	var str_func_new_body 	= str_func_body;
	
	//Was having problems calling some original GMail functions from inside of my rewritten 
	//functions, so I store all gmail functions and call them locally - this redeclares all 
	//the necessary functions
	str_func_new_body  		= gsf_escape_functions(str_func_new_body);
	

	//The line we are interested looks like this: d += Xp(e);
	//We do some fancy regular expressions this time around
	var arr_match			= str_func_new_body.match(/[a-zA-Z]+\s*\+=/);		//arr_match[0] = d +=
	var var_name			= arr_match[0].replace(/\s*\+=/, "");				//Strip out the +=, so var_name = d
	
	//Find the line that handles the signature
	var regexp				= new RegExp(escapeRegExChars(var_name) + "\\s*\\+=\\s*gsf_temp_window\\." + escapeRegExChars(sig_func_name) + "\\(.*\\);");
	var arr_match			= str_func_new_body.match(regexp);
	var sig_line			= arr_match[0];
	
	//Figure out the parameters being passed into the signature function, only doing this to make sure we pass them in appropriately
	var pos_open_paren		= sig_line.indexOf('(');
	var pos_close_paren		= sig_line.indexOf(')');
	var parameters			= sig_line.substring(pos_open_paren+1, pos_close_paren);
	
	//Create the new sig line, with the call to GM_getValue to decide if we are doing the float or not
	var new_sig_line 		= 'if ( GM_getValue("sig_above_quote", true) ) ' + var_name + " = signature_filter(gsf_temp_window." + sig_func_name + "(" + parameters + ")) + " + var_name + ";\n";
		new_sig_line 		+= ' else ' + var_name + " += signature_filter(gsf_temp_window." + sig_func_name + "(" + parameters + "))";
	
	//Replace the old sig line with the new
	str_func_new_body 		= str_func_new_body.replace(regexp, new_sig_line);
	
	//Create the new function as a string
	var str_func_final 		= str_func_def + "\n " + str_func_new_body + "\n }" + "\n var testme = " + func_name + ";";

	//Eval the string to an actual function with the varname "testme"
	eval(str_func_final);
	
	//Return the new function
	return testme;
	
}//end update_func_compose

/**
 * Using this function, we can apply different functions to the signature
 */
function signature_filter(signature)
{		
	if ( GM_getValue('clear_dashes', false) ) {
		signature = remove_dashes(signature);
		
	}
	
	if ( GM_getValue('allow_html', false) ) {
		signature = htmlspecialchars_decode(signature);
		
	}	
	
	return signature;
	
}//end signature_filter

/**
 * Removes dashes from signature
 */
function remove_dashes(signature)
{	
	if ( !GM_getValue('clear_dashes', false) ) {
		return signature;
		
	}
	
	var regexp_html = /^<br clear=all><br>-- <br>/;
	var regexp_plain = /^\n\n-- \n/;
	var has_html_signature = signature.search(regexp_html);
	var has_plain_signature = signature.search(regexp_plain);
	
	if ( has_html_signature != -1 ) {
		return signature.replace(regexp_html, '<br clear=all><br>');
		
	} else if ( has_plain_signature != -1 ) {
		return signature.replace(regexp_plain, "\n\n");
		
	} else {
		return signature;
		
	}
	
}//end removeDashes

function htmlspecialchars_decode(signature)
{
	signature = signature.replace(/&amp;/g, "&");
	
	signature = signature.replace(/&quot;/g, '"');
	
	signature = signature.replace(/&lt;/g, "<");
	
	signature = signature.replace(/&gt;/g, ">");
	
	return signature;
	
	
}//end html_unentities

/**
 * Returns the function definition portion of a function
 */
function gsf_get_func_def(func_name, str_func)
{
	var regex = new RegExp("function " + escapeRegExChars(func_name) + "\\(.*\\)\\s*{", "ig");
	var arr_match = str_func.match(regex);
	return arr_match[0];
	
}//end gsf_get_func_def

/**
 * Returns the body portion of the function
 */
function gsf_get_func_body(str_func)
{
	var firstpos = str_func.indexOf('{');
	var lastpos  = str_func.lastIndexOf('}');
	return str_func.substring(firstpos+1, lastpos);
	
}//end gsf_get_func_body

/**
 * Adds "gsf_temp_window." before all function calls in the function definition passed in
 * 
 */
function gsf_escape_functions(str_func)
{
	//var regexp = /[\.]?[(a-zA-Z|0-9|\$)]+\(/g;
	var regexp = /[\.\s\(]?[a-zA-Z0-9\$\_]+\(/g;
	arr = str_func.match(regexp);
	
	if ( !arr ) {
		//No external functions found
		return str_func;	
	}
	
	//Step through the results
	var offset = 0;
	for ( var i=0; i<arr.length; i++ ) {
		//As long as it's not a method call
		if ( arr[i].substring(0, 1) != "." ) {
			var regexp = new RegExp(escapeRegExChars(arr[i]));
			if ( arr[i].substr(0, 1) == " " || arr[i].substr(0, 1) == "(" ) {
				var prefix = arr[i].substr(0, 1);
				var function_name = arr[i].substr(1);
				
			} else {
				var prefix = "";
				var function_name = arr[i];
				
			}
			
			str_func = str_func.replace(regexp, prefix + "gsf_temp_window." + function_name);
		
		}
		
	}//for

	return str_func;
	
}//end gsf_escape_functions

/**
 * Escapes common RegExp characters that also show up in code
 */
function escapeRegExChars(str)
{
	//$
	str = str.replace(/\$/g, "\\$");
//	GM_log('escape $: ' + str);
	
	//(
	str = str.replace(/\(/g, "\\" + "(");
//	GM_log('escape (: ' + str);
								
	//)
	str = str.replace(/\)/g, "\\)");
//	GM_log('escape ): ' + str);
	
	//whitespace
	str = str.replace(/\s/g, " ");
								
	return str;
	
}//end escapeRegExChars

})();