package geneFinder;

/**
 * identify the set of ORFs optimizing the alignment scoring metric
 * Copyright (c) 2013,
 * Franziska Zickmann, 
 * ZickmannF@rki.de, Robert Koch-Institute, Berlin, Germany
 * Distributed under the GNU Lesser General Public License, version 3.0
 *
 */

import ilog.concert.IloException;
import ilog.cplex.IloCplex;

import java.io.*;
import java.util.Vector;

public class Operon_LP {


	public static Object[] writeOperon_LP(String inputSeq, int[] cov, Vector<int[]> allORFs, int maxLength, Vector<Integer> existingORFs){

		/*int[] covAll = new int[inputSeq.length()];
		covAll = calculateCoverage(allORFs, covAll);
		int[] covExist = new int[inputSeq.length()];
		
		Vector<int[]> tempExist = new Vector<int[]>();
		for(int i = 0; i<allORFs.size();++i){
			if(existingORFs != null && existingORFs.contains(i)){
				tempExist.add(allORFs.get(i));
			}
		}
		covExist = calculateCoverage(tempExist, covExist);*/
		
		int maxLengthOrf = 0;
		
		/*if(maxLength != -1){
			maxLengthOrf = maxLength;
		}else{*/
			for(int[] orf : allORFs){
				if(((orf[1]-orf[0]+1)) > maxLengthOrf){
					maxLengthOrf = orf[1]-orf[0]+1;
				}
			}
		//}

		StringBuffer binaryVars = new StringBuffer("");			// will include all variables

		boolean doNoOpti = false;
		int seqL = inputSeq.length();

		try {

			BufferedWriter bw = new BufferedWriter(new FileWriter(GeneFinder.pathOut+"input_operonLP.lp"));	// name of lp file

			bw.write("Maximize\n");

			StringBuffer targetFunction = new StringBuffer("");		// will include the target function

			// before write out, search overlaps

			int formerCov = cov[0];
			int beginPos = 0;
			
			int numZeros = 0;

			Vector<Vector<Integer>> overlaps = new Vector<Vector<Integer>>();

			for(int pos = 0; pos<cov.length;++pos){

				if(cov[pos] == 0){
					numZeros++;
				}
				
				if(formerCov != cov[pos]){
					// change, so check if write out necessary
					if(formerCov>1){
						Vector<Integer> temp = new Vector<Integer>();
						temp.add(beginPos);  
						temp.add(pos-1);		// inclusively
						temp.add(formerCov);

						overlaps.add(temp);

					}
					
					beginPos = pos;
					formerCov = cov[pos];
				}

			}

			StringBuffer match = new StringBuffer("");	
			StringBuffer openPenalty = new StringBuffer("");
			StringBuffer bounds = new StringBuffer("");
			
			int constNum = 0;

			for(int i = 0;i<allORFs.size();++i){
				targetFunction.append(" + x__"+i+ " + p__" + i + "\n");		
				binaryVars.append(" y__" + i + "\n");

				double matchScore = (double) (allORFs.get(i)[1]-allORFs.get(i)[0]+1.0);
				
				double penScore = ((double) seqL)/((double) (allORFs.get(i)[1]-allORFs.get(i)[0]+1.0)) * (((double) maxLengthOrf)/((double) (allORFs.get(i)[1]-allORFs.get(i)[0]+1.0)));
				
				match.append(" c" + (constNum++) + ": x__"+i+ " - " + matchScore + " y__" + i + " = 0 \n");
				
				if(existingORFs != null && existingORFs.contains(i)){
					match.append(" y__" + i + " = 1 \n");
				}
				
				if(penScore >= 0){
					openPenalty.append(" c" + (constNum++) + ": p__"+i+ " + " + penScore + " y__" + i + " = 0 \n");
				}else{
					penScore = -penScore;
					openPenalty.append(" c" + (constNum++) + ": p__"+i+ " - " + penScore + " y__" + i + " = 0 \n");
				}	
				
				bounds.append("-inf <= x__" + i + " <= +inf \n");
				bounds.append("-inf <= p__" + i + " <= +inf \n");
				
				if(overlaps.size() > 0){
					// search if this orf overlaps

					for(int pos = 0; pos < overlaps.size();++pos){
						int startO = overlaps.get(pos).get(0);
						int stopO = overlaps.get(pos).get(1);

						if(((startO >= allORFs.get(i)[0]) && (stopO <= allORFs.get(i)[1])) || ((startO <= allORFs.get(i)[1]) && (stopO >= allORFs.get(i)[1])) || ((startO <= allORFs.get(i)[0]) && (stopO >= allORFs.get(i)[0]))){
							// found overlap, so add position
							overlaps.get(pos).add(i);
						}
					}

				}
				
			}

			// check if overlap groups are correct
			
			if(overlaps.size() > 0){
				// search if this orf overlaps
				overlaps = checkOverlaps(overlaps,allORFs);
			}
			
			// now add all overlaps

			StringBuffer overlapPen = new StringBuffer("");	

			for(int pos = 0; pos < overlaps.size();++pos){
				
				targetFunction.append(" + ov__"+pos+ "\n");		// var is correlated to position in overlaps vector	
				
				double ovScore = (double) overlaps.get(pos).get(1) - overlaps.get(pos).get(0) + 1.0;
				
				/*if((covAll[overlaps.get(pos).get(0)] > covExist[overlaps.get(pos).get(0)]) && (covExist[overlaps.get(pos).get(0)] > 0)){
					ovScore = ovScore * 1.5;
				}*/
				
				// note: should be multiplied with -1.0, but instead we keep it positive for the lp format, to make ov_i negative
				StringBuffer orfNum = new StringBuffer("[ " + ovScore + " ");
				
				
				int numO = 0;
				if((overlaps.get(pos).size()-3) > 2){
					
					StringBuffer allPartOrfs = new StringBuffer("");
					
					for(int ovP = 3; ovP < overlaps.get(pos).size()-1;++ovP){
						for(int ovP_2 = ovP+1; ovP_2 < overlaps.get(pos).size();++ovP_2){
							orfNum.append("y__" + overlaps.get(pos).get(ovP) + " * y__" + overlaps.get(pos).get(ovP_2) + " ] <= 0");
							overlapPen.append(" c" + (constNum++) + ": ov__"+ pos + "__" + numO + " + " + orfNum.toString() + " \n");
							bounds.append("-inf <= ov__" + pos + "__" + numO + " <= 0 \n");
							
							allPartOrfs.append(" - " + "ov__" + pos + "__" + numO);
							numO++;
							orfNum = new StringBuffer("[ " + ovScore + " ");
						}
					}
					
					overlapPen.append(" c" + (constNum++) + ": ov__"+ pos + allPartOrfs.toString() + " <= 0 \n");
					bounds.append("-inf <= ov__" + pos + " <= 0 \n");
					
				}else{
					for(int orfs = 3;orfs < overlaps.get(pos).size();++orfs){
						orfNum.append("y__" + overlaps.get(pos).get(orfs) + " * ");
					}
					
					overlapPen.append(" c" + (constNum++) + ": ov__"+ pos + " + " + orfNum.substring(0,orfNum.length()-2) + "] <= 0 \n");
					bounds.append("-inf <= ov__" + pos + " <= 0 \n");
				}
				
				
			}

			if(allORFs.size() == 0){
				doNoOpti = true;
			}else{
				// write out the lp
				if(targetFunction.length() > 3){
					bw.write(targetFunction.substring(2));
					targetFunction = new StringBuffer("");
				}

				// now constraints

				bw.write("Subject To \n");

				bw.write(match.toString() + openPenalty.toString() + overlapPen.toString() + "\n");

				//now declare normal variables as binary

				bw.write("Bounds \n");
				
				bw.write(bounds.toString());
				
				bw.write("Binary \n");

				bw.write(binaryVars.toString());

				bw.write("END");				
			}

			bw.close();

		} catch (IOException e) {
			e.printStackTrace();
		}

		double score = -Double.MAX_VALUE;
		Vector<Integer> chosenORFs = new Vector<Integer>();
		
		if(!doNoOpti){  // now solve the lp

			if(GeneFinder.useCPLEX){  // use cplex

				solveOperonLPWithCPLEX();
			
				// parse solution file

				Object[] returnArr = parseOperonLP_CPLEX(allORFs,maxLengthOrf,seqL);
				score = (Double) returnArr[0];
				chosenORFs = (Vector<Integer>) returnArr[1];
				
			}
		}
		
		return new Object[] {score,chosenORFs,maxLengthOrf};

	}


