package geneFinder;

import types.*;
import java.util.*;

/**
 * for each gene all possible exons defined by alternative splicing are listed
* 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 FindExonsOfGene {

	/*
	 * method that goes through possibleIntron map of the gene and extracts all exons
	 * currentStart is initialized with startPos
	 */
	
	public static boolean findExonsForGene(Gene gene){
		
		if(gene.possibleIntrons.keySet().isEmpty()){
			// no splicing, whole gene is exon
			gene.exonsOfGene.add(new int[] {gene.startPos,gene.stopPos+1});
		}else{
			
			// first add the exon starting at the starting position
			
			int firstKey = gene.possibleIntrons.firstKey();
			
			gene.exonsOfGene.add(new int[] {gene.startPos,firstKey});
			
			if(((Integer) gene.possibleIntrons.get(firstKey)[2]) != -1){  // add the fussy exon	
				
				Vector<int[]> vecTemp = new Vector<int[]>();
				// note: the "-1" in [1] is necessary for transcript extraction, it is important to add it later!!!!!
				
				int endIndicator = 0;    // 0 if no end, 1 if it shall be a transcript end
				if(((Integer) gene.possibleIntrons.get(firstKey)[3]) != -2){
					endIndicator = 0;
				}else{
					// treat this fussy as a transcript end
					endIndicator = 1;
				}
				Object[] newFussy = {gene.startPos,((Integer) gene.possibleIntrons.get(firstKey)[2])-1,gene.startPos,gene.startPos,((Integer) gene.possibleIntrons.get(firstKey)[2]),vecTemp,endIndicator};
				if(!gene.alternativeTranscripts.isEmpty()){
					boolean doNotAdd = false;
					for(Object[] altTrans : gene.alternativeTranscripts){
						int start = (Integer) altTrans[0];
						int stop = (Integer) altTrans[1];
						
						if((altTrans.length >= 7) && (start == gene.startPos) && (stop == ((Integer) gene.possibleIntrons.get(firstKey)[2])-1)){
							doNotAdd = true;
							break;
						}
					}
					if(!doNotAdd){
						gene.alternativeTranscripts.add(newFussy);
					}
				}else{
					gene.alternativeTranscripts.add(newFussy);
				}
											
			}
			
			for(int key : gene.possibleIntrons.keySet()){
				
				boolean hadIntron = false;
				if(gene.possibleIntrons.get(key)[0] != null){
					
					Vector<int[]> exonSubSet = new Vector<int[]>();
					
					for(int[] intron : ((Vector<int[]>) gene.possibleIntrons.get(key)[0])){						
						hadIntron = true;
						if(!gene.intronEndsThatAreNotContinued.isEmpty()){
							boolean allowed = true;
							for(int[] forbiddenIntron : gene.intronEndsThatAreNotContinued){
								if((forbiddenIntron[0] == intron[0]) && (forbiddenIntron[1] == intron[1])){
									allowed = false;
									break;
								}else if((forbiddenIntron[0] != intron[0]) && (forbiddenIntron[1] == intron[1])){
									// another intron that has the same ending, could be a second transcript, so make a new one or first try to merge it some other alternative
									allowed = false;
									if(!gene.intronEndsThatAreNotContinued.contains(intron)){
										gene.intronEndsThatAreNotContinued.add(intron);
										
										Vector<int[]> vecTemp = new Vector<int[]>();
										vecTemp.add(intron);
										int begin = searchTranscriptWithCorrectEnd(gene,forbiddenIntron);
										if(begin == -1){
											System.err.println("Intron end not contained");
										}else{
											gene.alternativeTranscripts.add(new Object[] {begin,Integer.MAX_VALUE,-1,-1,intron[1],vecTemp});
										}	
									}
										
									break;
								}
							}
							if(allowed){
								exonSubSet = exonStopSearch(gene, key, intron[1], exonSubSet);
							}
						}else{
							exonSubSet = exonStopSearch(gene, key, intron[1], exonSubSet);
						}
								
					}
					if(((Vector<int[]>)gene.possibleIntrons.get(key)[0]).size() > ((double)(Math.pow(GeneFinder.minCoverage,2))/(double)GeneFinder.spliceLim)){
						testExonsIfLargeIntronKey(gene, key,exonSubSet);
					}
					
					
				}				
				
				if(!hadIntron){
					// this only marks an exon end point, so grab the next start				
					int nextStart = ((Integer) gene.possibleIntrons.get(key)[3]);  // very important that [2] is -1!
					if(nextStart >= 0){
						// this is the next start
						Vector<int[]> exonSub = new Vector<int[]>();
						exonStopSearch(gene, key, nextStart, exonSub);
						
					}
				}
			}
		}
		
		if(gene.exonsOfGene.isEmpty()){
			return false;
		}
		
		return true;
	
	}
	
	/*
	 * for each intron end, search for the next starting intron and extract the exon in between
	 */
	
	public static Vector<int[]> exonStopSearch(Gene gene, int key, int intronEnd, Vector<int[]> exonSubSet){
		
		if(gene.possibleIntrons.higherKey(key) != null){
			
			int higherKey = gene.possibleIntrons.higherKey(key);
			boolean foundEnd = false;
			
			do{					
				if(higherKey > intronEnd){
					int[] newExon = {intronEnd,higherKey};
					if(checkIfExonIsContained(gene.exonsOfGene, newExon) == 0){
						gene.exonsOfGene.add(newExon);	
						exonSubSet.add(newExon);
					}
					
					if(((Integer) gene.possibleIntrons.get(higherKey)[2]) != -1){  // add the fussy exon	
					
						int endIndicator = 0;    // 0 if no end, 1 if it shall be a transcript end
						if(((Integer) gene.possibleIntrons.get(higherKey)[3]) != -2){
							endIndicator = 0;
						}else{
							// treat this fussy as a transcript end
							endIndicator = 1;
						}
						
						Vector<int[]> vecTemp = new Vector<int[]>();
						Object[] newFussy = {intronEnd,((Integer) gene.possibleIntrons.get(higherKey)[2])-1,intronEnd,intronEnd,((Integer) gene.possibleIntrons.get(higherKey)[2]),vecTemp,endIndicator};
						
						if(!gene.alternativeTranscripts.isEmpty()){
							boolean doNotAdd = false;
							for(Object[] altTrans : gene.alternativeTranscripts){
								int start = (Integer) altTrans[0];
								int stop = (Integer) altTrans[1];
								
								if((altTrans.length >= 7) && (start == intronEnd) && (stop == ((Integer) gene.possibleIntrons.get(higherKey)[2])-1)){
									doNotAdd = true;
									break;
								}
							}
							if(!doNotAdd){
								gene.alternativeTranscripts.add(newFussy);
							}
						}else{
							gene.alternativeTranscripts.add(newFussy);
						}
													
					}
					foundEnd = true;
					break;
				}
				
				if(gene.possibleIntrons.higherKey(higherKey) == null){
					break;
				}else{
					higherKey = gene.possibleIntrons.higherKey(higherKey);
				}
			}while(!foundEnd);
			
			if(!foundEnd){
				int[] newExon = {intronEnd,(gene.stopPos+1)};
				if(checkIfExonIsContained(gene.exonsOfGene, newExon) == 0){
					gene.exonsOfGene.add(newExon);	
					exonSubSet.add(newExon);
				}
				
			}
			
		}else{
			int[] newExon = {intronEnd,(gene.stopPos+1)};
			if(checkIfExonIsContained(gene.exonsOfGene, newExon) == 0){
				gene.exonsOfGene.add(newExon);	
				exonSubSet.add(newExon);
			}
			
		}
		
		return exonSubSet;
	}

	
	/*
	 * check if the exon is already contained
	 */
	
	public static int checkIfExonIsContained(Vector<int[]> exonsOld, int[] newExon){
		
		for(int[] exon : exonsOld){
			if((exon[0] == newExon[0]) && (exon[1] == newExon[1])){
				return 1;
			}
		}
		
		return 0;
	}
	
	/*
	 * if a splice site has an unusually large number of introns, check if they can be condensed 
	 */
	
	public static void testExonsIfLargeIntronKey(Gene gene, int key, Vector<int[]> exonSubSet){
		
		HashMap<Integer,Vector<int[]>> endPosiToNum = new HashMap<Integer,Vector<int[]>>();
		for(int[] exon : exonSubSet){
			if(endPosiToNum.containsKey(exon[1])){
				endPosiToNum.get(exon[1]).add(exon);
			}else{
				Vector<int[]> vecTemp = new Vector<int[]>();
				vecTemp.add(exon);
				endPosiToNum.put(exon[1],vecTemp);
			}
		}
		
		for(int posi : endPosiToNum.keySet()){
			if(endPosiToNum.get(posi).size() > ((double)(Math.pow(GeneFinder.minCoverage,2))/(double)GeneFinder.spliceLim)){
				// condense to the smallest exon
				int maxStart = -1;
				for(int i = 0; i< endPosiToNum.get(posi).size();++i){
					int[] exon =  endPosiToNum.get(posi).get(i);
					if(exon[0] >= maxStart){
						maxStart = exon[0];
					}
				}
				
				gene.exonsOfGene.removeAll(endPosiToNum.get(posi));
				gene.exonsOfGene.add(new int[] {maxStart,posi});
				
				for(int j = ((Vector<int[]>)gene.possibleIntrons.get(key)[0]).size()-1;j >= 0; j--){
					int[] intron = ((Vector<int[]>)gene.possibleIntrons.get(key)[0]).get(j);
					if(intron[1] < maxStart){
						((Vector<int[]>)gene.possibleIntrons.get(key)[0]).removeElement(intron);
						((Vector<Vector<Rna>>)gene.possibleIntrons.get(key)[1]).removeElementAt(j);   //TODO: these rnas also have to be removed from the gene!!
					}
				}
			}
		}
		
	}
	
	/*
	 * when an intron has been declared as non supported, erase the corresponding exons if the do not have support of other introns
	 */
	
	public static void searchCorrespondingExons(Gene gene, int[] intron){
		
		for(int pos = gene.exonsOfGene.size() -1; pos >= 0; pos--){
			
			int[] exon =  gene.exonsOfGene.get(pos);
			if(exon[0] == intron[1]){			
				if(!searchOtherIntronSupport(gene,intron)){
					gene.exonsOfGene.removeElement(exon);
				}
			}
			
		}
	}
	
	/*
	 * if one exon is to be erased, first search for other intron supporting this exon
	 */
	
	public static boolean searchOtherIntronSupport(Gene gene, int[] intron){
		
		for(int key : gene.possibleIntrons.keySet()){
			if((key != intron[0]) && gene.possibleIntrons.get(key)[0] != null){
				for(int[] intronOfKey : (Vector<int[]>) gene.possibleIntrons.get(key)[0]){
					if((intronOfKey[1] == intron[1]) && (intronOfKey != intron)){
						return true;  // found other support
					}
				}
			}
		}
		
		return false;
	}
	
	/*
	 * sort exons via bubblesort
	 */
	
	public static void sortExons(Gene gene){
		
		int[] temp;
		for(int i=1; i<gene.exonsOfGene.size(); ++i) {
			for(int j=0; j<gene.exonsOfGene.size()-i; ++j) {
					if((gene.exonsOfGene.get(j)[0]) > (gene.exonsOfGene.get(j+1)[0])) {
						temp=gene.exonsOfGene.get(j);
						gene.exonsOfGene.setElementAt(gene.exonsOfGene.get(j+1),j);
						gene.exonsOfGene.setElementAt(temp,j+1);
					}
					
				}
			}	
	}
	
	/*
	 * sort exons in a vector via bubblesort
	 */
	
	public static Vector<int[]> sortExonsInVector(Vector<int[]> exonVec){
		
		int[] temp;
		for(int i=1; i<exonVec.size(); ++i) {
			for(int j=0; j<exonVec.size()-i; ++j) {
					if((exonVec.get(j)[0]) > (exonVec.get(j+1)[0])) {
						temp=exonVec.get(j);
						exonVec.setElementAt(exonVec.get(j+1),j);
						exonVec.setElementAt(temp,j+1);
					}
					
				}
			}
		
		return exonVec;
	}
	
	/*
	 * checks if the given exon set is already present from previous isoforms
	 */
	
	public static boolean checkIfExonsAlreadyPresent(Vector<int[]> exonsOfTranscript, Vector<Vector<int[]>> allTranscriptVecs){
		
		boolean isContained = false;
		
		for(Vector<int[]> formerSet : allTranscriptVecs){
			int counterSame = 0;			
			if(formerSet.size() == exonsOfTranscript.size()){
				for(int posi = 0; posi < formerSet.size(); ++posi){
					if((formerSet.get(posi)[0] == exonsOfTranscript.get(posi)[0]) && (formerSet.get(posi)[1] == exonsOfTranscript.get(posi)[1])){
						// same exon
						counterSame++;
					}else{
						break;
					}
				}
			}
			
			if(counterSame == exonsOfTranscript.size()){
				isContained = true;
				break;
			}
		}
		
		return isContained;
		
	}
	
	/*
	 * returns the correct value to generate the new alternative Transcripts
	 */
	
	public static int searchTranscriptWithCorrectEnd(Gene gene, int[] intron){
		
		int end = -1;
		
		for(Object[] altTrans : gene.alternativeTranscripts){
			if((((Integer)altTrans[1]).intValue() == Integer.MAX_VALUE) && (((Integer)altTrans[4]).intValue() == intron[1])){
				return ((Integer)altTrans[0]).intValue();
			}
		}
		
		return end;
	}
}
