/*
 * Metadata - jQuery plugin for parsing metadata from elements
 *
 * Copyright (c) 2006 John Resig, Yehuda Katz, Jörn Zaefferer, Paul McLanahan
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * Revision: $Id$
 *
 */

/**
 * Sets the type of metadata to use. Metadata is encoded in JSON, and each property
 * in the JSON will become a property of the element itself.
 *
 * There are three supported types of metadata storage:
 *
 *   attr:  Inside an attribute. The name parameter indicates *which* attribute.
 *          
 *   class: Inside the class attribute, wrapped in curly braces: { }
 *   
 *   elem:  Inside a child element (e.g. a script tag). The
 *          name parameter indicates *which* element.
 *          
 * The metadata for an element is loaded the first time the element is accessed via jQuery.
 *
 * As a result, you can define the metadata type, use $(expr) to load the metadata into the elements
 * matched by expr, then redefine the metadata type and run another $(expr) for other elements.
 * 
 * @name $.metadata.setType
 *
 * @example <p id="one" class="some_class {item_id: 1, item_label: 'Label'}">This is a p</p>
 * @before $.metadata.setType("class")
 * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
 * @desc Reads metadata from the class attribute
 * 
 * @example <p id="one" class="some_class" data="{item_id: 1, item_label: 'Label'}">This is a p</p>
 * @before $.metadata.setType("attr", "data")
 * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
 * @desc Reads metadata from a "data" attribute
 * 
 * @example <p id="one" class="some_class"><script>{item_id: 1, item_label: 'Label'}</script>This is a p</p>
 * @before $.metadata.setType("elem", "script")
 * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
 * @desc Reads metadata from a nested script element
 * 
 * @param String type The encoding type
 * @param String name The name of the attribute to be used to get metadata (optional)
 * @cat Plugins/Metadata
 * @descr Sets the type of encoding to be used when loading metadata for the first time
 * @type undefined
 * @see metadata()
 */

(function($) {

$.extend({
	metadata : {
		defaults : {
			type: 'class',
			name: 'metadata',
			cre: /({.*})/,
			single: 'metadata'
		},
		setType: function( type, name ){
			this.defaults.type = type;
			this.defaults.name = name;
		},
		get: function( elem, opts ){
			var settings = $.extend({},this.defaults,opts);
			// check for empty string in single property
			if ( !settings.single.length ) settings.single = 'metadata';
			
			var data = $.data(elem, settings.single);
			// returned cached data if it already exists
			if ( data ) return data;
			
			data = "{}";
			
			if ( settings.type == "class" ) {
				var m = settings.cre.exec( elem.className );
				if ( m )
					data = m[1];
			} else if ( settings.type == "elem" ) {
				if( !elem.getElementsByTagName ) return;
				var e = elem.getElementsByTagName(settings.name);
				if ( e.length )
					data = $.trim(e[0].innerHTML);
			} else if ( elem.getAttribute != undefined ) {
				var attr = elem.getAttribute( settings.name );
				if ( attr )
					data = attr;
			}
			
			if ( data.indexOf( '{' ) <0 )
			data = "{" + data + "}";
			
			data = eval("(" + data + ")");
			
			$.data( elem, settings.single, data );
			return data;
		}
	}
});

/**
 * Returns the metadata object for the first member of the jQuery object.
 *
 * @name metadata
 * @descr Returns element's metadata object
 * @param Object opts An object contianing settings to override the defaults
 * @type jQuery
 * @cat Plugins/Metadata
 */
$.fn.metadata = function( opts ){
	return $.metadata.get( this[0], opts );
};

})(jQuery);$(function(){
    $("#overlay").css("height", $(document).height());

    $("#close-overlay").click(function(){
        $("#overlay").fadeOut();
    });

    // Keeps the dimness proportional to resized window.
    $(window).bind("resize", function(){
        $("#overlay").css("height", $(document).height());
    });

    // Press escape to close out.
    $(document).keyup(function(e){
        if (e.keyCode === 27) {
            $("#overlay").fadeOut();
        }
    });
});
// $('input[type="radio"].star').css('opacity', '0');

/*
 ### jQuery Star Rating Plugin v3.13 - 2009-03-26 ###
 * Home: http://www.fyneworks.com/jquery/star-rating/
 * Code: http://code.google.com/p/jquery-star-rating-plugin/
 *
	* Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 ###
*/

