package geneFinder;

import java.util.HashMap;
import java.util.TreeMap;
import java.util.Vector;

import types.Contig;
import types.Gene;
import types.Rna;

/**
 * contains methods for various kinds of tasks required during cluster extraction, intron and exon search and frame determination
 * 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 HelperFunctions_GeneSearch {

	/*
	 *  if we do not take into account ambiguous hits, remove them from mapping rnas
	 * 
	 */
	
	public static void removeAmbiHits(Contig thisContig){
		
		for(int currentPos : thisContig.positionTOmappingRnas.keySet()){
			
			Vector<Rna> rnaWithoutMulti = new Vector<Rna>();
			for(Rna rna : thisContig.positionTOmappingRnas.get(currentPos)){
				if(rna.isMulti == 0){
					rnaWithoutMulti.add(rna);
				}else{
					// search splice sites and remove support, go through all info entries in case this read has mapped more than once on the same position
					for(Object[] info : rna.contigsMappedOn){
						if(((Integer) info[1] == currentPos)  && (((Contig) info[0]).equals(thisContig))){
							if(info[4] != null){
								int splitStart = ((int[]) info[4])[1];
								if(thisContig.splicePositions.containsKey(splitStart)){
									thisContig.splicePositions.put(splitStart,thisContig.splicePositions.get(splitStart)-1);
								}
								
							}
						}
					}
				}
			}
			
			thisContig.positionTOmappingRnas.put(currentPos,rnaWithoutMulti);
		}
		
	}
	
	/*
	 * remove all hits from posisitions they are not any longer assigned to
	 */
	
	public static void removeReassignedHits(Contig thisContig){
		
		for(int currentPos : thisContig.positionTOmappingRnas.keySet()){
			
			Vector<Rna> rnaWithoutMulti = new Vector<Rna>();
			
			for(Rna rna : thisContig.positionTOmappingRnas.get(currentPos)){
				if(rna.isMulti == 2){
					if(rna.contigsMappedOn.size() > 0){
							Object[] info = rna.contigsMappedOn.get(0);
							
							if(((Integer)info[1]).intValue() == currentPos){
								rnaWithoutMulti.add(rna);
							}
						}						
				}else{
					rnaWithoutMulti.add(rna);
				}
			}
			
			thisContig.positionTOmappingRnas.put(currentPos,rnaWithoutMulti);
		}
		
	}
	
	/*
	 * this method updates the posiCovMap that stores coverage-Add-Vectors for intron-starts and endings 
	 * here we subtract 1 for each covered position, because if we call this method the read has been erased 
	 */
	
	public static TreeMap<Integer,Vector<Integer>> updatePosiCovMap_AfterSpliceSwitch(TreeMap<Integer,Vector<Integer>> posiCovMap, int posi, int lengthThatIsLeft){
		
		if(posiCovMap.containsKey(posi)){
			
			for(int i = lengthThatIsLeft - 1; i >= 0; --i){ 
				if(posiCovMap.get(posi).size() > i){
					int oldCov = posiCovMap.get(posi).get(i);
					
					if(oldCov == 1){
						posiCovMap.get(posi).removeElementAt(i);
					}else{
						posiCovMap.get(posi).setElementAt((oldCov - 1),i);
					}
										
				}
			}
			
			if(posiCovMap.get(posi).size() == 0){
				posiCovMap.remove(posi);
			}
			
		}
		
		return posiCovMap;
	}
	
	
	/*
	 * this method updates the posiCovMap that stores coverage-Add-Vectors for intron-starts and endings 
	 */
	
	public static TreeMap<Integer,Vector<Integer>> updatePosiCovMap(TreeMap<Integer,Vector<Integer>> posiCovMap, int posi, int lengthThatIsLeft){
		
		if(posiCovMap.containsKey(posi)){
			
			for(int i = 0; i < lengthThatIsLeft; ++i){
				if(posiCovMap.get(posi).size() > i){
					int oldCov = posiCovMap.get(posi).get(i);
					posiCovMap.get(posi).setElementAt((oldCov + 1),i);
				}else{
					posiCovMap.get(posi).add(1);
				}
			}
			
		}else{
			Vector<Integer> vec_tmp = new Vector<Integer>();
			for(int i = 0; i < lengthThatIsLeft; ++i){
				vec_tmp.add(1);
			}
			posiCovMap.put(posi,vec_tmp);
		}
		
		return posiCovMap;
	}
	
	
	/*
	 * help function to update the original coverageVec according to entries stored in posiCovMap
	 */
	
	public static Object[] lookIntoPosiCovMap(TreeMap<Integer,Vector<Integer>> posiCovMap, int currentPos){
		
		Vector<Integer> covVecClone = new Vector<Integer>();    // stores all coverage add values derived by posiCovMap
		
		for(int arrPos=0;arrPos<GeneFinder.readLength;++arrPos){
			covVecClone.add(0);
		}
		
		boolean addedValues = false;
		
		//Vector<Integer> visitedPositions = new Vector<Integer>();   // positions that have been visited can be removed in map!
		
		if(posiCovMap.keySet().size() != 0){
			for(int position : posiCovMap.keySet()){
				if(Math.abs((position-currentPos)) <= GeneFinder.readLength){
					// add this coverages to cov vec
					Vector<Integer> tmpCovs = posiCovMap.get(position);
						
						if(position <= currentPos){
							int posInClone = 0;
							for(int posTmp = (currentPos - position); posTmp < tmpCovs.size(); ++posTmp){
								
								if(tmpCovs.get(posTmp) > 0){
									addedValues = true;
								}
								covVecClone.setElementAt((covVecClone.get(posInClone) + tmpCovs.get(posTmp)),posInClone++);
								posiCovMap.get(position).setElementAt(0,posTmp);
							}
						}else{
							int posInClone = (position - currentPos);
							int threshold = Math.min(tmpCovs.size(), (GeneFinder.readLength - (position - currentPos)));
							for(int posTmp = 0; posTmp < threshold; ++posTmp){
								if(tmpCovs.get(posTmp) > 0){
									addedValues = true;
								}
								covVecClone.setElementAt((covVecClone.get(posInClone) + tmpCovs.get(posTmp)),posInClone++);
								posiCovMap.get(position).setElementAt(0,posTmp);
							}
						}
					
				}else if(position > (currentPos + GeneFinder.readLength)){
					break;  
				}

			}
		}
		
		Object[] returnObject = {addedValues,covVecClone, posiCovMap};
		
		return returnObject;
	}
	
	/*
	 * method that rnas to map
	 */
	
	public static void addRnasFromVector(Gene cluster, Vector<Rna> newRnas){
		
		for(Rna potRna : newRnas){
			
			if(!cluster.idTOassociatedRnas.containsKey(potRna.rnaID)){
				cluster.idTOassociatedRnas.put(potRna.rnaID, new Object[] {potRna,new int[] {-1,-1},-1});
				potRna.assignedNum++;
			}else{
				cluster.moreThanOneHitRnas.add(potRna.rnaID);
			}
			
		}

	}
	
	/*
	 * method that rnas to map, without caring for duplications
	 */
	
	public static void addRnasFromVector_NotRespectDuplis(Gene cluster, Vector<Rna> newRnas){
		
		for(Rna potRna : newRnas){
						
			cluster.idTOassociatedRnas.put(potRna.rnaID, new Object[] {potRna,new int[] {-1,-1},-1});
						
		}

	}
	
	
	
	/*
	 * method to ensure that only reads that are not already included will be assigned to the vector
	 */
	
	public static void addAssociatedRnas(Gene cluster, HashMap<String, Object[]> newRnas){
		
		for(String potRna : newRnas.keySet()){
			if(!cluster.idTOassociatedRnas.containsKey(potRna)){
				cluster.idTOassociatedRnas.put(potRna, newRnas.get(potRna));
				((Rna) newRnas.get(potRna)[0]).assignedNum++;
			}else{
				cluster.moreThanOneHitRnas.add(potRna);
			}
		}

	}
	
	/*
	 * when we extract twin clusters, declare all their rnas as "shared"
	 */
	
	public static void declareAsShared(Gene cluster){
		
		for(String rnaKey : cluster.idTOassociatedRnas.keySet()){
			Rna rna = ((Rna) cluster.idTOassociatedRnas.get(rnaKey)[0]);
			rna.isSharedBy.add(cluster.geneID);
		}
	}
	
	/*
	 * when two clusters are merged, take care to correctly assign the shared rnas
	 */
	
	public static void declareShared_AfterMerge(Gene cluster, Gene clusterBef){
		
		for(String rnaKey : cluster.idTOassociatedRnas.keySet()){
			Rna rna = ((Rna) cluster.idTOassociatedRnas.get(rnaKey)[0]);
			if(rna.isSharedBy.contains(clusterBef.geneID)){
				
				rna.isSharedBy.removeElement(clusterBef.geneID);
				if(!rna.isSharedBy.contains(cluster.geneID)){
					rna.isSharedBy.add(cluster.geneID);
				}
			
			}			
		}
	}
	
	/*
	 * also update the shared rna information for the twin node
	 * note: the modulo operation tests whether this shared rna really is shared between cluster and twin or between cluster and another overlapping cluster
	 */
	
	public static void declareShared_For_FutureTwin(Gene cluster, Gene futureTwin){
		
		for(String rnaKey : cluster.idTOassociatedRnas.keySet()){
			Rna rna = ((Rna) cluster.idTOassociatedRnas.get(rnaKey)[0]);
			if(rna.isSharedBy.contains(cluster.geneID) && !rna.isSharedBy.contains(futureTwin.geneID)){			
				
				rna.isSharedBy.add(futureTwin.geneID);
				
			}			
		}
	}
	
	
	/*
	 * check whether there exists an intron end near nextPos, such that we can extract the number of bases, that define the overlapping region with nextPos
	 */
	
	public static Integer findIntronNearNextPos(Gene cluster, int nextPos){
		
		int posiCovPosition = -1;
		for(int key : cluster.possibleIntrons.keySet()){
			if(cluster.possibleIntrons.get(key)[0] != null){
				for(int[] intron : ( (Vector<int[]>) cluster.possibleIntrons.get(key)[0])){
					if(Math.abs(nextPos - intron[1]) <= GeneFinder.readLength){
						posiCovPosition = intron[1];
						break;
					}
				}
			}
			if(posiCovPosition != -1){
				break;
			}
		}
		
		return posiCovPosition;
	}
	
	
	/*
	 * check if nextPos is within readLength range of an intronEnd (exceeding it) 
	 * also check if a splice site opens after transcript start, if yes, take what occurs first, split start or intron end
	 */
	
	public static int[] checkIfNextPosExceedsIntronEnd(Gene cluster, int nextPos, int transcriptMode){
		
		int minEndKey = Integer.MAX_VALUE;
		int minEndIntron = Integer.MAX_VALUE;
		int keyThreshold = Integer.MAX_VALUE;
		
		int inNormalTranscriptKey = 1;   // 1 indicates "yes", 0 indicates "no"
		int inNormalTranscriptIntron = 1;   // 1 indicates "yes", 0 indicates "no"
		
		if(transcriptMode != -1){
			keyThreshold = transcriptMode;
		}
		
		for(int key : cluster.possibleIntrons.keySet()){
			if(cluster.possibleIntrons.get(key)[0] != null){ //&& key<=keyThreshold){  tryOut
				for(int[] intron : ( (Vector<int[]>) cluster.possibleIntrons.get(key)[0])){
					if((key >= transcriptMode) && (transcriptMode != -1)){
						if(key<minEndKey){
							minEndKey = key;
							if(key >= keyThreshold){
								inNormalTranscriptKey = 0;
							}
						}
						
					}
					if((nextPos - intron[1] <= GeneFinder.readLength) && (nextPos >= intron[1])){
						if(intron[1] < minEndIntron){
							minEndIntron = intron[1];
							if(key >= keyThreshold){
								inNormalTranscriptIntron = 0;
							}
						}
						
					}
				}
				
			}
			
		}
		if((minEndKey != Integer.MAX_VALUE) || (minEndIntron != Integer.MAX_VALUE)){
			if(minEndKey <= minEndIntron){
				return new int[] {minEndKey,inNormalTranscriptKey};
			}else{
				return new int[] {minEndIntron,inNormalTranscriptIntron};
			}
		}
		
		return new int[] {-1,0};
	}
	
	/*
	 * check if nextPos is within readLength range of an intronEnd (exceeding it) of a previous transcript
	 * also check if a splice site opens after transcript start, if yes, take what occurs first, split start or intron end
	 */
	
	public static int[] checkIfNextPosMergesWithPreviousIsoform(Gene cluster, int nextPos, int transcriptMode){
		
		int minEndKey = Integer.MAX_VALUE;
		int minEndIntron = Integer.MAX_VALUE;
		int keyThreshold = Integer.MAX_VALUE;
		
		int inNormalTranscriptKey = 1;   // 1 indicates "yes", 0 indicates "no"
		int inNormalTranscriptIntron = 1;   // 1 indicates "yes", 0 indicates "no"
		
		if(transcriptMode != -1){
			keyThreshold = transcriptMode;
		}
		
		for(int key : cluster.possibleIntrons.keySet()){
			if(cluster.possibleIntrons.get(key)[0] != null){ //&& key<=keyThreshold){  tryOut
				for(int[] intron : ( (Vector<int[]>) cluster.possibleIntrons.get(key)[0])){
					if((key >= transcriptMode) && (transcriptMode != -1)){
						if(key<minEndKey){
							minEndKey = key;
							if(key >= keyThreshold){
								inNormalTranscriptKey = 0;
							}
						}
						
					}
					if((nextPos - intron[1] <= GeneFinder.readLength) && (nextPos >= intron[1])){
						if(intron[1] < minEndIntron){
							minEndIntron = intron[1];
							if(key >= keyThreshold){
								inNormalTranscriptIntron = 0;
							}
						}
						
					}
				}
				
			}
			
		}
		if((minEndKey != Integer.MAX_VALUE) || (minEndIntron != Integer.MAX_VALUE)){
			
			if(inNormalTranscriptKey == 1 || inNormalTranscriptIntron == 1){
				if(inNormalTranscriptKey == 1 && inNormalTranscriptIntron == 1){
					if(minEndKey <= minEndIntron){
						return new int[] {minEndKey,inNormalTranscriptKey};
					}else{
						return new int[] {minEndIntron,inNormalTranscriptIntron};
					}
				}else{
					if(inNormalTranscriptKey == 1 && (minEndKey != Integer.MAX_VALUE)){
						return new int[] {minEndKey,inNormalTranscriptKey};
					}else if((minEndIntron != Integer.MAX_VALUE)){
						return new int[] {minEndIntron,inNormalTranscriptIntron};
					}
				}
			}else{
				if(minEndKey <= minEndIntron){
					return new int[] {minEndKey,inNormalTranscriptKey};
				}else{
					return new int[] {minEndIntron,inNormalTranscriptIntron};
				}
			}
		}
		
		return new int[] {-1,0};
	}
	
	/*
	 * check if the intron end leads to a previous transcript
	 */
	
	public static int[] checkIfIntronEndsInNormalTranscript(Gene cluster, int intronEnd, int transcriptMode){
		
		int minEndIntron = Integer.MAX_VALUE;
		int keyThreshold = Integer.MAX_VALUE;
		
		int inNormalTranscriptIntron = 1;   // 1 indicates "yes", 0 indicates "no"
		
		if(transcriptMode != -1){
			keyThreshold = transcriptMode;
		}
		
		for(int key : cluster.possibleIntrons.keySet()){
			if(cluster.possibleIntrons.get(key)[0] != null){
				for(int[] intron : ( (Vector<int[]>) cluster.possibleIntrons.get(key)[0])){
					
					if((Math.abs(intronEnd - intron[1]) <= GeneFinder.readLength)){
						if(intron[1] < minEndIntron){
							minEndIntron = intron[1];
							if(key >= keyThreshold){
								inNormalTranscriptIntron = 0;
							}
						}
						
					}
				}
				
			}
			
		}
		
		if(minEndIntron != Integer.MAX_VALUE){
			return new int[] {minEndIntron,inNormalTranscriptIntron};
		}
		
		return new int[] {-1,0};
	}
	
	
	
	/*
	 * find the intron that defined currentCompete
	 * return 1 means we go on with the gene
	 */
	
	public static int findIntronOfCurrentCompete(Gene gene, int currentCompete){
		
		for(int key : gene.possibleIntrons.keySet()){
			if(gene.possibleIntrons.get(key)[0] != null){
				for(int[] intron : ((Vector<int[]>)gene.possibleIntrons.get(key)[0])){
					if(intron[1] == currentCompete){
						// found intron
						if(((Vector<int[]>)gene.possibleIntrons.get(key)[0]).size() == 1){
							return 1;
						}else{
							return -1;
						}
					}
				}
			}
		}
		
		return 1;
	}
	
	/*
	 * test if also other splits support the currentCompete, such that it is better to goOn and leave candidate open
	 */
	
	public static boolean checkForOtherSupport(Gene gene, int currentCompete, int nextPos){
		
		int numBiggerThanNextPos = 0;
		
		for(int key : gene.possibleIntrons.keySet()){
			if(gene.possibleIntrons.get(key)[0] != null){
				for(int[] intron : ((Vector<int[]>)gene.possibleIntrons.get(key)[0])){
					if((intron[1] == currentCompete) || (intron[1] > nextPos)){
						// found intron
						numBiggerThanNextPos++;
					}
				}
			}
		}
		if(numBiggerThanNextPos >= 2){
			return true;
		}
		return false;
	}
	
	/*
	 * add rnas from the interval start-currentPos
	 */
	
	public static void addRnasFromInterval(Gene cluster, Contig thisContig, int currentPos, int formerPosi){
		
		int pos_temp = formerPosi;

		if(pos_temp != -1){
			do{

				HelperFunctions_GeneSearch.addRnasFromVector(cluster,thisContig.positionTOmappingRnas.get(pos_temp));

				if(thisContig.positionTOmappingRnas.higherKey(pos_temp) != null){
					pos_temp = thisContig.positionTOmappingRnas.higherKey(pos_temp);
				}else{
					pos_temp = currentPos;
				}
			}while((pos_temp != currentPos) && !(pos_temp > currentPos));
		}		
	}
	
	/*
	 * add rnas from the interval start-currentPos not respecting duplication
	 */
	
	public static void addRnasFromInterval_notRespectDuplis(Gene cluster, Contig thisContig, int currentPos, int formerPosi){
		
		int pos_temp = formerPosi;

		if(pos_temp != -1){
			do{

				HelperFunctions_GeneSearch.addRnasFromVector_NotRespectDuplis(cluster,thisContig.positionTOmappingRnas.get(pos_temp));

				if(thisContig.positionTOmappingRnas.higherKey(pos_temp) != null){
					pos_temp = thisContig.positionTOmappingRnas.higherKey(pos_temp);
				}else{
					pos_temp = currentPos;
				}
			}while((pos_temp != currentPos) && !(pos_temp > currentPos));
		}		
	}
}
