/**
 * 
 * Written by Márton Münz and Philip C Biggin
 * Copyright (c) University of Oxford, United Kingdom
 * Visit http://sbcb.bioch.ox.ac.uk/jgromacs/
 * 
 * This source code file is part of JGromacs v1.0.
 * 
 * JGromacs v1.0 is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * JGromacs v1.0. is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with JGromacs v1.0. If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package jgromacs.data;

import java.util.ArrayList;

import jgromacs.db.ResidueType;

/**
 * Objects of this class represent a sequence alignment
 *
 */
public class Alignment implements Cloneable{
	
	private ArrayList<Sequence> sequences = new ArrayList<Sequence>();
	
	// Constructors
	
	/**
	 * Constructs a new Alignment object
	 *
	 * 
	 */
	public Alignment(){
		
	}
		
	// Getters and setters
	
	
	/**
	 * Returns sequence #i of the alignment
	 * @return sequence #i 
	 * 
	 */
	public Sequence getSequence(int i){
		return sequences.get(i);
	}
	
	/**
	 * Returns sequences of the alignment as an ArrayList object
	 * @return list of sequences as an ArrayList
	 * 
	 */
	public ArrayList<Sequence> getSequencesAsArrayList(){
		return sequences;
	}
	
	/**
	 * Returns the number of sequences in the alignment
	 * @return number of sequences
	 * 
	 */
	public int getNumberOfSequences(){
		return sequences.size();
	}
	
	/**
	 * Returns the length of the longest sequence in the alignment
	 * @return length of longest sequence
	 * 
	 */
	public int getMaxSequenceLength(){
		int ret = -9999;
		for (int i = 0; i < getNumberOfSequences(); i++) {
			if (getSequence(i).getSequenceLength()>ret)
				ret = getSequence(i).getSequenceLength();
		}
		return ret;
	}
	
	/**
	 * Returns the length of the shortest sequence in the alignment
	 * @return length of shortest sequence
	 * 
	 */
	public int getMinSequenceLength(){
		int ret = 9999;
		for (int i = 0; i < getNumberOfSequences(); i++) {
			if (getSequence(i).getSequenceLength()<ret)
				ret = getSequence(i).getSequenceLength();
		}
		return ret;
	}
	
	// Modifications
	
	/**
	 * Adds a new sequence to the alignment
	 * @param seq new sequence
	 * 
	 */
	public void addSequence(Sequence seq){
		sequences.add(seq);
	}
	
	/**
	 * Replaces sequence #i of the alignment with a new sequence
	 * @param seq new sequence
	 * 
	 */
	public void setSequence(int i, Sequence seq){
		sequences.set(i,seq);
	}
	
	/**
	 * Removes sequence #i from the alignment
	 * 
	 */
	public void removeSequence(int i){
		sequences.remove(i);
	}
	
	/**
	 * Removes the given sequence from the alignment
	 * @param seq the sequence to be removed
	 * 
	 */
	public void removeSequence(Sequence seq){
		sequences.remove(seq);
	}
	
	/**
	 * Removes column #i from the alignment
	 * 
	 */
	public void removeColumn(int i){
		for (int j = 0; j < getNumberOfSequences(); j++)
			getSequence(j).removePosition(i);
	}
	
	// Miscellaneous
	/**
	 * Returns true if column #i is a match column (i.e. there is no gap in column #i of the alignment)
	 * 
	 */
	public boolean isMatchColumn(int i){
		for (int j = 0; j < getNumberOfSequences(); j++) {
			if (getSequence(j).isPositionAGap(i)) return false;
		}
		return true;
	}
	
	/**
	 * Returns the collapsed alignment in which only the match columns are included
	 * (i.e. the columns without gaps)
	 * @return collapsed alignment
	 * 
	 */
	public Alignment getCollapsedAlignment(){
		Alignment ret = new Alignment();
		for (int i = 0; i < getNumberOfSequences(); i++) 
		ret.addSequence(new Sequence(getSequence(i).getName()));
		int length = getSequence(0).getSequenceLengthWithGaps();
		for (int i = 0; i < length; i++){
			if (isMatchColumn(i)) {
				for (int k = 0; k < getNumberOfSequences(); k++) {
					ret.getSequence(k).addPosition(getSequence(k).getPosition(i));
				}
			}
		}
		return ret;
	}
	
