(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";
	 var inserttemplate_class = run_this_class + "-inserttemplate";
	 var chooselanguage_class = run_this_class + "-chooselanguage";	 

     // 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
			if ($(elt).is('pre')) { // if it's a pre tag
				code = elt.innerHTML;
			} else { // if it's a textarea
				code = elt.value;
			}	 
       }
       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 + "][input!=yes]");
	 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){
			if ($(elt).is('pre')) { // if it's a pre tag
				code += elt.innerHTML;
			} else { // if it's a textarea
				code += elt.value;
			}			 
	   });
       }
       return code;
     };
	 
     // extract the code to be executed remotely from the HTML as a string
     var getData = function(elt){
       var attr = $(elt).attr(config.attr);
       var data = "";	   
       if(!attr){
			// this is a standalone snippet of code
			return "";	 
       }
       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 + "][input=yes]");

	   related.each( function(idx,elt){
			if ($(elt).is('pre')) { // if it's a pre tag
				data += elt.innerHTML;
			} else { // if it's a textarea
				data += elt.value;
			}			 
	   });
       }
       return data;
     };	 
	 
	 // set code in box
	 var setCode = function(elt, code){
       var attr = $(elt).attr(config.attr);
       if(!attr){
			// this is a standalone snippet of code
			if ($(elt).is('pre')) { // if it's a pre tag
				$(elt).html(code);
			} else { // if it's a textarea
				$(elt).val(code);
			}	 
       }
       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){
						if ($(elt).is('pre')) { // if it's a pre tag
							$(elt).html(code);
						} else { // if it's a textarea
							$(elt).val(code);
						}		
						return;
				   });
       }
     };
	 
	 function encodeLang(lang) {
		lang = lang.toLowerCase();
		if (lang == 'c++') {
			return 'cpp';
		} else if (lang == 'c#') {
			return 'csharp';
		}
		return lang;
	 }
	 
	 function randomId() {
		return Math.floor(Math.random() * 1000000); // generate a random number between 1 and 1000000
	 }

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

       $("." + run_this_class).each(function(idx, elt){
	   var run_this_id = randomId();

       // HTML enhancements
       var $elt = $(elt);
       $elt.wrap("<div class=\"" + wrap_class + "\" id=\"wrapper-rt-" + run_this_id + "\"></div>");
       var wrap = $elt.parent();
	   
       // run button
       var button_elt = $("<div class=\"" + button_class + "\" id=\"footer-rt-" + run_this_id + "\">" +
			"<input type=\"button\" value=\"Run\" />  " + 
			"<img class=" + spinner_class + " src=\"" + config.spinner_path  + "\"></div>").appendTo(wrap);
			
       // insert template link
       var runbutton_elt = button_elt.find("input");

       // spinning wheel
       var spinner_elt = config.spinner ?
			   button_elt.find("." + spinner_class) : $([]);
			   
       // insert template link
	   var inserttemplate_elt = null;
		if ($elt.is('textarea') && $elt.attr('showsample') != 'no') {
			$("<a class=\"" + inserttemplate_class + "\">Insert Sample</a>").insertAfter(runbutton_elt);
			inserttemplate_elt = button_elt.find("." + inserttemplate_class);
		}
		
       // insert language drop down
	   var chooselanguage_elt = null;
		if ($elt.is('textarea') && $elt.attr('chooselanguage') != 'no') {
			$("Compile as <select name=\"lang\" class=\"" + chooselanguage_class + "\" id=\"language_choice\"></select>").insertAfter(runbutton_elt);
			chooselanguage_elt = button_elt.find("." + chooselanguage_class);
			// parse languages
			var langlist = "Ada, Assembler, AWK, Bash, bc, C, C++, C#, CLIPS, Clojure, COBOL, Common Lisp, D, Erlang, Forth, Fortran, Go, Haskell, Icon, Intercal, Java, JavaScript, Lua, Nemerle, Nice, Ocaml, Pascal, Perl, PHP, Pike, Prolog, Python, Python3, R, Ruby, Scala, Scheme, Smalltalk, Tcl, Unlambda, Visual Basic, Whitespace"
			var languages = langlist.split(", ");
			var real_lang = elt.lang.toLowerCase();
			
			for (var i = 0; i < languages.length; i++) {
				var la = languages[i];	
				$(chooselanguage_elt).append(new Option(la, la.toLowerCase()));
			}
			$(chooselanguage_elt).val(real_lang);			
		}		

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

       // create a specific callback for each "run-this" snippet
       var showResults = makeShowResults(run_this_class, spinner_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
		runbutton_elt.click(function(evt){
		   // data for POST request
			var code = getCode(elt).replace('&lt;', '<');
			var input = getData(elt);
			var lang = encodeLang(elt.lang);
			var cid = details_elt.attr('cid');
			var qid = details_elt.attr('qid');
			var data = "code=" + encodeURIComponent(code) + "&lang=" + lang + "&input=" + encodeURIComponent(input);
			if (cid != null) {
				data += "&cid=" + cid;
			}
			if (qid != null) {
				data += "&qid=" + qid;
			}

			// show spinner
			spinner_elt.show();

			// query the code execution
			flproxy.open("POST","/runthis");
			flproxy.send(data);
		});
		
		
		if (inserttemplate_elt != null) {
			// onclick handler
			inserttemplate_elt.click(function(evt){
				// show spinner
				spinner_elt.show();
				$.ajax({
					type: "GET",
					url: "/stylesheets/code_samples.xml",
					dataType: "xml",
					success: function(xml) { // XML done parsing
						spinner_elt.hide();						
						var lang = elt.lang;
						$(xml).find('Language').each(function(){
							var node_text = $(this).text();
							var name_text = $(this).attr('name');
							
							if (name_text == lang) {
								setCode(elt, node_text.substr(2)); // remove initial line break by taking substring
							}
						});
					}
				});				
			});		
		}
		
		// on change - update language
		if (chooselanguage_elt != null) {
			chooselanguage_elt.change(function(evt){
				elt.lang = chooselanguage_elt.val();
			});
		}
     });
    }

    // 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, spinner_elt, details_elt){

      return function(res){
	var table, implementation;
	
	// spinner
	spinner_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 + "\">direct link</a></span>" +
	  "</div>"
	).show();
		var directlink = $("#directlink");
		if (directlink != undefined && directlink != null) {
			directlink.html('<a href="' + res.link + '">direct link: http://www.careercup.com' + res.link + '</a>');
			
			if (res.cid != null) {
				details_elt.attr('cid', res.cid);
				var codemonkey_id = $("#codemonkey_id");
				if (codemonkey_id != undefined && codemonkey_id != null) {
					codemonkey_id.val(res.cid);
				}
			}
			if (res.qid != null) {
				details_elt.attr('qid', res.qid);
				var question_id = $("#question_id");
				if (question_id != undefined && question_id != null) {
					question_id.val(res.qid);
				}
			}
		}
      };
    };


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

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

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

