public static WaveletChromatid newRandomWaveletChromatid() {
    final WaveletChromatid newChromatid = new WaveletChromatid();

    while (newChromatid.sequencedGenes.size() <= 0) newChromatid.mutate(null);

    while (Mutations.mutationEvent(newChromatid.mutability)) newChromatid.mutate(null);

    return newChromatid;
  }
  private WaveletChromatid() {
    this.sequencedGenes = new ArrayList<AbstractWaveletGene>();
    this.promoters = new ArrayList<PromoterGene>();
    this.localSignalGenes = new ArrayList<SignalGene>();
    this.externalSignalGenes = new ArrayList<ExternalSignalGene>();

    mutability = Mutations.getRandom().nextDouble() * 10;
    this.centromerePosition = 0;
  }
  public void mutate(final Set<AbstractKey> keyPool) {
    // there is a chance we will remove a signal gene from the chromatid

    if (Mutations.mutationEvent(mutability))
      if (this.localSignalGenes.size() > 0)
        this.sequencedGenes.remove(
            this.localSignalGenes.remove(
                Mutations.getRandom().nextInt(this.localSignalGenes.size())));

    // there is a chance we will add a new gene to the chromatid
    if (Mutations.mutationEvent(mutability) && getGenes().size() < max) {
      // generate the new receptorKey used in the new gene
      ReceptorKey newReceptorKey = new ReceptorKey(randomKey(keyPool));

      // mutate new receptorKey before using it
      while (Mutations.mutationEvent(this.mutability))
        newReceptorKey = newReceptorKey.mutate(mutability);

      // create a new gene using the new receptor
      AbstractWaveletGene newGene;
      final SignalKey newSignalKey = new SignalKey(randomKey(keyPool));
      switch (RANDOM.nextInt(3)) {
        case 0:
          final MutableInteger initialDistance = (new MutableInteger(0)).mutate(mutability);
          newGene = new PromoterGene(newReceptorKey, initialDistance.intValue());
          this.promoters.add((PromoterGene) newGene);
          break;
        case 1:
          newGene = new SignalGene(newReceptorKey, newSignalKey);
          this.localSignalGenes.add((SignalGene) newGene);
          break;
        default:
          newGene = new ExternalSignalGene(newReceptorKey, newSignalKey, RANDOM.nextBoolean());
          this.externalSignalGenes.add((ExternalSignalGene) newGene);
      }
      // add the new gene to the sequence. there is an equal chance the
      // gene will be added to the head and tail
      if (RANDOM.nextBoolean()) this.sequencedGenes.add(0, newGene);
      else this.sequencedGenes.add(newGene);
    }

    // mutate each gene (the gene itself will handle if it actually mutates)
    for (AbstractWaveletGene currentGene : this.sequencedGenes) currentGene.mutate(keyPool);

    // mutate the mutability factor.
    if (Mutations.mutationEvent(mutability))
      this.mutability = Mutations.mutabilityMutation(this.mutability);
  }
public class WaveletChromatid implements Chromatid<AbstractWaveletGene>, Cloneable {
  private final int max = 50;
  // contains all the genes as their sequenced in the chromatid
  private List<AbstractWaveletGene> sequencedGenes;
  // contains all the promoter genes in an arbitrary order
  private List<PromoterGene> promoters;
  // contains just the local (non-external) signal genes in an arbitrary order.
  private List<SignalGene> localSignalGenes;
  // contains al the external signal genes in an arbitrary order.
  private List<ExternalSignalGene> externalSignalGenes;
  // Logger used to log debugging information.
  private static final Logger LOGGER = Logger.getLogger(WaveletChromatid.class);
  // Random used for all random values.
  private static final Random RANDOM = Mutations.getRandom();

  // Position of the gene's centromere. This is the origin where chromatid
  // pairs are joined.
  private int centromerePosition;

  // This chomatids chance of mutating. This value itself will mutate.
  private double mutability;