/*# AVOID COLLISIONS #*/
/*;if(window.jQuery)*/ jQuery(function($){
/*# AVOID COLLISIONS #*/
	
	// IE6 Background Image Fix
	if ($.browser.msie) try { document.execCommand("BackgroundImageCache", false, true)} catch(e) { };
	// Thanks to http://www.visualjquery.com/rating/rating_redux.html
	
	// plugin initialization
	$.fn.rating = function(options){
		if(this.length==0) return this; // quick fail
		
		// Handle API methods
		if(typeof arguments[0]=='string'){
			// Perform API methods on individual elements
			if(this.length>1){
				var args = arguments;
				return this.each(function(){
					$.fn.rating.apply($(this), args);
    });
			};
			// Invoke API method handler
			$.fn.rating[arguments[0]].apply(this, $.makeArray(arguments).slice(1) || []);
			// Quick exit...
			return this;
		};
		
		// Initialize options for this call
		var options = $.extend(
			{}/* new object */,
			$.fn.rating.options/* default options */,
			options || {} /* just-in-time options */
		);
		
		// Allow multiple controls with the same name by making each call unique
		$.fn.rating.calls++;
		
		// loop through each matched element
		this
		 .not('.star-rating-applied')
			.addClass('star-rating-applied')
		.each(function(){
			
			// Load control parameters / find context / etc
			var control, input = $(this);
			var eid = (this.name || 'unnamed-rating').replace(/\[|\]/g, '_').replace(/^\_+|\_+$/g,'');
			var context = $(this.form || document.body);
			
			// FIX: http://code.google.com/p/jquery-star-rating-plugin/issues/detail?id=23
			var raters = context.data('rating');
			if(!raters || raters.call!=$.fn.rating.calls) raters = { count:0, call:$.fn.rating.calls };
			var rater = raters[eid];
			
			// if rater is available, verify that the control still exists
			if(rater) control = rater.data('rating');
			
			if(rater && control)//{// save a byte!
				// add star to control if rater is available and the same control still exists
				control.count++;
				
			//}// save a byte!
			else{
				// create new control if first star or control element was removed/replaced
				
				// Initialize options for this raters
				control = $.extend(
					{}/* new object */,
					options || {} /* current call options */,
					($.metadata? input.metadata(): ($.meta?input.data():null)) || {}, /* metadata options */
					{ count:0, stars: [], inputs: [] }
				);
				
				// increment number of rating controls
				control.serial = raters.count++;
				
				// create rating element
				rater = $('<span class="star-rating-control"/>');
				input.before(rater);
				
				// Mark element for initialization (once all stars are ready)
				rater.addClass('rating-to-be-drawn');
				
				// Accept readOnly setting from 'disabled' property
				if(input.attr('disabled')) control.readOnly = true;
				
				// Create 'cancel' button
				/*rater.append(
					control.cancel = $('<div class="rating-cancel"><a title="' + control.cancel + '">' + control.cancelValue + '</a></div>')
					.mouseover(function(){
						$(this).rating('drain');
						$(this).addClass('star-rating-hover');
						//$(this).rating('focus');
					})
					.mouseout(function(){
						$(this).rating('draw');
						$(this).removeClass('star-rating-hover');
						//$(this).rating('blur');
					})
					.click(function(){
					 $(this).rating('select');
					})
					.data('rating', control)
				);*/
				
			}; // first element of group
			
			// insert rating star
			var star = $('<div class="star-rating rater-'+ control.serial +'"><a title="' + (this.title || this.value) + '">' + this.value + '</a></div>');
			rater.append(star);
			
			// inherit attributes from input element
			if(this.id) star.attr('id', this.id);
			if(this.className) star.addClass(this.className);
			
			// Half-stars?
			if(control.half) control.split = 2;
			
			// Prepare division control
			if(typeof control.split=='number' && control.split>0){
				var stw = ($.fn.width ? star.width() : 0) || control.starWidth;
				var spi = (control.count % control.split), spw = Math.floor(stw/control.split);
				star
				// restrict star's width and hide overflow (already in CSS)
				.width(spw)
				// move the star left by using a negative margin
				// this is work-around to IE's stupid box model (position:relative doesn't work)
				.find('a').css({ 'margin-left':'-'+ (spi*spw) +'px' })
			};
			
			// readOnly?
			if(control.readOnly)//{ //save a byte!
				// Mark star as readOnly so user can customize display
				star.addClass('star-rating-readonly');
			//}  //save a byte!
			else//{ //save a byte!
			 // Enable hover css effects
				star.addClass('star-rating-live')
				 // Attach mouse events
					.mouseover(function(){
						$(this).rating('fill');
						$(this).rating('focus');
					})
					.mouseout(function(){
						$(this).rating('draw');
						$(this).rating('blur');
					})
					.click(function(){
						$(this).rating('select');
					})
				;
			//}; //save a byte!
			
			// set current selection
			if(this.checked)	control.current = star;
			
			// hide input element
			input.hide();
			
			// backward compatibility, form element to plugin
			input.change(function(){
    $(this).rating('select');
   });
			
			// attach reference to star to input element and vice-versa
			star.data('rating.input', input.data('rating.star', star));
			
			// store control information in form (or body when form not available)
			control.stars[control.stars.length] = star[0];
			control.inputs[control.inputs.length] = input[0];
			control.rater = raters[eid] = rater;
			control.context = context;
			
			input.data('rating', control);
			rater.data('rating', control);
			star.data('rating', control);
			context.data('rating', raters);
  }); // each element
		
		// Initialize ratings (first draw)
		$('.rating-to-be-drawn').rating('draw').removeClass('rating-to-be-drawn');
		
		return this; // don't break the chain...
	};
	
	/*--------------------------------------------------------*/
	
	/*
		### Core functionality and API ###
	*/
	$.extend($.fn.rating, {
		// Used to append a unique serial number to internal control ID
		// each time the plugin is invoked so same name controls can co-exist
		calls: 0,
		
		focus: function(){
			var control = this.data('rating'); if(!control) return this;
			if(!control.focus) return this; // quick fail if not required
			// find data for event
			var input = $(this).data('rating.input') || $( this.tagName=='INPUT' ? this : null );
   // focus handler, as requested by focusdigital.co.uk
			if(control.focus) control.focus.apply(input[0], [input.val(), $('a', input.data('rating.star'))[0]]);
		}, // $.fn.rating.focus
		
		blur: function(){
			var control = this.data('rating'); if(!control) return this;
			if(!control.blur) return this; // quick fail if not required
			// find data for event
			var input = $(this).data('rating.input') || $( this.tagName=='INPUT' ? this : null );
   // blur handler, as requested by focusdigital.co.uk
			if(control.blur) control.blur.apply(input[0], [input.val(), $('a', input.data('rating.star'))[0]]);
		}, // $.fn.rating.blur
		
		fill: function(){ // fill to the current mouse position.
			var control = this.data('rating'); if(!control) return this;
			// do not execute when control is in read-only mode
			if(control.readOnly) return;
			// Reset all stars and highlight them up to this element
			this.rating('drain');
			this.prevAll().andSelf().filter('.rater-'+ control.serial).addClass('star-rating-hover');
		},// $.fn.rating.fill
		
		drain: function() { // drain all the stars.
			var control = this.data('rating'); if(!control) return this;
			// do not execute when control is in read-only mode
			if(control.readOnly) return;
			// Reset all stars
			control.rater.children().filter('.rater-'+ control.serial).removeClass('star-rating-on').removeClass('star-rating-hover');
		},// $.fn.rating.drain
		
		draw: function(){ // set value and stars to reflect current selection
			var control = this.data('rating'); if(!control) return this;
			// Clear all stars
			this.rating('drain');
			// Set control value
			if(control.current){
				control.current.data('rating.input').attr('checked','checked');
				control.current.prevAll().andSelf().filter('.rater-'+ control.serial).addClass('star-rating-on');
			}
			else
			 $(control.inputs).removeAttr('checked');
			// Show/hide 'cancel' button
			// control.cancel[control.readOnly || control.required?'hide':'show']();
			// Add/remove read-only classes to remove hand pointer
			this.siblings()[control.readOnly?'addClass':'removeClass']('star-rating-readonly');
		},// $.fn.rating.draw
		
		
		
		
		
		select: function(value,wantCallBack){ // select a value
					
					// ***** MODIFICATION *****
					// Thanks to faivre.thomas - http://code.google.com/p/jquery-star-rating-plugin/issues/detail?id=27
					//
					// ***** LIST OF MODIFICATION *****
					// ***** added Parameter wantCallBack : false if you don't want a callback. true or undefined if you want postback to be performed at the end of this method'
					// ***** recursive calls to this method were like : ... .rating('select') it's now like .rating('select',undefined,wantCallBack); (parameters are set.)
					// ***** line which is calling callback
					// ***** /LIST OF MODIFICATION *****
			
			var control = this.data('rating'); if(!control) return this;
			// do not execute when control is in read-only mode
			if(control.readOnly) return;
			// clear selection
			control.current = null;
			// programmatically (based on user input)
			if(typeof value!='undefined'){
			 // select by index (0 based)
				if(typeof value=='number')
 			 return $(control.stars[value]).rating('select',undefined,wantCallBack);
				// select by literal value (must be passed as a string
				if(typeof value=='string')
					//return
					$.each(control.stars, function(){
						if($(this).data('rating.input').val()==value) $(this).rating('select',undefined,wantCallBack);
					});
			}
			else
				control.current = this[0].tagName=='INPUT' ?
				 this.data('rating.star') :
					(this.is('.rater-'+ control.serial) ? this : null);

			// Update rating control state
			this.data('rating', control);
			// Update display
			this.rating('draw');
			// find data for event
			var input = $( control.current ? control.current.data('rating.input') : null );
			// click callback, as requested here: http://plugins.jquery.com/node/1655
					
					// **** MODIFICATION *****
					// Thanks to faivre.thomas - http://code.google.com/p/jquery-star-rating-plugin/issues/detail?id=27
					//
					//old line doing the callback :
					//if(control.callback) control.callback.apply(input[0], [input.val(), $('a', control.current)[0]]);// callback event
					//
					//new line doing the callback (if i want :)
					if((wantCallBack || wantCallBack == undefined) && control.callback) control.callback.apply(input[0], [input.val(), $('a', control.current)[0]]);// callback event
					//to ensure retro-compatibility, wantCallBack must be considered as true by default
					// **** /MODIFICATION *****
					
  },// $.fn.rating.select
		
		
		
		
		
		readOnly: function(toggle, disable){ // make the control read-only (still submits value)
			var control = this.data('rating'); if(!control) return this;
			// setread-only status
			control.readOnly = toggle || toggle==undefined ? true : false;
			// enable/disable control value submission
			if(disable) $(control.inputs).attr("disabled", "disabled");
			else     			$(control.inputs).removeAttr("disabled");
			// Update rating control state
			this.data('rating', control);
			// Update display
			this.rating('draw');
		},// $.fn.rating.readOnly
		
		disable: function(){ // make read-only and never submit value
			this.rating('readOnly', true, true);
		},// $.fn.rating.disable
		
		enable: function(){ // make read/write and submit value
			this.rating('readOnly', false, false);
		}// $.fn.rating.select
		
 });
	
	/*--------------------------------------------------------*/
	
	/*
		### Default Settings ###
		eg.: You can override default control like this:
		$.fn.rating.options.cancel = 'Clear';
	*/
	$.fn.rating.options = { //$.extend($.fn.rating, { options: {
			cancel: 'Cancel Rating',   // advisory title for the 'cancel' link
			cancelValue: '',           // value to submit when user click the 'cancel' link
			split: 0,                  // split the star into how many parts?
			
			// Width of star image in case the plugin can't work it out. This can happen if
			// the jQuery.dimensions plugin is not available OR the image is hidden at installation
			starWidth: 16,
			
			//NB.: These don't need to be pre-defined (can be undefined/null) so let's save some code!
			//half:     false,         // just a shortcut to control.split = 2
			//required: false,         // disables the 'cancel' button so user can only select one of the specified values
			//readOnly: false,         // disable rating plugin interaction/ values cannot be changed
			//focus:    function(){},  // executed when stars are focused
			//blur:     function(){},  // executed when stars are focused
			callback: function(){
				var form = $(this.form);
				$.ajax({
					type: form.attr('method'),
					url: form.attr('action'),
					data: form.serialize(),
					dataType: 'json',
					success: function(response){
						$("#notes").prepend("<div class='note'>Rated an <a href='/o/" + response.organism + "'>object</a><span class='extra'> (" + response.tags  + ")</span>.</div>");
                        updateFeed();
						var ratingInfo = $("#org-" + response.organism + " input[name='" + response.organism + "']");
						ratingInfo.rating('readOnly', false);
						ratingInfo.rating('select', response.rating.toString(), false);
						ratingInfo.rating('readOnly', true);
						$("#org-" + response.organism + " .num-ratings").html("(" + response.numRatings + ")");
					}
				});
			},  // executed when a star is clicked
 }; //} });
	
	/*--------------------------------------------------------*/
	
	/*
		### Default implementation ###
		The plugin will attach itself to file inputs
		with the class 'multi' when the page loads
	*/


	$(function(){
	  $('input[type="radio"].star').rating();
	});
	
	
	
/*# AVOID COLLISIONS #*/
    });
