package mapping;

import mapping.data.DeltaStar;
import mapping.data.JobClass;
import mapping.data.JobClassesTable;

import mapping.data.ServersTable;

import drasys.or.mp.*;
import drasys.or.mp.lp.*;
import drasys.or.matrix.Vect;

/**
 * This class is responsible for solving the LP allocation. The LP package used
 * in this class was taken from http://opsresearch.com/OR-Objects/
 * 
 * @author Majd Kokaly
 * 
 */
public class LP_Solver {
	/**
	 * A final value representing the name of the Greek letter alpha.
	 */
	final static String LAMBDA = "lambda";

	/**
	 * A final value representing the name of the Greek letter delta.
	 */
	final static String DELTA = "delta";

	/**
	 * The rounding error. Any value between -epsylon and +epsylon is considered
	 * zero.
	 */
	final static double epsylon = 0.000007;

	/**
	 * This method solves the LP allocation. Please refer to the LPAS_DG section
	 * in the thesis document.
	 * 
	 * @param serversTable
	 *            The table containing the servers
	 * @param jobClassesTable
	 *            The table containing the job classes.
	 * @return A DeltaStar object containing the delta & matrix
	 * @throws Exception
	 */
	public static DeltaStar solveWithAj(ServersTable serversTable, JobClassesTable jobClassesTable) throws Exception {
		int i, j;
		int classesNumber = 0;
		int serversNumber = 0;
		try {
			/* matirx's dimension is classesNumber x machinesNumber */
			classesNumber = jobClassesTable.size();
			serversNumber = serversTable.size();
		} catch (NullPointerException e) {
			System.err.print("Error: Invalid input in method LP_Solver.solve()");
			e.printStackTrace();
			return null;
		}

		/* Coefficient number is classesNumber + machinesNumber */
		/* Variables number is 1+(classesNumber*machinesNumber) LAMDA + DELTAs */
		SizableProblemI prob = new Problem(classesNumber + serversNumber, 1 + (classesNumber * serversNumber));
		prob.getMetadata().put("lp.isMaximize", "true");

		final byte LTE = Constraint.LESS;
		final byte GTE = Constraint.GREATER;

		/* Objective function is: LAMBDA */
		prob.newVariable(LAMBDA).setObjectiveCoefficient(1);

		/* Building Constraints */
		JobClass currentJobClass;
		for (i = 0; i < classesNumber; i++) {
			currentJobClass = jobClassesTable.get(i + 1);
			/* Creating a coefficient for every job class */
			prob.newConstraint("c" + i).setType(GTE).setRightHandSide(0); // Creating

			for (j = 0; j < serversNumber; j++) {
				prob.newVariable(DELTA + i + "_" + j).setLowerBound(0);
				/*
				 * Not necessary but maybe increases efficiency
				 */
				prob.getVariable(DELTA + i + "_" + j).setUpperBound(1);
				prob.setCoefficientAt("c" + i, DELTA + i + "_" + j, (serversTable.get(j + 1)).getProcessingRate(
						currentJobClass.getIndex()).getAssumedRate());
			}
			/* Moving Lambda from the left side to the right one */
			prob.setCoefficientAt("c" + i, LAMBDA, currentJobClass.getArrivalRate() * -1);
		}

		for (; i < classesNumber + serversNumber; i++) {
			prob.newConstraint("c" + i).setType(LTE).setRightHandSide(
					serversTable.get(i + 1 - classesNumber).getAvailability());
		}

		/*
		 * Every machine has it own constraint (Sum must not exceed Aj), outer
		 * for loop iterates machines.
		 */
		for (int k = 0; k < serversNumber; k++) {
			for (int l = 0; l < classesNumber; l++)
				prob.setCoefficientAt("c" + (classesNumber + k), DELTA + (l) + "_" + k, 1);
		}

		/* At this stage the problem has been completely built */

		LinearProgrammingI lp = new DenseSimplex(prob);

		double lamda = lp.solve();
		Vect solution = (Vect) lp.getSolution();

		/* Building the deltaStar */
		DeltaStar deltaStar = new DeltaStar(serversNumber);
		for (int l = 0; l < classesNumber; l++) {
			// loop iterates machines
			double e;
			for (int k = 0; k < serversNumber; k++) {
				e = solution.elementAt(l * serversNumber + k + 1);

				if (e < epsylon && e > -1 * epsylon) {
					deltaStar.addZero(l, k);
				}
			}
		}

		deltaStar.setLambdaStart(lamda);
		return deltaStar;

	}

