/*
 *                    BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 *
 * Created on June 7, 2010
 * Author: Mark Chapman
 */

package org.biojava.nbio.alignment;

import org.biojava.nbio.core.alignment.template.ProfilePair;
import org.biojava.nbio.core.alignment.template.SequencePair;
import org.biojava.nbio.core.alignment.matrices.SubstitutionMatrixHelper;
import org.biojava.nbio.core.alignment.template.Profile;
import org.biojava.nbio.core.alignment.template.SubstitutionMatrix;
import org.biojava.nbio.alignment.template.*;
import org.biojava.nbio.core.sequence.compound.AmbiguityDNACompoundSet;
import org.biojava.nbio.core.sequence.compound.AminoAcidCompoundSet;
import org.biojava.nbio.core.sequence.compound.DNACompoundSet;
import org.biojava.nbio.core.sequence.template.Compound;
import org.biojava.nbio.core.sequence.template.CompoundSet;
import org.biojava.nbio.core.sequence.template.Sequence;
import org.biojava.nbio.core.util.ConcurrencyTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * Static utility to easily run alignment routines.  To exit cleanly after running any parallel method that mentions
 * use of the {@link ConcurrencyTools} utility, {@link ConcurrencyTools#shutdown()} or
 * {@link ConcurrencyTools#shutdownAndAwaitTermination()} must be called.
 *
 * @author Mark Chapman
 */
public class Alignments {

	private final static Logger logger = LoggerFactory.getLogger(Alignments.class);

	/**
	 * List of implemented sequence pair in a profile scoring routines.
	 */
	public static enum PairInProfileScorerType {
		IDENTITIES,  // similar to MUSCLE
		SIMILARITIES
	}

	/**
	 * List of implemented pairwise sequence alignment routines.
	 */
	public static enum PairwiseSequenceAlignerType {
		GLOBAL,              // Needleman-Wunsch/Gotoh
		GLOBAL_LINEAR_SPACE, // Guan-Uberbacher
		LOCAL,               // Smith-Waterman/Gotoh
		LOCAL_LINEAR_SPACE   // Smith-Waterman/Gotoh with smart traceback at each maximum
	}

	/**
	 * List of implemented pairwise sequence scoring routines.
	 */
	public static enum PairwiseSequenceScorerType {
		GLOBAL,
		GLOBAL_IDENTITIES,   // similar to CLUSTALW and CLUSTALW2
		GLOBAL_SIMILARITIES,
		LOCAL,
		LOCAL_IDENTITIES,
		LOCAL_SIMILARITIES,
		KMERS,               // similar to CLUSTAL and MUSCLE
		WU_MANBER            // similar to KALIGN
	}

	/**
	 * List of implemented profile-profile alignment routines.
	 */
	public static enum ProfileProfileAlignerType {
		GLOBAL,              // similar to MUSCLE and KALIGN
		GLOBAL_LINEAR_SPACE, // similar to CLUSTALW and CLUSTALW2
		GLOBAL_CONSENSUS,    // similar to CLUSTAL
		LOCAL,
		LOCAL_LINEAR_SPACE,
		LOCAL_CONSENSUS
	}

	/**
	 * List of implemented profile refinement routines.
	 */
	public static enum RefinerType {
		PARTITION_SINGLE,     // similar to CLUSTALW2
		PARTITION_SINGLE_ALL, // similar to CLUSTALW2
		PARTITION_TREE,       // similar to MUSCLE
		PARTITION_TREE_ALL,
		RESCORE_IDENTITIES,   // similar to MUSCLE
		RESCORE_SIMILARITIES
	}

	// prevents instantiation
	private Alignments() { }

	// public factory methods

	/**
	 * Factory method which computes a sequence alignment for all {@link Sequence} pairs in the given {@link List}.
	 * This method runs the alignments in parallel by submitting all of the alignments to the shared thread pool of the
	 * {@link ConcurrencyTools} utility.
	 *
	 * @param <S> each {@link Sequence} of an alignment pair is of type S
	 * @param <C> each element of an {@link AlignedSequence} is a {@link Compound} of type C
	 * @param sequences the {@link List} of {@link Sequence}s to align
	 * @param type chosen type from list of pairwise sequence alignment routines
	 * @param gapPenalty the gap penalties used during alignment
	 * @param subMatrix the set of substitution scores used during alignment
	 * @return list of sequence alignment pairs
	 */
	public static <S extends Sequence<C>, C extends Compound> List<SequencePair<S, C>> getAllPairsAlignments(
			List<S> sequences, PairwiseSequenceAlignerType type, GapPenalty gapPenalty,
			SubstitutionMatrix<C> subMatrix) {
		return runPairwiseAligners(getAllPairsAligners(sequences, type, gapPenalty, subMatrix));
	}

	/**
	 * Factory method which computes a multiple sequence alignment for the given {@link List} of {@link Sequence}s.
	 *
	 * @param <S> each {@link Sequence} of the {@link List} is of type S
	 * @param <C> each element of a {@link Sequence} is a {@link Compound} of type C
	 * @param sequences the {@link List} of {@link Sequence}s to align
	 * @param settings optional settings that adjust the alignment
	 * @return multiple sequence alignment {@link Profile}
	 */
	public static <S extends Sequence<C>, C extends Compound> Profile<S, C> getMultipleSequenceAlignment(
			List<S> sequences, Object... settings) { // TODO convert other factories to this parameter style?
		CompoundSet<C> cs = sequences.get(0).getCompoundSet();
		PairwiseSequenceScorerType ps = PairwiseSequenceScorerType.GLOBAL_IDENTITIES;
		GapPenalty gapPenalty = new SimpleGapPenalty();
		SubstitutionMatrix<C> subMatrix = null;
		if (cs == AminoAcidCompoundSet.getAminoAcidCompoundSet()) {
			@SuppressWarnings("unchecked") // compound types must be equal since compound sets are equal
			SubstitutionMatrix<C> temp = (SubstitutionMatrix<C>) SubstitutionMatrixHelper.getBlosum62();
			subMatrix = temp;
		} else if (cs == DNACompoundSet.getDNACompoundSet()) {
			@SuppressWarnings("unchecked") // compound types must be equal since compound sets are equal
			SubstitutionMatrix<C> temp = (SubstitutionMatrix<C>) SubstitutionMatrixHelper.getNuc4_4();
			subMatrix = temp;

		} else if (cs == AmbiguityDNACompoundSet.getDNACompoundSet()) {
			@SuppressWarnings("unchecked") // compound types must be equal since compound sets are equal
			SubstitutionMatrix<C> temp = (SubstitutionMatrix<C>) SubstitutionMatrixHelper.getNuc4_4();
			subMatrix = temp;

		}
		ProfileProfileAlignerType pa = ProfileProfileAlignerType.GLOBAL;
		for (Object o : settings) {
			if (o instanceof PairwiseSequenceScorerType) {
				ps = (PairwiseSequenceScorerType) o;
			} else if (o instanceof GapPenalty) {
				gapPenalty = (GapPenalty) o;
			} else if (o instanceof SubstitutionMatrix<?>) {
				if (cs != ((SubstitutionMatrix<?>) o).getCompoundSet()) {
					throw new IllegalArgumentException(
							"Compound sets of the sequences and substitution matrix must match.");
				}
				@SuppressWarnings("unchecked") // compound types must be equal since compound sets are equal
				SubstitutionMatrix<C> temp = (SubstitutionMatrix<C>) o;
				subMatrix = temp;
			} else if (o instanceof ProfileProfileAlignerType) {
				pa = (ProfileProfileAlignerType) o;
			}
		}

		// stage 1: pairwise similarity calculation
		List<PairwiseSequenceScorer<S, C>> scorers = getAllPairsScorers(sequences, ps, gapPenalty, subMatrix);
		runPairwiseScorers(scorers);

		// stage 2: hierarchical clustering into a guide tree
		GuideTree<S, C> tree = new GuideTree<S, C>(sequences, scorers);
		scorers = null;

		// stage 3: progressive alignment
		Profile<S, C> msa = getProgressiveAlignment(tree, pa, gapPenalty, subMatrix);

		// TODO stage 4: refinement
		return msa;
	}

	/**
	 * Factory method which computes a sequence alignment for the given {@link Sequence} pair.
	 *
	 * @param <S> each {@link Sequence} of the pair is of type S
	 * @param <C> each element of an {@link AlignedSequence} is a {@link Compound} of type C
	 * @param query the first {@link Sequence}s to align
	 * @param target the second {@link Sequence}s to align
	 * @param type chosen type from list of pairwise sequence alignment routines
	 * @param gapPenalty the gap penalties used during alignment
	 * @param subMatrix the set of substitution scores used during alignment
	 * @return sequence alignment pair
	 */
	public static <S extends Sequence<C>, C extends Compound> SequencePair<S, C> getPairwiseAlignment(
			S query, S target, PairwiseSequenceAlignerType type, GapPenalty gapPenalty,
			SubstitutionMatrix<C> subMatrix) {
		return getPairwiseAligner(query, target, type, gapPenalty, subMatrix).getPair();
	}

	// default access (package private) factory methods

	/**
	 * Factory method which sets up a sequence alignment for all {@link Sequence} pairs in the given {@link List}.
	 *
	 * @param <S> each {@link Sequence} of an alignment pair is of type S
	 * @param <C> each element of an {@link AlignedSequence} is a {@link Compound} of type C
	 * @param sequences the {@link List} of {@link Sequence}s to align
	 * @param type chosen type from list of pairwise sequence alignment routines
	 * @param gapPenalty the gap penalties used during alignment
	 * @param subMatrix the set of substitution scores used during alignment
	 * @return list of pairwise sequence aligners
	 */
	static <S extends Sequence<C>, C extends Compound> List<PairwiseSequenceAligner<S, C>> getAllPairsAligners(
			List<S> sequences, PairwiseSequenceAlignerType type, GapPenalty gapPenalty,
			SubstitutionMatrix<C> subMatrix) {
		List<PairwiseSequenceAligner<S, C>> allPairs = new ArrayList<PairwiseSequenceAligner<S, C>>();
		for (int i = 0; i < sequences.size(); i++) {
			for (int j = i+1; j < sequences.size(); j++) {
				allPairs.add(getPairwiseAligner(sequences.get(i), sequences.get(j), type, gapPenalty, subMatrix));
			}
		}
		return allPairs;
	}

	/**
	 * Factory method which sets up a sequence pair scorer for all {@link Sequence} pairs in the given {@link List}.
	 *
	 * @param <S> each {@link Sequence} of a pair is of type S
	 * @param <C> each element of a {@link Sequence} is a {@link Compound} of type C
	 * @param sequences the {@link List} of {@link Sequence}s to align
	 * @param type chosen type from list of pairwise sequence scoring routines
	 * @param gapPenalty the gap penalties used during alignment
	 * @param subMatrix the set of substitution scores used during alignment
	 * @return list of sequence pair scorers
	 */
	public static <S extends Sequence<C>, C extends Compound> List<PairwiseSequenceScorer<S, C>> getAllPairsScorers(
			List<S> sequences, PairwiseSequenceScorerType type, GapPenalty gapPenalty,
			SubstitutionMatrix<C> subMatrix) {
		List<PairwiseSequenceScorer<S, C>> allPairs = new ArrayList<PairwiseSequenceScorer<S, C>>();
		for (int i = 0; i < sequences.size(); i++) {
			for (int j = i+1; j < sequences.size(); j++) {
				allPairs.add(getPairwiseScorer(sequences.get(i), sequences.get(j), type, gapPenalty, subMatrix));
			}
		}
		return allPairs;
	}

	/**
	 * Factory method which computes a sequence pair score for all {@link Sequence} pairs in the given {@link List}.
	 * This method runs the scorings in parallel by submitting all of the scorings to the shared thread pool of the
	 * {@link ConcurrencyTools} utility.
	 *
	 * @param <S> each {@link Sequence} of a pair is of type S
	 * @param <C> each element of a {@link Sequence} is a {@link Compound} of type C
	 * @param sequences the {@link List} of {@link Sequence}s to align
	 * @param type chosen type from list of pairwise sequence scoring routines
	 * @param gapPenalty the gap penalties used during alignment
	 * @param subMatrix the set of substitution scores used during alignment
	 * @return list of sequence pair scores
	 */
	public static <S extends Sequence<C>, C extends Compound> double[] getAllPairsScores( List<S> sequences,
			PairwiseSequenceScorerType type, GapPenalty gapPenalty, SubstitutionMatrix<C> subMatrix) {
		return runPairwiseScorers(getAllPairsScorers(sequences, type, gapPenalty, subMatrix));
	}

	/**
	 * Factory method which retrieves calculated elements from a list of tasks on the concurrent execution queue.
	 *
	 * @param <E> each task calculates a value of type E
	 * @param futures list of tasks
	 * @return calculated elements
	 */
	static <E> List<E> getListFromFutures(List<Future<E>> futures) {
		List<E> list = new ArrayList<E>();
		for (Future<E> f : futures) {
			// TODO when added to ConcurrencyTools, log completions and exceptions instead of printing stack traces
			try {
				list.add(f.get());
			} catch (InterruptedException e) {
				logger.error("Interrupted Exception: ", e);
			} catch (ExecutionException e) {
				logger.error("Execution Exception: ", e);
			}
		}
		return list;
	}

	/**
	 * Factory method which constructs a pairwise sequence aligner.
	 *
	 * @param <S> each {@link Sequence} of an alignment pair is of type S
	 * @param <C> each element of an {@link AlignedSequence} is a {@link Compound} of type C
	 * @param query the first {@link Sequence} to align
	 * @param target the second {@link Sequence} to align
	 * @param type chosen type from list of pairwise sequence alignment routines
	 * @param gapPenalty the gap penalties used during alignment
	 * @param subMatrix the set of substitution scores used during alignment
	 * @return pairwise sequence aligner
	 */
	public static <S extends Sequence<C>, C extends Compound> PairwiseSequenceAligner<S, C> getPairwiseAligner(
			S query, S target, PairwiseSequenceAlignerType type, GapPenalty gapPenalty,
			SubstitutionMatrix<C> subMatrix) {
		if (!query.getCompoundSet().equals(target.getCompoundSet())) {
			throw new IllegalArgumentException("Sequence compound sets must be the same");
		}
		switch (type) {
		default:
		case GLOBAL:
			return new NeedlemanWunsch<S, C>(query, target, gapPenalty, subMatrix);
		case LOCAL:
			return new SmithWaterman<S, C>(query, target, gapPenalty, subMatrix);
		case GLOBAL_LINEAR_SPACE:
		case LOCAL_LINEAR_SPACE:
			// TODO other alignment options (Myers-Miller, Thompson)
			throw new UnsupportedOperationException(Alignments.class.getSimpleName() + " does not yet support " +
					type + " alignment");
		}
	}

	/**
	 * Factory method which computes a similarity score for the given {@link Sequence} pair.
	 *
	 * @param <S> each {@link Sequence} of the pair is of type S
	 * @param <C> each element of a {@link Sequence} is a {@link Compound} of type C
	 * @param query the first {@link Sequence} to score
	 * @param target the second {@link Sequence} to score
	 * @param type chosen type from list of pairwise sequence scoring routines
	 * @param gapPenalty the gap penalties used during alignment
	 * @param subMatrix the set of substitution scores used during alignment
	 * @return sequence pair score
	 */
	static <S extends Sequence<C>, C extends Compound> double getPairwiseScore(S query, S target,
			PairwiseSequenceScorerType type, GapPenalty gapPenalty, SubstitutionMatrix<C> subMatrix) {
		return getPairwiseScorer(query, target, type, gapPenalty, subMatrix).getScore();
	}

	/**
	 * Factory method which constructs a pairwise sequence scorer.
	 *
	 * @param <S> each {@link Sequence} of a pair is of type S
	 * @param <C> each element of a {@link Sequence} is a {@link Compound} of type C
	 * @param query the first {@link Sequence} to score
	 * @param target the second {@link Sequence} to score
	 * @param type chosen type from list of pairwise sequence scoring routines
	 * @param gapPenalty the gap penalties used during alignment
	 * @param subMatrix the set of substitution scores used during alignment
	 * @return sequence pair scorer
	 */
	static <S extends Sequence<C>, C extends Compound> PairwiseSequenceScorer<S, C> getPairwiseScorer(
			S query, S target, PairwiseSequenceScorerType type, GapPenalty gapPenalty,
			SubstitutionMatrix<C> subMatrix) {
		switch (type) {
		default:
		case GLOBAL:
			return getPairwiseAligner(query, target, PairwiseSequenceAlignerType.GLOBAL, gapPenalty, subMatrix);
		case GLOBAL_IDENTITIES:
			return new FractionalIdentityScorer<S, C>(getPairwiseAligner(query, target,
					PairwiseSequenceAlignerType.GLOBAL, gapPenalty, subMatrix));
		case GLOBAL_SIMILARITIES:
			return new FractionalSimilarityScorer<S, C>(getPairwiseAligner(query, target,
					PairwiseSequenceAlignerType.GLOBAL, gapPenalty, subMatrix));
		case LOCAL:
			return getPairwiseAligner(query, target, PairwiseSequenceAlignerType.LOCAL, gapPenalty, subMatrix);
		case LOCAL_IDENTITIES:
			return new FractionalIdentityScorer<S, C>(getPairwiseAligner(query, target,
					PairwiseSequenceAlignerType.LOCAL, gapPenalty, subMatrix));
		case LOCAL_SIMILARITIES:
			return new FractionalSimilarityScorer<S, C>(getPairwiseAligner(query, target,
					PairwiseSequenceAlignerType.LOCAL, gapPenalty, subMatrix));
		case KMERS:
		case WU_MANBER:
			// TODO other scoring options
			throw new UnsupportedOperationException(Alignments.class.getSimpleName() + " does not yet support " +
					type + " scoring");
		}
	}

	/**
	 * Factory method which constructs a profile-profile aligner.
	 *
	 * @param <S> each {@link Sequence} of an alignment profile is of type S
	 * @param <C> each element of an {@link AlignedSequence} is a {@link Compound} of type C
	 * @param profile1 the first {@link Profile} to align
	 * @param profile2 the second {@link Profile} to align
	 * @param type chosen type from list of profile-profile alignment routines
	 * @param gapPenalty the gap penalties used during alignment
	 * @param subMatrix the set of substitution scores used during alignment
	 * @return profile-profile aligner
	 */
	static <S extends Sequence<C>, C extends Compound> ProfileProfileAligner<S, C> getProfileProfileAligner(
			Profile<S, C> profile1, Profile<S, C> profile2, ProfileProfileAlignerType type, GapPenalty gapPenalty,
			SubstitutionMatrix<C> subMatrix) {
		switch (type) {
		default:
		case GLOBAL:
			return new SimpleProfileProfileAligner<S, C>(profile1, profile2, gapPenalty, subMatrix);
		case GLOBAL_LINEAR_SPACE:
		case GLOBAL_CONSENSUS:
		case LOCAL:
		case LOCAL_LINEAR_SPACE:
		case LOCAL_CONSENSUS:
			// TODO other alignment options (Myers-Miller, consensus, local)
			throw new UnsupportedOperationException(Alignments.class.getSimpleName() + " does not yet support " +
					type + " alignment");
		}
	}

	/**
	 * Factory method which constructs a profile-profile aligner.
	 *
	 * @param <S> each {@link Sequence} of an alignment profile is of type S
	 * @param <C> each element of an {@link AlignedSequence} is a {@link Compound} of type C
	 * @param profile1 the first {@link Profile} to align
	 * @param profile2 the second {@link Profile} to align
	 * @param type chosen type from list of profile-profile alignment routines
	 * @param gapPenalty the gap penalties used during alignment
	 * @param subMatrix the set of substitution scores used during alignment
	 * @return profile-profile aligner
	 */
	static <S extends Sequence<C>, C extends Compound> ProfileProfileAligner<S, C> getProfileProfileAligner(
			Future<ProfilePair<S, C>> profile1, Future<ProfilePair<S, C>> profile2, ProfileProfileAlignerType type,
			GapPenalty gapPenalty, SubstitutionMatrix<C> subMatrix) {
		switch (type) {
		default:
		case GLOBAL:
			return new SimpleProfileProfileAligner<S, C>(profile1, profile2, gapPenalty, subMatrix);
		case GLOBAL_LINEAR_SPACE:
		case GLOBAL_CONSENSUS:
		case LOCAL:
		case LOCAL_LINEAR_SPACE:
		case LOCAL_CONSENSUS:
			// TODO other alignment options (Myers-Miller, consensus, local)
			throw new UnsupportedOperationException(Alignments.class.getSimpleName() + " does not yet support " +
					type + " alignment");
		}
	}

	/**
	 * Factory method which constructs a profile-profile aligner.
	 *
	 * @param <S> each {@link Sequence} of an alignment profile is of type S
	 * @param <C> each element of an {@link AlignedSequence} is a {@link Compound} of type C
	 * @param profile1 the first {@link Profile} to align
	 * @param profile2 the second {@link Profile} to align
	 * @param type chosen type from list of profile-profile alignment routines
	 * @param gapPenalty the gap penalties used during alignment
	 * @param subMatrix the set of substitution scores used during alignment
	 * @return profile-profile aligner
	 */
	static <S extends Sequence<C>, C extends Compound> ProfileProfileAligner<S, C> getProfileProfileAligner(
			Profile<S, C> profile1, Future<ProfilePair<S, C>> profile2, ProfileProfileAlignerType type,
			GapPenalty gapPenalty, SubstitutionMatrix<C> subMatrix) {
		switch (type) {
		default:
		case GLOBAL:
			return new SimpleProfileProfileAligner<S, C>(profile1, profile2, gapPenalty, subMatrix);
		case GLOBAL_LINEAR_SPACE:
		case GLOBAL_CONSENSUS:
		case LOCAL:
		case LOCAL_LINEAR_SPACE:
		case LOCAL_CONSENSUS:
			// TODO other alignment options (Myers-Miller, consensus, local)
			throw new UnsupportedOperationException(Alignments.class.getSimpleName() + " does not yet support " +
					type + " alignment");
		}
	}

	/**
	 * Factory method which constructs a profile-profile aligner.
	 *
	 * @param <S> each {@link Sequence} of an alignment profile is of type S
	 * @param <C> each element of an {@link AlignedSequence} is a {@link Compound} of type C
	 * @param profile1 the first {@link Profile} to align
	 * @param profile2 the second {@link Profile} to align
	 * @param type chosen type from list of profile-profile alignment routines
	 * @param gapPenalty the gap penalties used during alignment
	 * @param subMatrix the set of substitution scores used during alignment
	 * @return profile-profile aligner
	 */
	static <S extends Sequence<C>, C extends Compound> ProfileProfileAligner<S, C> getProfileProfileAligner(
			Future<ProfilePair<S, C>> profile1, Profile<S, C> profile2, ProfileProfileAlignerType type,
			GapPenalty gapPenalty, SubstitutionMatrix<C> subMatrix) {
		switch (type) {
		default:
		case GLOBAL:
			return new SimpleProfileProfileAligner<S, C>(profile1, profile2, gapPenalty, subMatrix);
		case GLOBAL_LINEAR_SPACE:
		case GLOBAL_CONSENSUS:
		case LOCAL:
		case LOCAL_LINEAR_SPACE:
		case LOCAL_CONSENSUS:
			// TODO other alignment options (Myers-Miller, consensus, local)
			throw new UnsupportedOperationException(Alignments.class.getSimpleName() + " does not yet support " +
					type + " alignment");
		}
	}

	/**
	 * Factory method which computes a profile alignment for the given {@link Profile} pair.
	 *
	 * @param <S> each {@link Sequence} of the {@link Profile} pair is of type S
	 * @param <C> each element of an {@link AlignedSequence} is a {@link Compound} of type C
	 * @param profile1 the first {@link Profile} to align
	 * @param profile2 the second {@link Profile} to align
	 * @param type chosen type from list of profile-profile alignment routines
	 * @param gapPenalty the gap penalties used during alignment
	 * @param subMatrix the set of substitution scores used during alignment
	 * @return alignment profile
	 */
	static <S extends Sequence<C>, C extends Compound> ProfilePair<S, C> getProfileProfileAlignment(
			Profile<S, C> profile1, Profile<S, C> profile2, ProfileProfileAlignerType type, GapPenalty gapPenalty,
			SubstitutionMatrix<C> subMatrix) {
		return getProfileProfileAligner(profile1, profile2, type, gapPenalty, subMatrix).getPair();
	}

	/**
	 * Factory method to run the profile-profile alignments of a progressive multiple sequence alignment concurrently.
	 * This method runs the alignments in parallel by submitting all of the alignment tasks to the shared thread pool
	 * of the {@link ConcurrencyTools} utility.
	 *
	 * @param <S> each {@link Sequence} of the {@link Profile} pair is of type S
	 * @param <C> each element of an {@link AlignedSequence} is a {@link Compound} of type C
	 * @param tree guide tree to follow aligning profiles from leaves to root
	 * @param type chosen type from list of profile-profile alignment routines
	 * @param gapPenalty the gap penalties used during alignment
	 * @param subMatrix the set of substitution scores used during alignment
	 * @return multiple sequence alignment
	 */
	public static <S extends Sequence<C>, C extends Compound> Profile<S, C> getProgressiveAlignment(GuideTree<S, C> tree,
			ProfileProfileAlignerType type, GapPenalty gapPenalty, SubstitutionMatrix<C> subMatrix) {

		// find inner nodes in post-order traversal of tree (each leaf node has a single sequence profile)
		List<GuideTreeNode<S, C>> innerNodes = new ArrayList<GuideTreeNode<S, C>>();
		for (GuideTreeNode<S, C> n : tree) {
			if (n.getProfile() == null) {
				innerNodes.add(n);
			}
		}

		// submit alignment tasks to the shared thread pool
		int i = 1, all = innerNodes.size();
		for (GuideTreeNode<S, C> n : innerNodes) {
			Profile<S, C> p1 = n.getChild1().getProfile(), p2 = n.getChild2().getProfile();
			Future<ProfilePair<S, C>> pf1 = n.getChild1().getProfileFuture(), pf2 = n.getChild2().getProfileFuture();
			ProfileProfileAligner<S, C> aligner =
					(p1 != null) ? ((p2 != null) ? getProfileProfileAligner(p1, p2, type, gapPenalty, subMatrix) :
							getProfileProfileAligner(p1, pf2, type, gapPenalty, subMatrix)) :
					((p2 != null) ? getProfileProfileAligner(pf1, p2, type, gapPenalty, subMatrix) :
							getProfileProfileAligner(pf1, pf2, type, gapPenalty, subMatrix));
			n.setProfileFuture(ConcurrencyTools.submit(new CallableProfileProfileAligner<S, C>(aligner), String.format(
					"Aligning pair %d of %d", i++, all)));
		}

		// retrieve the alignment results
		for (GuideTreeNode<S, C> n : innerNodes) {
			// TODO when added to ConcurrencyTools, log completions and exceptions instead of printing stack traces
			try {
				n.setProfile(n.getProfileFuture().get());
			} catch (InterruptedException e) {
				logger.error("Interrupted Exception: ", e);
			} catch (ExecutionException e) {
				logger.error("Execution Exception: ", e);
			}
		}

		// the alignment profile at the root of the tree is the full multiple sequence alignment
		return tree.getRoot().getProfile();
	}

	/**
	 * Factory method to run a list of alignments concurrently.  This method runs the alignments in parallel by
	 * submitting all of the alignment tasks to the shared thread pool of the {@link ConcurrencyTools} utility.
	 *
	 * @param <S> each {@link Sequence} of an alignment pair is of type S
	 * @param <C> each element of an {@link AlignedSequence} is a {@link Compound} of type C
	 * @param aligners list of alignments to run
	 * @return list of {@link SequencePair} results from running alignments
	 */
	static <S extends Sequence<C>, C extends Compound> List<SequencePair<S, C>>
			runPairwiseAligners(List<PairwiseSequenceAligner<S, C>> aligners) {
		int n = 1, all = aligners.size();
		List<Future<SequencePair<S, C>>> futures = new ArrayList<Future<SequencePair<S, C>>>();
		for (PairwiseSequenceAligner<S, C> aligner : aligners) {
			futures.add(ConcurrencyTools.submit(new CallablePairwiseSequenceAligner<S, C>(aligner),
					String.format("Aligning pair %d of %d", n++, all)));
		}
		return getListFromFutures(futures);
	}

	/**
	 * Factory method to run a list of scorers concurrently.  This method runs the scorers in parallel by submitting
	 * all of the scoring tasks to the shared thread pool of the {@link ConcurrencyTools} utility.
	 *
	 * @param <S> each {@link Sequence} of an alignment pair is of type S
	 * @param <C> each element of an {@link AlignedSequence} is a {@link Compound} of type C
	 * @param scorers list of scorers to run
	 * @return list of score results from running scorers
	 */
	public static <S extends Sequence<C>, C extends Compound> double[] runPairwiseScorers(
			List<PairwiseSequenceScorer<S, C>> scorers) {
		int n = 1, all = scorers.size();
		List<Future<Double>> futures = new ArrayList<Future<Double>>();
		for (PairwiseSequenceScorer<S, C> scorer : scorers) {
			futures.add(ConcurrencyTools.submit(new CallablePairwiseSequenceScorer<S, C>(scorer),
					String.format("Scoring pair %d of %d", n++, all)));
		}
		List<Double> results = getListFromFutures(futures);
		double[] scores = new double[results.size()];
		for (int i = 0; i < scores.length; i++) {
			scores[i] = results.get(i);
		}
		return scores;
	}

	/**
	 * Factory method to run a list of alignments concurrently.  This method runs the alignments in parallel by
	 * submitting all of the alignment tasks to the shared thread pool of the {@link ConcurrencyTools} utility.
	 *
	 * @param <S> each {@link Sequence} of the {@link Profile} pair is of type S
	 * @param <C> each element of an {@link AlignedSequence} is a {@link Compound} of type C
	 * @param aligners list of alignments to run
	 * @return list of {@link ProfilePair} results from running alignments
	 */
	static <S extends Sequence<C>, C extends Compound> List<ProfilePair<S, C>>
			runProfileAligners(List<ProfileProfileAligner<S, C>> aligners) {
		int n = 1, all = aligners.size();
		List<Future<ProfilePair<S, C>>> futures = new ArrayList<Future<ProfilePair<S, C>>>();
		for (ProfileProfileAligner<S, C> aligner : aligners) {
			futures.add(ConcurrencyTools.submit(new CallableProfileProfileAligner<S, C>(aligner),
					String.format("Aligning pair %d of %d", n++, all)));
		}
		return getListFromFutures(futures);
	}

}