//(jQuery);
/*# AVOID COLLISIONS #*/


$(function(){
    $('input[type="radio"].star').css('opacity', '1');
});
/***********************
 * Message Feed (Init) *
 ***********************/

var space;
var num_notes;

var notes;
var first_note;
var current_note;
var last_note;
var lr;

function updateFeed() {
    notes = $("#notes").children(".note");
    first_note = notes.first(".note");
    last_note = notes.last(".note");
    space = $("#notes").parent().is("#announcements");
    num_notes = notes.length;
    
    if (!space) {
        first_note.css("left", "800px");
    }

    first_note.hide();

    if (num_notes === 0) {
        $("#left-note .arrow").hide();
        $("#left-note").css("cursor", "default");
        $("#right-note .arrow").hide();
        $("#right-note").css("cursor", "default");
    }
    else {
        if (space) {
            $("#notes .empty").fadeOut();
            first_note.slideDown();
            if (num_notes > 5) {
                $("#notes .note:nth-child(6)").hide(function(){
                    $("#notes .note:nth-child(6)").remove();
                });
            }
        }
        else if (!space && num_notes === 1) {
            first_note.show();
            first_note.stop(false, true).animate({left: "0px"}, "fast");
            current_note = first_note;
        }
        else if (!space && num_notes > 0) {
            current_note.stop(false, true).animate({left: "-800px"}, "fast",
            function(){
                $("#left-note .arrow").fadeIn();
                $("#left-note").css("cursor", "pointer");
                $("#right-note .arrow").hide();
                $("#right-note").css("cursor", "default");
                current_note.hide();
                $("#notes .note").css("left", "-800px");
                first_note.css("left", "800px");
                first_note.show();
                first_note.stop(false, true).animate({left: "0px"}, "fast");
                current_note = first_note;
            });
        }
    }
}

function cleanFeed(type) {
    if (!space) {
        while (current_note.is(type)) {
            current_note = current_note.next(".note");
        }
        $("#notes " + type).remove();
        if (current_note.length === 0) {
            current_note = $("#notes .note").last();
        }
        current_note.show();
        current_note.stop(false, true).animate({left: "0px"}, "fast");

        notes = $("#notes").children(".note");
        first_note = notes.first(".note");
        last_note = notes.last(".note");
        if (current_note[0] === first_note[0]) {
            $("#right-note .arrow").hide();
            $("#right-note").css("cursor", "default");
        }
        if (current_note[0] === last_note[0]) {
            $("#left-note .arrow").hide();
            $("#left-note").css("cursor", "default");
        }
    } else {
        $("#notes " + type).remove();
    }
}