  private WaveletChromatid() {
    this.sequencedGenes = new ArrayList<AbstractWaveletGene>();
    this.promoters = new ArrayList<PromoterGene>();
    this.localSignalGenes = new ArrayList<SignalGene>();
    this.externalSignalGenes = new ArrayList<ExternalSignalGene>();

    mutability = Mutations.getRandom().nextDouble() * 10;
    this.centromerePosition = 0;
  }

  public WaveletChromatid(WaveletChromatid copy) {
    this.centromerePosition = copy.centromerePosition;
    this.mutability = copy.mutability;

    this.sequencedGenes = new ArrayList<AbstractWaveletGene>();
    this.promoters = new ArrayList<PromoterGene>();
    this.localSignalGenes = new ArrayList<SignalGene>();
    this.externalSignalGenes = new ArrayList<ExternalSignalGene>();

    for (AbstractWaveletGene currentGene : copy.sequencedGenes)
      this.sequencedGenes.add(currentGene.clone());
    for (PromoterGene currentGene : copy.promoters) this.promoters.add(currentGene.clone());
    for (SignalGene currentGene : copy.localSignalGenes)
      this.localSignalGenes.add(currentGene.clone());
    for (ExternalSignalGene currentGene : copy.externalSignalGenes)
      this.externalSignalGenes.add(currentGene.clone());
  }

  public static WaveletChromatid newRandomWaveletChromatid() {
    final WaveletChromatid newChromatid = new WaveletChromatid();

    while (newChromatid.sequencedGenes.size() <= 0) newChromatid.mutate(null);

    while (Mutations.mutationEvent(newChromatid.mutability)) newChromatid.mutate(null);

    return newChromatid;
  }

  Set<SignalKey> getExpressedSignals(final boolean external) {
    // calculate the signal concentrations
    final HashSet<SignalKey> allSignals = new HashSet<SignalKey>();
    for (AbstractWaveletGene waveletGene : this.sequencedGenes) {
      // if the current gene doesnt express a signal then skip it.
      if (!(waveletGene instanceof SignalGene)) continue;
      // convert the gene's type
      final SignalGene gene = (SignalGene) waveletGene;

      // check if the gene's signal is internal or external. continue if
      // it doesnt match
      if (external) {
        // if its not an outward pointing external gene then just skip it
        if (!(gene instanceof ExternalSignalGene)) continue;
        else if (!((ExternalSignalGene) gene).isOutward()) continue;
      } else {
        // if its an outward pointing external than just skip it.
        if ((gene instanceof ExternalSignalGene) && (((ExternalSignalGene) gene).isOutward()))
          continue;
      }

      allSignals.add(gene.getOutputSignal());
    }

    return Collections.unmodifiableSet(allSignals);
  }

  public Set<AbstractKey> getKeys() {
    final HashSet<AbstractKey> allKeys = new HashSet<AbstractKey>();
    for (AbstractWaveletGene gene : this.sequencedGenes) allKeys.addAll(gene.getKeys());
    return Collections.unmodifiableSet(allKeys);
  }

  public void preTick() {
    for (AbstractWaveletGene gene : this.sequencedGenes) gene.preTick();
  }

  public void tick() {
    // first we need to calculate the promotion of each site
    final Hashtable<Integer, Double> promotions = new Hashtable<Integer, Double>();
    for (PromoterGene promoter : this.promoters) {
      final int promoterIndex = this.sequencedGenes.indexOf(promoter);
      final int promotedIndex = promoter.getTargetDistance() + promoterIndex;
      if (promotedIndex < this.sequencedGenes.size()) {
        double promotion = 0.0;
        if (promotions.contains(promotedIndex)) promotion = promotions.get(promotedIndex);
        final double newPromotion = promotion + promoter.expressionActivity();
        if (newPromotion != 0.0) promotions.put(promotedIndex, newPromotion);
      }
    }

    for (int sequenceIndex = 0; sequenceIndex < this.sequencedGenes.size(); sequenceIndex++) {
      this.sequencedGenes
          .get(sequenceIndex)
          .tick(promotions.containsKey(sequenceIndex) ? promotions.get(sequenceIndex) : 0);
    }
  }

