/*
 *                    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/
 *
 */
package org.biojava.nbio.core.sequence.template;

import org.biojava.nbio.core.exceptions.TranslationException;
import org.biojava.nbio.core.sequence.io.template.SequenceCreatorInterface;

import java.util.*;


public abstract class AbstractCompoundTranslator<F extends Compound, T extends Compound>
		implements CompoundTranslator<F, T> {

	private final SequenceCreatorInterface<T> creator;
	private final Map<F, List<T>>          mapper;
	private final CompoundSet<F>              fromCompoundSet;
	private final CompoundSet<T>              toCompoundSet;

	public AbstractCompoundTranslator(SequenceCreatorInterface<T> creator,
			CompoundSet<F> fromCompoundSet, CompoundSet<T> toCompoundSet) {
		this.creator = creator;
		this.mapper = new HashMap<F, List<T>>();
		this.fromCompoundSet = fromCompoundSet;
		this.toCompoundSet = toCompoundSet;
	}

	public SequenceCreatorInterface<T> getCreator() {
		return creator;
	}

	public CompoundSet<F> getFromCompoundSet() {
		return fromCompoundSet;
	}

	public CompoundSet<T> getToCompoundSet() {
		return toCompoundSet;
	}

	@SuppressWarnings("unchecked")
	protected void addStrings(String source, String... targets) {
		F f = getFromCompoundSet().getCompoundForString(source);
		for (String t : targets) {
			addCompounds(f, getToCompoundSet().getCompoundForString(t));
		}
	}

	protected void addCompounds(F source, T... targets) {

	 List<T> l = mapper.get(source);
	 if ( l == null) {
		 l = new ArrayList<T>();
		 mapper.put(source, l);
	 }
		 l.addAll(Arrays.asList(targets));
	}

	@Override
	public List<T> translateMany(F fromCompound) {
		if (!mapper.containsKey(fromCompound)) {
			throw new TranslationException("Can not translate compound "+fromCompound);
		}
		return mapper.get(fromCompound);
	}

		@Override
	public T translate(F fromCompound) {
		List<T> compounds = translateMany(fromCompound);
		if (compounds.isEmpty()) {
			throw new TranslationException("No compounds found for " + fromCompound);
		}
		else if (compounds.size() > 1) {
			throw new TranslationException("Too many compounds found for "
					+ fromCompound);
		}
		else {
			return compounds.get(0);
		}
	}

		@Override
	public List<Sequence<T>> createSequences(Sequence<F> originalSequence) {
		List<List<T>> workingList = new ArrayList<List<T>>();
		for (F source : originalSequence) {
			List<T> compounds = translateMany(source);

			// Translate source to a list of possible compounds; if we have 1 then
			// just add onto the list. If we have n then start new paths in all
			// sequences i.e.
			//
			// MTAS (A & S have 2 routes) makes
			// AUG UGG GAU AGU
			// AUG UGG GAC AGU
			// AUG UGG GAU AGC
			// AUG UGG GAC AGC
			if (compounds.isEmpty()) {
				throw new TranslationException("Compound " + source + " resulted in "
						+ "no target compounds");
			}
			addCompoundsToList(compounds, workingList);
		}

		postProcessCompoundLists(workingList);

		return workingListToSequences(workingList);
	}

	protected abstract void postProcessCompoundLists(List<List<T>> compoundLists);

	protected void addCompoundsToList(List<T> compounds, List<List<T>> workingList) {
		int size = compounds.size();
		List<List<T>> currentWorkingList = new ArrayList<List<T>>();
		for (int i = 0; i < size; i++) {
			boolean last = (i == (size - 1));
			// If last run we add the compound to the top set of lists & then
			// add the remaining ones in
			if (last) {
				addCompoundToLists(workingList, compounds.get(i));
				if (!currentWorkingList.isEmpty()) {
					workingList.addAll(currentWorkingList);
				}
			}
			// Otherwise duplicate the current sequence set and add this compound
			else {
				List<List<T>> duplicate = duplicateList(workingList);
				addCompoundToLists(duplicate, compounds.get(i));
				currentWorkingList.addAll(duplicate);
			}
		}
	}

	protected List<Sequence<T>> workingListToSequences(List<List<T>> workingList) {
		List<Sequence<T>> sequences = new ArrayList<Sequence<T>>();
		for (List<T> seqList : workingList) {
			sequences.add(getCreator().getSequence(seqList));
		}
		return sequences;
	}

	private List<List<T>> duplicateList(List<List<T>> incoming) {
		List<List<T>> outgoing = new ArrayList<List<T>>();
		for (List<T> current : incoming) {
			outgoing.add(new ArrayList<T>(current));
		}
		return outgoing;
	}

	protected void addCompoundToLists(List<List<T>> list, T compound) {

		if (list.isEmpty()) {
			list.add(new ArrayList<T>());
		}

		for (List<T> current : list) {
			current.add(compound);
		}
	}

		@Override
	public Sequence<T> createSequence(Sequence<F> originalSequence) {
		Collection<Sequence<T>> sequences = createSequences(originalSequence);
		if (sequences.size() > 1) {
			throw new TranslationException("Too many sequences created; "
					+ "createSequence() assumes only one sequence can be created");
		}
		else if (sequences.isEmpty()) {
			throw new TranslationException("No sequences created");
		}
		return sequences.iterator().next();
	}

}