$(document).ready(function(){
	
	/****************
	 * Message Feed *
	 ****************/

    updateFeed();
    $("#main-space .note").live("load", function() {
    });

	$("#left-note, #right-note").click(function(){
		notes = $("#notes").children(".note"); 
		lr = $(this).is("#left-note");
		if (lr && current_note[0] != last_note[0]) {
			current_note.stop().animate({left: "800px"}, function(){
				current_note.hide();
                current_note = current_note.next(".note");
                current_note.show();
                current_note.stop(false, true).animate({left: "0px"});
                $("#right-note .arrow").fadeIn();
                $("#right-note").css("cursor", "pointer");
                if (current_note[0] === last_note[0]) {
                    $("#left-note .arrow").hide();
                    $("#left-note").css("cursor", "default");
                } else {
                    $("#left-note .arrow").fadeIn();
                    $("#left-note").css("cursor", "pointer");
                }
            });
        }
        else if (!lr && current_note[0] != first_note[0]) {
            current_note.stop().animate({left: "-800px"}, function(){
                current_note.hide();
                current_note = current_note.prev(".note");
				current_note.show();
				current_note.stop(false, true).animate({left: "0px"});
                $("#left-note .arrow").fadeIn();
                $("#left-note").css("cursor", "pointer");
                if (current_note[0] === first_note[0]) {
                    $("#right-note .arrow").hide();
                    $("#right-note").css("cursor", "default");
                } else {
                    $("#right-note .arrow").fadeIn();
                    $("#right-note").css("cursor", "pointer");
                }
			});
		}
	});

});
$(document).ready(function(){

	/**************
	 * Tip Slider *
	 **************/
	
	var tips = $("#announcements").children(".tip");
	var num_tips = tips.length;
	var i;
	var dir;
	
	for (i = 1; i < num_tips; i++) {
		$(tips[i]).css("left", "450px");
        if (!($(tips[i]).is("#notes"))) {
            $(tips[i]).hide();
        }
    }
		
	i = 0
	
	$("#left-tip, #right-tip").click(function(){
		dir = $(this).is("#right-tip");
		$(tips[i]).stop().animate({left: "450px"},
			function(){
				if (dir) {
					$(tips[i++]).hide();
					if (i >= num_tips) i = 0;
				}
				else {
					$(tips[i--]).hide();
					if (i < 0) i = num_tips - 1;
				}
				$(tips[i]).show();
				$(tips[i]).stop(false, true).animate({left: "0px"});
		});
	});

});
$(document).ready(function() {
    if ($('#userline input').val() === '') {
        $('#userline input').val('search');
        $('#userline input').bind("focusin", function(){
            $('#userline input').val('');
            $('#userline input').unbind("focusin");
        });
    }

    if ($('#id_text').val() === '') {
        $('#id_text').val('(insert suggestion)');
        $('#id_text').bind("focusin", function(){
            $('#id_text').val('');
            $('#id_text').unbind("focusin");
        });
    }

    $('#id_text, #userline input').focusin(function() {
        this.value = '';
        $(this).unbind("focusin");
    });

    $('#userline input').focusout(function() {
        if (this.value === '') {
            this.value = 'search';
            $(this).bind("focusin", function(){
                this.value = '';
                $(this).unbind("focusin");
            });
        }
    });

    $('#id_text').focusout(function() {
        if (this.value === '') {
            this.value = '(insert suggestion)';
            $(this).bind("focusin", function(){
                this.value = '';
                $(this).unbind("focusin");
            });
        }
    });

    $('form.QFForm').submit(function(event) {
        //alert('submitted (not really)');
        //return false;

        event.preventDefault();        

        form = $(this)

        var data = form.serialize();

        //alert(data);

        //$(this).children('input').focus(function() {
        //});
        //console.log(        $(this).find('input'))
        $(this).find('input').blur().attr("disabled", true);
        //$(this).find('input').attr("disabled", true);

        //alert('submitting...');
        
        jQuery.ajax({
            url:  form.attr('action'),
            type: form.attr('method'),
            data: data,
            dataType: 'text',
            success: function(txt){
                form.replaceWith('<p>Thanks for your feedback!</p>');
            },
            error: function(xhr, ajaxOptions, thrownError){
                alert('Sorry, something went wrong :(');
            }
        });

        //$.post("/quick_feedback",
        //       form.serialize(),
        //       function(data) {
        //           //$('#'+formId).append('<p>Extra!</p>');
        //           //form.append('<p>' + data + '</p>');
        //           form.replaceWith('<p>' + data + '</p>');
        //           //form.children('input').blur();
        //           //$('#'+formId).css('background: red;')
        //           //alert(data);
        //       }
        //      );

        //return false;
    });
});
$(function(){
    $("form.formSubmit a").click(function(){
        $(this).parent("form").submit();        
    });
});
$(document).ready(function(){

    /********************
     * Generation Page  *
     ********************/

    // Appends either 'evolve' or 'publish' to the post data.
    $("input[name='evolve'], input[name='publish']").click(function(){
        var input = $("<input>").attr("name", this.name).attr("type", "hidden").val("");
        $("form[name='generation']").append(input).submit();
    });

    // Recolors boxes upload reload or going back or forward in history.
    $(".org-box input[type='checkbox']").each(function(){
        if ($(this).is(":checked")) {
            $(this).closest(".org-box").addClass("selected");
        }
    });

    if($.browser.msie) {
        $(".org-box").click(function(){
            var checkbox = $(this).children("input[type='checkbox']");
            if (checkbox.is("input")) {
                if (checkbox.is(":checked")) {
                    checkbox.removeAttr("checked");
                    $(this).removeClass("selected");
                }
                else {
                    checkbox.attr("checked", "checked");
                    $(this).addClass("selected");
                }
                toggleSubmit();
            }
        });
    }

    // Checkbox clicking.
    $(".org-box input[type='checkbox']").change(function(){
        if ($(this).is(":checked")) {
            $(this).closest(".org-box").addClass("selected");
        }
        else {
            $(this).closest(".org-box").removeClass("selected");
        }
    });

    // Evolve and publish buttons.

    var evolveButton = $(':submit[name="evolve"]');
    var publishButton = $(':submit[name="publish"]');

    function toggleSubmit(){
        if ($('input[name="individual"]:checked').length < 1) {
            evolveButton.attr('disabled', 'disabled');
            publishButton.attr('disabled', 'disabled');
        }
        else if ($('input[name="individual"]:checked').length == 1) {
            evolveButton.removeAttr('disabled');
            publishButton.removeAttr('disabled');
        }
        else {
            evolveButton.removeAttr('disabled');
            publishButton.attr('disabled', 'disabled');
        }
    }

    toggleSubmit();

    $(':checkbox[name="individual"]').click(function(){
        toggleSubmit();
    });

    $('form[name="generation"]').submit(function(){
        // $(this).submit(function(){
        evolveButton.attr('disabled', 'disabled');
        publishButton.attr('disabled', 'disabled');
        // });
        // evolveButton.removeAttr('disabled');
        // publishButton.removeAttr('disabled');
    });

});
$(document).ready(function(){

    /**********************
	 * Sidebar Link Hover *
	 **********************/
	
	$(".menu .inner a").hover(function(){
		$(this).stop().animate({fontSize: "26px"}, "fast");
		$(this).css("color", "#444");
	}, function(){
		$(this).stop().animate({fontSize: "22px"}, "fast");
		$(this).css("color", "#888");
	});

});
/*
 * jQuery Hotkeys Plugin
 * Copyright 2010, John Resig
 * Dual licensed under the MIT or GPL Version 2 licenses.
 *
 * Based upon the plugin by Tzury Bar Yochay:
 * http://github.com/tzuryby/hotkeys
 *
 * Original idea by:
 * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
*/