  public boolean bind(final SignalKeyConcentration concentration, final boolean isExternal) {
    boolean bound = false;
    for (AbstractWaveletGene gene : this.sequencedGenes)
      if (gene.bind(concentration, isExternal)) bound = true;
    return bound;
  }

  public int getCentromerePosition() {
    return this.centromerePosition;
  }

  public List<AbstractWaveletGene> getGenes() {
    return Collections.unmodifiableList(this.sequencedGenes);
  }

  public List<PromoterGene> getPromoterGenes() {
    return Collections.unmodifiableList(this.promoters);
  }

  public List<SignalGene> getLocalSignalGenes() {
    return Collections.unmodifiableList(this.localSignalGenes);
  }

  public List<ExternalSignalGene> getExternalSignalGenes() {
    return Collections.unmodifiableList(this.externalSignalGenes);
  }

  public List<AbstractWaveletGene> crossover(final int point) {
    final int index = point + this.centromerePosition;

    if ((index < 0) || (index > this.sequencedGenes.size())) return null;
    if ((index == 0) || (index == this.sequencedGenes.size()))
      return Collections.unmodifiableList(new ArrayList<AbstractWaveletGene>());

    if (point < 0) return Collections.unmodifiableList(this.sequencedGenes.subList(0, index));
    else
      return Collections.unmodifiableList(
          this.sequencedGenes.subList(index, this.sequencedGenes.size()));
  }

  public void crossover(final List<AbstractWaveletGene> geneticSegment, final int point) {
    final int index = point + this.centromerePosition;
    if (geneticSegment == null) return;
    if ((index < 0) || (index > this.sequencedGenes.size()))
      throw new IllegalArgumentException("point " + index + "is out of range for crossover");

    // calculate new centromere position
    final int newCentromerePostion = this.centromerePosition - (index - geneticSegment.size());

    // create new sequence of genes after crossover
    ArrayList<AbstractWaveletGene> newGenes;
    List<AbstractWaveletGene> oldGenes;
    if (point < 0) {
      newGenes = new ArrayList<AbstractWaveletGene>(geneticSegment);
      newGenes.addAll(this.sequencedGenes.subList(index, this.sequencedGenes.size()));

      oldGenes = this.sequencedGenes.subList(0, index);
    } else {
      newGenes = new ArrayList<AbstractWaveletGene>(this.sequencedGenes.subList(0, index));
      newGenes.addAll(geneticSegment);

      oldGenes = this.sequencedGenes.subList(index, this.sequencedGenes.size());
    }

    // remove displaced genes from specific gene type lists
    for (AbstractWaveletGene oldGene : oldGenes) {
      if (oldGene instanceof PromoterGene) this.promoters.remove(oldGene);
      else if (oldGene instanceof ExternalSignalGene) this.externalSignalGenes.remove(oldGene);
      else if (oldGene instanceof SignalGene) this.localSignalGenes.remove(oldGene);
    }

    // add new genes to the specific gene type list
    for (AbstractWaveletGene newGene : geneticSegment) {
      if (newGene instanceof PromoterGene) this.promoters.add((PromoterGene) newGene);
      else if (newGene instanceof ExternalSignalGene)
        this.externalSignalGenes.add((ExternalSignalGene) newGene);
      else if (newGene instanceof SignalGene) this.localSignalGenes.add((SignalGene) newGene);
    }

    // update sequence genes to use the new genes
    this.sequencedGenes = newGenes;
    this.centromerePosition = newCentromerePostion;
  }

