text: (function(ext) {
  
  /**
   * Provides a set of classes and methods for handling Neuroevolution and
   * genetic algorithms.
   *
   * @param {options} An object of options for Neuroevolution.
   */
  var Neuroevolution = function(options){
      var self = this;  // reference to the top scope of this module
  
      self.ELITISM_BY_RATE = "RATE";
      self.ELITISM_BY_COUNT = "COUNT",
  
      // Declaration of module parameters (options) and default values
      self.options = {
          /**
           * Logistic activation function.
           *
       * @param {a} Input value.
       * @return Logistic function output.
       */
          activation: function(a){
              ap = (-a)/1;
              return (1/(1 + Math.exp(ap)))
          },
  
          /**
           * Returns a random value between -1 and 1.
           *
           * @return Random value.
           */
          randomClamped: function(){
              return Math.random() * 2 - 1;
          },
  
          // various factors and parameters (along with default values).
          network:[1, [1], 1],    // Perceptron network structure (1 hidden
                      // layer).
          population:50,          // Population by generation.
          elitismRate:0.2,            // Best networks kepts unchanged for the next
          elitismCount:1,            // Best networks kepts unchanged for the next
  
          elitismMode:self.ELITISM_BY_COUNT,            // elitism mode
                          // generation (rate).
          randomBehaviour:0.2,    // New random networks for the next generation
                          // (rate).
          mutationRate:0.1,       // Mutation rate on the weights of synapses.
          mutationRange:0.5,      // Interval of the mutation changes on the
                          // synapse weight.
          historic:0,             // Latest generations saved.
          lowHistoric:false,      // Only save score (not the network).
          scoreSort:-1,           // Sort order (-1 = desc, 1 = asc).
          nbChild:1               // Number of children by breeding.
  
      }
  
      /**
       * Override default options.
       *
       * @param {options} An object of Neuroevolution options.
       * @return void
       */
      self.set = function(options){
          for(var i in options){
                  if(this.options[i] != undefined){ // Only override if the passed in value
                                                    // is actually defined.
                  self.options[i] = options[i];
              }
          }
      }
  
      // Overriding default options with the pass in options
      self.set(options);
  
  
  /*NEURON**********************************************************************/
      /**
       * Artificial Neuron class
       *
       * @constructor
       */
      var Neuron = function(){
          this.value = 0;
          this.weights = [];
      }
  
      /**
       * Initialize number of neuron weights to random clamped values.
       *
       * @param {nb} Number of neuron weights (number of inputs).
       * @return void
       */
      Neuron.prototype.populate = function(nb){
          this.weights = [];
          for(var i = 0; i < nb; i++){
              this.weights.push(self.options.randomClamped());
          }
      }
  
  
  /*LAYER***********************************************************************/
      /**
       * Neural Network Layer class.
       *
       * @constructor
       * @param {index} Index of this Layer in the Network.
       */
      var Layer = function(index){
          this.id = index || 0;
          this.neurons = [];
      }
  
      /**
       * Populate the Layer with a set of randomly weighted Neurons.
       *
       * Each Neuron be initialied with nbInputs inputs with a random clamped
       * value.
       *
       * @param {nbNeurons} Number of neurons.
       * @param {nbInputs} Number of inputs.
       * @return void
       */
      Layer.prototype.populate = function(nbNeurons, nbInputs){
          this.neurons = [];
          for(var i = 0; i < nbNeurons; i++){
              var n = new Neuron();
              n.populate(nbInputs);
              this.neurons.push(n);
          }
      }
  
  
  /*NEURAL NETWORK**************************************************************/
      /**
       * Neural Network class
       *
       * Composed of Neuron Layers.
       *
       * @constructor
       */
      var Network = function(){
          this.layers = [];
      }
  
      /**
       * Generate the Network layers.
       *
       * @param {input} Number of Neurons in Input layer.
       * @param {hidden} Number of Neurons per Hidden layer.
       * @param {output} Number of Neurons in Output layer.
       * @return void
       */
      Network.prototype.perceptronGeneration = function(input, hiddens, output){
          var index = 0;
          var previousNeurons = 0;
          var layer = new Layer(index);
              layer.populate(input, previousNeurons); // Number of Inputs will be set to
                                                      // 0 since it is an input layer.
          previousNeurons = input;  // number of input is size of previous layer.
          this.layers.push(layer);
          index++;
          for(var i in hiddens){
              // Repeat same process as first layer for each hidden layer.
              var layer = new Layer(index);
              layer.populate(hiddens[i], previousNeurons);
              previousNeurons = hiddens[i];
              this.layers.push(layer);
              index++;
          }
          var layer = new Layer(index);
          layer.populate(output, previousNeurons);  // Number of input is equal to
                                                        // the size of the last hidden
                                // layer.
          this.layers.push(layer);
      }
  
      /**
       * Create a copy of the Network (neurons and weights).
       *
       * Returns number of neurons per layer and a flat array of all weights.
       *
       * @return Network data.
       */
      Network.prototype.getSave = function(){
          var datas = {
              neurons:[], // Number of Neurons per layer.
              weights:[]  // Weights of each Neuron's inputs.
          };
  
          for(var i in this.layers){
              datas.neurons.push(this.layers[i].neurons.length);
              for(var j in this.layers[i].neurons){
                  for(var k in this.layers[i].neurons[j].weights){
                      // push all input weights of each Neuron of each Layer into a flat
                      // array.
                      datas.weights.push(this.layers[i].neurons[j].weights[k]);
                  }
              }
          }
          return datas;
      }
  
      /**
       * Apply network data (neurons and weights).
       *
       * @param {save} Copy of network data (neurons and weights).
       * @return void
       */
      Network.prototype.setSave = function(save){
          var previousNeurons = 0;
          var index = 0;
          var indexWeights = 0;
          this.layers = [];
          for(var i in save.neurons){
              // Create and populate layers.
              var layer = new Layer(index);
              layer.populate(save.neurons[i], previousNeurons);
              for(var j in layer.neurons){
                  for(var k in layer.neurons[j].weights){
                      // Apply neurons weights to each Neuron.
                      layer.neurons[j].weights[k] = save.weights[indexWeights];
  
                      indexWeights++; // Increment index of flat array.
                  }
              }
              previousNeurons = save.neurons[i];
              index++;
              this.layers.push(layer);
          }
      }
  
      /**
       * Compute the output of an input.
       *
       * @param {inputs} Set of inputs.
       * @return Network output.
       */
      Network.prototype.compute = function(inputs){
          // Set the value of each Neuron in the input layer.
          for(var i in inputs){
              if(this.layers[0] && this.layers[0].neurons[i]){
                  this.layers[0].neurons[i].value = inputs[i];
              }
          }
  
          var prevLayer = this.layers[0]; // Previous layer is input layer.
          for(var i = 1; i < this.layers.length; i++){
              for(var j in this.layers[i].neurons){
                  // For each Neuron in each layer.
                  var sum = 0;
                  for(var k in prevLayer.neurons){
                      // Every Neuron in the previous layer is an input to each Neuron in
                      // the next layer.
                      sum += prevLayer.neurons[k].value
                                  * this.layers[i].neurons[j].weights[k];
                  }
  
                  // Compute the activation of the Neuron.
                  this.layers[i].neurons[j].value = self.options.activation(sum);
              }
              prevLayer = this.layers[i];
          }
  
          // All outputs of the Network.
          var out = [];
          var lastLayer = this.layers[this.layers.length - 1];
          for(var i in lastLayer.neurons){
              out.push(lastLayer.neurons[i].value);
          }
          return out;
      }
  
  
  /*GENOME**********************************************************************/
      /**
       * Genome class.
       *
       * Composed of a score and a Neural Network.
       *
       * @constructor
       *
       * @param {score}
       * @param {network}
       */
      var Genome = function(score, network){
          this.score = score || 0;
          this.network = network || null;
      }
  
  
  /*GENERATION******************************************************************/
      /**
       * Generation class.
       *
       * Composed of a set of Genomes.
       *
       * @constructor
       */
      var Generation = function(){
          this.genomes = [];
      }
  
      /**
       * Add a genome to the generation.
       *
       * @param {genome} Genome to add.
       * @return void.
       */
      Generation.prototype.addGenome = function(genome){
              // Locate position to insert Genome into.
              // The gnomes should remain sorted.
          for(var i = 0; i < this.genomes.length; i++){
                  // Sort in descending order.
              if(self.options.scoreSort < 0){
                  if(genome.score > this.genomes[i].score){
                      break;
                  }
              // Sort in ascending order.
              }else{
                  if(genome.score < this.genomes[i].score){
                      break;
                  }
              }
  
          }
  
          // Insert genome into correct position.
          this.genomes.splice(i, 0, genome);
      }
  
      /**
       * Breed to genomes to produce offspring(s).
       *
       * @param {g1} Genome 1.
       * @param {g2} Genome 2.
       * @param {nbChilds} Number of offspring (children).
       */
      Generation.prototype.breed = function(g1, g2, nbChilds){
          var datas = [];
          for(var nb = 0; nb < nbChilds; nb++){
              // Deep clone of genome 1.
              var data = JSON.parse(JSON.stringify(g1));
              for(var i in g2.network.weights){
                  // Genetic crossover
                  // 0.5 is the crossover factor.
                  // FIXME Really should be a predefined constant.
                  if(Math.random() <= 0.5){
                      data.network.weights[i] = g2.network.weights[i];
                  }
              }
  
              // Perform mutation on some weights.
              for(var i in data.network.weights){
                  if(Math.random() <= self.options.mutationRate){
                      data.network.weights[i] += Math.random()
                          * self.options.mutationRange
                          * 2
                          - self.options.mutationRange;
                  }
              }
              datas.push(data);
          }
  
          return datas;
      }
  
      /**
       * Generate the next generation.
       *
       * @return Next generation data array.
       */
      Generation.prototype.generateNextGeneration = function(){
          var nexts = [];
  
          for(var i = 0; i < (self.options.elitismMode==self.ELITISM_BY_COUNT? self.options.elitismCount :Math.round(self.options.elitism
                                   * self.options.population)); i++){
              if(nexts.length < self.options.population){
                      // Push a deep copy of ith Genome's Nethwork.
                  nexts.push(JSON.parse(JSON.stringify(this.genomes[i].network)));
              }
          }
  
          for(var i = 0; i < Math.round(self.options.randomBehaviour
                                   * self.options.population); i++){
              var n = JSON.parse(JSON.stringify(this.genomes[0].network));
              for(var k in n.weights){
                  n.weights[k] = self.options.randomClamped();
              }
              if(nexts.length < self.options.population){
                  nexts.push(n);
              }
          }
  
          var max = 0;
          while(true){
              for(var i = 0; i < max; i++){
                      // Create the children and push them to the nexts array.
                      var childs = this.breed(this.genomes[i], this.genomes[max],
                              (self.options.nbChild > 0 ? self.options.nbChild : 1) );
                  for(var c in childs){
                      nexts.push(childs[c].network);
                      if(nexts.length >= self.options.population){
                          // Return once number of children is equal to the
                          // population by generatino value.
                          return nexts;
                      }
                  }
              }
              max++;
              if(max >= this.genomes.length - 1){
                  max = 0;
              }
          }
      }
  
  
  /*GENERATIONS*****************************************************************/
      /**
       * Generations class.
       *
       * Hold's previous Generations and current Generation.
       *
       * @constructor
       */
      var Generations = function(){
          this.generations = [];
          var currentGeneration = new Generation();
      }
  
      /**
       * Create the first generation.
       *
       * @param {input} Input layer.
       * @param {input} Hidden layer(s).
       * @param {output} Output layer.
       * @return First Generation.
       */
      Generations.prototype.firstGeneration = function(input, hiddens, output){
              // FIXME input, hiddens, output unused.
  
          var out = [];
          for(var i = 0; i < self.options.population; i++){
                  // Generate the Network and save it.
              var nn = new Network();
              nn.perceptronGeneration(self.options.network[0],
                          self.options.network[1],
                                          self.options.network[2]);
              out.push(nn.getSave());
          }
  
          this.generations.push(new Generation());
          return out;
      }
  
      /**
       * Create the next Generation.
       *
       * @return Next Generation.
       */
      Generations.prototype.nextGeneration = function(){
          if(this.generations.length == 0){
              // Need to create first generation.
              return false;
          }
  
          var gen = this.generations[this.generations.length - 1]
                  .generateNextGeneration();
          this.generations.push(new Generation());
          return gen;
      }
  
      /**
       * Add a genome to the Generations.
       *
       * @param {genome}
       * @return False if no Generations to add to.
       */
      Generations.prototype.addGenome = function(genome){
              // Can't add to a Generation if there are no Generations.
          if(this.generations.length == 0) return false;
  
           // FIXME addGenome returns void.
          return this.generations[this.generations.length - 1].addGenome(genome);
      }
  
  
  /*SELF************************************************************************/
      self.generations = new Generations();
  
      /**
       * Reset and create a new Generations object.
       *
       * @return void.
       */
      self.restart = function(){
          self.generations = new Generations();
      }
  
      /**
       * Create the next generation.
       *
       * @return Neural Network array for next Generation.
       */
      self.nextGeneration = function(){
          var networks = [];
  
          if(self.generations.generations.length == 0){
                  // If no Generations, create first.
              networks = self.generations.firstGeneration();
          }else{
                  // Otherwise, create next one.
              networks = self.generations.nextGeneration();
          }
  
              // Create Networks from the current Generation.
          var nns = [];
          for(var i in networks){
              var nn = new Network();
              nn.setSave(networks[i]);
              nns.push(nn);
          }
  
          if(self.options.lowHistoric){
                  // Remove old Networks.
              if(self.generations.generations.length >= 2){
                  var genomes =
                      self.generations
                          .generations[self.generations.generations.length - 2]
                                  .genomes;
                  for(var i in genomes){
                      delete genomes[i].network;
                  }
              }
          }
  
          if(self.options.historic != -1){
                  // Remove older generations.
              if(self.generations.generations.length > self.options.historic + 1){
                      self.generations.generations.splice(0,
                          self.generations.generations.length - (self.options.historic + 1));
              }
          }
  
          return nns;
      }
  
      /**
       * Adds a new Genome with specified Neural Network and score.
       *
       * @param {network} Neural Network.
       * @param {score} Score value.
       * @return void.
       */
      self.networkScore = function(network, score){
          self.generations.addGenome(new Genome(score, network.getSave()));
      }
  }
  
  
      window.restartNeuralNetwork = function() {
          window.gen = [];
          window.Neuvol = new Neuroevolution({
                  population:50,
                  network:[2, [2], 1],
              });
      }
      window.restartNeuralNetwork();
      
  
      ext._shutdown = function() {
          
      };
  
      ext._getStatus = function() {
          return {status: 2, msg: 'Ready'};
      };
  
      /************\
      |   BLOCKS   |
      \************/
  
      ext.setPopulation = function(populationCount) {
          window.Neuvol.options.population = populationCount;
      }
  
      ext.setElitismRate = function(elitismRate) {
          window.Neuvol.options.elitismMode = window.Neuvol.ELITISM_BY_RATE;
          window.Neuvol.options.elitismRate = elitismRate;        
      };
  
      ext.setElitismCount = function(elitismCount) {
          window.Neuvol.options.elitismMode = window.Neuvol.ELITISM_BY_COUNT;
          window.Neuvol.options.elitismCount = elitismCount;        
      };
  
      ext.setNetwork = function(networkString) {
          var networkArray = (new Function("return " + networkString+ ";")());
          if (Array.isArray(networkArray)) {
              window.Neuvol.options.network = networkArray;
          }
      }
  
      ext.setMutationRate = function(mutationRate) {
          window.Neuvol.options.mutationRate = mutationRate;
      }
  
      ext.setMutationRange = function(mutationRange) {
          window.Neuvol.options.mutationRange = mutationRange;
      }
  
      ext.compute = function(i, inputs) {
          var inputArray = (new Function("return " + inputs+ ";")());
          if (Array.isArray(inputArray)) {
              return window.gen[i].compute(inputArray);
          } 
          return 0;
      }
  
      ext.setNetworkScore = function(i, score) {
          window.Neuvol.networkScore(window.gen[i], score);
      }
  
      ext.nextGeneration = function() {
          window.gen = window.Neuvol.nextGeneration();
      }  
  
      ext.restartNeuralNetwork = function() {
          window.restartNeuralNetwork();
      } 
  
      ext.nextTutorialStep = function(callback) {
          showModal(["tutorial-welldone"]);
          //window.location.href = './?step='+(window.tutorial_step++);
      }    
  
      var descriptor = {
          blocks: [
              [' ', 'set population %n', 'setPopulation', 10],
              [' ', 'set elitism rate %n', 'setElitismRate', 0.2],
              [' ', 'set elitism count %n', 'setElitismCount', 1],
              [' ', 'set network structure %s', 'setNetwork', '[2, [2], 1]'],
              [' ', 'set mutation rate %n', 'setMutationRate', 0.1],
              [' ', 'set mutation range %n', 'setMutationRange', 0.5],
              ['r', 'generation[%n ] compute inputs %s', 'compute', 1, '[1,1]'],
              [' ', 'set network[%n ] with score: %n', 'setNetworkScore', '1', '0'],
              [' ', 'generate next generation', 'nextGeneration'],
              [' ', 'restart neural network', 'restartNeuralNetwork'],
              ['w', 'next tutorial step', 'nextTutorialStep']
          ]
      };
  
      ScratchExtensions.register('Dalton-AI-Lab-FlappyML', descriptor, ext);
      
      
  
  
  })({});