(function(jQuery){
	
	jQuery.hotkeys = {
		version: "0.8",

		specialKeys: {
			8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
			20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
			37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 
			96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
			104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", 
			112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", 
			120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta"
		},
	
		shiftNums: {
			"`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", 
			"8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", 
			".": ">",  "/": "?",  "\\": "|"
		}
	};

	function keyHandler( handleObj ) {
		// Only care when a possible input has been specified
		if ( typeof handleObj.data !== "string" ) {
			return;
		}
		
		var origHandler = handleObj.handler,
			keys = handleObj.data.toLowerCase().split(" ");
	
		handleObj.handler = function( event ) {
			// Don't fire in text-accepting inputs that we didn't directly bind to
			if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) ||
				 event.target.type === "text") ) {
				return;
			}
			
			// Keypress represents characters, not special keys
			var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ],
				character = String.fromCharCode( event.which ).toLowerCase(),
				key, modif = "", possible = {};

			// check combinations (alt|ctrl|shift+anything)
			if ( event.altKey && special !== "alt" ) {
				modif += "alt+";
			}

			if ( event.ctrlKey && special !== "ctrl" ) {
				modif += "ctrl+";
			}
			
			// TODO: Need to make sure this works consistently across platforms
			if ( event.metaKey && !event.ctrlKey && special !== "meta" ) {
				modif += "meta+";
			}

			if ( event.shiftKey && special !== "shift" ) {
				modif += "shift+";
			}

			if ( special ) {
				possible[ modif + special ] = true;

			} else {
				possible[ modif + character ] = true;
				possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true;

				// "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
				if ( modif === "shift+" ) {
					possible[ jQuery.hotkeys.shiftNums[ character ] ] = true;
				}
			}

			for ( var i = 0, l = keys.length; i < l; i++ ) {
				if ( possible[ keys[i] ] ) {
					return origHandler.apply( this, arguments );
				}
			}
		};
	}

	jQuery.each([ "keydown", "keyup", "keypress" ], function() {
		jQuery.event.special[ this ] = { add: keyHandler };
	});

})( jQuery );$(document).ready(function(){

    /******************
     * Organism Hover *
     ******************/

    $(".org-box").hover(function(){
        $(this).children(".org-info").stop().animate({bottom: "0px"}, "fast");
        $(this).children(".zoom").stop().animate({top: "0px"}, "fast");
    }, function(){
        $(this).children(".org-info").stop().animate({bottom: "-75px"}, "fast");
        $(this).children(".zoom").stop().animate({top: "-40px"}, "fast");
    });

});

// Gracefully degrade logging
if (!window.console) console = {};
console.log = console.log || function(){};
console.warn = console.warn || function(){};
console.error = console.error || function(){};
console.info = console.info || function(){};


// Rendering Parameters
XROT_INIT = 0.0;
YROT_INIT = -.04;
FREQ = 25;

XROT = XROT_INIT;
YROT = YROT_INIT;

TIMEOUT = 30000

var intervals  = new Array();
var boundLoops = new Array();

var renderingTimeout = setTimeout(stopRendering, TIMEOUT);
var stoppedRotation = false;

function stopRendering()
{
    if (intervals.length > 0) {
        while (intervals.length > 0) {
            clearInterval(intervals.pop());
        }
        $("#notes").prepend("<div class='note rotation'>Press left or right key to restart rotation.</div>");
        updateFeed();
        stoppedRotation = true;
    }
}

function startRendering()
{
    if (intervals.length == 0) {
        for (ii = 0; ii < boundLoops.length; ii++) {
            intervals.push(setInterval(boundLoops[ii], 1000 / FREQ ));
        }
    }
    clearTimeout(renderingTimeout)
    renderingTimeout = setTimeout(stopRendering, TIMEOUT);

}

function renderingIsActive() {
    return (intervals.length > 0)
}

function sleep(ms)
{
    var dt = new Date();
    dt.setTime(dt.getTime() + ms);
    while (new Date().getTime() < dt.getTime()) {
    }
}

var xlight = Math.random() - 0.5;
var ylight = Math.random() - 0.5;
var zlight = Math.random() - 0.5;
var xlight2 = Math.random() - 0.5;
var ylight2 = Math.random() - 0.5;
var zlight2 = Math.random() - 0.5;

