/**
  * Reallocates an array with a new size, and copies the contents of the old array to the new
  * array.
  *
  * @param oldArray the old array, to be reallocated.
  * @param newSize the new array size.
  * @return A new array with the same contents.
  */
 public static Object resizeArray(Object oldArray, int newSize) {
   int oldSize = java.lang.reflect.Array.getLength(oldArray);
   @SuppressWarnings("rawtypes")
   Class elementType = oldArray.getClass().getComponentType();
   Object newArray = java.lang.reflect.Array.newInstance(elementType, newSize);
   int preserveLength = Math.min(oldSize, newSize);
   if (preserveLength > 0) System.arraycopy(oldArray, 0, newArray, 0, preserveLength);
   return newArray;
 }
  /**
   * Tries to detect symmetry in an alignment.
   *
   * <p>Conceptually, an alignment is a function f:A->B between two sets of integers. The function
   * may have simple topology (meaning that if two elements of A are close, then their images in B
   * will also be close), or may have more complex topology (such as a circular permutation). This
   * function checks <i>alignment</i> against a reference function <i>identity</i>, which should
   * have simple topology. It then tries to determine the symmetry order of <i>alignment</i>
   * relative to <i>identity</i>, up to a maximum order of <i>maxSymmetry</i>.
   *
   * <p><strong>Details</strong><br>
   * Considers the offset (in number of residues) which a residue moves after undergoing <i>n</i>
   * alternating transforms by alignment and identity. If <i>n</i> corresponds to the intrinsic
   * order of the alignment, this will be small. This algorithm tries increasing values of <i>n</i>
   * and looks for abrupt decreases in the root mean squared offset. If none are found at
   * <i>n</i><=maxSymmetry, the alignment is reported as non-symmetric.
   *
   * @param alignment The alignment to test for symmetry
   * @param identity An alignment with simple topology which approximates the sequential
   *     relationship between the two proteins. Should map in the reverse direction from alignment.
   * @param maxSymmetry Maximum symmetry to consider. High values increase the calculation time and
   *     can lead to overfitting.
   * @param minimumMetricChange Percent decrease in root mean squared offsets in order to declare
   *     symmetry. 0.4f seems to work well for CeSymm.
   * @return The order of symmetry of alignment, or 1 if no order <= maxSymmetry is found.
   * @see IdentityMap For a simple identity function
   */
  public static int getSymmetryOrder(
      Map<Integer, Integer> alignment,
      Map<Integer, Integer> identity,
      final int maxSymmetry,
      final float minimumMetricChange) {
    List<Integer> preimage = new ArrayList<Integer>(alignment.keySet()); // currently unmodified
    List<Integer> image = new ArrayList<Integer>(preimage);

    int bestSymmetry = 1;
    double bestMetric = Double.POSITIVE_INFINITY; // lower is better
    boolean foundSymmetry = false;

    if (debug) {
      logger.trace("Symm\tPos\tDelta");
    }

    for (int n = 1; n <= maxSymmetry; n++) {
      int deltasSq = 0;
      int numDeltas = 0;
      // apply alignment
      for (int i = 0; i < image.size(); i++) {
        Integer pre = image.get(i);
        Integer intermediate = (pre == null ? null : alignment.get(pre));
        Integer post = (intermediate == null ? null : identity.get(intermediate));
        image.set(i, post);

        if (post != null) {
          int delta = post - preimage.get(i);

          deltasSq += delta * delta;
          numDeltas++;

          if (debug) {
            logger.debug("%d\t%d\t%d\n", n, preimage.get(i), delta);
          }
        }
      }

      // Metrics: RMS compensates for the trend of smaller numDeltas with higher order
      // Not normalizing by numDeltas favors smaller orders

      double metric = Math.sqrt((double) deltasSq / numDeltas); // root mean squared distance

      if (!foundSymmetry && metric < bestMetric * minimumMetricChange) {
        // n = 1 is never the best symmetry
        if (bestMetric < Double.POSITIVE_INFINITY) {
          foundSymmetry = true;
        }
        bestSymmetry = n;
        bestMetric = metric;
      }

      // When debugging need to loop over everything. Unneeded in production
      if (!debug && foundSymmetry) {
        break;
      }
    }
    if (foundSymmetry) {
      return bestSymmetry;
    } else {
      return 1;
    }
  }
  /**
   * Takes a structure and sequence corresponding to an alignment between a structure or sequence
   * and itself (or even a structure with a sequence), where the result has a circular permutation
   * site {@link cpSite} residues to the right.
   *
   * @param first The unpermuted sequence
   * @param second The sequence permuted by cpSite
   * @param cpSite The number of residues from the beginning of the sequence at which the circular
   *     permutation site occurs; can be positive or negative; values greater than the length of the
   *     sequence are acceptable
   * @throws StructureException
   */
  public static AFPChain cpFastaToAfpChain(
      ProteinSequence first, ProteinSequence second, Structure structure, int cpSite)
      throws StructureException {

    if (structure == null) {
      throw new IllegalArgumentException("The structure is null");
    }

    if (first == null) {
      throw new IllegalArgumentException("The sequence is null");
    }

    // we need to find the ungapped CP site
    int gappedCpShift = 0;
    int ungappedCpShift = 0;
    while (ungappedCpShift < Math.abs(cpSite)) {
      char c;
      try {
        if (cpSite <= 0) {
          c = second.getSequenceAsString().charAt(gappedCpShift);
        } else {
          c = second.getSequenceAsString().charAt(first.getLength() - 1 - gappedCpShift);
        }
      } catch (StringIndexOutOfBoundsException e) {
        throw new IllegalArgumentException("CP site of " + cpSite + " is wrong");
      }
      if (c != '-') {
        ungappedCpShift++;
      }
      gappedCpShift++;
    }

    Atom[] ca1 = StructureTools.getRepresentativeAtomArray(structure);
    Atom[] ca2 =
        StructureTools.getRepresentativeAtomArray(
            structure); // can't use cloneCAArray because it doesn't set parent
                        // group.chain.structure

    ProteinSequence antipermuted = null;
    try {
      antipermuted =
          new ProteinSequence(
              SequenceTools.permuteCyclic(second.getSequenceAsString(), gappedCpShift));
    } catch (CompoundNotFoundException e) {
      // this can't happen, the original sequence comes from a ProteinSequence
      logger.error(
          "Unexpected error while creating protein sequence: {}. This is most likely a bug.",
          e.getMessage());
    }

    ResidueNumber[] residues = StructureSequenceMatcher.matchSequenceToStructure(first, structure);
    ResidueNumber[] antipermutedResidues =
        StructureSequenceMatcher.matchSequenceToStructure(antipermuted, structure);

    ResidueNumber[] nonpermutedResidues = new ResidueNumber[antipermutedResidues.length];
    SequenceTools.permuteCyclic(antipermutedResidues, nonpermutedResidues, -gappedCpShift);

    // nullify ResidueNumbers that have a lowercase sequence character
    if (first.getUserCollection() != null) {
      CasePreservingProteinSequenceCreator.setLowercaseToNull(first, residues);
    }
    if (second.getUserCollection() != null) {
      CasePreservingProteinSequenceCreator.setLowercaseToNull(second, nonpermutedResidues);
    }

    //		for (int i = 0; i < residues.length; i++) {
    //			if (residues[i] == null) {
    //				System.out.print("=");
    //			} else {
    //				System.out.print(sequence.getSequenceAsString().charAt(i));
    //			}
    //		}
    //		System.out.println();
    //		for (int i = 0; i < residues.length; i++) {
    //			if (nonpermutedResidues[i] == null) {
    //				System.out.print("=");
    //			} else {
    //				System.out.print(second.getSequenceAsString().charAt(i));
    //			}
    //		}
    //		System.out.println();

    return buildAlignment(ca1, ca2, residues, nonpermutedResidues);
  }