  @Override
  public WaveletChromatid clone() {
    try {
      final WaveletChromatid copy = (WaveletChromatid) super.clone();

      copy.centromerePosition = this.centromerePosition;
      copy.mutability = this.mutability;

      copy.sequencedGenes = new ArrayList<AbstractWaveletGene>();
      copy.promoters = new ArrayList<PromoterGene>();
      copy.localSignalGenes = new ArrayList<SignalGene>();
      copy.externalSignalGenes = new ArrayList<ExternalSignalGene>();

      for (AbstractWaveletGene currentGene : this.sequencedGenes)
        copy.sequencedGenes.add(currentGene.clone());
      for (PromoterGene currentGene : this.promoters) copy.promoters.add(currentGene.clone());
      for (SignalGene currentGene : this.localSignalGenes)
        copy.localSignalGenes.add(currentGene.clone());
      for (ExternalSignalGene currentGene : this.externalSignalGenes)
        copy.externalSignalGenes.add(currentGene.clone());

      return copy;
    } catch (CloneNotSupportedException caught) {
      LOGGER.error("CloneNotSupportedException caught but not expected!", caught);
      throw new UnexpectedDannError("CloneNotSupportedException caught but not expected", caught);
    }
  }

  private static AbstractKey randomKey(final Set<AbstractKey> keyPool) {
    if ((keyPool != null) && (!keyPool.isEmpty())) {
      // select a random key from the pool
      AbstractKey randomKey = null;
      int keyIndex = RANDOM.nextInt(keyPool.size());
      for (AbstractKey key : keyPool) {
        if (keyIndex <= 0) {
          randomKey = key;
          break;
        } else keyIndex--;
      }
      if (randomKey == null) {
        LOGGER.error("randomKey was null which shouldnt be possible");
        throw new UnexpectedDannError("randomKey was unexpectidly null");
      }
      return new ReceptorKey(randomKey);
    }

    return new ReceptorKey();
  }

  public void mutate(final Set<AbstractKey> keyPool) {
    // there is a chance we will remove a signal gene from the chromatid

    if (Mutations.mutationEvent(mutability))
      if (this.localSignalGenes.size() > 0)
        this.sequencedGenes.remove(
            this.localSignalGenes.remove(
                Mutations.getRandom().nextInt(this.localSignalGenes.size())));

    // there is a chance we will add a new gene to the chromatid
    if (Mutations.mutationEvent(mutability) && getGenes().size() < max) {
      // generate the new receptorKey used in the new gene
      ReceptorKey newReceptorKey = new ReceptorKey(randomKey(keyPool));

      // mutate new receptorKey before using it
      while (Mutations.mutationEvent(this.mutability))
        newReceptorKey = newReceptorKey.mutate(mutability);

      // create a new gene using the new receptor
      AbstractWaveletGene newGene;
      final SignalKey newSignalKey = new SignalKey(randomKey(keyPool));
      switch (RANDOM.nextInt(3)) {
        case 0:
          final MutableInteger initialDistance = (new MutableInteger(0)).mutate(mutability);
          newGene = new PromoterGene(newReceptorKey, initialDistance.intValue());
          this.promoters.add((PromoterGene) newGene);
          break;
        case 1:
          newGene = new SignalGene(newReceptorKey, newSignalKey);
          this.localSignalGenes.add((SignalGene) newGene);
          break;
        default:
          newGene = new ExternalSignalGene(newReceptorKey, newSignalKey, RANDOM.nextBoolean());
          this.externalSignalGenes.add((ExternalSignalGene) newGene);
      }
      // add the new gene to the sequence. there is an equal chance the
      // gene will be added to the head and tail
      if (RANDOM.nextBoolean()) this.sequencedGenes.add(0, newGene);
      else this.sequencedGenes.add(newGene);
    }

    // mutate each gene (the gene itself will handle if it actually mutates)
    for (AbstractWaveletGene currentGene : this.sequencedGenes) currentGene.mutate(keyPool);

    // mutate the mutability factor.
    if (Mutations.mutationEvent(mutability))
      this.mutability = Mutations.mutabilityMutation(this.mutability);
  }
}