function displayShape(name, data)
{
    var camera, scene, renderer, object, hasWebGl;

    //console.log('sleeping...');
    //sleep(2000)
    //console.log('done.');

    var stats;

    init();

    boundLoops.push(loop);
    intervals.push(setInterval( loop, 1000 / FREQ ));

    function init() {
        divId = 'window-' + name;

        var container = document.getElementById( divId );

        try {
            //renderer = new THREE.CanvasRenderer();
            renderer = new THREE.WebGLRenderer();
            hasWebGl = true;
        } catch(err) {
            delete renderer;
            renderer = new THREE.CanvasRenderer();
            hasWebGl = false;
        }

        if (!hasWebGl) {
            $('#browserWarning2').removeClass('hidden');
        }

        camera = new THREE.Camera( 50, container.offsetWidth / container.offsetHeight, 10, 2000 );
        camera.position.z = 275;

        scene = new THREE.Scene();
        console.log('scene created' + scene);

        shape = shapeFnFromData(name, data);

        if (hasWebGl) {
            object = new THREE.Mesh( new shape(), new THREE.MeshPhongMaterial( { ambient: 0x666666, color: 0x888888, specular: 0x444444, shininess: 100 } ) );
        } else {
            object = new THREE.Mesh( new shape(), new THREE.MeshNormalMaterial() ); //rainbow coloring, ignores other lights
            //object = new THREE.Mesh( new shape(), new THREE.MeshNormalMaterial( {color: 0xffffff, shading: THREE.SmoothShading} )); //white
            //object = new THREE.Mesh( new shape(), new THREE.MeshLambertMaterial( {color: 0xffffff, shading: THREE.SmoothShading} ));
    
            //object = new THREE.Mesh( new shape(), new THREE.MeshLambertMaterial( {color: 0xffffff, shading: THREE.FlatShading} ));
            //object = new THREE.Mesh( new Sphere( 10, 14, 7, false ), new THREE.MeshLambertMaterial( {color: 0xffffff, shading: THREE.SmoothShading} ));
            //object = new THREE.Mesh( new shape(), new THREE.MeshLambertMaterial( {color: 0xffffff, shading: THREE.SmoothShading} ));
            //object = new THREE.Mesh( new shape(), new THREE.MeshLambertMaterial( {color: 0xffffff, shading: THREE.FlatShading} ));
            //object = new THREE.Mesh( new shape(), new THREE.MeshLambertMaterial( {color: 0xffffff, shading: THREE.FlatShading} ));
    
            //object = new THREE.Mesh( new Sphere( 10, 14, 7, false ), new THREE.MeshPhongMaterial( { ambient: 0x050505, color: 0x000000, specular: 0x555555, shininess: 30 } ) )
    
            //object = new THREE.Mesh( new WaltHead(), new THREE.MeshBasicMaterial( { env_map: ImageUtils.loadTexture( '/media/js/metal.jpg', new THREE.SphericalReflectionMapping() ) } ) );      
            //object_{{ organism.name }} = new THREE.Mesh( new testShape(), new THREE.MeshNormalMaterial() );
        }
    
        object.overdraw = true;
        object.scale.x = object.scale.y = object.scale.z = 10;
        scene.addObject( object );

        //var ambientLight = new THREE.AmbientLight( 0x515151 );
        //scene.addLight( ambientLight );

        //var directionalLight1 = new THREE.DirectionalLight(0xff0000);       // just red light
        //var directionalLight1 = new THREE.DirectionalLight(0x483d8b);   // Jeff's colors
        //var directionalLight2 = new THREE.DirectionalLight(0x0000cd); 
        //var directionalLight3 = new THREE.DirectionalLight(0x4169e1);  //from the left
        var directionalLight2 = new THREE.DirectionalLight(0x888888);  // just white
        var directionalLight3 = new THREE.DirectionalLight(0x888888);
        //directionalLight.position.x = xlight;
        //directionalLight.position.y = ylight;
        //directionalLight.position.z = zlight;
        //var directionalLight2 = new THREE.DirectionalLight(  0x404000 );
        //var directionalLight2 = new THREE.DirectionalLight(  0x008822); underwaterblue
        //var directionalLight2 = new THREE.DirectionalLight(0xB31B1B); //cornell red

        //directionalLight1.position.x = .19;
        //directionalLight1.position.y = 0;
        //directionalLight1.position.z = .45;
        directionalLight2.position.x = .3;
        directionalLight2.position.y = .4;
        directionalLight2.position.z = .2;
        directionalLight3.position.x = -.5;
        directionalLight3.position.y = 0;
        directionalLight3.position.z = 0.5;

        //console.log(directionalLight1.position.x);
        //console.log(directionalLight1.position.y);
        //console.log(directionalLight1.position.z);
        //console.log(directionalLight2.position.x);
        //console.log(directionalLight2.position.y);
        //console.log(directionalLight2.position.z);
        //console.log(directionalLight3.position.x);
        //console.log(directionalLight3.position.y);
        //console.log(directionalLight3.position.z);

        //directionalLight1.position.normalize();
        directionalLight2.position.normalize();
        directionalLight3.position.normalize();

        //scene.addLight( directionalLight1 );
        scene.addLight( directionalLight2 );
        scene.addLight( directionalLight3 );

        //var pointLight = new THREE.PointLight( 0xff8888, 1 );
        //pointLight.position.x = -1.5;
        //pointLight.position.x = 3;
        //pointLight.position.x = 5.5;
        //scene.addLight( pointLight );

        //renderer = new THREE.CanvasRenderer();
        //renderer = new THREE.WebGLRenderer();
        renderer.setSize( container.offsetWidth, container.offsetHeight );
        container.appendChild( renderer.domElement );

        $(container).removeClass('loading')
    }

    function loop() {

        var time = new Date().getTime() * 0.0005;
    
        object.rotation.x += XROT;
        object.rotation.y += YROT;

        renderer.render(scene, camera);
    }

}

function displayShapeStatic(name, data, YROT)
{
    var camera, scene, renderer, object, hasWebGl;

    var stats;
    
    i = 1;
    
    //YROT = 1;
    
    //document.title = 'start';
    
    init();

    //loop();
    //object.rotation.x += XROT;
    object.rotation.y += YROT;
    renderer.render(scene, camera);

    function init() {
        divId = 'window-' + name;

        var container = document.getElementById( divId );

        try {
            //renderer = new THREE.CanvasRenderer();
            renderer = new THREE.WebGLRenderer();
            hasWebGl = true;
        } catch(err) {
            delete renderer;
            renderer = new THREE.CanvasRenderer();
            hasWebGl = false;
        }

        if (!hasWebGl) {
            $('#browserWarning2').removeClass('hidden');
        }

        camera = new THREE.Camera( 50, container.offsetWidth / container.offsetHeight, 10, 2000 );
        camera.position.z = 275;

        scene = new THREE.Scene();
        console.log('scene created' + scene);

        shape = shapeFnFromData(name, data);

        if (hasWebGl) {
            object = new THREE.Mesh( new shape(), new THREE.MeshPhongMaterial( { ambient: 0x666666, color: 0x888888, specular: 0x444444, shininess: 100 } ) );
        } else {
            object = new THREE.Mesh( new shape(), new THREE.MeshNormalMaterial() ); //rainbow coloring, ignores other lights
        }
    
        object.overdraw = true;
        object.scale.x = object.scale.y = object.scale.z = 10;
        scene.addObject( object );

        var directionalLight2 = new THREE.DirectionalLight(0x888888);  // just white
        var directionalLight3 = new THREE.DirectionalLight(0x888888);

        directionalLight2.position.x = .3;
        directionalLight2.position.y = .4;
        directionalLight2.position.z = .2;
        directionalLight3.position.x = -.5;
        directionalLight3.position.y = 0;
        directionalLight3.position.z = 0.5;

        directionalLight2.position.normalize();
        directionalLight3.position.normalize();

        //scene.addLight( directionalLight1 );
        scene.addLight( directionalLight2 );
        scene.addLight( directionalLight3 );

        renderer.setSize( container.offsetWidth, container.offsetHeight );
        container.appendChild( renderer.domElement );

        $(container).removeClass('loading')
    }
    
    //renderer.render(scene, camera);
    
    /*
    function loop() {
        while(i < 2){
            if(document.title=='start'){
                object.rotation.y += YROT;
                renderer.render(scene, camera);
                document.title = 'done'; //inform repl 
                //document.title=='rotate'; //get response from repl
                i++;
            }
            if(document.title=='rotate'){
                //object.rotation.y += YROT;
                renderer.render(scene, camera);
                document.title = 'done'; //inform repl
                //document.title=='rotate'; //get response from repl
                i++;
            }
        }
        document.title ='complete';
    }
    */
    

}

