package geneFinder;

import java.util.*;

import types.*;

/**
 * as a final step, define alternative transcripts for all genes with contradicting splicing events
 * 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 DefineAlternativeTranscripts {
	
	
	/*
	 * go through all splice  sites, if a site has more than one intron or is fuzzy, then create a new transcript, defined by the intron
	 */
	
	public static boolean searchForTranscripts(Gene gene){
		
		boolean onlyTranscriptStarts = true;
		
		for(int split : gene.possibleIntrons.keySet()){
			
			if((gene.possibleIntrons.get(split)[0] != null) && ((Vector<int[]>) gene.possibleIntrons.get(split)[0]).size() > 1){
				
				if(firstTestIfEraseIntron(gene, split) > 1){
					
					for(int i = 0; i<((Vector<int[]>) gene.possibleIntrons.get(split)[0]).size(); ++i){						
						int[] intron = ((Vector<int[]>) gene.possibleIntrons.get(split)[0]).get(i);
						
						if(!findSuitableTranscriptPartner(gene, intron)){
							Vector<int[]> correspondingPartner = new Vector<int[]>();
							correspondingPartner.add(intron);						
							gene.alternativeTranscripts.add(new Object[] {gene.startPos, gene.startPos, gene.startPos, gene.startPos, gene.startPos, correspondingPartner});
							onlyTranscriptStarts = false;
						}
															
					}
				}
	
			}
		}
		
		return onlyTranscriptStarts;
	}
	
	/*
	 * before different transcripts are assigned, check if there are indeed more than 1 left
	 */
	
	public static int firstTestIfEraseIntron(Gene gene, int split){
		
		for(int i = ((Vector<int[]>) gene.possibleIntrons.get(split)[0]).size() - 1; i >= 0; i--){
			
			int[] intron = ((Vector<int[]>) gene.possibleIntrons.get(split)[0]).get(i);
			
			if(gene.eraseIntrons_temp.contains(intron)){		
				
				FindExonsOfGene.searchCorrespondingExons(gene, intron);
				((Vector<int[]>) gene.possibleIntrons.get(split)[0]).removeElementAt(i);
				((Vector<int[]>) gene.possibleIntrons.get(split)[1]).removeElementAt(i);
				
			}
			
		}
		
		return ((Vector<int[]>) gene.possibleIntrons.get(split)[0]).size();
	}
	
	/*
	 * we have an alternative splicing event, so now check whether we can assign it to an existing transcript
	 */
	
	public static boolean findSuitableTranscriptPartner(Gene gene, int[] intron){

		for(int posi = 0; posi < gene.alternativeTranscripts.size(); ++posi){
			
			Object[] transcriptPartner = gene.alternativeTranscripts.get(posi);
			
			if((((Integer) transcriptPartner[0]).intValue() < intron[0]) && (((Integer) transcriptPartner[1]).intValue() < intron[0])){
				boolean suitable = true;
				if(transcriptPartner.length >= 6){
					if((((Integer) transcriptPartner[4]).intValue() == -1) || ((transcriptPartner.length == 7) && (((Integer) transcriptPartner[6]).intValue() != 0))){  // because at the moment these ones are erased anyway or are indicating isoform ends
						suitable = false;
						break;
					}else{
						for(int[] partner : ( (Vector<int[]>) transcriptPartner[5])){
							if(!((partner[0] < intron[0]) && (partner[1] < intron[0]))){
								suitable = false;
								break;
							}
						}
					}
				}
				
				if(suitable){
					
					if(transcriptPartner.length >= 6){
						((Vector<int[]>) transcriptPartner[5]).add(intron);
					}else{
						Vector<int[]> partner_temp = new Vector<int[]>();
						partner_temp.add(intron);		
						if(transcriptPartner.length >= 5){
							gene.alternativeTranscripts.setElementAt(new Object[] {transcriptPartner[0], transcriptPartner[1], transcriptPartner[2], transcriptPartner[3], transcriptPartner[4], partner_temp},posi);
						}else{
							gene.alternativeTranscripts.setElementAt(new Object[] {transcriptPartner[0], transcriptPartner[1], transcriptPartner[2], transcriptPartner[3], transcriptPartner[0], partner_temp},posi);
						}
											
					}
					
					return true;
				}
			}
		}
		
		return false;
	}
	
	/*
	 * during output writing, it is important which exon has to be assigned to which transcript
	 */
	
	public static Object[] assignExonsToTranscripts(Gene gene, Object[] altTranscript){
		
		Vector<int[]> exons = new Vector<int[]>();
		
		int maxExonEnd = -1;
		int minExonStart = Integer.MAX_VALUE;
		
		if(((Integer) altTranscript[0]).intValue() == ((Integer) altTranscript[1]).intValue()){
			if(gene.onRevStrand && (((Integer) altTranscript[3]).intValue() != -1)){
				minExonStart = (Integer) altTranscript[3];
			}else if(!gene.onRevStrand && (((Integer)altTranscript[2]).intValue() != -1)){
				minExonStart = (Integer) altTranscript[2];
			}else if(((Integer) altTranscript[2] == -1) && (((Integer)altTranscript[3]).intValue() == -1)){
				minExonStart = (Integer) altTranscript[0];
			}else{
				minExonStart = Math.max(((Integer) altTranscript[2]).intValue(),((Integer) altTranscript[3]).intValue());
			}
			
			int intronEndForFirstExon = -1;
			boolean goOn = true;
			
			if(altTranscript.length == 6){
				intronEndForFirstExon = ((Integer) altTranscript[4]).intValue();
			}else if(altTranscript.length == 7){
				int valueSeven = ((Integer) altTranscript[6]);
				if(valueSeven == Integer.MAX_VALUE){
					maxExonEnd = ((Integer) altTranscript[4]).intValue() + GeneFinder.readLength;
					int[] newExon = {minExonStart,maxExonEnd};
					exons.add(newExon);
					goOn = false;
				}
			}
			
			if(goOn){

				boolean foundFirstExon = false;

				for(int[] exon : gene.exonsOfGene){
					if(exon[0] >= ((Integer)altTranscript[0]).intValue()){
						// is not contained in intron, so its part of this transcript

						if(checkCorrespondingPartner(altTranscript,exon)){
							if(exon[0] == intronEndForFirstExon){
								foundFirstExon = true;
								int startNew = (Integer)altTranscript[0];
								if(startNew != minExonStart){
									startNew = minExonStart;
								}
								int[] newExon = {startNew,exon[1]};
								if(FindExonsOfGene.checkIfExonIsContained(exons, newExon) == 0){
									exons.add(newExon);
									altTranscript = addExonAsFakeTranscriptPartner(altTranscript,newExon);
								}
								if(maxExonEnd < exon[1]){
									maxExonEnd = exon[1];
								}
							}else{
								if(FindExonsOfGene.checkIfExonIsContained(exons, exon) == 0){
									exons.add(exon);
									altTranscript = addExonAsFakeTranscriptPartner(altTranscript,exon);
								}

								if(maxExonEnd < exon[1]){
									maxExonEnd = exon[1];
								}
							}		
						}

					}
				}

				if(!foundFirstExon && altTranscript.length >= 6 && intronEndForFirstExon != -1){

					int startNew = (Integer)altTranscript[0];
					if(startNew != minExonStart){
						startNew = minExonStart;
					}

					int[] newExon = {startNew,intronEndForFirstExon};
					if(FindExonsOfGene.checkIfExonIsContained(exons, newExon) == 0){
						exons.add(newExon);	
						altTranscript = addExonAsFakeTranscriptPartner(altTranscript,newExon);
					}

					if(maxExonEnd <intronEndForFirstExon){
						maxExonEnd = intronEndForFirstExon;
					}
				}
			}
		}else{
			
			boolean exonAfterEnd = false;
			boolean haveIsoformEndFussy = false;
			if(altTranscript.length == 7){
				// this is a transcript because of a fussy exon, so treat first two positions not as an intron
				exons.add(new int[] {(Integer)altTranscript[0],((Integer)altTranscript[1])+1});
				if(maxExonEnd < (((Integer)altTranscript[1])+1)){
					maxExonEnd = ((Integer)altTranscript[1])+1;
				}
				if(minExonStart > ((Integer)altTranscript[0])){
					minExonStart = (Integer)altTranscript[0];
				}
				
				int endIso = (Integer) altTranscript[6];
				if(endIso == 1){
					haveIsoformEndFussy = true;
				}
			}
			
			for(int[] exon : gene.exonsOfGene){
				if(((exon[0] < ((Integer)altTranscript[0]).intValue()) && (exon[1] <= ((Integer)altTranscript[0]).intValue())) || (exon[0] >= ((Integer)altTranscript[1]).intValue() && exon[1] > ((Integer)altTranscript[1]).intValue())){
					// is not contained in intron, so its part of this transcript
					
					if((haveIsoformEndFussy && ((exon[0] >= ((Integer)altTranscript[1]).intValue() && exon[1] > ((Integer)altTranscript[1]).intValue())))){
						exonAfterEnd = true;
					}else{
						if(checkCorrespondingPartner(altTranscript,exon)){
							
							if(FindExonsOfGene.checkIfExonIsContained(exons, exon) == 0){
								exons.add(exon);
								altTranscript = addExonAsFakeTranscriptPartner(altTranscript,exon);
							}
							
							if(maxExonEnd < exon[1]){
								maxExonEnd = exon[1];
							}
							if(minExonStart > exon[0]){
								minExonStart = exon[0];
							}
							
						}		
					}
								
				}
			}
			
			if(haveIsoformEndFussy && !exonAfterEnd){
				//the last exon has to assigned stopPos of gene
				int[] thisExon = {(Integer)altTranscript[0],((Integer)altTranscript[1])+1};
				for(int[] exonTmp : exons){
					if((thisExon[0] == exonTmp[0]) && (thisExon[1] == exonTmp[1])){
						exons.removeElement(exonTmp);
						break;
					}
				}
				
				exons.add(new int[] {(Integer)altTranscript[0],gene.stopPos+1});
				maxExonEnd = gene.stopPos+1;
			}
			
			if((Integer) altTranscript[1] == Integer.MAX_VALUE){
				// this is a transcript end
				int start = (Integer)altTranscript[4];
				if(start == Integer.MIN_VALUE){
					start = gene.startPos;
					minExonStart = start;
				}else if(start < minExonStart){
					minExonStart = start;
				}
				
				exons.add(new int[] {start,((Integer)altTranscript[0])});
				maxExonEnd = ((Integer)altTranscript[0]);
			}
		}
		

		return new Object[] {exons,minExonStart,maxExonEnd};
	}
	
	/*
	 * the exon looks as if it can be assigned to the transcript, but check the corresponding partner alternatives first
	 */
	
	public static boolean checkCorrespondingPartner(Object[] transcriptAlt, int[] exon){
		
		if(transcriptAlt.length >= 6){
			for(int[] intron : ((Vector<int[]>)transcriptAlt[5])){
				if(!(((exon[1] <= intron[0]) || ((exon[1] > intron[1]) && (exon[0] >= intron[1]))) || ((exon[0] >= intron[1]) || ((exon[0] < intron[0]) && (exon[1] <= intron[0]))))){			
					return false;
				}
			}
		}
		
		return true;
	}
	
	/*
	 * when you add an exon, than make sure that no overlapping exon can be assigned to this transcript by adding it as a fake "transcript" partner
	 */
	
	public static Object[] addExonAsFakeTranscriptPartner(Object[] transcriptAlt, int[] exon){
		
		if(transcriptAlt.length >= 6){
			((Vector<int[]>) transcriptAlt[5]).add(exon);
		}else{
			Vector<int[]> partner_temp = new Vector<int[]>();
			partner_temp.add(exon);		
			if(transcriptAlt.length >= 5){
				transcriptAlt = new Object[] {transcriptAlt[0], transcriptAlt[1], transcriptAlt[2], transcriptAlt[3], transcriptAlt[4], partner_temp};
				
			}else{
				transcriptAlt = new Object[] {transcriptAlt[0], transcriptAlt[1], transcriptAlt[2], transcriptAlt[3], transcriptAlt[0], partner_temp};
				
			}
								
		}
		
		return transcriptAlt;
	}
	
	/*
	 * during final gene candidate extraction, check if there are alternative transcripts that are outside the extraction gene
	 * (might happen due to non supported large introns, where we first start a  new transcript but later define the end to be the last position of the "normal" transcript)
	 */
	
	public static void finalIni_alternativeStartsCheck(Gene gene){
		
		if(gene.alternativeTranscripts.size() > 0){
			for(int posi = gene.alternativeTranscripts.size()-1; posi >= 0; posi--){
				Object[] altTrans = gene.alternativeTranscripts.get(posi);
				if(((Integer)altTrans[0]) > gene.stopPos){
					gene.alternativeTranscripts.removeElementAt(posi);
				}else if((altTrans.length < 6) || ((altTrans.length >= 6) && (((Integer)altTrans[4]).intValue() == -1))){
					gene.alternativeTranscripts.removeElementAt(posi);
				}
			}
		}
	}
	
	/*
	 * final check if by chance we included to identical transcripts
	 */
	
	public static void eraseEqualTranscripts(Gene gene){
		
		Vector<Integer> similarTranscripts = new Vector<Integer>();
		
		for(int i = 0;i<gene.alternativeTranscripts.size()-1;++i){
			
			Object[] alt_i = gene.alternativeTranscripts.get(i);
			
			Vector<Integer> similarTranscripts_i = new Vector<Integer>();
			
			for(int j = i+1;j<gene.alternativeTranscripts.size();++j){
				
				Object[] alt_j = gene.alternativeTranscripts.get(j);
				
				int numSame = 0;
				if(alt_i.length == alt_j.length){
					for(int pos = 0; pos<alt_i.length;++pos){
						if(pos != 5){
							if(((Integer)alt_i[pos]).intValue() == ((Integer)alt_j[pos]).intValue()){
								numSame++;
							}
						}else{
							int samePartner = 0;
							for(int[] exon : ((Vector<int[]>) alt_i[pos])){
								for(int[] exon_j : ((Vector<int[]>) alt_j[pos])){
									if((exon[0] == exon_j[0]) && (exon[1] == exon_j[1])){
										samePartner++;
										break;
									}
								}
							}
							
							if((samePartner == ((Vector<int[]>) alt_i[pos]).size()) && (samePartner == ((Vector<int[]>) alt_j[pos]).size())){
								numSame++;
							}
						}
					}
				}
				
				if((alt_i.length == alt_j.length) && (numSame == alt_i.length )){
					similarTranscripts_i.add(j);
				}
			}
			
			if(!similarTranscripts_i.isEmpty()){
				for(int posSim : similarTranscripts_i){
					if(!similarTranscripts.contains(posSim)){
						similarTranscripts.add(posSim);
					}
				}
			}
		}
		
		if(!similarTranscripts.isEmpty()){
			for(int i = gene.alternativeTranscripts.size()-1;i >= 0;i--){
				if(similarTranscripts.contains(i)){
					gene.alternativeTranscripts.removeElementAt(i);
					System.out.println("Removed twin transcript! Gene: " + gene.geneID);
				}
			}
		}
	}
}
