package geneFinder;

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

/**
 * contains methods to correctly merge two extracted raw clusters
 * 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 MergeClusters {

	
	/*
	 * check if merging is necessary and grab pairs according to their exon length
	 */
	
	public static boolean checkIfNeedMerge(Gene cluster, Gene clusterBeforeInVec, Contig thisContig, StringBuffer contigSeq){
		
		boolean wantMerge = false;
		int tolerance = GeneFinder.interval;
		
		if(cluster.startPos <= (clusterBeforeInVec.stopPos+1+tolerance)){
			
			wantMerge = true;
			
		}else if(cluster.twinNode != null){
			
			if(cluster.twinNode.startPos <= (clusterBeforeInVec.stopPos+1+tolerance)){
				
				wantMerge = true;
				
			} else if(clusterBeforeInVec.twinNode != null && (cluster.twinNode.startPos <= (clusterBeforeInVec.twinNode.stopPos+1+tolerance))){
				
				wantMerge = true;
				
			}
			
		}else if(clusterBeforeInVec.twinNode != null && (cluster.startPos <= (clusterBeforeInVec.twinNode.stopPos+1+tolerance))){
			
			wantMerge = true;
		}
		
		if(wantMerge){
			
			// grab the right pair
			
			Gene firstClust = null;  // will be assigned accordingly
			Gene secondClust = null;
			
			if(cluster.twinNode != null && (!cluster.freeToResolve && !cluster.twinNode.freeToResolve)){
				if(cluster.isMergedTwin){
					firstClust = cluster;
				}else{
					firstClust = cluster.twinNode;
				}
			}else{
				firstClust = cluster;
			}
			
			if(clusterBeforeInVec.twinNode != null && (!clusterBeforeInVec.freeToResolve && !clusterBeforeInVec.twinNode.freeToResolve)){
				if(clusterBeforeInVec.isMergedTwin){
					secondClust = clusterBeforeInVec;
				}else{
					secondClust = clusterBeforeInVec.twinNode;
				}
			}else{
				secondClust = clusterBeforeInVec;
			}
			
			wantMerge = doOverlapMerge(firstClust, secondClust, thisContig, contigSeq);   // we took the largest possible exon length partners to perform the merge
		}
		
		return wantMerge;
	}
	
	/*
	 * when two clusters overlap after the start and stop codon determination, merge both
	 * new: do not pay attention to the direction, only grab the largest possible exon length!
	 * erase other twin
	 */
	
	public static boolean doOverlapMerge(Gene cluster, Gene clusterBeforeInVec, Contig thisContig, StringBuffer contigSeq){
		
		HelperFunctions_GeneSearch.addAssociatedRnas(cluster, clusterBeforeInVec.idTOassociatedRnas);
		
		HelperFunctions_GeneSearch.declareShared_AfterMerge(cluster,clusterBeforeInVec);
		
		if(clusterBeforeInVec.twinNode != null){
			
			for(String rnaKey : cluster.idTOassociatedRnas.keySet()){
				
				Rna rna = ((Rna) cluster.idTOassociatedRnas.get(rnaKey)[0]);
				
				if(rna.isSharedBy.contains(clusterBeforeInVec.twinNode.geneID)){
					
					rna.isSharedBy.removeElement(clusterBeforeInVec.twinNode.geneID);
					if(rna.isSharedBy.contains(cluster.geneID)){
						rna.isSharedBy.removeElement(cluster.geneID);
					}
				
				}			
			}
		}
		
		if(cluster.twinNode != null){
			for(String rnaKey : cluster.idTOassociatedRnas.keySet()){
				
				Rna rna = ((Rna) cluster.idTOassociatedRnas.get(rnaKey)[0]);
				
				if(rna.isSharedBy.contains(cluster.twinNode.geneID)){
					
					rna.isSharedBy.removeElement(cluster.twinNode.geneID);
					if(rna.isSharedBy.contains(cluster.geneID)){
						rna.isSharedBy.removeElement(cluster.geneID);
					}
				
				}			
			}
			
			cluster.twinNode = null;
			cluster.hadTwinBefore = true;
			cluster.isMergedTwin = true;
			cluster.freeToResolve = false;
		}
		
		cluster.possibleStarts_Forward = clusterBeforeInVec.possibleStarts_Forward;
		cluster.possibleStarts_Reverse = clusterBeforeInVec.possibleStarts_Reverse;
		cluster.startPos = clusterBeforeInVec.startPos;
		cluster.hasStop_temp = true;
		
		cluster.direcCounter[0] += clusterBeforeInVec.direcCounter[0];
		cluster.direcCounter[1] += clusterBeforeInVec.direcCounter[1];
		
		cluster.possibleIntrons.putAll(clusterBeforeInVec.possibleIntrons);
		cluster.possibleFussyExons.putAll(clusterBeforeInVec.possibleFussyExons);
		cluster.alternativeTranscripts.addAll(clusterBeforeInVec.alternativeTranscripts);
		cluster.intronEndsThatAreNotContinued.addAll(clusterBeforeInVec.intronEndsThatAreNotContinued);
		
		ExtractGeneCandidates.handleFrameSearchWithoutTwin(cluster, cluster.possibleStops_Forward[0], cluster.possibleStops_Reverse[0], contigSeq,true);
		
		if(GeneFinder.isProkaryote || GeneFinder.inprogeaCall){
			cluster.sequence = contigSeq.substring(cluster.startPos,cluster.stopPos+1);
		}		
	
		thisContig.allGenes.remove(thisContig.allGenes.size()-1);
		thisContig.allGenes.add(cluster);
		
		return true;
	}
	
	/*
	 * manages all the steps necessary to perform a merge of two clusters (inkl. twin)
	 */
	
	public static void mergeWithOneIncludeTwin(Gene cluster, Gene clusterBef, StringBuffer contigSeq,boolean freeToResolve, boolean isLeading, boolean hasStop){
		
		updateCluster_AfterMerging(cluster,clusterBef,cluster.stopPos-2,contigSeq);
		HelperFunctions_GeneSearch.declareShared_AfterMerge(cluster,clusterBef);		
		handleTwinsWhenMerge(cluster,clusterBef,freeToResolve,isLeading,hasStop);
	}
	
	/*
	 * after we merged the current cluster with the one before, perform the update stuff
	 * deleted: boolean overlaps
	 */
	
	public static void updateCluster_AfterMerging(Gene cluster, Gene clusterBef, int stopPos, StringBuffer contigSeq){
		
		HelperFunctions_GeneSearch.addAssociatedRnas(cluster, clusterBef.idTOassociatedRnas);
		
		cluster.possibleIntrons.putAll(clusterBef.possibleIntrons);
		cluster.possibleFussyExons.putAll(clusterBef.possibleFussyExons);
		cluster.alternativeTranscripts.addAll(clusterBef.alternativeTranscripts);
		cluster.intronEndsThatAreNotContinued.addAll(clusterBef.intronEndsThatAreNotContinued);
		
		if(GeneFinder.isProkaryote || GeneFinder.inprogeaCall){
			cluster.sequence = contigSeq.substring(clusterBef.startPos,stopPos + 3);
		}
		
		cluster.startPos = clusterBef.startPos;
		cluster.possibleStarts_Forward = clusterBef.possibleStarts_Forward;
		cluster.possibleStarts_Reverse = clusterBef.possibleStarts_Reverse;
		
		cluster.direcCounter[0] += clusterBef.direcCounter[0];
		cluster.direcCounter[1] += clusterBef.direcCounter[1];
		
		cluster.stopPos = stopPos+2;
		cluster.hasStop_temp = true;
		
	}
	
	/*
	 * when previous cluster and current cluster are merged, do the necessary updating stuff
	 */
	
	public static void handleTwinsWhenMerge(Gene cluster, Gene clusterBef, boolean isFreeToResolve, boolean clusterIsLeading, boolean hasStop){
		
		cluster.freeToResolve = isFreeToResolve;
		cluster.onRevStrand = clusterBef.onRevStrand;
		
		cluster.twinNode = clusterBef.twinNode;
		cluster.twinNode.twinNode = cluster;
		
		cluster.twinNode.freeToResolve = isFreeToResolve;
		
		cluster.hasStop_temp = hasStop;
		cluster.isMergedTwin = clusterIsLeading;
		cluster.twinNode.isMergedTwin = false;

		cluster.hadTwinBefore = true;
		cluster.twinNode.hadTwinBefore = true;
	}
	
	/*
	 * we have a leading twin, so find out which one and merge
	 * is necessary for the cluster extraction merge, if the cluster before has a twin and either we found no stop or a stop in both directions
	 */
	
	public static void findLeadingTwinAndMerge(Gene cluster, Gene clusterBef, boolean has_Stop, boolean overlaps, boolean overlapsTwin, int[] intronBef, int[] intronBef_twin, StringBuffer contigSeq){
		
		if(!clusterBef.freeToResolve && !clusterBef.twinNode.freeToResolve){
			if(clusterBef.isMergedTwin){
				// only merge with clusterBef
				mergeWithOneIncludeTwin(cluster,clusterBef,contigSeq,false,true,has_Stop);
				if(!overlaps){
					IntronExonSearch.checkIfIntronIsIncluded_AndAdd(cluster,intronBef);
				}
			}else if(clusterBef.twinNode.isMergedTwin){
				// only merge with twin									
				mergeWithOneIncludeTwin(cluster,clusterBef.twinNode,contigSeq,false,true,has_Stop);
				if(!overlapsTwin){
					IntronExonSearch.checkIfIntronIsIncluded_AndAdd(cluster,intronBef_twin);
				}
			}
		}else{
			if(overlaps){	
				mergeWithOneIncludeTwin(cluster,clusterBef,contigSeq,false,true,has_Stop);

			}else{									
				mergeWithOneIncludeTwin(cluster,clusterBef.twinNode,contigSeq,false,true,has_Stop);
			}
		}
	}
	
	/*
	 * we have to merge the current cluster that shall be extracted with both twins before
	 */
	
	public static void mergeClustWithBothBefs(Gene cluster, Gene clusterBef, StringBuffer contigSeq, boolean hasStop){
	
		clusterBef.twinNode.possibleIntrons.putAll(cluster.possibleIntrons);
		clusterBef.twinNode.possibleFussyExons.putAll(cluster.possibleFussyExons);
		clusterBef.twinNode.alternativeTranscripts.addAll(cluster.alternativeTranscripts);
		clusterBef.twinNode.intronEndsThatAreNotContinued.addAll(cluster.intronEndsThatAreNotContinued);
		clusterBef.twinNode.direcCounter[0] += cluster.direcCounter[0];
		clusterBef.twinNode.direcCounter[1] += cluster.direcCounter[1];
		
		HelperFunctions_GeneSearch.declareAsShared(cluster);
		HelperFunctions_GeneSearch.declareShared_For_FutureTwin(cluster, clusterBef.twinNode);

		HelperFunctions_GeneSearch.addAssociatedRnas(clusterBef.twinNode,cluster.idTOassociatedRnas);
		
		clusterBef.twinNode.possibleStops_Forward = cluster.possibleStops_Forward;
		clusterBef.twinNode.possibleStops_Reverse = cluster.possibleStops_Reverse;

		mergeWithOneIncludeTwin(cluster,clusterBef,contigSeq,true,false,hasStop);

		if(GeneFinder.isProkaryote || GeneFinder.inprogeaCall){
			cluster.twinNode.sequence = contigSeq.substring(cluster.twinNode.startPos,cluster.stopPos+1);
		}
									
		cluster.twinNode.stopPos = cluster.stopPos;

		cluster.twinNode.hasStop_temp = hasStop;
		
	}
}