	/**
	 * Returns the set of position indices in sequence #i that are included in the match columns of alignment
	 * @return index set of match positions
	 * 
	 */
	public IndexSet getMatchPositionIndices(int i){
		Sequence seq = getSequence(i);
		IndexSet ret = new IndexSet("MatchColumns("+seq.getName()+")");
		for (int j = 0; j < seq.getSequenceLengthWithGaps(); j++) {
			if (isMatchColumn(j)) ret.addIndex(seq.getIndexOfPosition(j));
		}
		return ret;
	}
	
	/**
	 * Returns the list of match position index sets (i.e. match position index group 
	 * of each sequence in the alignment)
	 * @return list of match positions index set
	 * 
	 */
	public IndexSetList getMatchPositionIndexSets(){
		IndexSetList setlist = new IndexSetList();
		for (int i = 0; i < getNumberOfSequences(); i++)
			setlist.addIndexSet(getMatchPositionIndices(i));
		return setlist;
	}
	
	/**
	 * Returns the list of match position indices in sequence #i as an ArrayList object
	 * @return ArrayList of match positions
	 * 
	 */
	public ArrayList<Integer> getMatchPositionIndicesAsArrayList(int i){
		Sequence seq = getSequence(i);
		ArrayList<Integer> ret = new ArrayList<Integer>();
		for (int j = 0; j < seq.getSequenceLengthWithGaps(); j++) {
			if (isMatchColumn(j)) ret.add(seq.getIndexOfPosition(j));
		}
		return ret;
	}
	
	/**
	 * Returns the most frequent residue type in column #i of the alignment
	 *
	 * @return most frequent residue type
	 */
	public ResidueType getMostFrequentResidueType(int i){
		ArrayList<Integer> histogram = new ArrayList<Integer>();
		for (int j = 0; j < 22; j++) histogram.add(0);
		for (int k = 0; k < getNumberOfSequences(); k++) {
			ResidueType type = getSequence(k).getResidueTypeOfPosition(i);
			if (type.get1LetterCode().equals("A"))  histogram.set(0, histogram.get(0)+1);
			if (type.get1LetterCode().equals("R"))  histogram.set(1, histogram.get(1)+1);
			if (type.get1LetterCode().equals("N"))  histogram.set(2, histogram.get(2)+1);
			if (type.get1LetterCode().equals("D"))  histogram.set(3, histogram.get(3)+1);
			if (type.get1LetterCode().equals("C"))  histogram.set(4, histogram.get(4)+1);
			if (type.get1LetterCode().equals("E"))  histogram.set(5, histogram.get(5)+1);
			if (type.get1LetterCode().equals("Q"))  histogram.set(6, histogram.get(6)+1);
			if (type.get1LetterCode().equals("G"))  histogram.set(7, histogram.get(7)+1);
			if (type.get1LetterCode().equals("H"))  histogram.set(8, histogram.get(8)+1);
			if (type.get1LetterCode().equals("I"))  histogram.set(9, histogram.get(9)+1);
			if (type.get1LetterCode().equals("L"))  histogram.set(10, histogram.get(10)+1);
			if (type.get1LetterCode().equals("K"))  histogram.set(11, histogram.get(11)+1);
			if (type.get1LetterCode().equals("M"))  histogram.set(12, histogram.get(12)+1);
			if (type.get1LetterCode().equals("F"))  histogram.set(13, histogram.get(13)+1);
			if (type.get1LetterCode().equals("P"))  histogram.set(14, histogram.get(14)+1);
			if (type.get1LetterCode().equals("S"))  histogram.set(15, histogram.get(15)+1);
			if (type.get1LetterCode().equals("T"))  histogram.set(16, histogram.get(16)+1);
			if (type.get1LetterCode().equals("W"))  histogram.set(17, histogram.get(17)+1);
			if (type.get1LetterCode().equals("Y"))  histogram.set(18, histogram.get(18)+1);
			if (type.get1LetterCode().equals("V"))  histogram.set(19, histogram.get(19)+1);
			if (type.get1LetterCode().equals("X"))  histogram.set(20, histogram.get(20)+1);
			if (getSequence(k).getIndexOfPosition(i)==-999)  histogram.set(21, histogram.get(21)+1);	
		}
		int max = -1;
		for (int j = 0; j < histogram.size(); j++) {
			if (histogram.get(j)>max) max=histogram.get(j);
		}
		if (max==0) return null;
		for (int j = 0; j < histogram.size(); j++) {
			if (histogram.get(j)==max) {
				if (j==0) return new ResidueType(1);
				if (j==1) return new ResidueType(2);
				if (j==2) return new ResidueType(3);
				if (j==3) return new ResidueType(4);
				if (j==4) return new ResidueType(5);
				if (j==5) return new ResidueType(6);
				if (j==6) return new ResidueType(7);
				if (j==7) return new ResidueType(8);
				if (j==8) return new ResidueType(9);
				if (j==9) return new ResidueType(10);
				if (j==10) return new ResidueType(11);
				if (j==11) return new ResidueType(12);
				if (j==12) return new ResidueType(13);
				if (j==13) return new ResidueType(14);
				if (j==14) return new ResidueType(15);
				if (j==15) return new ResidueType(16);
				if (j==16) return new ResidueType(17);
				if (j==17) return new ResidueType(18);
				if (j==18) return new ResidueType(19);
				if (j==19) return new ResidueType(20);
				if (j==20) return new ResidueType(23);
				if (j==21) return new ResidueType();
			}
		}
		return null;
	}
	
