/**
   * Adds a mutation to a {@link MutationLibrary}. Six versions of of each mutation are added to
   * mutationLibrary entry. the method fill's for the given mutation all the possible vector in the
   * given rotations
   *
   * <p>0 degrees on axis x
   *
   * <p>90 degrees on axis x
   *
   * <p>-90 degrees on axis x
   *
   * <p>180 degrees on axis x
   *
   * <p>90 degrees on axis y
   *
   * <p>-90 degrees on axis y
   *
   * @param mutation the mutation
   * @param library the mutationLibrary
   */
  private void addLibraryEntry(Mutation mutation, MutationLibrary library) {
    Vector3f positionVector = new Vector3f();

    // TODO: check for duplicity in the Dictionary while putting mutation
    positionVector.sub(mutation.getLastMonomerVector(), mutation.getFirstMonomerVector());
    // put in library as it is
    MutationLibraryEntry newEntry =
        new MutationLibraryEntry(mutation, 'x', 0, MonomerDirection.FORWARD, dimensions, matrixMan);
    library.put(positionVector, newEntry);

    if (dimensions == Dimensions.THREE) {
      // rotation of 90 deg around x axis
      addToLib(mutation, library, positionVector, (Math.PI / 2), 'x', MonomerDirection.UP);
      // rotation of -90 deg around x axis
      addToLib(mutation, library, positionVector, (-Math.PI / 2), 'x', MonomerDirection.DOWN);
    }

    // rotation of 180 deg around x axis
    // TODO: check definition in here !!!
    addToLib(mutation, library, positionVector, Math.PI, 'x', MonomerDirection.FORWARD);
    // rotation of 90 deg around y axis

    addToLib(mutation, library, positionVector, (Math.PI / 2), 'z', MonomerDirection.LEFT);
    // rotation of -90 deg around y axis
    addToLib(mutation, library, positionVector, (-Math.PI / 2), 'z', MonomerDirection.RIGHT);
  }
  private void addToLib(
      Mutation mutation,
      MutationLibrary library,
      Vector3f positionVector,
      double degree,
      char axis,
      MonomerDirection relative) {

    if ((dimensions == Dimensions.THREE) || (relative.ordinal() < 3)) {
      MutationLibraryEntry newEntry =
          new MutationLibraryEntry(mutation, axis, degree, relative, dimensions, matrixMan);
      library.put(matrixMan.LorentzTransformation(positionVector, axis, degree), newEntry);
    }
  }
  /**
   * @param protein - mutation will be created from this protein
   * @param out - protein with low fitness. if mutation process is successful out will become the
   *     result of the mutation, else out will be reset.
   * @param max_tries - max tries for creating a mutation.
   */
  public void mutate(Protein protein, Protein out, int max_tries, int monomerIndex) {
    if (protein.size() < 10)
      throw new RuntimeException(
          "A protein of length " + protein.size() + " Shorter than the predefined mutations");
    if (protein.getConformation().size() == 0)
      throw new RuntimeException("protein.conformation.size() == 0");
    int nTries = 0;
    boolean success = false;
    int mutationLength;
    int mutationStartMonomer;
    int mutationLastMonomer;
    Vector3f representativeVector = new Vector3f();
    Vector3f start, end;
    ArrayList<MutationLibraryEntry> list;
    if (originalConformationIn == null) // first time here
    originalConformationIn = new Conformation(protein.getConformation().size());
    if (originalConformationOut == null) // first time here
    originalConformationOut = new Conformation(protein.getConformation().size());
    int selectedMutationIndex;
    originalConformationIn.copy(protein.getConformation());
    originalConformationOut.copy(out.getConformation());
    if (originalConformationIn.size() == 0)
      throw new RuntimeException(
          "originalConformation.size() == 0 ; protein.size() = "
              + protein.size()
              + "; protein.conformation.size() = "
              + protein.getConformation().size());
    MutationLibraryEntry entry;

    while (nTries < max_tries) { // upon success the method will return
      nTries++;
      out.reset();
      if (monomerIndex > 0) {
        mutationLength =
            random.nextInt(Math.min(protein.size() - monomerIndex, mutationLibraries.length) - 2)
                + 2;
      } else {
        // Generate a number between [1..protein.size-2]
        mutationLength =
            random.nextInt(Math.min(protein.size() - 1, mutationLibraries.length) - 2) + 2;
      }
      // the start monomer may be between 1 and protein size- mutation
      // length
      if (monomerIndex > 0) mutationStartMonomer = monomerIndex;
      else mutationStartMonomer = random.nextInt(protein.size() - mutationLength - 1) + 1;
      mutationLastMonomer = mutationLength + mutationStartMonomer - 1;
      start = protein.get(mutationStartMonomer).getR(); // get position
      // vector of start monomer.
      end = protein.get(mutationLastMonomer).getR(); // get position
      // vector of end monomer.
      representativeVector.sub(end, start);

      // if start monomer and end monomer are on a straight line no
      // mutation is possible.
      if (representativeVector.length() + 1 == mutationLength) continue;

      mutationLibrary = mutationLibraries[mutationLength];
      list = mutationLibrary.get(representativeVector);
      if (list == null) // list not found for given arguments
      throw new RuntimeException(
            "ERROR: \nstart vector:"
                + start
                + " \nend Vector"
                + end
                + "\nsub:"
                + representativeVector
                + "\n"
                + "Mutation length:"
                + mutationLength
                + "\n"
                + "Mutation Start Monomer:"
                + mutationStartMonomer
                + "\n"
                + "Mutation Last Monomer:"
                + mutationLastMonomer
                + "\n"
                + "Protein:"
                + protein.toString());
      if (list.size() > 0) {

        /* Filter mutations - save only valid mutations */

        // collect position of the protein before and after the
        // mutation.
        // save them not as absolut position but as offset from the star
        // of mutation, this
        // helps us compare them to the position needed by the mutation.
        proteinPositions.clear();
        for (int i = 0; i < protein.size(); i++) {
          if ((i < mutationStartMonomer || i > mutationLastMonomer)) {
            // TODO: avoid creating a new vector.
            Vector3f vec = new Vector3f(protein.get(i).getR());
            vec.sub(start); // Save position as an offset from start
            // of mutation.
            proteinPositions.add(vec);
          }
        }
        // Save in candidates only the mutations that do no overlap with
        // already occupied position of the protein.
        // (the position collected in the previous loop)
        candidates.clear();
        for (MutationLibraryEntry lib : list) {
          boolean found = false;
          for (Vector3f vec : lib.positionOffsets) {
            if ((found = proteinPositions.contains(vec))) break;
          }
          if (!found) candidates.add(lib);
        }
        if (candidates.isEmpty()) // if no candidates try new mutation.
        continue;

        // Select random mutation from candidates.
        selectedMutationIndex = selectMutationNumber(candidates);
        entry = candidates.get(selectedMutationIndex);
        success =
            activateMutationOnProtein(
                out,
                protein,
                entry,
                mutationStartMonomer,
                mutationLastMonomer,
                originalConformationIn);
      }
      if (success) return;
    }
    out.reset();
    out.setConformation(originalConformationOut);
  }