private void assignNeuronDepth(Gene[] nodeGenes, int depth, Chromosome mutated) {
    int i;
    NEATNodeGene node;
    // ArrayList nodeGenes = this.findOutputNodes(this.candidateNodes(genes));

    for (i = 0; i < nodeGenes.length; i++) {
      node = (NEATNodeGene) nodeGenes[i];
      if (node.getType() == NEATNodeGene.OUTPUT) {
        if (depth == 1) {
          node.setDepth(depth);
          this.assignNeuronDepth(
              this.findSourceNodes(node.id(), mutated.genes()), depth + 1, mutated);
        }
      } else if (node.getType() == NEATNodeGene.HIDDEN) {
        if (node.getDepth() == 0) {
          // we have an unassigned depth
          node.setDepth(depth);
          this.assignNeuronDepth(
              this.findSourceNodes(node.id(), mutated.genes()), depth + 1, mutated);
        }
      } else if (node.getType() == NEATNodeGene.INPUT) {
        node.setDepth(Integer.MAX_VALUE);
      }
    }
  }
  /** Mutates the chromsome based on the set of probabilities. */
  public Chromosome mutate(Chromosome mutatee) {
    Gene[] genes = mutatee.genes();
    int originalSize = genes.length;
    NEATChromosome mutated;
    int i;
    for (i = 0; i < genes.length; i++) {
      if (genes[i] instanceof NEATLinkGene) {
        genes[i] = this.mutateLink((NEATLinkGene) genes[i]);
      } else if (genes[i] instanceof NEATNodeGene) {
        genes[i] = this.mutateNode((NEATNodeGene) genes[i]);
      } else if (genes[i] instanceof NEATFeatureGene) {
        genes[i] = this.mutateFeature((NEATFeatureGene) genes[i]);
      }
    }
    mutated = new NEATChromosome(genes);
    mutated.setSpecieId(((NEATChromosome) mutatee).getSpecieId());
    this.mutateAddLink(mutated);
    this.mutateAddNode(mutated);

    // now update chrome for depth and recurrency legality
    this.updateDepthInfo(mutated);
    mutated.updateChromosome(this.ensureLegalLinks(mutated.genes()));

    if (mutated.genes().length < originalSize) {
      System.out.println(
          "Mutation -- Original: " + originalSize + " new: " + mutated.genes().length);
    }

    return (mutated);
  }
  private void mutateAddNode(Chromosome mutatee) {
    double nodeRandVal = nodeRand.nextDouble();
    ArrayList nodeLinks;
    // ArrayList nodes;
    NEATLinkGene chosen;
    NEATNodeGene newNode;
    NEATLinkGene newLower;
    NEATLinkGene newUpper;
    int newChromoIdx = mutatee.genes().length;
    // Gene[] newChromo = new Gene[newChromoIdx + 3];
    Gene[] newChromo = new Gene[newChromoIdx + 2];
    System.arraycopy(mutatee.genes(), 0, newChromo, 0, newChromoIdx);
    int linkIdx;

    if (nodeRandVal < this.pAddNode) {
      // add a node on an existing enabled connection
      // find an existing connection to intercept
      nodeLinks = this.candidateLinks(mutatee.genes(), true);
      if (nodeLinks.size() > 0) {
        // ensure there is a link to split
        linkIdx = nodeRand.nextInt(nodeLinks.size());
        chosen = (NEATLinkGene) nodeLinks.get(linkIdx);
        // disable old link
        chosen.setEnabled(false);
        newNode = InnovationDatabase.database().submitNodeInnovation(chosen);
        // newNode.setBias(MathUtils.nextPlusMinusOne());
        newLower =
            InnovationDatabase.database().submitLinkInnovation(chosen.getFromId(), newNode.id());
        newUpper =
            InnovationDatabase.database().submitLinkInnovation(newNode.id(), chosen.getToId());
        // set weights according to Stanley et al's NEAT document
        newLower.setWeight(1);
        newUpper.setWeight(chosen.getWeight());
        // now update the chromosome with new node and 2 new links
        newChromo[this.findChosenIndex(chosen, mutatee)] = newNode;
        // newChromo[newChromoIdx++] = newNode;
        newChromo[newChromoIdx++] = newLower;
        newChromo[newChromoIdx] = newUpper;
        mutatee.updateChromosome(newChromo);
      }
    }
  }
  private void mutateAddLink(Chromosome mutatee) {
    double linkRandVal = linkRand.nextDouble();
    NEATNodeGene from;
    NEATNodeGene to;
    int rIdx;
    int i = 0;
    ArrayList links;
    ArrayList nodes;
    Gene[] genes = new Gene[mutatee.size() + 1];
    System.arraycopy(mutatee.genes(), 0, genes, 0, mutatee.genes().length);
    Gene newLink = null;

    if (linkRandVal < this.pAddLink) {
      nodes = this.candidateNodes(mutatee.genes());
      links = this.candidateLinks(mutatee.genes(), false);
      // find a new available link
      while (newLink == null && i < MAX_LINK_ATTEMPTS) {
        rIdx = linkRand.nextInt(nodes.size());
        from = ((NEATNodeGene) nodes.get(rIdx));
        rIdx = linkRand.nextInt(nodes.size());
        to = ((NEATNodeGene) nodes.get(rIdx));
        // TODO Remove
        if (from.getInnovationNumber() == 2 && to.getInnovationNumber() == 5) {
          System.out.println("a");
        }
        if (!this.linkIllegal(from, to, links)) {
          // set it to a random value
          newLink = InnovationDatabase.database().submitLinkInnovation(from.id(), to.id());
          ((NEATLinkGene) newLink).setWeight(MathUtils.nextPlusMinusOne());
          // add link between 2 unconnected nodes
          genes[genes.length - 1] = newLink;
          mutatee.updateChromosome(genes);
        }
        i++;
      }
    }
  }
  private int findChosenIndex(NEATLinkGene chosen, Chromosome mutatee) {
    int idx = -1;
    int i = 0;
    Gene[] genes = mutatee.genes();
    int mutateeSize = genes.length;

    while (i < mutateeSize && idx == -1) {
      if (genes[i] instanceof NEATLinkGene
          && ((NEATLinkGene) genes[i]).getFromId() == chosen.getFromId()
          && ((NEATLinkGene) genes[i]).getToId() == chosen.getToId()) {
        idx = i;
      } else {
        i++;
      }
    }

    return (idx);
  }
  private void updateDepthInfo(Chromosome mutated) {
    // use descriptor's chromo to create net
    ArrayList nodes = new ArrayList();
    ArrayList links = new ArrayList();
    int i;
    Gene[] genes = mutated.genes();

    for (i = 0; i < genes.length; i++) {
      if (genes[i] instanceof NEATNodeGene) {
        nodes.add(genes[i]);
      } else if (genes[i] instanceof NEATLinkGene) {
        if (((NEATLinkGene) genes[i]).isEnabled()) {
          // only add enabled links to the net structure
          links.add(genes[i]);
        }
      }
    }

    this.assignNeuronDepth(this.findOutputNodes(this.candidateNodes(genes)), 1, mutated);
  }