package geneFinder;

import java.util.Vector;

import types.Contig;
import types.Gene;

/**
 * contains methods necessary for the frame determination of candidate gene regions
 * Copyright (c) 2013,
 * Franziska Zickmann, 
 * ZickmannF@rki.de, Robert Koch-Institute, Berlin, Germany
 * Distributed under the GNU Lesser General Public License, version 3.0
 *
 */

public class FrameSearch {

	/*
	 * new way of gene extraction by remembering all starts that are not in the same frame (maxNum = 3)
	 * after that, starts and stops are checked if we find a combination that defines the frame of the cluster
	 * 
	 * tempStop is initially (int) (cluster.startPos+3) ; after that it is the position of the last detected ATG
	 * recursive call
	 */
	
	public static int findPossibleStarts_Forward(Gene cluster, StringBuffer contigSeq, int posAr, int tempStop){
		
		if(tempStop < (int)Math.max(0,(cluster.startPos-GeneFinder.readLength))){
			return -1;
		}
		
		int start1 = contigSeq.substring((int)Math.max(0,(cluster.startPos-GeneFinder.readLength)),tempStop).lastIndexOf("ATG");
		
		if(start1 == -1){
			return start1;
		}
		
		start1 = (int)Math.max(0,(cluster.startPos-GeneFinder.readLength))+start1;
		
		boolean foundSameFrame = false;
		
		for(int i = 0; i<posAr;++i){
			if((cluster.possibleStarts_Forward[i] - start1) % 3 == 0){
				foundSameFrame = true;
				break;
			}
		}
		
		if(!foundSameFrame){
			cluster.possibleStarts_Forward[posAr++] = start1;
			findPossibleStarts_Forward(cluster,contigSeq,posAr,start1);
		}else{
			findPossibleStarts_Forward(cluster,contigSeq,posAr,start1);
		}
		
		return start1;
	}
	
	/*
	 * new way of gene extraction by remembering all starts that are not in the same frame (maxNum = 3)
	 * after that, starts and stops are checked if we find a combination that defines the frame of the cluster
	 * 
	 * tempStop is initially (int) (cluster.startPos+3) ; after that it is the position of the last detected StopCodon (reverse)
	 * search for TTA, TCA and CTA
	 * 
	 * recursive call
	 */
	
	public static int findPossibleStarts_Reverse(Gene cluster, StringBuffer contigSeq, int posAr, int tempStop){
		
		int start_RE = -1;
		
		if(tempStop < (int)Math.max(0,(cluster.startPos-GeneFinder.readLength))){
			return -1;
		}
		
		String startPart = contigSeq.substring((int)Math.max(0,(cluster.startPos-GeneFinder.readLength)), tempStop);
		
		int startSub[] = {startPart.lastIndexOf("TTA"),startPart.lastIndexOf("TCA"),startPart.lastIndexOf("CTA")};
		
		java.util.Arrays.sort(startSub);
		
		if(startSub[2] > -1){ 
			start_RE = (int) Math.max(0,(cluster.startPos-GeneFinder.readLength)) + startSub[2]; 
		}else if(startSub[1] > -1){
			start_RE = (int) Math.max(0,(cluster.startPos-GeneFinder.readLength))+ startSub[1];
		} else if(startSub[0] > -1){
			start_RE = (int )Math.max(0,(cluster.startPos-GeneFinder.readLength)) + startSub[0];
		}
		
		if(start_RE == -1){
			return start_RE;
		}
		
		boolean foundSameFrame = false;
		
		for(int i = 0; i<posAr;++i){
			if((cluster.possibleStarts_Reverse[i] - start_RE) % 3 == 0){
				foundSameFrame = true;
				break;
			}
		}
		
		if(!foundSameFrame){
			cluster.possibleStarts_Reverse[posAr++] = start_RE;
			findPossibleStarts_Reverse(cluster,contigSeq,posAr,start_RE);
		}else{
			findPossibleStarts_Reverse(cluster,contigSeq,posAr,start_RE);
		}
		
		return start_RE;
	}
	
	/*
	 * new way of gene extraction by remembering all starts that are not in the same frame (maxNum = 3)
	 * after that, starts and stops are checked if we find a combination that defines the frame of the cluster
	 * 
	 * tempStart is initially (int) (cluster.stopPos - 2) ; after that it is the position of the last detected CAT+3
	 * recursive call
	 */
	
