package beast.evolution.alignment;



import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import beast.core.Description;
import beast.core.Input;


@Description("A TaxonSet is an ordered set of taxa. The order on the taxa is provided at the time of construction" +
        " either from a list of taxon objects or an alignment.")
public class TaxonSet extends Taxon {

    final public Input<List<Taxon>> taxonsetInput = new Input<>("taxon", "list of taxa making up the set", new ArrayList<>());

    final public Input<Alignment> alignmentInput = new Input<>("alignment", "alignment where each sequence represents a taxon");

    protected List<String> taxaNames;
    protected List<Taxon> taxonList;

    public TaxonSet() {
    }

    public TaxonSet(final List<Taxon> taxa) {
        taxonsetInput.setValue(taxa, this);
        initAndValidate();
    }
    
    public TaxonSet(final Alignment alignment) {
        alignmentInput.setValue(alignment, this);
        initAndValidate();
    }

    // for testing purposes (Huw)
    public TaxonSet(final String id, final List<Taxon> taxa) {
        setID(id);
        taxonsetInput.setValue(taxa, this);
        initAndValidate();
    }

    @Override
    public void initAndValidate() {

        taxonList = taxonsetInput.get();
        if (alignmentInput.get() != null) {
            if (taxonList.size() > 0) {
                throw new IllegalArgumentException("Only one of taxon and alignment should be specified, not both (id=" + getID() + ").");
            }
            taxaNames = alignmentInput.get().taxaNames;
        } else {
            if (taxonList.size() == 0) {
                throw new IllegalArgumentException(getID() + ": Either taxon or alignment should be specified (id=" + getID() + ").");
            }
            taxaNames = new ArrayList<>();
            for (final Taxon taxon : taxonList) {
            	taxaNames.add(taxon.getID());
            }
        }
    }

    public Set<Taxon> getTaxonSet() {
        final Set<Taxon> unorderedTaxa = new HashSet<>(taxonList);
        return unorderedTaxa;
    }

    /**
     * @return an unmodifiable list of taxa names as strings.
     */
    public List<String> asStringList() {
        if (taxaNames == null) return null;
        return Collections.unmodifiableList(taxaNames);
    }

    /**
     * @return the taxa names as a set of strings.
     */
    public Set<String> getTaxaNames() {
        return new TreeSet<>(taxaNames);
    }

    /**
     * @return the ID of the i'th taxon.
     */
    public String getTaxonId(int taxonIndex) {
        return taxaNames.get(taxonIndex);
    }

    /**
     * return index of given Taxon name
     * @param id
     * @return  -1 if not found
     */
    public int getTaxonIndex(String id) {
        for (int i = 0; i < taxaNames.size(); i++) {
            if (getTaxonId(i).contentEquals(id)) return i;
        }
        return -1;
    }

    /**
     * return Taxon matching the given id
     * @param id
     * @return  null if not found
     */
    public Taxon getTaxon(String id) {
        for (int i = 0; i < taxonList.size(); i++) {
            Taxon taxon = taxonList.get(i);
            if (taxon.getID().equals(id)) return taxon;
        }
        return null;
    }

    //  convenience methods

    public boolean containsAny(final Collection<String> taxa) {
        final List<String> me = asStringList();
        for (final String taxon : taxa ) {
            if (me.contains(taxon)) {
                return true;
            }
        }
        return false;
    }

    public boolean containsAll(final Collection<String> taxa) {
        final List<String> me = asStringList();
        for (final String taxon : taxa ) {
            if (!me.contains(taxon)) {
                return false;
            }
        }
        return true;
    }

    /**
     * @return true if at least 1 member of taxa contained in this set.
     * @param taxa a collection of taxa
     */
    public boolean containsAny(final TaxonSet taxa) {
        return containsAny(taxa.asStringList());
    }

    /**
     * @return true if taxa is a subset of this set
     * @param    taxa
     */
    public boolean containsAll(final TaxonSet taxa) {
        return containsAll(taxa.asStringList());
    }
    
    /**
     * @return number of taxa in this taxon set
     */
    public int getTaxonCount() {
        if (taxaNames == null) return 0;
        return taxaNames.size();
    }
    
    /**
     * @return number of taxa in this taxon set
     * @deprecated Exists only for consistency with method in Alignment. Use
     * getTaxonCount() instead.
     */
    @Deprecated
    public int getNrTaxa() {
        return getTaxonCount();
    }
    
    @Override
    public String toString() {
    	return toString("\t");
    }

    @Override
	protected String toString(String indent) {
		final StringBuilder buf = new StringBuilder();
		buf.append(indent).append(getID()).append("\n");
		indent += "\t";
		for (final Taxon taxon : taxonsetInput.get()) {
			buf.append(taxon.toString(indent));
		}
		return buf.toString();
	}
}