	/**
	 * Returns the (majority) consensus sequence of the alignment
	 *
	 * @return consensus sequence
	 */
	public Sequence getConsensusSequence(){
		Sequence ret = new Sequence("Consensus");
		int lenght = getSequence(0).getSequenceLengthWithGaps();
		for (int i = 0; i < lenght; i++) {
			ResidueType type = getMostFrequentResidueType(i);
			if (!type.equals(new ResidueType()))
			ret.addPosition(type,"");
			else ret.addGap();
		}
		return ret;
	}
	
	// toString, clone, equals
	
	/**
	 * Returns the String representation of alignment
	 *
	 * @return String representation
	 */
	public String toString(){
		StringBuffer buf = new StringBuffer();
		for (int i = 0; i < getNumberOfSequences(); i++){
			if (i>0) buf.append("\n");
			buf.append(getSequence(i).toString());
		}
		return buf.toString();
	}
	
	/**
	 * Returns summary information about the alignment
	 *
	 * @return summary information
	 */
	public String toStringInfo(){
		return "Alignment: ("+getNumberOfSequences()+" sequences)";
	}
	
	/**
	 * Returns an identical Alignment object 
	 *
	 * @return clone of the alignment
	 */
	public Object clone(){
		try {
			Alignment ret = (Alignment)super.clone();
			ret.sequences = new ArrayList<Sequence>();
			for (int i = 0; i < sequences.size(); i++)
			ret.addSequence((Sequence)sequences.get(i).clone());
			return ret;
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
			return null;
		}
	}
	
	/**
	 * Returns true if the two alignments are identical
	 * @param other the other alignment
	 * 
	 */
	public boolean equals(Object other){
		if (other == null) return false;
		if (other == this) return true;
		if (this.getClass() != other.getClass()) return false;
		Alignment alignment = (Alignment)other;
		if (alignment.getNumberOfSequences()!=getNumberOfSequences()) return false;
		for (int i = 0; i < getNumberOfSequences(); i++) {
			if (!getSequence(i).equals(alignment.getSequence(i))) return false;
		}
		return true;
	}
	
	/**
	 * Returns hash code
	 * 
	 */
	public int hashCode() {
		return sequences.size();
	}
	
}