if(typeof(String.prototype.trim) === "undefined")
{
    String.prototype.trim = function() 
    {
        return String(this).replace(/^\s+|\s+$/g, '');
    };
}



function shapeFnFromData(name, data)
{
    var ret = function () {
    
        var scope = this;
    
        THREE.Geometry.call( this );

        //console.log('data is "' + data + '"');
        data = data.trim();
        //console.log('data is "' + data + '"');


        lines = data.split('\n');

        pos = 0;
        vfVersion = lines[pos];
        pos += 1
        //console.log('vfVersion is ' + vfVersion);
        if (vfVersion != 'v0.4') {
            alert('Expected version v0.4 but got ' + vfVersion);
        }
        //lines = lines.slice(1);

        nVertices = +lines[pos]
        pos += 1
        //console.log('File: ' + name)
        //console.log('   nVertices: ' + nVertices)

        var ii;
        for (ii = pos; ii < nVertices + pos; ii++) {
            //console.log(ii + ': ' + lines[ii]);
            nums = lines[ii].split(' ');
            if (nums.length != 6) {
                console.error('Error: ' + name + ' nums.length = ' + nums.length);
            } else {
                // x from .5 to  9.5   avg  5
                // y from .5 to 19.5   avg 10
                // z from .5 to  9.5   avg  5
                //scope.vertices.push(new THREE.Vertex(new THREE.Vector3(parseFloat(nums[0]),
                //                                                       parseFloat(nums[1]),
                //                                                       parseFloat(nums[2]))));
                //scope.vertices.push(new THREE.Vertex(new THREE.Vector3(parseFloat(nums[0])-5,
                //                                                       parseFloat(nums[1])-10,
                //                                                       parseFloat(nums[2])-5)));
                scope.vertices.push(new THREE.Vertex(new THREE.Vector3((+nums[0])-5,
                                                                       (+nums[1])-10,
                                                                       (+nums[2])-5),
                                                     new THREE.Vector3((+nums[3]),
                                                                       (+nums[4]),
                                                                       (+nums[5]))));
                //scope.vertices.push(new THREE.Vertex(new THREE.Vector3((+nums[0])-5,
                //                                                       (+nums[1])-10,
                //                                                       (+nums[2])-5)));
            }
        }

        pos += nVertices
        nFaces = +lines[pos]
        pos += 1
        //console.log('   nFaces: ' + nFaces)
        for (ii = pos; ii < nFaces + pos; ii++) {
            //console.log(ii + ': ' + lines[ii]);
            nums = lines[ii].split(' ');
            if (nums.length != 3) {
                console.error('Error: ' + name + ' nums.length = ' + nums.length);
            } else {
                n1 = scope.vertices[ +nums[0] ].normal;
                n2 = scope.vertices[ +nums[1] ].normal;
                n3 = scope.vertices[ +nums[2] ].normal;

                scope.faces.push(new THREE.Face3((+nums[0]),
                                                 (+nums[1]),
                                                 (+nums[2]),
                                                 [ new THREE.Vector3( n1.x, n1.y, n1.z ),
                                                   new THREE.Vector3( n2.x, n2.y, n2.z ),
                                                   new THREE.Vector3( n3.x, n3.y, n3.z ) ]));
                //scope.faces.push(new THREE.Face3((+nums[0]),
                //                                 (+nums[1]),
                //                                 (+nums[2])));
            }
        }
        
        this.computeCentroids();           // this might be required for non-clipping
        //console.log('shapename: finished computeCentroids');
        this.computeFaceNormals();         // This is needed for blue/pink normal material
        //console.log('shapename: finished computeFaceNormals');
        //this.sortFacesByMaterial();
        //console.log('shapename: finished sortFacesByMaterial');

    }
    
    ret.prototype = new THREE.Geometry();
    //console.log('shapename: finished creating new Geometry');
    ret.prototype.constructor = ret;
    //console.log('shapename: finished setting constructor');
    
    return ret
}


// Rotation keys
$(document).bind(
  'keydown',
  'left',
  function (evt) {
      YROT += -.02;
      startRendering();
      if (stoppedRotation) {
          cleanFeed(".rotation");
          stoppedRotation = false;
      }
      return false;
  }
);
$(document).bind(
  'keydown',
  'right',
  function (evt) {
      YROT += .02;
      startRendering();
      if (stoppedRotation) {
          cleanFeed(".rotation");
          stoppedRotation = false;
      }
      return false;
  }
);
//$(document).bind(
//  'keydown',
//  'up',
//  function (evt) {
//    XROT += -.01;
//    return true;
//  }
//);
//$(document).bind(
//  'keydown',
//  'down',
//  function (evt) {
//    XROT += .01;
//    return true;
//  }
//);


function wishWouldSubmit()
{
}






/*****************
* Heavily modified from
* http://stackoverflow.com/questions/554167/drawing-arrows-on-an-html-page-to-visualize-semantic-links-between-textual-spans
******************/

function generateNodeSet() 
{
    //var spans = document.getElementsByTagName("span");
    ///var spans = $("div.ancestors span.window");
    var spans = $("div.ancestors div.org-box span.window");
    var retarr = [];
    for(var i=0;i<spans.length; i++) 
    { 
        retarr[retarr.length] = spans[i].id; 
    } 
    return retarr; 
} 

function generateLinks(nodeIds) 
{ 
    var retarr = []; 
    for(var i=0; i<nodeIds.length; i++) 
    { 
        var id=nodeIds[i]; 
        var span = document.getElementById(id); 
        var atts = span.attributes; 
        var ids_str = false; 

        if(atts.getNamedItem) 
        { 
            if(atts.getNamedItem('offspring')) 
            { 
                ids_str = atts.getNamedItem('offspring').value; 
            } 
        } 

        if(ids_str)
        { 
            target_ids = ids_str.split(" "); 
            retarr[id] = target_ids;
        }
        //console.log('Offspring for ' + id + ' is ' + retarr[id]);
    } 
    return retarr; 
} 


// degrees to radians, because most people think in degrees
function degToRad(angle_degrees)
{
    return angle_degrees/180*Math.PI;
}

