(function($) {
   $.fn.runThis = function (options) {

     var config = {
       run_this_class: "run-this", // css class on code to run
       attr: 'title',              // attribute to use to group related snippets of code
       spinner: true,              // show a spinning wheel until completion ?
       spinner_path: './spinner.gif'   // location of the spinner image
     };

     if (options) $.extend(config, options);

     // CSS class for the code sample wrapper
     var run_this_class = config.run_this_class;
     // add a button to the code samples on the page (marked with run_this_class)
     var button_class = run_this_class + "-button";
     var spinner_class = run_this_class + "-spinner";
     var details_class = run_this_class + "-details";
     var wrap_class = run_this_class + "-wrapper";

     // extract the code to be executed remotely from the HTML as a string
     var getCode = function(elt){
       var attr = $(elt).attr(config.attr);
       var code = "";
       if(!attr){
	 // this is a standalone snippet of code
	 code = elt.innerHTML;
       }
       else {
	 // related code blocks must have the same 'config.attr' attribute
	 // we gather the code scattered over elts with the same attribute value
         // up to the current code block ('elt')
	 var related = $("[" + config.attr  + "=" + attr + "]");
	 var reachedCurrent = false;
	 var relatedUpTo =  $.grep(related, function(block, i){
				     var res = !reachedCurrent;
				     if(block === elt) reachedCurrent = true;
				     return res;
				   });

	   $(relatedUpTo).each( function(idx,elt){
	     code += elt.innerHTML; // jQuery ordering is crucial here !
	   });
       }
       return code;
     };

     // for each code block to enhance
     function enhanceCodeBlocks(){

       $("." + run_this_class).each(function(idx, elt){

       // HTML enhancements
       var $elt = $(elt);
       $elt.wrap("<div class=\"" + wrap_class + "\"></div>");
       var wrap = $elt.parent();

       // run button
       var button_elt = $("<div class=\"" + button_class + "\">" +
			"<input type=\"button\" value=\"Run\" /> <img class=" + spinner_class +
			" src=\"" + config.spinner_path  + "\"></div>").appendTo(wrap);

       // spinning wheel
       var spinner_elt = config.spinner ?
			   button_elt.find("." + spinner_class) : $([]);

       // code execution output
       var details_elt = $("<div class=\"" + details_class + "\"></div>")
			  .appendTo(wrap);

       // data for POST request
       var code = getCode(elt);
       var lang = elt.lang;
       var data = "code=" + encodeURIComponent(code) + "&lang=" + lang;

       // create a specific callback for each "run-this" snippet
       var showResults = makeShowResults(run_this_class, button_elt, details_elt);

       // flensed object + handlers
       var flLoadingHandler = function (XHRobj) {
	 if (XHRobj.readyState == 4) {

	   showResults(eval( "(" + XHRobj.responseText + ")" ));
           flproxy.Reset();
	 }
       };

       var flErrorHandler = function (errObj) {
	 alert("Error: "+errObj.number
	       +"\nType: "+errObj.name
	       +"\nDescription: "+errObj.description
	       +"\nSource Object Id: "+errObj.srcElement.instanceId
	      );
       };

       var flproxy = new flensed.flXHR({ autoUpdatePlayer:true,
					 instanceId:"runthis",
					 xmlResponseText:false, // JSON
					 onerror:flErrorHandler,
					 onreadystatechange: flLoadingHandler
					 //,
					 //timeout: 10000, // 10s timeout
					 //ontimeout: function(x) { alert("Execution took over 10 seconds !");}
				       });

       // onclick handler
       button_elt.click(function(evt){

	 // show spinner
	 spinner_elt.show();

	 // query the code execution
	 flproxy.open("POST","http://run-this.appspot.com/runthis");
	 flproxy.send(data);
       });
     });
    }

    // generate the callback function to display the results
    // res is the object resulting from the Ajax call
    //    res.output = output of codepad
    //    res.link   = link to codepad page
    // Customize the inner function if you want to display results differently
    var makeShowResults = function(class_prefix, button_elt, details_elt){

      return function(res){
	var table, implementation;

	// hide button + spinner
	button_elt.hide();

	// display details of execution
	// do we have an error ?
	if(res.stderr){
	table = "<table><tr class=\"" + class_prefix  + "-stderr\"><th>stderr</th><td>" +
		  res.stderr + "</td></tr></table>" ;
	implementation = "&nbsp;";
	}

	else {
	  implementation =  res.langName + " (" + res.langVersion  +") ";

	  table = "<table>" +
	  // output
	  "<tr class=\"" + class_prefix  + "-output\"><th>output        </th><td>" + res.output+ "</td></tr>" +
	  // execution time
	  "<tr class=\"" + class_prefix  + "-time\">  <th>time         </th><td>" + res.time + "s</td></tr>" +
	  // memory usage
	  "<tr class=\"" + class_prefix  + "-memory\"><th>memory        </th><td>" + res.memory + "KB</td></tr>" +
	  "</table>";
	}

	details_elt.html( table +
	  // language + implementation details
	  "<div>" + implementation +
	  // Ideone link
	  "<span class=\"" + class_prefix  + "-link\"><a href=\"" + res.link + "\">Ideone.com</a></span>" +
	  "</div>"
	).show();
      };
    };


    // enhance code blocks on the page
    enhanceCodeBlocks();

    // for chaining
    return this;
 };
})(jQuery);

// on DOM ready
$(document).ready(function(){
		    $().runThis({ spinner_path: '/images/spinner.gif' });
});