	public static int findPossibleStops_Forward(Gene cluster, StringBuffer contigSeq, int posAr, int tempStart){
		
		int stop_FO = -1;
		
		if(tempStart > (int) Math.min(contigSeq.length(),cluster.stopPos-2 + GeneFinder.readLength + 1)){
			return -1;
		}
		
		String stopPart = contigSeq.substring(tempStart, (int) Math.min(contigSeq.length(),cluster.stopPos-2 + GeneFinder.readLength + 1));
		int stopSub[] = {stopPart.indexOf("TAA"),stopPart.indexOf("TGA"),stopPart.indexOf("TAG")};
		
		java.util.Arrays.sort(stopSub);

		if(stopSub[0] > -1){ 
			stop_FO = tempStart + stopSub[0]; 
		}else if(stopSub[1] > -1){
			stop_FO = tempStart + stopSub[1];
		} else if(stopSub[2] > -1){
			stop_FO = tempStart + stopSub[2];
		}
		
		if(stop_FO == -1){
			return stop_FO;
		}
		
		boolean foundSameFrame = false;
		
		for(int i = 0; i<posAr;++i){
			if((cluster.possibleStops_Forward[i] - stop_FO) % 3 == 0){
				foundSameFrame = true;
				break;
			}
		}
		
		if(!foundSameFrame){
			cluster.possibleStops_Forward[posAr++] = stop_FO;
			findPossibleStops_Forward(cluster,contigSeq,posAr,stop_FO+3);
		}else{
			findPossibleStops_Forward(cluster,contigSeq,posAr,stop_FO+3);
		}
		
		return stop_FO;
	}
	
	/*
	 * new way of gene extraction by remembering all starts that are not in the same frame (maxNum = 3)
	 * after that, starts and stops are checked if we find a combination that defines the frame of the cluster
	 * 
	 * tempStart is initially (int) (cluster.stopPos - 2) ; after that it is the position of the last detected CAT+3
	 * recursive call
	 */
	
	public static int findPossibleStops_Reverse(Gene cluster, StringBuffer contigSeq, int posAr, int tempStart){
		
		if(tempStart > (int) Math.min(contigSeq.length(),cluster.stopPos-2 + GeneFinder.readLength + 1)){
			return -1;
		}
		
		int start1 = contigSeq.substring(tempStart, (int) Math.min(contigSeq.length(),cluster.stopPos-2 + GeneFinder.readLength + 1)).indexOf("CAT");
		
		if(start1 == -1){
			return start1;
		}
		
		start1 = start1 + tempStart;
		
		boolean foundSameFrame = false;
		
		for(int i = 0; i<posAr;++i){
			if((cluster.possibleStops_Reverse[i] - start1) % 3 == 0){
				foundSameFrame = true;
				break;
			}
		}
		
		if(!foundSameFrame){
			cluster.possibleStops_Reverse[posAr++] = start1;
			findPossibleStops_Reverse(cluster,contigSeq,posAr,start1+3);
		}else{
			findPossibleStops_Reverse(cluster,contigSeq,posAr,start1+3);
		}
		
		return start1;
	}
	
	/*
	 * test if there is one of the possible start-stop codon pairs which is in frame
	 * take the smallest interval possible
	 */
	
	public static int[] checkAndChooseReadingFrame(int[] startPosis, int[] stopPosis){

		Vector<int[]> pairs = new Vector<int[]>();
		for(int start : startPosis){
			if(start >= 0){
				for(int stop : stopPosis){
					if(stop >= 0 && (stop - start) % 3 == 0 && (stop - start) > 0){
						// both are in frame
						int[] pairInfo = {start,stop,(stop-start)};
						if(pairs.size() != 0){
							if(pairs.get(0)[2] > (stop-start)){
								pairs.remove(0);
								pairs.add(pairInfo);
							}
						}else{
							pairs.add(pairInfo);
						}

					}
				}
			}
		}

		if(pairs.size() == 0){
			return null;
		}else{
			return pairs.get(0);
		}
	}
	
	/*
	 * especially for spliced genes, because here the in-frame search becomes less important
	 * take the smallest interval of start and stop codon that is possible
	 */
	