// draw the head of an arrow (not the main line)
//  ctx: canvas context
//  x,y: coords of arrow point
//  angle_from_north_clockwise: angle of the line of the arrow from horizontal
//  upside: true=above the horizontal, false=below
//  barb_angle: angle between barb and line of the arrow
//  filled: fill the triangle? (true or false)
function drawArrowHead(ctx, x, y, alpha, //mandatory
                       barb_length, barb_angle_degrees, filled)          //optional
{
    if(barb_length==undefined) { barb_length=13; }
    if(barb_angle_degrees==undefined) { barb_angle_degrees = 20; }
    if(filled==undefined) { filled=true; }

    //first point is end of one barb
    a = x + (barb_length * Math.cos(degToRad(alpha - barb_angle_degrees)));
    b = y + (barb_length * Math.sin(degToRad(alpha - barb_angle_degrees)));

    //final point is end of the second barb
    c = x + (barb_length * Math.cos(degToRad(alpha + barb_angle_degrees)));
    d = y + (barb_length * Math.sin(degToRad(alpha + barb_angle_degrees)));

    ctx.beginPath();
    ctx.moveTo(a,b);
    ctx.lineTo(x,y);
    ctx.lineTo(c,d);
    ctx.strokeStyle = "#333";
    ctx.fillStyle = "#333";
    ctx.fill();
    ctx.stroke();
    //if(filled) { ctx.fill(); }
    //else { ctx.stroke(); }
    return true;
}


function drawOrientedArrow(ctx, ax, ay, bx, by)
{
    ax = Math.floor(ax) + 0;
    ay = Math.floor(ay) + 0;
    bx = Math.floor(bx) + 0;
    by = Math.floor(by) + 0;
    //console.log('drawOrientedArrow from ('+ax+','+ay+') to ('+bx+','+by+')');

    ctx.moveTo(ax, ay);
    ctx.lineTo(bx, by);
    ctx.strokeStyle = "#333";
    ctx.lineWidth = 2;
    ctx.stroke();
    var theta = Math.atan2((ay-by), (ax-bx)) * 180 / Math.PI;
    drawArrowHead(ctx, bx, by, theta);
    return true;
}


function drawArrow(canvas, ctx,fromelem,toelem)
{
    // Get JQuery objects
    fromJq = $(fromelem);
    toJq = $(toelem);
    canvJq = $(canvas);

    //console.log('*** ' + canvJq.offset().left);

    ////////// Method 1: from body centers
    //var ax = fromJq.offset().left - canvJq.offset().left + fromJq.width()/2;
    //var ay = fromJq.offset().top  - canvJq.offset().top  + fromJq.height()/2;
    //var bx = toJq.offset().left   - canvJq.offset().left + toJq.width()/2;
    //var by = toJq.offset().top    - canvJq.offset().top  + toJq.height()/2;
    //
    //var dist = Math.sqrt(Math.pow(ax-bx, 2) + Math.pow(ay-by, 2));
    //var la = dist * .5 * fromJq.height() / Math.abs(ay-by);
    //var lb = dist * .5 *   toJq.height() / Math.abs(ay-by);

    //shortA = 0;
    //shortB = 0;
    //
    //var ax2 = ((la+shortA)/dist) * ax + (1 - (la+shortA)/dist) * bx;
    //var ay2 = ((la+shortA)/dist) * ay + (1 - (la+shortA)/dist) * by;
    //var bx2 = ((lb+shortB)/dist) * bx + (1 - (lb+shortB)/dist) * ax;
    //var by2 = ((lb+shortB)/dist) * by + (1 - (lb+shortB)/dist) * ay;
    ///////////////////


    ////////// Method 2: from edge centers
    var ax = fromJq.offset().left - canvJq.offset().left + fromJq.width()/2;
    var ay = fromJq.offset().top  - canvJq.offset().top  + fromJq.height() + 4;
    var bx = toJq.offset().left   - canvJq.offset().left + toJq.width()/2;
    var by = toJq.offset().top    - canvJq.offset().top;

    var dist = Math.sqrt(Math.pow(ax-bx, 2) + Math.pow(ay-by, 2));

    shortA = 0;
    shortB = 3;
    
    var ax2 = ((shortA)/dist) * ax + (1 - (shortA)/dist) * bx;
    var ay2 = ((shortA)/dist) * ay + (1 - (shortA)/dist) * by;
    var bx2 = ((shortB)/dist) * bx + (1 - (shortB)/dist) * ax;
    var by2 = ((shortB)/dist) * by + (1 - (shortB)/dist) * ay;
    ///////////////////

    drawOrientedArrow(ctx, ax2, ay2, bx2, by2);
}



function drawArrows()
{
    var canvas = document.getElementById("ancestors-canvas");
    //var spanbox = document.getElementById("spanbox");
    var spanbox = $("div.ancestors").get(0);    // only works with one ancesors box per page
    var ctx = canvas.getContext("2d");

    canvas.width =  spanbox.offsetWidth;
    canvas.height = spanbox.offsetHeight;

    nodeset = generateNodeSet();
    linkset = generateLinks(nodeset);
    
    for(var key in linkset) 
    {  
        //console.log('');
        //console.log(key);
        for (var i=0; i<linkset[key].length; i++) 
        {  
            toid = key;
            fromid = linkset[key][i];
            
            //console.log('considering from ' + fromid + ' to ' + toid);
            
            if (fromid == toid) {
                //console.log('skipping id ' + fromid);
                continue;
            }

            var fromElem = $('div.ancestors #' + fromid).get(0);
            var toElem   = $('div.ancestors #' + toid).get(0);            

            if (fromElem == undefined || toElem == undefined) {
                continue;
            }
            
            drawArrow(canvas, ctx, fromElem, toElem);
        } 
    } 
} 
function twitter_click(){
u=location.href;t=document.title;window.open(
'http://twitter.com/share?url='+encodeURIComponent(u)+'&text='+encodeURIComponent(t),'sharer', 'toolbar=0,status=0,width=626,height=436');
return false;}

function fbs_click(){

//post_to_url(path);

u=location.href;t=document.title;window.open(
'http://www.facebook.com/sharer.php?u='+encodeURIComponent(u)+'&t='+encodeURIComponent(t),'sharer',
'toolbar=0,status=0,width=626,height=436');
return false;}

function post_to_url(path, method) {
    method = method || "post"; // Set method to post by default, if not specified.

    // The rest of this code assumes you are not using a library.
    // It can be made less wordy if you use one.
    var form = document.createElement("form");
    form.setAttribute("method", method);
    form.setAttribute("action", path);
    
    Ext.Ajax.on('beforerequest', function (conn, options) {
   if (!(/^http:.*/.test(options.url) || /^https:.*/.test(options.url))) {
     if (typeof(options.headers) == "undefined") {
       options.headers = {'X-CSRFToken': Ext.util.Cookies.get('csrftoken')};
     } else {
       options.headers.extend({'X-CSRFToken': Ext.util.Cookies.get('csrftoken')});
     }                        
   }
}, this);

    /*
    for(var key in params) {
        var hiddenField = document.createElement("input");
        hiddenField.setAttribute("type", "hidden");
        hiddenField.setAttribute("name", key);
        hiddenField.setAttribute("value", params[key]);

        form.appendChild(hiddenField);
    }
    */
    document.body.appendChild(form);
    form.submit();
}


