content: package aliview.sequences;
  
  import java.io.IOException;
  import java.io.StringWriter;
  import java.io.UnsupportedEncodingException;
  import java.io.Writer;
  import java.util.ArrayList;
  import java.util.Arrays;
  
  import org.apache.commons.lang.ArrayUtils;
  import org.apache.log4j.Logger;
  
  import utils.nexus.CodonPos;
  import utils.nexus.CodonPositions;
  import aliview.AliView;
  import aliview.AminoAcid;
  import aliview.GeneticCode;
  import aliview.NucleotideUtilities;
  import aliview.alignment.NotUsed_AATranslator;
  import aliview.sequencelist.AlignmentListModel;
  import aliview.utils.ArrayUtilities;
  
  public class TranslatedBases implements Bases{
  	private static final Logger logger = Logger.getLogger(TranslatedBases.class);
  	private static final String TEXT_FILE_BYTE_ENCODING = "ASCII";
  	private Bases delegate;
  	private Sequence parentSequence;
  
  	public TranslatedBases(Bases delegate, Sequence sequence) {
  		this.delegate = delegate;
  		this.parentSequence = sequence;
  	}
  
  	public TranslatedBases getCopy(){
  
  		logger.debug("delegate.getLength()" + delegate.getLength());
  		logger.debug("delegate.getCopy().getLength()" + delegate.getCopy().getLength());
  
  		return new TranslatedBases(delegate.getCopy(), parentSequence);
  	}
  
  
  	public int getLength(){
  		// or translated
  		return getTranslatedAminAcidSequenceLength();
  		//return backend.length;
  	}
  
  	public byte get(int n) {
  		// or translated
  		return getAAinTranslatedPos(n).getCodeByteVal();
  		//return backend[n];
  	}
  
  	public void moveBaseLeft(int n) {
  		int nTrans = getCodonPosInTranslatedPos(n).startPos;	
  		delegate.set(nTrans - 3, delegate.get(nTrans));
  		delegate.set(nTrans - 2, delegate.get(nTrans + 1));
  		delegate.set(nTrans - 1, delegate.get(nTrans + 2));	
  	}
  
  	public void moveBaseRight(int n) {
  		int nTrans = getCodonPosInTranslatedPos(n).startPos;
  		delegate.set(nTrans + 3, delegate.get(nTrans));
  		delegate.set(nTrans + 4, delegate.get(nTrans + 1));
  		delegate.set(nTrans + 5, delegate.get(nTrans + 2));
  	}
  
  	public char charAt(int n) {
  		// or translated
  		return getAAinTranslatedPos(n).getCodeCharVal();
  		//ureturn (char) backend[n];
  	}
  
  	public byte[] toByteArray() {
  		// or translated
  		return getTranslatedAsString().getBytes();
  		//return backend;
  	}
  
  	public byte[] toByteArray(int startIndexInclusive, int endIndexInclusive){
  		byte[] subArray = ArrayUtils.subarray(this.toByteArray(), startIndexInclusive, endIndexInclusive + 1);
  		return subArray;	
  	}
  
  	@Override
  	public String toString() {
  		String asString = null;
  		// or translated
  		return getTranslatedAsString();
  		/*
  		try {
  			asString = new String(backend, TEXT_FILE_BYTE_ENCODING);
  		} catch (UnsupportedEncodingException e) {
  			// TODO Auto-generated catch block
  			e.printStackTrace();
  		}
  		return asString;
  		 */
  	}
  
  	public void set(int n, byte aaAsByte) {
  		// or translated
  		replace(n, n, new byte[]{aaAsByte});
  
  		//backend[n] = newBase;
  	}
  
  
  
  	// helper method
  	private byte[] getTripletsFromAA(byte[] inAAs){
  		StringBuilder triplets = new StringBuilder(inAAs.length * 3);
  
  
  		for(byte aaAsByte: inAAs){
  			byte[] triplet = getTripletFromAA(aaAsByte);
  			triplets.append(new String(triplet));
  		}
  		return triplets.toString().getBytes();
  	}
  
  	// helper method
  	private byte[] getTripletFromAA(byte aaAsByte) {
  		// TODO Auto-generated method stub
  		byte bytes[] = "---".getBytes();
  		//		logger.info("bytes:");
  		//		for(byte aByte: bytes){
  		//			logger.info("aByte=" + aByte);
  		//		}
  		return bytes;
  	}
  
  	public void insertAt(int n, byte[] aminoAAs) {
  		byte[] triplets = getTripletsFromAA(aminoAAs);
  
  		int nucleotidePos = getCodonPosInTranslatedPos(n).startPos;
  
  		delegate.insertAt(nucleotidePos, triplets);
  
  
  		//byte[] newArray = ArrayUtilities.insertAt(backend, n, newBytes);
  		//backend = newArray;
  	}
  
  	public void replace(int startReplaceIndex, int stopReplaceIndex, byte[] insertAAs) {
  		// or translated
  		byte[] triplets = getTripletsFromAA(insertAAs);
  
  		int nucleotidePos = getCodonPosInTranslatedPos(startReplaceIndex).startPos;
  
  		delegate.replace(nucleotidePos, nucleotidePos + triplets.length - 1, triplets);
  
  	}
  
  	public void delete(int[] toDeletePos) {
  		// or translated	
  		// translate toDelete
  
  		ArrayList<Integer> toDeleteNucleotidePos = new ArrayList<Integer>();
  
  		for(int aaPos : toDeletePos){		
  			CodonPos codon = getCodonPosInTranslatedPos(aaPos);
  			for(int n = codon.startPos; n <= codon.endPos; n++){
  				toDeleteNucleotidePos.add(new Integer(n));
  			}
  		}
  
  		int[] toDeleteArray = ArrayUtils.toPrimitive(toDeleteNucleotidePos.toArray(new Integer[0]));
  
  		for(int toDel: toDeleteArray){
  			logger.info("toDel" + toDel);
  		}
  
  		delegate.delete(toDeleteArray);	
  	}
  
  	public void deleteAll(byte val) {
  		// TODO Auto-generated method stub	
  	}
  
  
  	// ?????
  	public void complement() {
  		delegate.complement();		
  	}
  
  	// ?????
  	public void reverse() {
  		delegate.reverse();
  	}
  
  
  
  	// convenience method
  	public void set(int n, char c) {
  		set(n, (byte) c);
  	}
  
  	// convenience
  	public void delete(int pos) {
  		delete(new int[]{pos});
  	}
  
  	// convenience
  	public void insertAt(int n, byte newByte) {
  		insertAt(n, new byte[]{newByte});
  	}
  
  	// convenience
  	public void append(byte[] newBytes) {
  		insertAt(getLength(), newBytes);
  	}
  
  
  
  
  
  	// Translating help
  	private int cachedClosestTranslatedNucleotideStartPos = -1;
  	private int cachedAminoTripletAcidPos = -1;
  	private AminoAcid cachedAminoAcid;
  
  
  	private CodonPositions getCodonPositions() {
  		return parentSequence.getAlignmentModel().getAlignmentMeta().getCodonPositions();
  	}
  
  	private GeneticCode getGeneticCode() {
  		return parentSequence.getAlignmentModel().getAlignmentMeta().getGeneticCode();
  	}
  
  	private boolean isFullCodonStartingAt(int x){
  		return getCodonPositions().isFullCodonStartingAt(x);
  	}
  
  	public AminoAcid getAminoAcidAtNucleotidePos(int x){
  		if(isFullCodonStartingAt(x)){
  			return getAminoAcidFromTripletStartingAt(x);
  		}else if(isFullCodonStartingAt(x - 1)){
  			return getAminoAcidFromTripletStartingAt(x - 1);
  		}else if(isFullCodonStartingAt(x - 2)){
  			return getAminoAcidFromTripletStartingAt(x - 2);
  		}else{
  			return AminoAcid.GAP;
  		}
  	}
  
  	public boolean isCodonSecondPos(int x){
  		if(isFullCodonStartingAt(x - 1)){
  			return true;
  		}else{
  			return false;
  		}
  	}
  
  	public byte[] getTripletAt(int x){
  		byte[] codon = new byte[3];
  		for(int n = 0; n < 3; n ++){
  			if(x + n < delegate.getLength()){
  				codon[n] = delegate.get(x + n);
  			}
  			else{
  				codon[n] = SequenceUtils.GAP_SYMBOL;
  			}
  		}
  		return codon;
  	}
  
  	public int getCachedClosestStartPos(){
  		return cachedClosestTranslatedNucleotideStartPos;
  	}
  
  	/*
  		public AminoAcid getAAinNoGapTranslatedPos(int x) {
  			return getNoGapAminoAcidAtNucleotidePos(x * 3);
  		}
  	 */
  
  	public AminoAcidAndPosition getNoGapAminoAcidAtNucleotidePos(int target){
  		int tripCount = 0;
  		byte[] triplet = new byte[3];
  		int seqLen = delegate.getLength();
  
  
  		// skip pos depending on ReadingFrame
  		int startPos = 0;
  
  		// if cached pos not is set, get a start pos
  		if(cachedClosestTranslatedNucleotideStartPos == -1 || target < cachedClosestTranslatedNucleotideStartPos){ //
  			int skipCount = 0;
  			int readingFrame = getCodonPositions().getReadingFrame();
  			if(readingFrame > 1){
  				for(int n = 0; n < seqLen; n ++){
  					byte base = delegate.get(n);
  					if(! NucleotideUtilities.isGap(base) && getCodonPositions().isCoding(n)){
  						skipCount ++;
  					}
  					if(skipCount == readingFrame){
  						startPos = n;
  					}
  				}
  			}
  		}else{
  			startPos = cachedClosestTranslatedNucleotideStartPos;
  		}
  
  		if(target >= startPos){
  			for(int n = startPos; n < seqLen; n++){
  				byte base = delegate.get(n);
  				if(NucleotideUtilities.isGap(base) || getCodonPositions().isNonCoding(n)){			
  					if(n >= target && tripCount == 0){
  						return new AminoAcidAndPosition(AminoAcid.GAP, startPos); 
  					}			
  				}else{
  
  					tripCount ++;
  					triplet[tripCount - 1] = base;
  
  					if(tripCount == 1){
  						cachedClosestTranslatedNucleotideStartPos = n;
  					}
  
  					if(tripCount == 3){				
  						if(n >= target){
  							AminoAcid aa = AminoAcid.getAminoAcidFromCodon(triplet, getGeneticCode());
  							return new AminoAcidAndPosition(aa, startPos);
  						}
  						triplet = new byte[3];
  						tripCount = 0;
  					}			
  				}
  			}
  			return new AminoAcidAndPosition(AminoAcid.X, startPos);
  		}else{
  			return new AminoAcidAndPosition(AminoAcid.GAP, startPos);//AminoAcid.GAP;
  		}
  	}
  
  	public AminoAcid getAminoAcidFromTripletStartingAt(int x){
  		if(cachedAminoTripletAcidPos != x){
  			cachedAminoTripletAcidPos = x;
  			cachedAminoAcid = AminoAcid.getAminoAcidFromCodon(getTripletAt(cachedAminoTripletAcidPos),getGeneticCode());
  		}
  		return cachedAminoAcid;
  	}
  
  	public String getTranslatedAsString(){
  		StringWriter out = new StringWriter(delegate.getLength()/3);
  		writeTranslation(out);
  		return out.toString();
  	}
  
  	public void writeTranslation(Writer out){
  		int x = 0;
  		int gap = 0;
  
  		try {
  			while(x < delegate.getLength()){
  				if(isFullCodonStartingAt(x)){
  					byte[] codon = getTripletAt(x);
  					out.append(AminoAcid.getAminoAcidFromCodon(codon).getCodeCharVal());
  					// clear gap
  					gap = 0;
  					x = x + 3; // move one frame ahead (this is a full codon)
  				}
  				else{
  					// there is a orfan in protein translation
  					gap ++;
  					// Add a protein X in sequence for every 3 gaps
  					if(gap % 3 == 1){
  						out.append(AminoAcid.X.getCodeCharVal());
  					}
  					x = x + 1;	
  				}
  			}
  		} catch (IOException e) {
  			// TODO Auto-generated catch block
  			e.printStackTrace();
  		}
  	}
  
  	public int getTranslatedAminAcidSequenceLength(){
  		if(delegate.getLength() == 0){
  			return 0;
  		}
  		int lastPos = getCodonPositions().getAminoAcidPosFromNucleotidePos(delegate.getLength() - 1);
  		return lastPos + 1;
  	}
  
  	public AminoAcid getAAinTranslatedPos(int x) {
  		CodonPos codonPos = getCodonPositions().getCodonInTranslatedPos(x);
  		if(codonPos == null){
  			return null;
  		}
  		if(! codonPos.isCoding()){
  			return AminoAcid.X;
  		}else{
  			byte[] codon = getTripletAt(codonPos.startPos);
  			AminoAcid aa = AminoAcid.getAminoAcidFromCodon(codon, getGeneticCode());
  			return aa;
  		}
  	}
  
  	public byte[] getGapPaddedCodonInTranslatedPos(int x) {
  		byte[] codon = getCodonInTranslatedPos(x);
  		if(codon.length != 3){
  			byte[] padded = new byte[3];
  			Arrays.fill(padded, SequenceUtils.GAP_SYMBOL);
  			for(int n = 0; n < codon.length; n++){
  				padded[n] = codon[n];
  			}
  			codon = padded;
  		}
  		return codon;
  	}
  
  	private CodonPos getCodonPosInTranslatedPos(int x) {
  		return getCodonPositions().getCodonInTranslatedPos(x);
  	}
  
  	public byte[] getCodonInTranslatedPos(int x) {
  		CodonPos codonPos = getCodonPositions().getCodonInTranslatedPos(x);	
  		byte[] codon = getNucleotidesAtCodon(codonPos);
  		return codon;
  	}
  
  
  	private byte[] getNucleotidesAtCodon(CodonPos codonPos) {
  		if(codonPos == null || codonPos.startPos >= delegate.getLength()){
  			return new byte[]{SequenceUtils.GAP_SYMBOL};
  		}
  
  		int start = codonPos.startPos;
  		int end = Math.min(codonPos.endPos, delegate.getLength() -1);
  
  		byte[] codonBytes = new byte[end - start + 1];
  		for(int n = start; n <= end; n++){
  			codonBytes[n - start] = delegate.get(n); 
  		}
  
  
  		//byte[] codonBytes = delegate.getBetween(codonPos.startPos, codonPos.endPos + 1);
  
  		return codonBytes;
  	}
  
  
  
  	/*
  		public int findFistPosSkipStop(int nextFindPos, AminoAcid target){
  			for(int n = nextFindPos; n < getCodonPositions().getLengthOfTranslatedPos(); n++){
  				AminoAcid nextAA = getAAinTranslatedPos(n);
  
  				if(nextAA != null && nextAA == AminoAcid.STOP){
  					// Skip to next
  				}	
  				else if(nextAA != null && nextAA.getCodeCharVal() == target.getCodeCharVal()){
  					return n;
  				}
  			}
  			return -1;
  		}
  
  		public int findFistPos(int nextFindPos, AminoAcid target){
  			for(int n = nextFindPos; n < getCodonPositions().getLengthOfTranslatedPos(); n++){
  				AminoAcid nextAA = getAAinTranslatedPos(n);
  
  				if(nextAA != null && nextAA.getCodeCharVal() == target.getCodeCharVal()){
  					return n;
  				}
  			}
  			return -1;
  		}
  	 */
  
  	public int countStopCodon(){
  		int counter = 0;
  		int transLen = getTranslatedAminAcidSequenceLength();
  		//			logger.info("transLen" + transLen);
  		for(int n = 0; n < transLen; n++){
  			AminoAcid aa = getAAinTranslatedPos(n);
  			if(aa == AminoAcid.STOP){
  				//logger.info(sequence.getName() + "n=" + n);
  				counter ++;
  			}
  		}
  		return counter;
  	}
  
  
  
  
  }
  
  