	public static int[] checkAndChooseReadingFrame_SplicingVariant(int[] startPosis, int[] stopPosis){

		Vector<int[]> pairs = new Vector<int[]>();
		for(int start : startPosis){
			if(start >= 0){
				for(int stop : stopPosis){
					if(stop >= 0 && (stop - start) > 0){
						// both might be not in frame!
						int[] pairInfo = {start,stop,(stop-start)};
						if(pairs.size() != 0){
							if(pairs.get(0)[2] > (stop-start)){
								pairs.remove(0);
								pairs.add(pairInfo);
							}
						}else{
							pairs.add(pairInfo);
						}

					}
				}
			}
		}

		if(pairs.size() == 0){
			return null;
		}else{
			return pairs.get(0);
		}
	}
	
	
	/*
	 * it appears that a new transcript started within an intron, so search for its start in the given interval
	 */
	
	public static int[] lookForStartOfIsoform(int oldStart, StringBuffer contigSeq){
		
		int startFO = contigSeq.substring((int)Math.max(0,(oldStart-GeneFinder.readLength)),oldStart+3).lastIndexOf("ATG");
		if(startFO != -1){
			startFO = (int)Math.max(0,(oldStart-GeneFinder.readLength))+startFO; 
		}
				
		int startRE = -1;
		String startPart = contigSeq.substring((int)Math.max(0,(oldStart-GeneFinder.readLength)), oldStart+3);
		
		int startSub[] = {startPart.lastIndexOf("TTA"),startPart.lastIndexOf("TCA"),startPart.lastIndexOf("CTA")};
		
		java.util.Arrays.sort(startSub);
		
		if(startSub[2] > -1){ 
			startRE = (int) Math.max(0,(oldStart-GeneFinder.readLength)) + startSub[2]; 
		}else if(startSub[1] > -1){
			startRE = (int) Math.max(0,(oldStart-GeneFinder.readLength))+ startSub[1];
		} else if(startSub[0] > -1){
			startRE = (int )Math.max(0,(oldStart-GeneFinder.readLength)) + startSub[0];
		}
		
		return new int[] {startFO,startRE};
	}
	
	
	/*
	 * use the information of direc counter to refine the frame
	 */
	
	public static void useDirecInfo(Gene gene, StringBuffer contigSeq){

		if(!(gene.possibleIntrons.keySet().isEmpty())){
			if(gene.direcCounter[0] > gene.direcCounter[1]){
				// try forward				
				
				if(gene.onRevStrand){
					//System.out.println("should be forward!");
					int [] pair_FO = checkAndChooseReadingFrame_SplicingVariant(gene.possibleStarts_Forward,gene.possibleStops_Forward);
					if(pair_FO != null){
						//System.out.println("changed to forward");
						gene = ExtractGeneCandidates.refineExtractedCluster(gene, contigSeq, pair_FO[0], pair_FO[1]+2,false);
						gene.realDirectionNotKnown = false;
					}else{
						if(gene.possibleStarts_Forward[0] != -1 || gene.possibleStops_Forward[0] != -1){
							//System.out.print("changed to forward without pair for gene " + gene.geneID);
							int start = -1;
							int stop = -1;
							
							if(gene.possibleStarts_Forward[0] != -1){								
								int[] temp = gene.possibleStarts_Forward;
								start = biggestNonNegativeArrayEntry(temp);
								stop = gene.stopPos;
								//System.out.println(" Old start: " + gene.startPos + " new start: " + start);
							}else{
								int[] temp = gene.possibleStops_Forward;
								stop = smallestNonNegativeArrayEntry(temp);
								start = gene.startPos;
								//System.out.println(" Old stop: " + gene.stopPos + " new stop: " + stop);
							}
							
							gene = ExtractGeneCandidates.refineExtractedCluster(gene, contigSeq, start,stop+2,false);
							gene.realDirectionNotKnown = false;
						}else{
							// change both start and stop
							gene = ExtractGeneCandidates.refineExtractedCluster(gene, contigSeq,  gene.startPos, gene.stopPos+2,false);
							gene.realDirectionNotKnown = false;							
						}
					}
				}
				
			}else if(gene.direcCounter[1] > gene.direcCounter[0]){
				// try reverse				
				
				if(!gene.onRevStrand){
					int [] pair_RE = checkAndChooseReadingFrame_SplicingVariant(gene.possibleStarts_Reverse,gene.possibleStops_Reverse);
					if(pair_RE != null){
						gene = ExtractGeneCandidates.refineExtractedCluster(gene, contigSeq, pair_RE[0], pair_RE[1]+2,true);
						gene.realDirectionNotKnown = false;
					}else{
						if(gene.possibleStarts_Reverse[0] != -1 || gene.possibleStops_Reverse[0] != -1){
							//System.out.print("changed to reverse without pair for gene " + gene.geneID);
							int start = -1;
							int stop = -1;
							if(gene.possibleStarts_Reverse[0] != -1){								
								int[] temp = gene.possibleStarts_Reverse;
								start = biggestNonNegativeArrayEntry(temp); 
								stop = gene.stopPos;
							}else{
								int[] temp = gene.possibleStops_Reverse;
								stop = smallestNonNegativeArrayEntry(temp);
								start = gene.startPos;
							}
							
							gene = ExtractGeneCandidates.refineExtractedCluster(gene, contigSeq, start, stop+2,true);
							gene.realDirectionNotKnown = false;
						}else{
							// change both start and stop
							gene = ExtractGeneCandidates.refineExtractedCluster(gene, contigSeq,  gene.startPos, gene.stopPos+2,true);
							gene.realDirectionNotKnown = false;							
						}
					}
				}
			}
		}
	}
	