	/**
	 * This method solves the LP allocation.
	 * 
	 * @param serversTable
	 *            The table containing the servers
	 * @param jobClassesTable
	 *            The table containing the job classes.
	 * @return both objects of the Delta* and lamda*
	 * @throws Exception
	 */
	public static Object[] solveWithAjAndReturnFullDeltaStar(ServersTable serversTable, JobClassesTable jobClassesTable)
			throws Exception {
		int i, j;
		int classesNumber = 0;
		int serversNumber = 0;
		try {
			/* matrix's dimension is classesNumber x machinesNumber */
			classesNumber = jobClassesTable.size();
			serversNumber = serversTable.size();
		} catch (NullPointerException e) {
			System.err.print("Error: Invalid input in method LP_Solver.solve()");
			e.printStackTrace();
			return null;
		}

		/* Coefficient number is classesNumber + machinesNumber */
		/* Variables number is 1+(classesNumber*machinesNumber) LAMDA + DELTAs */
		SizableProblemI prob = new Problem(classesNumber + serversNumber, 1 + (classesNumber * serversNumber));
		prob.getMetadata().put("lp.isMaximize", "true");

		final byte LTE = Constraint.LESS;
		final byte GTE = Constraint.GREATER;

		/* Objective function is: LAMBDA */
		prob.newVariable(LAMBDA).setObjectiveCoefficient(1);

		/* Building Constraints */
		JobClass currentJobClass;
		for (i = 0; i < classesNumber; i++) {
			currentJobClass = jobClassesTable.get(i + 1);
			/* Creating a coefficient for every job class */
			prob.newConstraint("c" + i).setType(GTE).setRightHandSide(0); // Creating

			for (j = 0; j < serversNumber; j++) {
				prob.newVariable(DELTA + i + "_" + j).setLowerBound(0);
				/*
				 * Not necessary but maybe increases efficiency
				 */
				prob.getVariable(DELTA + i + "_" + j).setUpperBound(1);
				prob.setCoefficientAt("c" + i, DELTA + i + "_" + j, (serversTable.get(j + 1)).getProcessingRate(
						currentJobClass.getIndex()).getAssumedRate());
			}
			/* Moving Lambda from the left side to the right one */
			prob.setCoefficientAt("c" + i, LAMBDA, currentJobClass.getArrivalRate() * -1);
		}

		for (; i < classesNumber + serversNumber; i++) {
			prob.newConstraint("c" + i).setType(LTE).setRightHandSide(
					serversTable.get(i + 1 - classesNumber).getAvailability());
		}

		/*
		 * Every machine has it own constraint (Sum must not exceed Aj), outer
		 * for loop iterates machines.
		 */
		for (int k = 0; k < serversNumber; k++) {
			for (int l = 0; l < classesNumber; l++)
				prob.setCoefficientAt("c" + (classesNumber + k), DELTA + (l) + "_" + k, 1);
		}

		/* At this stage the problem has been completely built */
		LinearProgrammingI lp = new DenseSimplex(prob);
		double lamda = lp.solve();
		Vect solution = (Vect) lp.getSolution();

		double[][] fullDeltaStar = new double[classesNumber][serversNumber];
		// Building the deltaStar
		for (int l = 0; l < classesNumber; l++) {
			// loop iterates machines

			for (int k = 0; k < serversNumber; k++) {
				fullDeltaStar[l][k] = solution.elementAt(l * serversNumber + k + 1);

			}

		}

		Object[] objects = new Object[2];
		objects[0] = fullDeltaStar;
		objects[1] = new Double(lamda);
		return objects;

	}

}