	/*
	 * solves MaxFlow with the CPLEX optimizer
	 */

	public static void solveOperonLPWithCPLEX(){

		try {
			IloCplex cplex = new IloCplex();
			cplex.setOut(null);
			cplex.importModel(GeneFinder.pathOut+"input_operonLP.lp");
			cplex.setParam(IloCplex.IntParam.Threads,GeneFinder.numberThreads);

			cplex.solve();

			cplex.writeSolution(GeneFinder.pathOut+"solution_operonLP.sol");


		} catch (IloException e2) {
			e2.printStackTrace();
		}

	}

	/*
	 * method that parses the solution file of cplex
	 */

	public static Object[] parseOperonLP_CPLEX(Vector<int[]> allOrfs,int maxLengthOrf, int seqL){

		Vector<Integer> chosenOrfs = new Vector<Integer>();
		double objective = -Double.MAX_VALUE;
		
		try{
			BufferedReader br = new BufferedReader(new FileReader(GeneFinder.pathOut+"solution_operonLP.sol"));

			String line = "";

			while((line = br.readLine()) != null){    // go through file until we reach the part where we find the variables
				
				if(line.contains("objectiveValue")){
					String[] objectiveArr = line.split("\"");
					objective = Double.parseDouble(objectiveArr[1]);
				}
				
				if(line.contains("<variables>")){
					break;
				}
			}

			while(((line = br.readLine()) != null) && (!line.contains("</variables>"))){  // make sure to stop when variable part is over

				String[] lineSplit1 = line.split(" ");   // position 1 contains name, 3 contains value

				String[] valueSplit = lineSplit1[5].split("\"");  // to extract the value		

				if(line.contains("y__")){
					int score = 0;					

					if(!(valueSplit[1].equals("0") || valueSplit[1].equals("1"))){  // sometimes cplex does not round
						double sol = Double.parseDouble(valueSplit[1]);
						if(sol<=0.5){	// if 2 variables are both 0.5, then we cannot take both! so remove both because no real support!
							score = 0;
						}else{
							score = 1;
						}
					}else{
						score = Integer.parseInt(valueSplit[1]);  // directly grab the score, without rounding
					}
					
					if(score == 1){
						String[] name = lineSplit1[3].split("__");
						String[] name2 = name[1].split("\"");
						int pos = Integer.parseInt(name2[0]);
						chosenOrfs.add(pos);
						
						// recompute penalty
						
						double penOld = ((double) seqL)/((double) (allOrfs.get(pos)[1]-allOrfs.get(pos)[0]+1.0)) * (((double) maxLengthOrf)/((double) (allOrfs.get(pos)[1]-allOrfs.get(pos)[0]+1.0)));
						double penNew = ((double) seqL)/((double) (allOrfs.get(pos)[1]-allOrfs.get(pos)[0]+1.0));
						
						objective = objective - penOld + penNew;
						
					}
				}
				
			}

			br.close();
			
		} catch (IOException e) {

		}

		return new Object[] {objective,chosenOrfs};
	}
	
