package geneFinder;

import java.util.Vector;
import types.Gene;

/**
 * class that includes method especially applied in prokaryotic gene finding
 * 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 Prokaryote_Specials {


	public static Vector<Integer> bestCombi;		// only store the best possible combination achieved so far
	public static boolean bestIsForward;
	
	public static int covered_Local;
	
	public static int notCounted;  // counts how often we rejected an ORF due to the size threshold
	public static int alreadyCovered; // counts how often we rejected an ORF because it is contained in a bigger one
	
	public static int[] cov; // necessary for divide and conquer
	
	/*
	 * search all ORFs and determine the best set
	 */
	
	public static Object[] define_OrfsInOperon(String inputSeq, Gene gene){

		// first extract all possible ORFs
		
		//String report = "";
		
		bestCombi = new Vector<Integer>();
		bestIsForward = true;
		
		notCounted = 0;
		alreadyCovered = 0;
		cov = new int[inputSeq.length()];
			
		Vector<int[]> allORFs_FO = searchFO_orfs(inputSeq);		
		//report += notCounted + " below, " + alreadyCovered + " covered, " + allORFs_FO.size() + " left (FO). ";
		
		Object[] returnArr_FO = Operon_LP.writeOperon_LP(inputSeq,cov,allORFs_FO,-1,null);
		double scoreFO = (Double) returnArr_FO[0];
		Vector<Integer> chosenORFs_FO = (Vector<Integer>) returnArr_FO[1];
		int maxL_FO = (Integer) returnArr_FO[2];
		
		notCounted = 0;
		alreadyCovered = 0;
		cov = new int[inputSeq.length()];
		Vector<int[]> allORFs_RE = searchRE_orfs(inputSeq);
		
		//report += notCounted + " below, " + alreadyCovered + " covered, " + allORFs_RE.size() + " left (RE). ";
		
		Object[] returnArr_RE = Operon_LP.writeOperon_LP(inputSeq,cov,allORFs_RE,-1,null);
		double scoreRE = (Double) returnArr_RE[0];
		Vector<Integer> chosenORFs_RE = (Vector<Integer>) returnArr_RE[1];
		int maxL_RE = (Integer) returnArr_RE[2];
			
		// now determine the BIC and remember the maximum 
		
		Vector<int[]> orfsVec = new Vector<int[]>();
				
		if(scoreRE > scoreFO){
			bestIsForward = false;
			bestCombi.clear();
			bestCombi = chosenORFs_RE;
			
			// second iteration
			
			Vector<int[]> mergedORFs = new Vector<int[]>();
			Vector<Integer> posChosen = new Vector<Integer>(); 
			
			int posNum = 0;
			
			for(int pos : chosenORFs_RE){
				int[] tmp = {(allORFs_RE.get(pos)[0]), (allORFs_RE.get(pos)[1])};
				mergedORFs.add(tmp);
				posChosen.add(posNum++);
			}
			
			mergedORFs.addAll(allORFs_FO);
			cov = new int[inputSeq.length()];
			calculateCoverage(mergedORFs);
			Object[] returnArr_RE_2 = Operon_LP.writeOperon_LP(inputSeq,cov,mergedORFs,maxL_RE,posChosen);
			double scoreRE_2 = (Double) returnArr_RE_2[0];
			Vector<Integer> chosenORFs_RE_2 = (Vector<Integer>) returnArr_RE_2[1];
			
			if(chosenORFs_RE_2.size() > chosenORFs_RE.size()){
				
				// start final iteration
				
				Vector<int[]> mergedORFs_2 = new Vector<int[]>();
				Vector<Integer> posChosen_2 = new Vector<Integer>(); 
				
				int pos2_temp = 0;
				for(int pos : chosenORFs_RE_2){
					int[] tmp = {(mergedORFs.get(pos)[0]), (mergedORFs.get(pos)[1])};
					mergedORFs_2.add(tmp);
					if(!posChosen.contains(pos)){
						posChosen_2.add(pos2_temp);
					}		
					pos2_temp++;
				}
				
				cov = new int[inputSeq.length()];
				calculateCoverage(mergedORFs_2);
				
				Object[] returnArr_RE_3 = Operon_LP.writeOperon_LP(inputSeq,cov,mergedORFs_2,maxL_RE,posChosen_2);
				double scoreRE_3 = (Double) returnArr_RE_3[0];
				Vector<Integer> chosenORFs_RE_3 = (Vector<Integer>) returnArr_RE_3[1];
				
				orfsVec = determineBestComposition(mergedORFs_2,chosenORFs_RE_3,posChosen_2,inputSeq,gene,0);
				
			}else{
				int[] first = {1};
				orfsVec.add(first);
				for(int pos : bestCombi){
					int[] tmp = {(gene.startPos + allORFs_RE.get(pos)[0]), (gene.startPos + allORFs_RE.get(pos)[1])};
					orfsVec.add(tmp);
				}
			}
			
		}else{
			bestIsForward = true;
			bestCombi.clear();
			bestCombi = chosenORFs_FO;
			
			// second iteration
			
			Vector<int[]> mergedORFs = new Vector<int[]>();
			Vector<Integer> posChosen = new Vector<Integer>(); 
			
			int posNum = 0;
			
			for(int pos : chosenORFs_FO){
				int[] tmp = {(allORFs_FO.get(pos)[0]), (allORFs_FO.get(pos)[1])};
				mergedORFs.add(tmp);
				posChosen.add(posNum++);
			}
			
			mergedORFs.addAll(allORFs_RE);
			cov = new int[inputSeq.length()];
			calculateCoverage(mergedORFs);
			Object[] returnArr_FO_2 = Operon_LP.writeOperon_LP(inputSeq,cov,mergedORFs,maxL_FO,posChosen);
			double scoreFO_2 = (Double) returnArr_FO_2[0];
			Vector<Integer> chosenORFs_FO_2 = (Vector<Integer>) returnArr_FO_2[1];
			
			if(chosenORFs_FO_2.size() > chosenORFs_FO.size()){
				
				// start final iteration
				
				Vector<int[]> mergedORFs_2 = new Vector<int[]>();
				Vector<Integer> posChosen_2 = new Vector<Integer>(); 
				
				int pos2_temp = 0;
				for(int pos : chosenORFs_FO_2){
					int[] tmp = {(mergedORFs.get(pos)[0]), (mergedORFs.get(pos)[1])};
					mergedORFs_2.add(tmp);
					if(!posChosen.contains(pos)){
						posChosen_2.add(pos2_temp);
					}	
					pos2_temp++;
				}
				
				cov = new int[inputSeq.length()];
				calculateCoverage(mergedORFs_2);
				
				Object[] returnArr_FO_3 = Operon_LP.writeOperon_LP(inputSeq,cov,mergedORFs_2,maxL_FO,posChosen_2);
				double scoreFO_3 = (Double) returnArr_FO_3[0];
				Vector<Integer> chosenORFs_FO_3 = (Vector<Integer>) returnArr_FO_3[1];
				
				orfsVec = determineBestComposition(mergedORFs_2,chosenORFs_FO_3,posChosen_2,inputSeq,gene,1);
				
			}else{
				int[] first = {1};
				orfsVec.add(first);
				for(int pos : bestCombi){
					int[] tmp = {(gene.startPos + allORFs_FO.get(pos)[0]), (gene.startPos + allORFs_FO.get(pos)[1])};
					orfsVec.add(tmp);
				}
			}
			
		}
		
		/*if(bestIsForward){
			for(int pos : bestCombi){
				int[] tmp = {(gene.startPos + allORFs_FO.get(pos)[0]), (gene.startPos + allORFs_FO.get(pos)[1])};
				orfsVec.add(tmp);
			}
		}else{
			for(int pos : bestCombi){
				int[] tmp = {(gene.startPos + allORFs_RE.get(pos)[0]), (gene.startPos + allORFs_RE.get(pos)[1])};
				orfsVec.add(tmp);
			}
		}*/
		
		//report += "OrfNum: " + orfsVec.size() + ", FO:" + bestIsForward + ", time: " + (double) (timeAft_all-timeBef_all)/1000.0 +"s.";
		
		return new Object[]{orfsVec,bestIsForward};
	}
	
	/*
	 * sort reverse ORFs beginning with left-most start position (via bubblesort)
	 * necessary for combination determination
	 */
	
	public static Vector<int[]> sort_ORFs(Vector<int[]> allORFs_RE){
		
		int[] temp;
		for(int i=1; i<allORFs_RE.size(); ++i) {
			for(int j=0; j<allORFs_RE.size()-i; ++j) {
					if((allORFs_RE.get(j)[0]) > (allORFs_RE.get(j+1)[0])) {
						temp=allORFs_RE.get(j);
						allORFs_RE.setElementAt(allORFs_RE.get(j+1),j);
						allORFs_RE.setElementAt(temp,j+1);
					}
					
				}
			}
		
		return allORFs_RE;
	}

	/*
	 * searches all ORFs assuming forward direction
	 * note: no length limit is set, ORFs too short should be penalized in the BIC scoring
	 */
	
	public static Vector<int[]> searchFO_orfs(String inputSeq){
		
		Vector<int[]> allORFs_FO = new Vector<int[]>();
		
		int foundNewATG = 1;
		int posLastATG = 0;
		
		do{
			int startPos = inputSeq.substring(posLastATG).indexOf("ATG");
			int stopPos = -1;
			
			int posLastStart = -1;
			
			if(startPos == -1){
				foundNewATG = 0;
				break;
			}else{
				startPos = startPos  + posLastATG;
				posLastATG = startPos + 3;
				posLastStart = startPos + 3;
			}

			int goOn = 0;
			
			do{
				goOn = 0;
				
				String stopPart = inputSeq.substring(posLastStart);
				int stopSub[] = {stopPart.indexOf("TAA"),stopPart.indexOf("TGA"),stopPart.indexOf("TAG")};
				
				java.util.Arrays.sort(stopSub);

				if((stopSub[0] > -1)){ 
					if(((((posLastStart + stopSub[0])-startPos) % 3) == 0)){
						stopPos = posLastStart + stopSub[0]; 
					}else{
						posLastStart = posLastStart + stopSub[0]+1;
						goOn = 1;
					}
				}else if((stopSub[1] > -1)){
					if(((((posLastStart + stopSub[1])-startPos) % 3) == 0)){
						stopPos = posLastStart + stopSub[1];
					}else{
						posLastStart = posLastStart + stopSub[1]+1;
						goOn = 1;
					}				
				} else if((stopSub[2] > -1)){
					if(((((posLastStart + stopSub[2])-startPos) % 3) == 0)){
						stopPos = posLastStart + stopSub[2];
					}else{
						posLastStart = posLastStart + stopSub[2]+1;
						goOn = 1;
					}				
				}
				
				if(stopPos != -1){
					
					if(stopPos-startPos > 30){
						if(!checkIfORFcovered(allORFs_FO,new int[]{startPos,(stopPos+2)})){
							allORFs_FO.add(new int[]{startPos,(stopPos+2)});
							for(int i=startPos;i<=stopPos+2;++i){
								cov[i]++;
							}
						}else{
							alreadyCovered++;
						}						
					}else{
						notCounted++;
					}
					
					break;
				}
				
			}while(goOn == 1);
			
			
		}while(foundNewATG == 1);
		
		
		return allORFs_FO;
	}
	
	/*
	 * searches all ORFs assuming reverse direction
	 * note: no length limit is set, ORFs too short should be penalized in the BIC scoring
	 */
	
	public static Vector<int[]> searchRE_orfs(String inputSeq){
		
		Vector<int[]> allORFs_RE= new Vector<int[]>();
		
		int foundNewCAT = 1;
		int posLastCAT = inputSeq.length();
		
		do{
			int startPos = inputSeq.substring(0,posLastCAT).lastIndexOf("CAT");
			int stopPos = -1;
			
			int posLastStop = -1;
			
			if(startPos == -1){
				foundNewCAT = 0;
				break;
			}else{
				posLastCAT = startPos;
				posLastStop = startPos;
			}
			
			int goOn = 0;
			
			do{
				goOn = 0;
				
				String stopPart = inputSeq.substring(0,posLastStop);
				int stopSub[] = {stopPart.lastIndexOf("TTA"),stopPart.lastIndexOf("TCA"),stopPart.lastIndexOf("CTA")};
				
				java.util.Arrays.sort(stopSub);

				if((stopSub[2] > -1)){
					if(((startPos-stopSub[2]) % 3) == 0){
						stopPos = stopSub[2];
					}else{
						posLastStop = stopSub[2]+2;
						goOn = 1;
					}				 
				}else if((stopSub[1] > -1)){
					if(((startPos-stopSub[1]) % 3) == 0){
						stopPos = stopSub[1];
					}else{
						posLastStop = stopSub[1]+2;
						goOn = 1;
					}
				} else if((stopSub[0] > -1)){
					if(((startPos-stopSub[0]) % 3) == 0){
						stopPos = stopSub[0];
					}else{
						posLastStop = stopSub[0]+2;
						goOn = 1;
					}
				}
				
				if(stopPos != -1){
					
					if(startPos-stopPos > 30){
						if(!checkIfORFcovered(allORFs_RE,new int[]{stopPos,(startPos+2)})){
							allORFs_RE.add(new int[]{stopPos,(startPos+2)});
							for(int i=stopPos;i<=startPos+2;++i){
								cov[i]++;
							}
						}else{
							alreadyCovered++;
						}					
					}else{
						notCounted++;
					}
					
					
					break;
				}
				
			}while(goOn == 1);
			
			
		}while(foundNewCAT == 1);
		
		
		return allORFs_RE;
	}
	
	/*
	 * filter out all orfs that are completely included in bigger ones
	 */
	
	public static boolean checkIfORFcovered(Vector<int[]> allORFs,int[] thisORF){
		
		for(int[] orf : allORFs){
			if((orf[0] <= thisORF[0]) && (orf[1] >= thisORF[1])){
				return true;
			}
		}
				
		return false;
		
	}
	
	
	/*
	 * derive the coverage from a set of given ORFs
	 */
	
	public static void calculateCoverage(Vector<int[]> orfs){
		
		for(int[] orf : orfs){
			for(int i=orf[0];i<=orf[1];++i){
				cov[i]++;
			}
		}
		
	}
	
	/*
	 * if we have chosen additional ORFs for the current direction, try to find the best combination of operons
	 * orgForward = 1 indicates, that original set was on forward strand, = 0 means reverse
	 */
	
	public static Vector<int[]> determineBestComposition(Vector<int[]> mergedORFs, Vector<Integer> chosenORFs, Vector<Integer> posChosenBef,String inputSeq,Gene gene, int orgForward){
		
		Vector<int[]> orfsVec = new Vector<int[]>();
		
		int orgAdd = -1;
		if(orgForward == 1){
			orgAdd = 0;
		}else{
			orgAdd = 1;
		}
		
		Vector<int[]> originalStrandORFs = new Vector<int[]>();
		Vector<int[]> additionalORFs = new Vector<int[]>();
		Vector<int[]> connectedComp_add = new Vector<int[]>();   // contains one connected component per int[] (boundaries at 0,1 and the posis of genes at following positions) for addtional orfs
		Vector<int[]> connectedComp_norm = new Vector<int[]>();
		
		for(int i : chosenORFs){
			
			if(posChosenBef.contains(i)){
				additionalORFs.add(mergedORFs.get(i));
			}else{
				originalStrandORFs.add(mergedORFs.get(i));
			}
			
		}
		
		// determine connected comp for additional orfs
		
		additionalORFs = sort_ORFs(additionalORFs);
		
		cov = new int[inputSeq.length()];
		calculateCoverage(originalStrandORFs);
		
		connectedComp_add = defineConnectedComps(additionalORFs,connectedComp_add);
		
		// now for original orfs
		
		originalStrandORFs = sort_ORFs(originalStrandORFs);
		
		cov = new int[inputSeq.length()];
		calculateCoverage(additionalORFs);
		
		connectedComp_norm = defineConnectedComps(originalStrandORFs,connectedComp_norm);
		
		// fill orfsVec
		
		orfsVec.add(new int[] {-1});
		
		if(originalStrandORFs.get(0)[0] <= additionalORFs.get(0)[0]){
			orfsVec = fillVec(connectedComp_norm,orgForward,gene,orfsVec,0);
			orfsVec = fillVec(connectedComp_add,orgAdd,gene,orfsVec,connectedComp_add.get(0)[0]);					
		}else{		
			orfsVec = fillVec(connectedComp_add,orgAdd,gene,orfsVec,0);
			orfsVec = fillVec(connectedComp_norm,orgForward,gene,orfsVec,connectedComp_norm.get(0)[0]);
		}
		
		return orfsVec;
	}
	
	/*
	 * fill the orf vector with operons for this candidate gene
	 */
	
	public static Vector<int[]> fillVec(Vector<int[]> connectedComp,int strand,Gene gene,Vector<int[]> orfsVec,int startPosition){
		
		for(int i = 0; i<connectedComp.size();++i){
			int[] temp = new int[connectedComp.get(i).length+1];
			temp[0] = strand;
			if(i==0){
				temp[1] = gene.startPos + startPosition;
			}else{
				temp[1] = gene.startPos + connectedComp.get(i)[0];
			}		
			temp[2] = gene.startPos + connectedComp.get(i)[1];
			
			for(int pos = 2;pos<connectedComp.get(i).length;++pos){
				temp[pos+1] = gene.startPos + connectedComp.get(i)[pos];
			}
			
			orfsVec.add(temp);
		}
		
		return orfsVec;
	}
	
	/*
	 * search for connected components in additional Orfs
	 */
	
	public static Vector<int[]> defineConnectedComps(Vector<int[]> additionalORFs, Vector<int[]> connectedComp){
			
		int[] tempBound = new int[2];
		Vector<int[]> tempOrfs = new Vector<int[]>();
		tempBound[0] = additionalORFs.get(0)[0];
		tempBound[1] = additionalORFs.get(0)[1];
		tempOrfs.add(additionalORFs.get(0));
		
		for(int i = 1; i<additionalORFs.size();++i){
			int[] orf = additionalORFs.get(i);
			
			if(orf[0] <= tempBound[1]){  // connected
				tempBound[1] = orf[1];
				tempOrfs.add(orf);
			}else{
				int orfWithin = 0;
				for(int pos = tempBound[1]+1; pos < orf[0];++pos){
					if(cov[pos] > 0){
						orfWithin = 1;
						break;
					}
				}
				
				if(orfWithin == 0){
					tempBound[1] = orf[1];
					tempOrfs.add(orf);
				}else{
					// define as not connected
					int[] tempComp = new int[((tempOrfs.size()*2)+2)];
					tempComp[0] = tempBound[0];
					tempComp[1] = tempBound[1];
					int posArr = 2;
					for(int[] tempOrf : tempOrfs){
						tempComp[posArr++] = tempOrf[0];
						tempComp[posArr++] = tempOrf[1];
					}
					connectedComp.add(tempComp);
					
					// newly initialize
					
					tempBound = new int[2];
					tempOrfs = new Vector<int[]>();
					tempBound[0] = orf[0];
					tempBound[1] = orf[1];
					tempOrfs.add(orf);
				}
			}
		}
		
		int[] tempComp = new int[((tempOrfs.size()*2)+2)];
		tempComp[0] = tempBound[0];
		tempComp[1] = tempBound[1];
		int posArr = 2;
		for(int[] tempOrf : tempOrfs){
			tempComp[posArr++] = tempOrf[0];
			tempComp[posArr++] = tempOrf[1];
		}
		connectedComp.add(tempComp);
		return connectedComp;
	}
}