text: var getUrlParameter = function getUrlParameter(sParam) {
      var sPageURL = decodeURIComponent(window.location.search.substring(1)),
          sURLVariables = sPageURL.split('&'),
          sParameterName,
          i;
  
      for (i = 0; i < sURLVariables.length; i++) {
          sParameterName = sURLVariables[i].split('=');
  
          if (sParameterName[0] === sParam) {
              return sParameterName[1] === undefined ? true : sParameterName[1];
          }
      }
  };
  
  var highlightPause = false;
  
  function resetFormElement(e) {
    e.wrap('<form>').closest('form').get(0).reset();
    e.unwrap();
  
    // Prevent form submission
    //e.stopPropagation();
    //e.preventDefault();
  }
  
  function getBlocklyHelper(maxBlocks, nbTestCases) {
     return {
        textFile: null,
        extended: false,
        programs: [],
        languages: [],
        player: 0,
        workspace: null,
        prevWidth: 0,
        definitions: {},
        generators: [],
        groupByCategory: true,
        includedAll: true,
        includedCategories : [],
        includedBlocks: [],
        availableVariables: [],
        languageStrings: {
           fr: {
              actions: "Actions",
              sensors: "Capteurs",
              debug: "Débuggage",
              colour: "Couleurs",
              dicts: "Dictionnaires",
              inputs: "Entrées",
              lists: "Listes",
              logic: "Logique",
              loops: "Boucles",
              math: "Maths",
              text: "Texte",
              variables: "Variables",
              functions: "Fonctions",
              invalidContent: "Contenu invalide",
              unknownFileType: "Type de fichier non reconnu",
              download: "télécharger",
              smallestOfTwoNumbers: "Plus petit des deux nombres",
              greatestOfTwoNumbers: "Plus grand des deux nombres",
              programOfRobot: "Programme du robot",
              tooManyIterations: "Votre programme met trop de temps à se terminer !",
              if: "si",
              do: "faire",
              else: "sinon",
              previous: "Précédent",
              next: "Suivant",
              submitProgram: "Valider le programme",
              runProgram: "Exécuter sur ce test",
              stopProgram: "Recommencer",
              speed: "Vitesse :",
              slowSpeed: "Lent",
              mediumSpeed: "Moyen",
              fastSpeed: "Rapide",
              ludicrousSpeed: "Très rapide",
              selectLanguage: "Langage :",
              blocklyLanguage: "Blockly",
              javascriptLanguage: "Javascript",
              importFromBlockly: "Repartir de blockly",
              saveOrLoadProgram: "Enregistrer ou recharger votre programme :",
              avoidReloadingOtherTask: "Attention : ne rechargez pas le programme d'un autre sujet !",
              reloadProgram: "Recharger :",
              saveProgram: "Enregistrer",
              limitBlocks1: " blocs restants sur ",
              limitBlocks2: " autorisés.",
              allTests: "Tous les tests : "
           },
           en: {
              actions: "Actions",
              sensors: "Sensors",
              debug: "Debug",
              colour: "Colors",
              dicts: "Dictionnaries",
              inputs: "Inputs",
              lists: "Lists",
              logic: "Logic",
              loops: "Loops",
              math: "Math",
              text: "Text",
              variables: "Variables",
              functions: "Functions",
              invalidContent: "Invalid content",
              unknownFileType: "Unrecognized file type",
              download: "download",
              smallestOfTwoNumbers: "Smallest of the two numbers",
              greatestOfTwoNumbers: "Greatest of the two numbers",
              programOfRobot: "Robot's program",
              tooManyIterations: "Too many iterations before an action!",
              if: "if",
              do: "do",
              else: "else",	
              previous: "Previous",
              next: "Next",	
              submitProgram: "Validate this program",
              runProgram: "Run this program",
              stopProgram: "Stop",
              speed: "Speed:",
              slowSpeed: "Slow",
              mediumSpeed: "Medium",
              fastSpeed: "Fast",
              ludicrousSpeed: "Very fast",
              selectLanguage: "Language :",
              blocklyLanguage: "Blockly",
              javascriptLanguage: "Javascript",
              importFromBlockly: "Generate from blockly",
              saveOrLoadProgram: "Save or reload your code:",
              avoidReloadingOtherTask: "Warning: do not reload code for another task!",
              reloadProgram: "Reload:",
              saveProgram: "Save",
              limitBlocks1: " blocks remaining out of ",
              limitBlocks2: " available.",
              allTests: "All tests : "
           },
           de: {
              actions: "Aktionen",
              sensors: "Sensoren",
              debug: "Debug",
              colour: "Farben",
              dicts: "Diktionär",
              inputs: "Eingaben",
              lists: "Listen",
              logic: "Logik",
              loops: "Schleifen",
              math: "Mathe",
              text: "Texte",
              variables: "Variablen",
              functions: "Funktionen",
              invalidContent: "Ungültiger Inhalt",
              unknownFileType: "Ungültiger Datentyp",
              download: "Herunterladen",
              smallestOfTwoNumbers: "Kleinste von zwei Zahlen",
              greatestOfTwoNumbers: "Größte von zwei Zahlen",
              programOfRobot: "Programm des Roboters",
              tooManyIterations: "Zu viele Iterationen vor einer Aktion!",
              if: "wenn",
              do: "mache",
              else: "sonst",
              previous: "Zurück",
              next: "Weiter",
              submitProgram: "Programm überprüfen lassen",
              runProgram: "Programm ausführen",
              stopProgram: "Stop",
              speed: "Ablaufgeschwindigkeit:",
              slowSpeed: "Langsam",
              mediumSpeed: "Mittel",
              fastSpeed: "Schnell",
              ludicrousSpeed: "Sehr schnell",
              selectLanguage: "Sprache:",
              blocklyLanguage: "Blockly",
              javascriptLanguage: "Javascript",
              importFromBlockly: "Generiere von Blockly-Blöcken",
              saveOrLoadProgram: "Speicher oder lade deinen Quelltext:",
              avoidReloadingOtherTask: "Warnung: Lade keinen Quelltext von einer anderen Aufgabe!",
              reloadProgram: "Laden:",
              saveProgram: "Speichern",
              limitBlocks1: " Blöcke von ",
              limitBlocks2: " noch verfügbar.",
              allTests: "Alle Tests : "
           },
           nl: {
              actions: "Acties",
              sensors: "Sensoren",
              debug: "Debuggen",
              logic: "Logica",
              loops: "Loops",
              math: "Wiskunde",
              text: "Tekst",
              variables: "Variabelen",
              functions: "Functies",
              invalidContent: "Ongeldige inhoud",
              unknownFileType: "Bestandstype onbekend",
              download: "downloaden",
              smallestOfTwoNumbers: "Kleinste van twee getallen",
              greatestOfTwoNumbers: "Grootste van twee getallen",
              programOfRobot: "Programma van de robot",
              tooManyIterations: "Jouw programma heeft teveel tijd nodig om te eindigen!",
              if: "als",
              do: "doe",
              else: "anders",
              previous: "Vorige",
              next: "Volgende",
              submitProgram: "Valideer het programma",
              runProgram: "Uitvoeren op deze test",
              stopProgram: "Stop en Reset",
              speed: "Snelheid:",
              slowSpeed: "Traag",
              mediumSpeed: "Gemiddeld",
              fastSpeed: "Snel",
              ludicrousSpeed: "Razendsnel",
              selectLanguage: "Taal:",
              blocklyLanguage: "Blockly",
              javascriptLanguage: "Javascript",
              importFromBlockly: "Genereer vanuit blockly",
              saveOrLoadProgram: "Bewaar of herlaad jouw programma:",
              avoidReloadingOtherTask: "Let op: herlaad niet het programma van een andere vraag!",
              reloadProgram: "Herladen:",
              saveProgram: "Bewaren",
              limitBlocks1: " blokken resterend van ",
              limitBlocks2: " in totaal.",
              allTests: "Alle tests: "
           },
        },
        loadHtml: function(nbTestCases) {
           var strMaxBlocks = "";
           if (maxBlocks != undefined) {
              strMaxBlocks = "<span class='blocklyCapacity' style='display:inline-block;width:24px;text-align:right'>XXX</span>" + this.strings.limitBlocks1 + maxBlocks + this.strings.limitBlocks2;
           }
           $("#blocklyLibContent").html(
              "         <xml id='toolbox' style='display: none'>" +
              "         </xml>" +
              "          <div style='height: 40px;display:none' id='lang'>" +
              "            <p>" +
              "               " + this.strings.selectLanguage +
              "               <select id='selectLanguage' onchange='task.displayedSubTask.blocklyHelper.changeLanguage()'>" +
              "                  <option value='blockly'>" + this.strings.blocklyLanguage + "</option>" +
              "                  <option value='javascript'>" + this.strings.javascriptLanguage + "</option>" +
              "               </select>" +
              "               <input type='button' class='language_javascript' value='" + this.strings.importFromBlockly + "' style='display:none' onclick='task.displayedSubTask.blocklyHelper.importFromBlockly()' />" +
              "            </p>" +
              "         </div>" +
                        "<div style='clear:both;'>" + strMaxBlocks + "</div>" +
              "         <div id='blocklyContainer' style='resize:vertical;overflow:auto;height:600px;padding-bottom:10px;border: solid black 1px;width: 100%;position:relative'>" +
              "            <div id='blocklyDiv' class='language_blockly' style='height: 100%; width: 100%'></div>" +
              "            <textarea id='program' class='language_javascript' style='width:100%;height:100%;display:none'></textarea>" +
              "         </div>" +
              "         <div id='saveOrLoad'> "+
              "            <p><b>" + this.strings.saveOrLoadProgram + "</b></p>" +
              "            <p>" + this.strings.avoidReloadingOtherTask + "</p>" +
              "            <p>" + this.strings.reloadProgram + " <input type='file' id='input' onchange='task.displayedSubTask.blocklyHelper.handleFiles(this.files);resetFormElement($(\"#input\"))'></p>" +
              "            <p><input type='button' value='" + this.strings.saveProgram + "' onclick='task.displayedSubTask.blocklyHelper.saveProgram()' /><span id='saveUrl'></span>" +
              "         </div>"
           );
  
           var gridButtonsBefore = "";
           if (nbTestCases > 1) {
                gridButtonsBefore += "<div>" +
                   "<input type='button' value='" + this.strings.previous + "' onclick='task.displayedSubTask.changeTest(-1)'/>" +
                   "<span id='testCaseName' style='padding-left: 20px; padding-right: 20px'>Test 1</span>" +
                   "<input type='button' value='" + this.strings.next + "' onclick='task.displayedSubTask.changeTest(1)'/>" +
                   "</div>";
           }
           $("#gridButtonsBefore").html(gridButtonsBefore);
  
  
           var gridButtonsAfter =
              this.strings.speed +
              "            <select id='selectSpeed' onchange='task.displayedSubTask.changeSpeed()'>" +
              "               <option value='200'>" + this.strings.slowSpeed + "</option>" +
              "               <option value='50'>" + this.strings.mediumSpeed + "</option>" +
              "               <option value='5'>" + this.strings.fastSpeed + "</option>" +
              "               <option value='0'>" + this.strings.ludicrousSpeed + "</option>" +
              "            </select>&nbsp;&nbsp;" +
              "<input type='button' value='" + this.strings.stopProgram + "' onclick='task.displayedSubTask.stop()'/><br/><br/>";
  
           if (nbTestCases > 1) {
              gridButtonsAfter += "<input type='button' value='" + this.strings.runProgram + "' onclick='task.displayedSubTask.run()'/>&nbsp;&nbsp;";
           }
           gridButtonsAfter += "<input type='button' value='" + this.strings.submitProgram + "' onclick='task.displayedSubTask.submit()' /><br/>" +
              "<div id='errors' style='color:red;padding-top:10px;'></div>";
           $("#gridButtonsAfter").html(gridButtonsAfter);
        },
  
        load: function(language, display, nbTestCases, options) {
           if (language == undefined) {
              language = "fr";
           }
           if (options == undefined) options = {};
           if (!options.divId) options.divId = 'blocklyDiv';
           this.strings = this.languageStrings[language];
  
           this.createGeneratorsAndBlocks(this.generators);
  
           if (display) {
              this.loadHtml(nbTestCases);
              var xml = this.getToolboxXml();
              var wsConfig = {
                 toolbox: "<xml>"+xml+"</xml>",
                 sounds: false,
                 media: "https://manage-static.be-oi.be/contestAssets/blockly/"
              };
              if (!this.groupByCategory) {
                 wsConfig.comments = true;
                 wsConfig.scrollbars = true;
                 wsConfig.trashcan = true;
              }
              if (maxBlocks != undefined) {
                 wsConfig.maxBlocks = maxBlocks;
              }
              if (options.readOnly) {
                 wsConfig.readOnly = true;
              }
              this.addExtraBlocks();
              this.workspace = Blockly.inject(options.divId, wsConfig);
              Blockly.Trashcan.prototype.MARGIN_SIDE_ = 410;
              $(".blocklyToolboxDiv").css("background-color", "rgba(168, 168, 168, 0.5)");
              var that = this;
              function onchange(event) {
                 window.focus();
                 $('.blocklyCapacity').html(that.workspace.remainingCapacity());
              }
              this.workspace.addChangeListener(onchange);
              onchange();
           } else {
              this.workspace = new Blockly.Workspace();
           }
  
           this.programs = [];
           for (var iPlayer = this.mainContext.nbRobots - 1; iPlayer >= 0; iPlayer--) {
              this.programs[iPlayer] = {blockly: null, blocklyJS: "", blocklyPython: "", javascript: ""};
              this.languages[iPlayer] = "blockly";
              this.setPlayer(iPlayer);
              if(!options.noRobot) {
                 var xml = '<xml><block type="robot_start" deletable="false" movable="false"></block></xml>';
                 Blockly.Events.recordUndo = false;
                 Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), this.workspace);
                 Blockly.Events.recordUndo = true;
              }
              this.savePrograms();
           }
        },
  
        unload: function() {
           //var ws = Blockly.getMainWorkspace('blocklyDiv');
           var ws = this.workspace;
           if (ws != null) {
              ws.dispose();
              // TODO: this should be in a global unload function
              if (false) {
                 $(".blocklyWidgetDiv").remove();
                 $(".blocklyTooltipDiv").remove();
                 document.removeEventListener("keydown", Blockly.onKeyDown_); // TODO: find correct way to remove all event listeners
                 delete Blockly;
              }
           }
        },
  
        initXML: function() {
           var categories = ["actions", "sensors", "debug"];
           for (var iCategory = 0; iCategory < categories.length; iCategory++) {
              var categoryStr = "";
              for (var objectName in this.generators) {
                 for (var iGen = 0; iGen < this.generators[objectName].length; iGen++) {
                    var generator = this.generators[objectName][iGen];
                    if (generator.category == categories[iCategory]) {
                       categoryStr += "<block type='";
                       categoryStr += (objectName == '.') ? '' : objectName + "_";
                       categoryStr += generator.labelEn + "__'></block>";
                    }
                 }
              }
              $("#blockly_" + categories[iCategory]).html(categoryStr);
           }
        },
  
        createSelection: function(id, start, end) {
           var field = document.getElementById(id)
           if (field.createTextRange) {
              var selRange = field.createTextRange();
              selRange.collapse(true);
              selRange.moveStart('character', start);
              selRange.moveEnd('character', end);
              selRange.select();
           } else if (field.setSelectionRange) {
              field.setSelectionRange(start, end);
           } else if (field.selectionStart) {
              field.selectionStart = start;
              field.selectionEnd = end;
           }
           field.focus();
        },
  
        showStep: function(interpreter, id) {
           if (interpreter.stateStack[0]) {
             var node = interpreter.stateStack[0].node;
             var start = node.start;
             var end = node.end;
           } else {
             var start = 0;
             var end = 0;
           }
           this.createSelection(id, start, end);
        },
  
        setPlayer: function(newPlayer) {
           this.player = newPlayer;
           $("#selectPlayer").val(this.player);
           $(".robot0, .robot1").hide();
           $(".robot" + this.player).show();
        },
  
        changePlayer: function() {
           this.loadPlayer($("#selectPlayer").val());
        },
  
        loadPlayer: function(player) {
           this.savePrograms();
           this.player = player;
           for (var iRobot = 0; iRobot < this.mainContext.nbRobots; iRobot++) {
              $(".robot" + iRobot).hide();
           }
           $(".robot" + this.player).show();
  
           $(".language_blockly, .language_javascript").hide();
           $(".language_" + this.languages[this.player]).show();
  
           var blocklyElems = $(".blocklyToolboxDiv, .blocklyWidgetDiv");
           $("#selectLanguage").val(this.languages[this.player]);
           if (this.languages[this.player] == "blockly") {
              blocklyElems.show();
           } else {
              blocklyElems.hide();
              $(".blocklyTooltipDiv").hide();
           }
           this.loadPrograms();
        },
  
        getCodeFromXml: function(xmlText, language) {
           try {
             var xml = Blockly.Xml.textToDom(xmlText)
           } catch (e) {
             alert(e);
             return;
           }
           var tmpWorkspace = new Blockly.Workspace();
           Blockly.Xml.domToWorkspace(xml, tmpWorkspace);
           return this.getCode(language, tmpWorkspace);
        },
  
  
        getCode: function(language, codeWorkspace) {
           if (codeWorkspace == undefined) {
              codeWorkspace = this.workspace;
           }
           blocks = codeWorkspace.getTopBlocks(true);
           var languageObj = null;
           if (language == "javascript") {
              languageObj = Blockly.JavaScript;
           }
           if (language == "python") {
              languageObj = Blockly.Python;
           }
           languageObj.init(codeWorkspace);
  
           var code = [];
           var comments = [];
           for (var b = 0; b < blocks.length; b++) {
              var block = blocks[b];
              var blockCode = languageObj.blockToCode(block);
              if (["procedures_defnoreturn", "procedures_defreturn"].indexOf(block.type) > -1) {
                 // For function blocks, the code is stored in languageObj.definitions_
              } else {
                 if (block.type == "robot_start") {
                    comments.push(blockCode);
                 }
              }
           }
  
           for (var def in languageObj.definitions_) {
              code.push(languageObj.definitions_[def]);
           }
  
           var code = code.join("\n");
           code += comments.join("\n");
           return code;
        },
  
        savePrograms: function() {
           this.programs[this.player].javascript = $("#program").val();
           if (this.workspace != null) {
              var xml = Blockly.Xml.workspaceToDom(this.workspace);
              this.programs[this.player].blockly = Blockly.Xml.domToText(xml);
              this.programs[this.player].blocklyJS = this.getCode("javascript");
              this.programs[this.player].blocklyPython = this.getCode("python");
           }
        },
  
        loadPrograms: function() {
           $("#program").val(this.programs[this.player].javascript);
           if (this.workspace != null) {
              var xml = Blockly.Xml.textToDom(this.programs[this.player].blockly);
              this.workspace.clear();
              Blockly.Xml.domToWorkspace(xml, this.workspace);
           }
        },
  
        changeLanguage: function() {
           this.languages[this.player] = $("#selectLanguage").val();
           this.loadPlayer(this.player);
        },
  
        importFromBlockly: function() {
            //var player = $("#selectPlayer").val();
            var player = 0;
            this.programs[player].javascript = this.getCode("javascript");
            $("#program").val(this.programs[player].javascript);
        },
  
        handleFiles: function(files) {
           var that = this;
           if (files.length < 0) {
              return;
           }
           var file = files[0];
           var textType = /text.*/;
           if (file.type.match(textType)) {
              var reader = new FileReader();
  
              reader.onload = function(e) {
                 var code = reader.result;
                 if (code[0] == "<") {
                    try {
                       var xml = Blockly.Xml.textToDom(code);
                       that.programs[that.player].blockly = code;
                    } catch(e) {
                       if(window.quickAlgoInterface) {
                        window.quickAlgoInterface.displayError(that.strings.invalidContent);
                       } else {
                        $("#errors").html(that.strings.invalidContent);
                       }
                    }
                    that.languages[that.player] = "blockly";
                 } else {
                    that.programs[that.player].javascript = code;
                    that.languages[that.player] = "javascript";
                 }
                 that.loadPrograms();
                 that.loadPlayer(that.player);
              }
  
              reader.readAsText(file);
           } else {
              if(window.quickAlgoInterface) {
                window.quickAlgoInterface.displayError(that.strings.unknownFileType);
               } else {
                $("#errors").html(that.strings.unknownFileType);
               }
           }
        },
  
        saveProgram: function() {
           this.savePrograms();
           var code = this.programs[this.player][this.languages[this.player]];
           var data = new Blob([code], {type: 'text/plain'});
  
           // If we are replacing a previously generated file we need to
           // manually revoke the object URL to avoid memory leaks.
           if (this.textFile !== null) {
             window.URL.revokeObjectURL(this.textFile);
           }
  
           this.textFile = window.URL.createObjectURL(data);
  
           // returns a URL you can use as a href
           $("#saveUrl").html(" <a href='" + this.textFile + "' download='robot_" + this.languages[this.player] + "_program.txt'>" + this.strings.download + "</a>");
           return this.textFile;
        },
  
        toggleSize: function() {
           if (!this.extended) {
              this.extended = true;
              $("#blocklyContainer").css("width", "800px");
              $("#extendButton").val("<<");
           } else {
              this.extended = false;
              $("#blocklyContainer").css("width", "500px");
              $("#extendButton").val(">>");
           }
           this.updateSize();
        },
  
        updateSize: function() {
           var panelWidth = 500;
           if (this.languages[this.player] == "blockly") {
              panelWidth = $("#blocklyDiv").width() - 10;
           } else {
              panelWidth = $("#program").width() + 20;
           }
           if (panelWidth != this.prevWidth) {
              $("#taskIntro").css("width", panelWidth);
              $("#grid").css("left", panelWidth + 20 + "px");
              if (this.languages[this.player] == "blockly") {
                 Blockly.Trashcan.prototype.MARGIN_SIDE_ = panelWidth - 90;
                 Blockly.svgResize(this.workspace);
              }
           }
           this.prevWidth = panelWidth;
        },
  
        createGenerator: function(label, code, type, nbParams) {
           var jsDefinitions = this.definitions['javascript'] ? this.definitions['javascript'] : [];
           var pyDefinitions = this.definitions['python'] ? this.definitions['python'] : [];
           Blockly.JavaScript[label] = function(block) {
              for (var iDef=0; iDef < jsDefinitions.length; iDef++) {
                 var def = jsDefinitions[iDef];
                 Blockly.Javascript.definitions_[def.label] = def.code;
              }
              var params = "";
              for (var iParam = 0; iParam < nbParams; iParam++) {
                 if (iParam != 0) {
                    params += ", ";
                 }
                 params += Blockly.JavaScript.valueToCode(block, 'NAME_' + (iParam + 1), Blockly.JavaScript.ORDER_ATOMIC);
              }
             if (type == 0) {
                return code + "(" + params + ");\n";
             } else if (type == 1){
                return [code + "(" + params + ")", Blockly.JavaScript.ORDER_NONE];
             }
           };
           Blockly.Python[label] = function(block) {
              for (var iDef=0; iDef < pyDefinitions.length; iDef++) {
                 var def = pyDefinitions[iDef];
                 Blockly.Python.definitions_[def.label] = def.code;
              }
              var params = "";
              for (var iParam = 0; iParam < nbParams; iParam++) {
                 if (iParam != 0) {
                    params += ", ";
                 }
                 params += Blockly.Python.valueToCode(block, 'NAME_' + (iParam + 1), Blockly.Python.ORDER_ATOMIC);
              }
              if (type == 0) {
                 return code + "(" + params + ")\n";
              } else if (type == 1) {
                 return [code + "(" + params + ")", Blockly.Python.ORDER_NONE];
              }
           };
        },
  
        createBlock: function(label, code, type, nbParams) {
           Blockly.Blocks[label] = {
             init: function() {
               this.appendDummyInput()
                   .appendField(code);
               if (type == 0) {
                  this.setPreviousStatement(true);
                  this.setNextStatement(true);
               }
               if (type == 1) {
                  this.setOutput(true);
               }
               this.setInputsInline(true);
               for (var iParam = 0; iParam < nbParams; iParam++) {
                  this.appendValueInput("NAME_" + (iParam + 1)).setCheck(null);
               }
               this.setColour(210);
               this.setTooltip('');
               this.setHelpUrl('');
             }
           };
        },
  
        createGeneratorsAndBlocks: function(generators) {
           for (var objectName in generators) {
              for (var iGen = 0; iGen < generators[objectName].length; iGen++) {
                 var generator = generators[objectName][iGen];
                 if(objectName == '.') {
                   var label = generator.labelEn + "__";
                   var code = generator.codeFr;
                 } else {
                   var label = objectName + "_" + generator.labelEn + "__";
                   var code = objectName + "." + generator.codeFr;
                 }
                 this.createGenerator(label, code, generator.type, generator.nbParams);
                 this.createBlock(label, generator.labelFr, generator.type, generator.nbParams);
              }
           }
        },
  
        getBlocklyLibCode: function(generators) {
           var strCode = "";
           for (var objectName in generators) {
              strCode += "var " + objectName + " = {\n";
              for (var iGen = 0; iGen < generators[objectName].length; iGen++) {
                 var generator = generators[objectName][iGen];
  
                 if (generator.nbParams == 0) {
                    strCode += generator.codeFr + ": function() { ";
                 } else {
                    strCode += generator.codeFr + ": function(param1) { ";
                 }
                 if (generator.type == 1) {
                    strCode += "return ";
                 }
                 if (generator.nbParams == 0) {
                    strCode += objectName + "_" + generator.labelEn + "(); }";
                 } else {
                    strCode += objectName + "_" + generator.labelEn + "(param1); }";
                 }
                 if (iGen < generators[objectName].length - 1) {
                    strCode += ",";
                 }
                 strCode += "\n";
              }
              strCode += "};\n\n";
           }
           strCode += "Math['max'] = function(a, b) { if (a > b) return a; return b; };\n";
           strCode += "Math['min'] = function(a, b) { if (a > b) return b; return a; };\n";
           return strCode;
        },
  
        getStdBlocks: function() {
           return [
              {
                 category: "input",
                 name: this.strings.inputs,
                 colour: 345,
                 blocks: [
                    {
                          name: "input_num",
                          xml: "<block type='input_num'></block>"
                    },
                    {
                          name: "input_char",
                          xml: "<block type='input_char'></block>"
                    },
                    {
                          name: "input_word",
                          xml: "<block type='input_word'></block>"
                    },
                    {
                          name: "input_line",
                          xml: "<block type='input_line'></block>"
                    }
                 ]
              },
              {
                 category: "logic",
                 name: this.strings.logic,
                 colour: 65,
                 blocks: [
                    {
                          name: "controls_if",
                          xml: "<block type='controls_if' colour='65'></block>"
                    },
                    {
                          name: "controls_if_else",
                          xml: "<block type='controls_if_else' colour='65'></block>"
                    },
                    {
                          name: "logic_compare",
                          xml: "<block type='logic_compare'></block>"
                    },
                    {
                          name: "logic_operation",
                          xml: "<block type='logic_operation'></block>"
                    },
                    {
                          name: "logic_negate",
                          xml: "<block type='logic_negate'></block>"
                    },
                    {
                          name: "logic_boolean",
                          xml: "<block type='logic_boolean'></block>"
                    }
                 ]
              },
              {
                 category: "loops",
                 name: this.strings.loops,
                 colour: 120,
                 blocks: [
                    {
                          name: "controls_repeat",
                          xml: "<block type='controls_repeat'></block>"
                    },
                    {
                          name: "controls_repeat_ext",
                          xml: "<block type='controls_repeat_ext'><value name='TIMES'><block type='math_number'><field name='NUM'>10</field></block></value></block>"
                    },
                    {
                          name: "controls_whileUntil",
                          xml: "<block type='controls_whileUntil'></block>"
                    },
                    {
                          name: "controls_for",
                          xml: "<block type='controls_for'>" +
              "                    <value name='FROM'>" +
              "                      <shadow type='math_number'>" +
              "                        <field name='NUM'>1</field>" +
              "                      </shadow>" +
              "                    </value>" +
              "                    <value name='TO'>" +
              "                      <shadow type='math_number'>" +
              "                        <field name='NUM'>10</field>" +
              "                      </shadow>" +
              "                    </value>" +
              "                    <value name='BY'>" +
              "                      <shadow type='math_number'>" +
              "                        <field name='NUM'>1</field>" +
              "                      </shadow>" +
              "                    </value>" +
              "                  </block>"
                    },
                    {
                       name: "controls_forEach",
                       excludedByDefault: true,
                       xml: "<block type='controls_forEach'></block>"
                    },
                    {
                          name: "controls_flow_statements",
                          xml: "<block type='controls_flow_statements'></block>"
                    }
                 ]
              },
              {
                 category: "math",
                 name: this.strings.math,
                 colour: 230,
                 blocks: [
                    {
                          name: "math_number",
                          xml: "<block type='math_number' gap='32'></block>"
                    },
                    {
                          name: "math_arithmetic",
                          xml: "<block type='math_arithmetic'>" +
              "                    <value name='A'>" +
              "                      <shadow type='math_number'>" +
              "                        <field name='NUM'>1</field>" +
              "                      </shadow>" +
              "                    </value>" +
              "                    <value name='B'>" +
              "                      <shadow type='math_number'>" +
              "                        <field name='NUM'>1</field>" +
              "                      </shadow>" +
              "                    </value>" +
              "                  </block>"
                    },
                    {
                          name: "math_number_property",
                          xml: "<block type='math_number_property'>" +
              "                    <value name='NUMBER_TO_CHECK'>" +
              "                      <shadow type='math_number'>" +
              "                        <field name='NUM'>0</field>" +
              "                      </shadow>" +
              "                    </value>" +
              "                  </block>"
                    },
                    {
                          name: "math_change",
                          xml: "<block type='math_change'>" +
              "                    <value name='DELTA'>" +
              "                      <shadow type='math_number'>" +
              "                        <field name='NUM'>1</field>" +
              "                      </shadow>" +
              "                    </value>" +
              "                  </block>"
                    },
                    {
                          name: "math_round",
                          xml: "<block type='math_round'>" +
              "                    <value name='NUM'>" +
              "                      <shadow type='math_number'>" +
              "                        <field name='NUM'>3.1</field>" +
              "                      </shadow>" +
              "                    </value>" +
              "                  </block>"
                    },
                    {
                          name: "math_extra_single",
                          xml: "<block type='math_extra_single'>" +
              "                    <value name='NUM'>" +
              "                      <shadow type='math_number'>" +
              "                        <field name='NUM'>3.1</field>" +
              "                      </shadow>" +
              "                    </value>" +
              "                  </block>"
                    },
                    {
                          name: "math_extra_double",
                          xml: "<block type='math_extra_double'>" +
              "                    <value name='A'>" +
              "                      <shadow type='math_number'>" +
              "                        <field name='A'>2</field>" +
              "                      </shadow>" +
              "                    </value>" +
              "                    <value name='B'>" +
              "                      <shadow type='math_number'>" +
              "                        <field name='B'>2</field>" +
              "                      </shadow>" +
              "                    </value>" +
              "                  </block>"
                    },
                    {
                          name: "math_modulo",
                          xml: "<block type='math_modulo'>" +
              "                    <value name='DIVIDEND'>" +
              "                      <shadow type='math_number'>" +
              "                        <field name='NUM'>64</field>" +
              "                      </shadow>" +
              "                    </value>" +
              "                    <value name='DIVISOR'>" +
              "                      <shadow type='math_number'>" +
              "                        <field name='NUM'>10</field>" +
              "                      </shadow>" +
              "                    </value>" +
              "                  </block>"
                    }
                 ]
              },
              {
                 category: "text",
                 name: this.strings.text,
                 colour: 210,
                 blocks: [
                    {
                          name: "text",
                          xml: "<block type='text'></block>"
                    },
                    {
                          name: "text_join",
                          xml: "<block type='text_join'></block>"
                    },
                    {
                          name: "text_append",
                          xml: "<block type='text_append'></block>"
                    },
                    {
                       name: "text_length",
                       xml: "<block type='text_length'>" +
                            "    <value name='VALUE'>" +
                            "        <shadow type='text'>" +
                            "        <field name='TEXT'>abc</field>" +
                            "        </shadow>" +
                            "    </value>" +
                            "</block>"
                    },
                    {
                       name: "text_isEmpty",
                       xml: "<block type='text_isEmpty'>" +
                            "    <value name='VALUE'>" +
                            "        <shadow type='text'>" +
                            "        <field name='TEXT'></field>" +
                            "        </shadow>" +
                            "    </value>" +
                            "</block>"
                    },
                    {
                       name: "text_indexOf",
                       xml: "<block type='text_indexOf'>" +
                            "    <value name='VALUE'>" +
                            "        <block type='variables_get'>" +
                            "        <field name='VAR'>{textVariable}</field>" +
                            "        </block>" +
                            "    </value>" +
                            "    <value name='FIND'>" +
                            "        <shadow type='text'>" +
                            "        <field name='TEXT'>abc</field>" +
                            "        </shadow>" +
                            "    </value>" +
                            "</block>"
                    },
                    {
                       name: "text_charAt",
                       xml: "<block type='text_charAt'>" +
                            "    <value name='VALUE'>" +
                            "        <block type='variables_get'>" +
                            "        <field name='VAR'>{textVariable}</field>" +
                            "        </block>" +
                            "    </value>" +
                            "</block>"
                    },
                    {
                       name: "text_getSubstring",
                       xml: "<block type='text_getSubstring'>" +
                            "    <value name='STRING'>" +
                            "        <block type='variables_get'>" +
                            "        <field name='VAR'>{textVariable}</field>" +
                            "        </block>" +
                            "    </value>" +
                            "</block>"
                    },
                    {
                       name: "text_changeCase",
                       xml: "<block type='text_changeCase'>" +
                            "    <value name='TEXT'>" +
                            "        <shadow type='text'>" +
                            "        <field name='TEXT'>abc</field>" +
                            "        </shadow>" +
                            "    </value>" +
                            "</block>"
                    },
                    {
                       name: "text_trim",
                       xml: "<block type='text_trim'>" +
                            "    <value name='TEXT'>" +
                            "        <shadow type='text'>" +
                            "        <field name='TEXT'>abc</field>" +
                            "        </shadow>" +
                            "    </value>" +
                            "</block>"
                    },
                    {
                       name: "text_print",
                       xml: "<block type='text_print'>" +
                            "    <value name='TEXT'>" +
                            "        <shadow type='text'>" +
                            "        <field name='TEXT'>abc</field>" +
                            "        </shadow>" +
                            "    </value>" +
                            "</block>"
                    },
                    {
                       name: "text_print_noend",
                       xml: "<block type='text_print_noend'></block>"
                    },
                    {
                       name: "text_prompt_ext",
                       xml: "<block type='text_prompt_ext'>" +
                            "    <value name='TEXT'>" +
                            "        <shadow type='text'>" +
                            "        <field name='TEXT'>abc</field>" +
                            "        </shadow>" +
                            "    </value>" +
                            "</block>"
                    }
                 ]
              },
              {
                 category: "lists",
                 name: this.strings.lists,
                 colour: 260,
                 blocks: [
                    {
                       name: "lists_create_with_empty",
                       xml: "<block type='lists_create_with'>" +
                            "    <mutation items='0'></mutation>" +
                            "</block>"
                    },
                    {
                       name: "lists_create_with",
                       xml: "<block type='lists_create_with'></block>"
                    },
                    {
                       name: "lists_repeat",
                       xml: "<block type='lists_repeat'>" +
                            "    <value name='NUM'>" +
                            "        <shadow type='math_number'>" +
                            "            <field name='NUM'>5</field>" +
                            "        </shadow>" +
                            "    </value>" +
                            "</block>"
                    },
                    {
                       name: "lists_length",
                       xml: "<block type='lists_length'></block>"
                    },
                    {
                       name: "lists_isEmpty",
                       xml: "<block type='lists_isEmpty'></block>"
                    },
                    {
                       name: "lists_indexOf",
                       xml: "<block type='lists_indexOf'>" +
                            "    <value name='VALUE'>" +
                            "        <block type='variables_get'>" +
                            "            <field name='VAR'>{listVariable}</field>" +
                            "        </block>" +
                            "    </value>" +
                            "</block>"
                    },
                    {
                       name: "lists_getIndex",
                       xml: "<block type='lists_getIndex'>" +
                            "    <value name='VALUE'>" +
                            "        <block type='variables_get'>" +
                            "            <field name='VAR'>{listVariable}</field>" +
                            "        </block>" +
                            "    </value>" +
                            "</block>"
                    },
                    {
                       name: "lists_setIndex",
                       xml: "<block type='lists_setIndex'>" +
                            "    <value name='LIST'>" +
                            "        <block type='variables_get'>" +
                            "            <field name='VAR'>{listVariable}</field>" +
                            "        </block>" +
                            "    </value>" +
                            "</block>"
                    },
                    {
                       name: "lists_getSublist",
                       xml: "<block type='lists_getSublist'>" +
                            "    <value name='LIST'>" +
                            "        <block type='variables_get'>" +
                            "            <field name='VAR'>{listVariable}</field>" +
                            "        </block>" +
                            "    </value>" +
                            "</block>"
                    },
                    {
                       name: "lists_sort",
                       xml: "<block type='lists_sort'></block>"
                    },
                    {
                       name: "lists_split",
                       xml: "<block type='lists_split'>" +
                            "    <value name='DELIM'>" +
                            "        <shadow type='text'>" +
                            "            <field name='TEXT'>,</field>" +
                            "        </shadow>" +
                            "    </value>" +
                            "</block>"
                    },
                    {
                       name: "lists_append",
                       xml: "<block type='lists_append'></block>"
                    }
                 ]
              },
              {
                 category: "colour",
                 name: this.strings.colour,
                 colour: 210,
                 blocks: [
                    {
                       name: "colour_picker",
                       xml: "<block type='colour_picker'></block>"
                    },
                    {
                       name: "colour_random",
                       xml: "<block type='colour_random'></block>"
                    },
                    {
                       name: "colour_rgb",
                       xml: "<block type='colour_rgb'>" +
                            "    <value name='RED'>" +
                            "        <shadow type='math_number'>" +
                            "            <field name='NUM'>100</field>" +
                            "        </shadow>" +
                            "    </value>" +
                            "    <value name='GREEN'>" +
                            "        <shadow type='math_number'>" +
                            "            <field name='NUM'>50</field>" +
                            "        </shadow>" +
                            "    </value>" +
                            "    <value name='BLUE'>" +
                            "        <shadow type='math_number'>" +
                            "            <field name='NUM'>0</field>" +
                            "        </shadow>" +
                            "    </value>" +
                            "</block>"
                    },
                    {
                       name: "colour_blend",
                       xml: "<block type='colour_blend'>" +
                            "    <value name='COLOUR1'>" +
                            "        <shadow type='colour_picker'>" +
                            "            <field name='COLOUR'>#ff0000</field>" +
                            "        </shadow>" +
                            "    </value>" +
                            "    <value name='COLOUR2'>" +
                            "        <shadow type='colour_picker'>" +
                            "            <field name='COLOUR'>#3333ff</field>" +
                            "        </shadow>" +
                            "    </value>" +
                            "    <value name='RATIO'>" +
                            "        <shadow type='math_number'>" +
                            "            <field name='NUM'>0.5</field>" +
                            "        </shadow>" +
                            "    </value>" +
                            "</block>"
                    }
                 ]
              },
              {
                 category: "dicsts",
                 name: this.strings.dicts,
                 colour: 0,
                 blocks: [
                    {
                          name: "dict_get_literal",
                          xml: "<block type='dict_get_literal'></block>"
                    },
                    {
                          name: "dict_keys",
                          xml: "<block type='dict_keys'></block>"
                    },
                    {
                          name: "dicts_create_with",
                          xml: "<block type='dicts_create_with'></block>"
                    }
                 ]
              },
              {
                    category: "variables",
                    name: this.strings.variables,
                    colour: 330,
                    custom: "VARIABLE",
                    blocks: []
              },
              {
                    category: "functions",
                    name: this.strings.functions,
                    colour: 290,
                    custom: "PROCEDURE",
                    blocks: []
              }
           ];
        },
  
        getToolboxXml: function() {
           var blocksByCategory = {
           }
           for (var objectName in this.generators) {
              for (var iGen = 0; iGen < this.generators[objectName].length; iGen++) {
                 var generator = this.generators[objectName][iGen];
                 if (blocksByCategory[generator.category] == undefined) {
                    blocksByCategory[generator.category] = [];
                 }
                 var blockName = (objectName == '.') ? generator.labelEn + "__" : objectName + "_" + generator.labelEn + "__";
                 blocksByCategory[generator.category].push(blockName);
              }
           }
           xml = "";
           for (var category in blocksByCategory) {
              if (this.groupByCategory) {
                 xml += "<category name='" + this.strings[category] + "' colour='210'>";
              }
              var blocks = blocksByCategory[category];
              for (var iBlock = 0; iBlock < blocks.length; iBlock++) {
                 xml += "<block type='" + blocks[iBlock] + "'></block>";
              }
              if (this.groupByCategory) {
                 xml += "</category>";
              }
           }
           var stdBlocks = this.getStdBlocks();
           for (var iCategory = 0; iCategory < stdBlocks.length; iCategory++) {
              var category = stdBlocks[iCategory];
              var catXml = "";
              if (this.groupByCategory) {
                 catXml = "<category name='" + category.name + "' colour='" + category.colour + "'";
              }
              var isIncluded = false;
              if (category.custom != undefined) {
                 catXml += " custom='" + category.custom + "'";
                 if (this.includedAll || ($.inArray(category.category, this.includedCategories) != -1)) {
                    isIncluded = true;
                 }
              }
              catXml += ">";
              for (var iBlock = 0; iBlock < category.blocks.length; iBlock++) {
                 var block = category.blocks[iBlock];
                 if (this.includedAll ||
                     ($.inArray(category.category, this.includedCategories) != -1) ||
                     ($.inArray(block.name, this.includedBlocks) != -1)) {
                    if (!block.excludedByDefault || ($.inArray(block.name, this.includedBlocks) != -1)) {
                       catXml += block.xml;
                       isIncluded = true;
                    }
                 }
              }
              if (this.groupByCategory) {
                 catXml += "</category>";
              }
              if (isIncluded) {
                 xml += catXml;
              }
           }
  
           // Handle variable blocks, which are normally automatically added with
           // the VARIABLES category but can be customized here
           if (this.availableVariables.length > 0 ||
                 ($.inArray('variables_get', this.includedBlocks) != -1) ||
                 ($.inArray('variables_set', this.includedBlocks) != -1)) {
              if (this.groupByCategory) {
                 xml += "<category name='" + this.strings.variables + "' colour='330'>";
              }
  
              // block for each availableVariable
              for (var iVar = 0; iVar < this.availableVariables.length; iVar++) {
                 xml += "<block type='variables_get' editable='false'><field name='VAR'>" + this.availableVariables[iVar] + "</field></block>";
              }
              // generic modifyable block
              if ($.inArray('variables_get', this.includedBlocks) != -1) {
                 xml += "<block type='variables_get'></block>"
              }
  
              // same for setting variables
              for (var iVar = 0; iVar < this.availableVariables.length; iVar++) {
                 xml += "<block type='variables_set' editable='false'><field name='VAR'>" + this.availableVariables[iVar] + "</field></block>";
              }
              if ($.inArray('variables_set', this.includedBlocks) != -1) {
                 xml += "<block type='variables_set'></block>"
              }
  
              if (this.groupByCategory) {
                 xml += "</category>";
              }
           }
           return xml;
        },
  
        addExtraBlocks: function() {
           var that = this;
           Blockly.Blocks['math_extra_single'] = {
             /**
              * Block for advanced math operators with single operand.
              * @this Blockly.Block
              */
             init: function() {
               var OPERATORS =
                   [
                    [Blockly.Msg.MATH_SINGLE_OP_ABSOLUTE, 'ABS'],
                    ['-', 'NEG']
               ];
               this.setHelpUrl(Blockly.Msg.MATH_SINGLE_HELPURL);
               this.setColour(Blockly.Blocks.math.HUE);
               this.setOutput(true, 'Number');
               this.appendValueInput('NUM')
                   .setCheck('Number')
                   .appendField(new Blockly.FieldDropdown(OPERATORS), 'OP');
               // Assign 'this' to a variable for use in the tooltip closure below.
               var thisBlock = this;
               this.setTooltip(function() {
                 var mode = thisBlock.getFieldValue('OP');
                 var TOOLTIPS = {
                   'ABS': Blockly.Msg.MATH_SINGLE_TOOLTIP_ABS,
                   'NEG': Blockly.Msg.MATH_SINGLE_TOOLTIP_NEG
                 };
                 return TOOLTIPS[mode];
               });
             }
           };
  
           Blockly.JavaScript['math_extra_single'] = Blockly.JavaScript['math_single'];
           Blockly.Python['math_extra_single'] = Blockly.Python['math_single'];
  
  
           Blockly.Blocks['math_extra_double'] = {
             /**
              * Block for advanced math operators with double operand.
              * @this Blockly.Block
              */
             init: function() {
               var OPERATORS =
                   [
                    ['min', 'MIN'],
                    ['max', 'MAX']
               ];
               this.setColour(Blockly.Blocks.math.HUE);
               this.setInputsInline(true);
               this.setOutput(true, 'Number');
               this.appendDummyInput('OP').appendField(new Blockly.FieldDropdown([["min", "MIN"], ["max", "MAX"], ["", ""]]), "OP");
               this.appendDummyInput().appendField(" entre ");
               this.appendValueInput('A').setCheck('Number');
               this.appendDummyInput().appendField(" et ");
               this.appendValueInput('B').setCheck('Number');
               // Assign 'this' to a variable for use in the tooltip closure below.
               var thisBlock = this;
               this.setTooltip(function() {
                 var mode = thisBlock.getFieldValue('OP');
                 var TOOLTIPS = {
                   'MIN': that.strings.smallestOfTwoNumbers,
                   'MAX': that.strings.greatestOfTwoNumbers
                 };
                 return TOOLTIPS[mode];
               });
             }
           };
  
           Blockly.JavaScript['math_extra_double'] = function(block) {
             // Math operators with double operand.
             var operator = block.getFieldValue('OP');
             var arg1 = Blockly.JavaScript.valueToCode(block, 'A',  Blockly.JavaScript.ORDER_NONE) || '0';
             var arg2 = Blockly.JavaScript.valueToCode(block, 'B',  Blockly.JavaScript.ORDER_NONE) || '0';
             if (operator == 'MIN') {
               var code = "Math.min(" + arg1 + ", " + arg2 + ")";
             }
             if (operator == 'MAX') {
               var code = "Math.max(" + arg1 + ", " + arg2 + ")";
             }
             return [code, Blockly.JavaScript.ORDER_FUNCTION_CALL];
           };
  
           Blockly.Python['math_extra_double'] = function(block) {
             // Math operators with double operand.
             var operator = block.getFieldValue('OP');
             var arg1 = Blockly.Python.valueToCode(block, 'A',  Blockly.Python.ORDER_NONE) || '0';
             var arg2 = Blockly.Python.valueToCode(block, 'B',  Blockly.Python.ORDER_NONE) || '0';
             if (operator == 'MIN') {
               var code = "Math.min(" + arg1 + ", " + arg2 + ")";
             }
             if (operator == 'MAX') {
               var code = "Math.max(" + arg1 + ", " + arg2 + ")";
             }
             return [code, Blockly.Python.ORDER_FUNCTION_CALL];
           };
  
  
           var old = Blockly.Blocks.controls_if.init;
           Blockly.Blocks.controls_if.init = function() {
              old.call(this);
              this.setMutator(undefined)
           };
  
           Blockly.Blocks['controls_if_else'] = {
             init: function() {
               this.appendValueInput("IF")
                   .setCheck("Boolean")
                   .appendField(that.strings.if);
               this.appendDummyInput()
                   .appendField(that.strings.do);
               this.appendStatementInput("DO");
               this.appendDummyInput()
                   .appendField(that.strings.else);
               this.appendStatementInput("ELSE");
               this.setInputsInline(false);
               this.setPreviousStatement(true);
               this.setNextStatement(true);
               this.setColour(65);
               this.setTooltip('');
           //    this.setHelpUrl('http://www.example.com/');
             }
           };
  
           Blockly.JavaScript['controls_if_else'] = function(block) {
             var condition = Blockly.JavaScript.valueToCode(block, 'IF',  Blockly.JavaScript.ORDER_NONE) || 'false';
             var stmtIf = Blockly.JavaScript.statementToCode(block, 'DO');
             var stmtElse = Blockly.JavaScript.statementToCode(block, 'ELSE');
             var code = "if (" + condition + ") {\n" + stmtIf + "} else {\n" + stmtElse + "}\n";
             return code;
           };
  
           Blockly.Python['controls_if_else'] = function(block) {
             var condition = Blockly.Python.valueToCode(block, 'IF',  Blockly.Python.ORDER_NONE) || 'false';
             var stmtIf = Blockly.Python.statementToCode(block, 'DO');
             var stmtElse = Blockly.Python.statementToCode(block, 'ELSE');
             var code = "if (" + condition + "):\n" + stmtIf + "else:\n" + stmtElse + "\n";
             return code;
           };
  
  
           Blockly.Blocks['robot_start'] = {
             init: function() {
               this.appendDummyInput()
                   .appendField(that.strings.programOfRobot);
               this.setNextStatement(true);
               this.setColour(210);
               this.setTooltip('');
               this.deletable_ = false;
               this.editable_ = false;
               this.movable_ = false;
           //    this.setHelpUrl('http://www.example.com/');
             }
           };
  
           Blockly.JavaScript['robot_start'] = function(block) {
             return "";
           };
  
           Blockly.Python['robot_start'] = function(block) {
             return "";
           };
        },
  
        run: function() {
           var that = this;
           var nbRunning = this.mainContext.runner.nbRunning();
           if (nbRunning > 0) {
              this.mainContext.runner.stop();
              this.mainContext.delayFactory.createTimeout("run" + Math.random(), function() {
                 that.run()
              }, 1000);
              return;
           }
           if (this.mainContext.display) {
              Blockly.JavaScript.STATEMENT_PREFIX = 'highlightBlock(%1);\n';
              Blockly.JavaScript.addReservedWords('highlightBlock');
           } else {
              Blockly.JavaScript.STATEMENT_PREFIX = '';
           }
           this.savePrograms();
           var codes = [];
           for (var iRobot = 0; iRobot < this.mainContext.nbRobots; iRobot++) {
              var language = this.languages[iRobot];
              if (language == "blockly") {
                 language = "blocklyJS";
              }
              codes[iRobot] = this.getFullCode(this.programs[iRobot][language]);
           }
           that.highlightPause = false;
           that.workspace.traceOn(true);
           that.workspace.highlightBlock(null);
           this.mainContext.runner.runCodes(codes);
        },
  
        getFullCode: function(code) {
           return this.getBlocklyLibCode(this.generators) + code + "program_end()";
        }
     }
  }
  
  function initBlocklyRunner(context, messageCallback) {
     init(context, [], [], [], false, {});
  
     function init(context, interpreters, isRunning, toStop, stopPrograms, runner) {
        runner.waitDelay = function(callback, value, delay) {
           if (delay > 0) {
              context.delayFactory.createTimeout("wait" + context.curRobot + "_" + Math.random(), function() {
                    runner.noDelay(callback, value);
                 },
                 delay
              );
           } else {
              runner.noDelay(callback, value);
           }
        };
  
        runner.noDelay = function(callback, value) {
           var primitive = undefined;
           if (value != undefined) {
              primitive = interpreters[context.curRobot].createPrimitive(value);
           }
           if (Math.random() < 0.1) {
              context.delayFactory.createTimeout("wait_" + Math.random(), function() {
                 callback(primitive);
                 runner.runSyncBlock();
              }, 0);
           } else {
              callback(primitive);
              runner.runSyncBlock();
           }
        };
  
        runner.initInterpreter = function(interpreter, scope) {
           for (var objectName in context.generators) {
              for (var iGen = 0; iGen < context.generators[objectName].length; iGen++) {
                 var generator = context.generators[objectName][iGen];
                 interpreter.setProperty(scope, objectName + "_" + generator.labelEn, interpreter.createAsyncFunction(generator.fct));
              }
           }
           interpreter.setProperty(scope, "program_end", interpreter.createAsyncFunction(context.program_end));
  
           function highlightBlock(id) {
              if (context.display) {
                 context.blocklyHelper.workspace.highlightBlock(id);
                 highlightPause = true;
              }
           }
  
           // Add an API function for highlighting blocks.
           var wrapper = function(id) {
             id = id ? id.toString() : '';
             return interpreter.createPrimitive(highlightBlock(id));
           };
           interpreter.setProperty(scope, 'highlightBlock', interpreter.createNativeFunction(wrapper));
        };
  
        runner.stop = function() {
           for (var iInterpreter = 0; iInterpreter < interpreters.length; iInterpreter++) {
              if (isRunning[iInterpreter]) {
                 toStop[iInterpreter] = true;
              }
           }
           context.reset();
        };
  
        runner.runSyncBlock = function() {
           var maxIter = 20000;
     /*      if (turn > 90) {
              task.program_end(function() {
                 that.stop();
              });
              return;
           }*/
           try {
              for (var iInterpreter = 0; iInterpreter < interpreters.length; iInterpreter++) {
                 context.curRobot = iInterpreter;
                 if (context.infos.checkEndEveryTurn) {
                    context.infos.checkEndCondition(context, false);
                 }
                 var interpreter = interpreters[iInterpreter];
                 while (context.curSteps[iInterpreter] < maxIter) {
                    if (!interpreter.step() || toStop[iInterpreter]) {
                       isRunning[iInterpreter] = false;;
                       break;
                    }
                    if (interpreter.paused_) {
                       break;
                    }
                    context.curSteps[iInterpreter]++;
                 }
                 if (context.curSteps[iInterpreter] >= maxIter) {
                    isRunning[iInterpreter] = false;;
                    throw context.blocklyHelper.strings.tooManyIterations;
                 }
              }
           } catch (e) {
              for (var iInterpreter = 0; iInterpreter < interpreters.length; iInterpreter++) {
                 isRunning[iInterpreter] = false;
              }
  
              var message = e.toString();
              if ((context.nbTestCases != undefined) && (context.nbTestCases > 1)) {
                 if (context.success) {
                    message = context.messagePrefixSuccess + message;
                 } else {
                    message = context.messagePrefixFailure + message;
                 }
              }
              if (context.success) {
                 message = "<span style='color:green;font-weight:bold'>" + message + "</span>";
                 if (context.linkBack) {
                    //message += "<br/><span onclick='window.parent.backToList()' style='font-weight:bold;cursor:pointer;text-decoration:underline;color:blue'>Retour à la liste des questions</span>";
                 }
              }
              messageCallback(message);
           }
        };
  
        runner.runCodes = function(codes) {
           //this.mainContext.delayFactory.stopAll(); pb: it would top existing graders
           interpreters = [];
           context.programEnded = [];
           context.curSteps = [];
           context.reset();
           for (var iInterpreter = 0; iInterpreter < codes.length; iInterpreter++) {
              context.curSteps[iInterpreter] = 0;
              context.programEnded[iInterpreter] = false;
              interpreters.push(new Interpreter(codes[iInterpreter], runner.initInterpreter));
              isRunning[iInterpreter] = true;
              toStop[iInterpreter] = false;
           }
           runner.runSyncBlock();
        };
  
        runner.nbRunning = function() {
           var nbRunning = 0;
           for (var iInterpreter = 0; iInterpreter < interpreters.length; iInterpreter++) {
              if (isRunning[iInterpreter]) {
                 nbRunning++;
              }
           }
           return nbRunning;
        };
  
        context.runner = runner;
        context.callCallback = runner.noDelay;
        context.programEnded = [];
     }
  }
  
  
  var initBlocklySubTask = function(subTask) {
     subTask.blocklyHelper = getBlocklyHelper(subTask.gridInfos.maxInstructions);
     subTask.answer = null;
     subTask.state = {};
     subTask.iTestCase = 0;
  
     if (subTask.data["medium"] == undefined) {
        subTask.load = function(views, callback) {
           subTask.loadLevel("easy");
           callback();
        };
     }
  
  
     subTask.loadLevel = function(curLevel) {
        this.level = curLevel;
  
        // TODO: fix bebras platform to make this unnecessary
        try {
           $('#question-iframe', window.parent.document).css('width', '100%');
        } catch(e) {
        }
        $('body').css('width', '100%').css('max-width', '1200px').css('margin', 'auto');
        window.focus();
  
        this.iTestCase = 0;
        this.nbTestCases = subTask.data[curLevel].length;
        if (this.display) {
           var gridHtml = "<center>";
           gridHtml += "<div id='gridButtonsBefore'></div>";
           gridHtml += "<div id='grid' style='width:400px;height:200px;padding:10px'></div>";
           gridHtml += "<div id='gridButtonsAfter'></div>";
           gridHtml += "</center>";
           $("#gridContainer").html(gridHtml)
           if (this.gridInfos.hideSaveOrLoad) {
              // TODO: do without a timeout
              setTimeout(function() {
              $("#saveOrLoad").hide();
              }, 0);
           }
        }
  
        var props = ["includedAll", "groupByCategory", "includedCategories", "includedBlocks", "availableVariables"];
        for (var iProp = 0; iProp < props.length; iProp++) {
           var prop = props[iProp];
           if (subTask.gridInfos[prop] != undefined) {
              var taskProp = subTask.gridInfos[prop];
              if ((typeof taskProp == "object") && (taskProp["easy"] != undefined)) {
                 taskProp = taskProp[curLevel];
              }
              subTask.blocklyHelper[prop] = taskProp;
           }
        }
  
        this.context = getRobotGridContext(this.display, this.gridInfos, curLevel);
        this.context.raphaelFactory = this.raphaelFactory;
        this.context.delayFactory = this.delayFactory;
        this.context.blocklyHelper = this.blocklyHelper;
  
        this.blocklyHelper.mainContext = this.context;
  
        //this.answer = task.getDefaultAnswerObject();
        displayHelper.hideValidateButton = true;
        displayHelper.timeoutMinutes = 30;
  
        this.blocklyHelper.generators = this.context.generators;
        this.blocklyHelper.load(stringsLanguage, this.display, this.data[curLevel].length);
  
        subTask.changeTest(0);
     };
  
     subTask.updateScale = function() {
        this.context.updateScale();
        this.blocklyHelper.updateSize();
     };
  
     var resetScores = function() {
     };
  
     var updateScores = function() {
     };
  
     function changeScore(robot, deltaScore) {
        scores[robot] += deltaScore;
        updateScores();
     };
  
     subTask.unloadLevel = function(callback) {
        this.context.unload();
        this.blocklyHelper.unload();
        callback();
     };
  
     subTask.unload = subTask.unloadLevel;
  
     subTask.reset = function() {
        this.context.reset();
     };
  
     subTask.program_end = function(callback) {
        this.context.program_end(callback);
     };
  
     var initContextForLevel = function(iTestCase) {
        subTask.iTestCase = iTestCase;
        subTask.context.reset(subTask.data[subTask.level][iTestCase]);
        subTask.context.iTestCase = iTestCase;
        subTask.context.nbTestCases = subTask.nbTestCases;
        var prefix = "Test " + (subTask.iTestCase + 1) + "/" + subTask.nbTestCases + " : ";
        subTask.context.messagePrefixFailure = prefix;
        subTask.context.messagePrefixSuccess = prefix;
        subTask.context.linkBack = false;
     };
  
     subTask.run = function() {
        initBlocklyRunner(subTask.context, function(message, success) {
          if(window.quickAlgoInterface) {
            window.quickAlgoInterface.displayError(message);
           } else {
            $("#errors").html(message);
           }
        });
        initContextForLevel(subTask.iTestCase);
        subTask.blocklyHelper.run(subTask.context);
     };
  
     subTask.submit = function() {
        this.context.display = false;
        this.getAnswerObject(); // to fill this.answer;
        this.getGrade(function(result) {
           subTask.context.display = true;
           subTask.changeSpeed();
           initBlocklyRunner(subTask.context, function(message, success) {
              if(window.quickAlgoInterface) {
               window.quickAlgoInterface.displayError(message);
              } else {
               $("#errors").html(message);
              }
              platform.validate("done");
           });
           subTask.changeTest(result.iTestCase - subTask.iTestCase);
           initContextForLevel(result.iTestCase);
           subTask.context.linkBack = true;
           subTask.context.messagePrefixSuccess = "Tous les tests / Alle Tests : ";
           subTask.blocklyHelper.run(subTask.context);
        });
     };
  
     subTask.stop = function() {
        this.context.runner.stop();
     };
  
     subTask.reloadStateObject = function(stateObj) {
        this.state = stateObj;
  //      this.level = state.level;
  
  //      initContextForLevel(this.level);
  
  //      this.context.runner.stop();
     };
  
     subTask.getDefaultStateObject = function() {
        return { level: "easy" };
     };
  
     subTask.getStateObject = function() {
        this.state.level = this.level;
        return this.state;
     };
  
     subTask.changeSpeed =  function() {
        this.context.changeDelay(parseInt($("#selectSpeed").val()));
     };
  
     subTask.getAnswerObject = function() {
        this.blocklyHelper.savePrograms();
  
        this.answer = this.blocklyHelper.programs;
        return this.answer;
     };
  
     subTask.reloadAnswerObject = function(answerObj) {
        if(typeof answerObj === "undefined") {
          this.answer = this.getDefaultAnswerObject();
        } else {
          this.answer = answerObj;
        }
        this.blocklyHelper.programs = this.answer;
        if (this.answer != undefined) {
           this.blocklyHelper.loadPrograms();
        }
     };
  
     subTask.getDefaultAnswerObject = function() {
        var defaultBlockly = '<xml xmlns="http://www.w3.org/1999/xhtml"><block type="robot_start" deletable="false" movable="false" x="0" y="0"></block><block type="robot_start" deletable="false" movable="false" x="0" y="0"></block></xml>';
        return [{javascript:"", blockly: defaultBlockly, blocklyJS: ""}];
     };
  
     subTask.changeTest = function(delta) {
        var newTest = subTask.iTestCase + delta;
        if ((newTest >= 0) && (newTest < this.nbTestCases)) {
           initContextForLevel(newTest);
           $("#testCaseName").html("Test " + (newTest + 1) + "/" + this.nbTestCases);
        }
     };
  
     subTask.getGrade = function(callback) {
        subTask.context.changeDelay(0);
        var code = subTask.blocklyHelper.getCodeFromXml(subTask.answer[0].blockly, "javascript");
        var codes = [subTask.blocklyHelper.getFullCode(code)];
        subTask.iTestCase = 0;
        initBlocklyRunner(subTask.context, function(message, success) {
           subTask.testCaseResults[subTask.iTestCase] = subTask.gridInfos.computeGrade(subTask.context, message);
           subTask.iTestCase++;
           if (subTask.iTestCase < subTask.nbTestCases) {
              initContextForLevel(subTask.iTestCase);
              subTask.context.runner.runCodes(codes);
           } else {
              var iWorstTestCase = 0;
              var worstRate = 1;
              for (var iCase = 0; iCase < subTask.nbTestCases; iCase++) {
                 if (subTask.testCaseResults[iCase].successRate < worstRate) {
                    worstRate = subTask.testCaseResults[iCase].successRate;
                    iWorstTestCase = iCase;
                 }
              }
              subTask.testCaseResults[iWorstTestCase].iTestCase = iWorstTestCase;
              callback(subTask.testCaseResults[iWorstTestCase]);
           }
        });
        subTask.iTestCase = 0;
        subTask.testCaseResults = [];
        initContextForLevel(subTask.iTestCase);
        subTask.context.linkBack = true;
        subTask.context.messagePrefixSuccess = "Tous les tests / Alle Tests : ";
        subTask.context.runner.runCodes(codes);
     };
  }
  
  // We need to be able to clean all events
  
  if (EventTarget.prototype.addEventListenerBase == undefined) {
     EventTarget.prototype.addEventListenerBase = EventTarget.prototype.addEventListener;
     EventTarget.prototype.addEventListener = function(type, listener)
     {
         if(!this.EventList) { this.EventList = []; }
         this.addEventListenerBase.apply(this, arguments);
         if(!this.EventList[type]) { this.EventList[type] = []; }
         var list = this.EventList[type];
         for(var index = 0; index != list.length; index++)
         {
             if(list[index] === listener) { return; }
         }
         list.push(listener);
     };
  
     EventTarget.prototype.removeEventListenerBase = EventTarget.prototype.removeEventListener;
     EventTarget.prototype.removeEventListener = function(type, listener)
     {
         if(!this.EventList) { this.EventList = []; }
         if(listener instanceof Function) { this.removeEventListenerBase.apply(this, arguments); }
         if(!this.EventList[type]) { return; }
         var list = this.EventList[type];
         for(var index = 0; index != list.length;)
         {
             var item = list[index];
             if(!listener)
             {
                 this.removeEventListenerBase(type, item);
                 list.splice(index, 1); continue;
             }
             else if(item === listener)
             {
                 list.splice(index, 1); break;
             }
             index++;
         }
         if(list.length == 0) { delete this.EventList[type]; }
     };
  }
  
  function removeBlockly() {
     $(".blocklyWidgetDiv").remove();
     $(".blocklyTooltipDiv").remove();
     document.removeEventListener("keydown"); //, Blockly.onKeyDown_); // TODO: find correct way to remove all event listeners
     delete Blockly;
  }
  