	/*
	 * check overlaps
	 */
	
	public static Vector<Vector<Integer>> checkOverlaps(Vector<Vector<Integer>> overlaps, Vector<int[]> allORFs){
		
		for(int pos = overlaps.size() - 1; pos >= 0;pos--){
			
			if((overlaps.get(pos).size()-3) > 2){
				int startO = overlaps.get(pos).get(0);
				int stopO = overlaps.get(pos).get(1);
				
				Vector<Vector<Integer>> newORFs = new Vector<Vector<Integer>>();  // contains new intervals if those are necessary
				
				for(int i = 3;i<overlaps.get(pos).size();++i){
					int start = allORFs.get(overlaps.get(pos).get(i))[0];
					int stop = allORFs.get(overlaps.get(pos).get(i))[1];
					
					if(start < startO && stop < stopO){
						Vector<Integer> newOV = new Vector<Integer>();
						newOV.add(startO);
						newOV.add(stop);
						newOV.add(overlaps.get(pos).get(2));
						
						newORFs.add(newOV);
						
					}else if(start > startO && stop > stopO){
						Vector<Integer> newOV = new Vector<Integer>();
						newOV.add(start);
						newOV.add(stopO);
						newOV.add(overlaps.get(pos).get(2));
						
						newORFs.add(newOV);
					}
				}
				
				if(!newORFs.isEmpty()){
					for(int posNew = 0; posNew < newORFs.size();++posNew){
						int startOnew = newORFs.get(posNew).get(0);
						int stopOnew = newORFs.get(posNew).get(1);
						
						for(int i = 3;i<overlaps.get(pos).size();++i){

							if(((startOnew >= allORFs.get(overlaps.get(pos).get(i))[0]) && (stopOnew <= allORFs.get(overlaps.get(pos).get(i))[1]))){
								// found overlap, so add position
								newORFs.get(posNew).add(overlaps.get(pos).get(i));
							}
						}
						
						overlaps.add(newORFs.get(posNew));
					}
					
					overlaps.removeElementAt(pos);
				}
			}
			
		}
		
		return overlaps;
	}
	
	/*
	 * derive the coverage from a set of given ORFs
	 */
	
	/*public static int[] calculateCoverage(Vector<int[]> orfs, int[] cov){
		
		for(int[] orf : orfs){
			for(int i=orf[0];i<=orf[1];++i){
				cov[i]++;
			}
		}
		
		return cov;
	}*/
}