	/*
	 * small function that returns the smallest position not -1
	 */
	
	public static int smallestNonNegativeArrayEntry(int[] arrayOrg){
		
		int smallest = -1;
		
		int[] array = new int[arrayOrg.length];
		for(int entry = 0; entry < arrayOrg.length;++entry){
			array[entry] = arrayOrg[entry];
		}
		
		java.util.Arrays.sort(array);

		if(array[0] > -1){ 
			smallest = array[0]; 
		}else if(array[1] > -1){
			smallest =  array[1];
		} else if(array[2] > -1){
			smallest = array[2];
		}
		
		return smallest;
	}
	
	/*
	 * small function that returns the biggest position not -1
	 */
	
	public static int biggestNonNegativeArrayEntry(int[] arrayOrg){
		
		int biggest = -1;
		
		int[] array = new int[arrayOrg.length];
		for(int entry = 0; entry < arrayOrg.length;++entry){
			array[entry] = arrayOrg[entry];
		}
		
		java.util.Arrays.sort(array);

		if(array[2] > -1){ 
			biggest = array[2]; 
		}else if(array[1] > -1){
			biggest =  array[1];
		} else if(array[0] > -1){
			biggest = array[0];
		}
		
		return biggest;
	}
	
	/*
	 * manages the frame search
	 */
	
	public static void findFrameAndCheckWithNeighbors(Gene cluster, Contig thisContig, StringBuffer contigSeq){
	
		// now that we found the high-coverage area, search for start and stop codons

		int possibleStart_FO = FrameSearch.findPossibleStarts_Forward(cluster, contigSeq, 0, (int) (cluster.startPos+3));  // now the start positions are directly the right ones
		int possibleStart_RE = FrameSearch.findPossibleStarts_Reverse(cluster,contigSeq,0,(int) (cluster.startPos+3));

		int possibleStop_FO = -1;
		int possibleStop_RE = -1;

		possibleStop_FO = FrameSearch.findPossibleStops_Forward(cluster,contigSeq,0,(int) cluster.stopPos-2);
		possibleStop_RE = FrameSearch.findPossibleStops_Reverse(cluster,contigSeq,0,(int) cluster.stopPos-2);

		// first have a look, if already forward or reverse direction is excluded due to missing start or stop

		if(possibleStart_FO == -1 && possibleStart_RE != -1){   // reverse start found

			// if it is an ORF, a reverse one is more likely, so search only for a possible start position of an reverse gene

			if(possibleStop_RE != -1){

				testFrames(cluster.possibleStarts_Reverse,cluster.possibleStops_Reverse,true,cluster,contigSeq);
				
			}else{
				cluster.onRevStrand = true;
				ExtractGeneCandidates.declareClusterAsNotCompleted(cluster,possibleStart_RE,contigSeq);
			}

		}else if(possibleStart_RE == -1 && possibleStart_FO != -1){   // forward start found

			// if it is an ORF, a forward one is more likely, so search only for possible stop positions of a forward gene

			if(possibleStop_FO != -1){		

				testFrames(cluster.possibleStarts_Forward,cluster.possibleStops_Forward,false,cluster,contigSeq);
				
			}else{
				cluster.onRevStrand = false;
				ExtractGeneCandidates.declareClusterAsNotCompleted(cluster,possibleStart_FO,contigSeq);
			}

		}else if(possibleStart_RE != -1 && possibleStart_FO != -1){    // both starts found

			// at the moment both directions are equally likely, so first search for the right frames

			if((possibleStop_FO == -1) && (possibleStop_RE == -1)){
				ExtractGeneCandidates.declareClusterAsNotCompleted(cluster,Math.max(possibleStart_FO,possibleStart_RE),contigSeq); // use the smallest possible interval
			}else if((possibleStop_FO != -1) && (possibleStop_RE == -1)){
				testFrames(cluster.possibleStarts_Forward,cluster.possibleStops_Forward,false,cluster,contigSeq);
			}else if((possibleStop_FO == -1) && (possibleStop_RE != -1)){
				testFrames(cluster.possibleStarts_Reverse,cluster.possibleStops_Reverse,true,cluster,contigSeq);
			}else{
				// both are still equally likely, so check both of them

				int[] pair_FO = null;
				int[] pair_RE = null;

				if(cluster.possibleIntrons.keySet().isEmpty()){
					pair_FO = FrameSearch.checkAndChooseReadingFrame(cluster.possibleStarts_Forward,cluster.possibleStops_Forward);
					pair_RE = FrameSearch.checkAndChooseReadingFrame(cluster.possibleStarts_Reverse,cluster.possibleStops_Reverse);
				}else{

					pair_FO = FrameSearch.checkAndChooseReadingFrame_SplicingVariant(cluster.possibleStarts_Forward,cluster.possibleStops_Forward);
					pair_RE = FrameSearch.checkAndChooseReadingFrame_SplicingVariant(cluster.possibleStarts_Reverse,cluster.possibleStops_Reverse);
				}

				if(pair_FO == null && pair_RE == null){

					// if possible, refer to XS tag		

					if((possibleStop_FO - possibleStart_FO) <= (possibleStop_RE - possibleStart_RE)){
						cluster.startPos = possibleStart_FO;
						cluster.stopPos = possibleStop_FO + 2;
						cluster.onRevStrand = false;
					}else{
						cluster.startPos = possibleStart_RE;
						cluster.stopPos = possibleStop_RE + 2;
						cluster.onRevStrand = true;
					}


				} else if(pair_FO != null && pair_RE == null){
					cluster = ExtractGeneCandidates.refineExtractedCluster(cluster, contigSeq, pair_FO[0], pair_FO[1]+2,false);
				} else if(pair_FO == null && pair_RE != null){
					cluster = ExtractGeneCandidates.refineExtractedCluster(cluster, contigSeq, pair_RE[0], pair_RE[1]+2,true);
				}else if(pair_FO != null && pair_RE != null){
					// if possible, refer to XS tag	
					if(!(cluster.possibleIntrons.keySet().isEmpty()) && cluster.direcCounter[0] > cluster.direcCounter[1]){
						cluster = ExtractGeneCandidates.refineExtractedCluster(cluster, contigSeq, pair_FO[0], pair_FO[1]+2,false);
					}else if(!(cluster.possibleIntrons.keySet().isEmpty()) && cluster.direcCounter[0] < cluster.direcCounter[1]){
						cluster = ExtractGeneCandidates.refineExtractedCluster(cluster, contigSeq, pair_RE[0], pair_RE[1]+2,true);
					}else{						
						// take longest interval, because this region is likely to be overlapping with other regions
						
						if(pair_FO[2] >= pair_RE[2]){
							cluster = ExtractGeneCandidates.refineExtractedCluster(cluster, contigSeq, pair_FO[0], pair_FO[1]+2,false);
						}else{
							cluster = ExtractGeneCandidates.refineExtractedCluster(cluster, contigSeq, pair_RE[0], pair_RE[1]+2,true);
						}	
						
					}

				}

			}

		} 

	}
	
	/*
	 * test if there exist a pair in frame
	 */
	
	public static void testFrames(int[] starts, int[] stops, boolean isReverse, Gene cluster, StringBuffer contigSeq){
		
		int[] pair = null;

		if(cluster.possibleIntrons.keySet().isEmpty()){
			pair = FrameSearch.checkAndChooseReadingFrame(starts,stops);
		}else{
			pair = FrameSearch.checkAndChooseReadingFrame_SplicingVariant(starts,stops);
		}			

		if(pair != null){
			cluster = ExtractGeneCandidates.refineExtractedCluster(cluster, contigSeq, pair[0], pair[1]+2,isReverse);
		}else{

			cluster.startPos = starts[0];
			cluster.stopPos = stops[0] + 2;

			ExtractGeneCandidates.checkIfAdequateAndRefine(cluster, isReverse, starts, contigSeq);

		}

	}
}
