package mapping;

import java.io.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.GregorianCalendar;

import executing.MapperSideExecuter; //import executing.MapperSideExecuter_BuiltIn;
import executing.MapperSideExecuter_BuiltIn;

import logging.Logger;
import mapping.data.Job;
import mapping.data.JobClass;
import mapping.data.JobClassesTable;
import mapping.data.JobsTable;
import mapping.data.Server;
import mapping.data.IDsQueue;
import mapping.data.ServersTable;

import adjusting.Adjuster;
import adjusting.availability_adjusting.FailureTrace;

/**
 * This class is the centre of operation to the software. The jobs table,
 * servers table and the job classes table are data members of this class. Along
 * with the MappingScheme this class is responsible for the mapping decisions.
 * The jobs are submitted to this class.
 * 
 * @author Majd Kokaly
 * 
 */
public class Mapper implements Serializable {

	/**
	 * The serialVersionUID is used to universally identify this version of this
	 * class.
	 */
	private static final long serialVersionUID = 6092448329256274523L;

	/**
	 * This final long is used by the Mapping schemes when sending a job fails.
	 */
	public static final long JOB_FAILED = -1;

	/**
	 * This final long is used by the Mapping schemes when sending a job fails
	 * due to actual error like communication error.
	 */
	public static final long JOB_FAILED_DUE_ACTUAL_ERROR = -2;

	/**
	 * This final long is used by the Mapping schemes when the secondary id is
	 * not known.
	 */
	public static final long JOB_SECONDARY_ID_NOT_KNOWN = -3;

	/**
	 * The table of servers.
	 */
	private ServersTable serversTable;

	/**
	 * The table of the job classes.
	 */
	private JobClassesTable classesTable;

	/**
	 * This table contains all the jobs in a test.
	 */
	private JobsTable jobsTable;

	/**
	 * The active Mapping scheme.
	 */
	private MappingScheme mappingScheme;

	/**
	 * The execution layer object.
	 */
	private MapperSideExecuter executer;

	/**
	 * The minutes in one time unit. Default is 3 minutes.
	 */
	private double timeUnitInMinutes = 3;

	/**
	 * This is used to initialize jobs table.
	 */
	private final int DEFAULT_JOBS_NUMBER = 3000;

	/** A logger object responsible for logging events */
	transient private Logger errorsLogger;

	/** A logger object responsible for logging failures events */
	transient private Logger failuresLogger;

	/** A thread responsible for announcing the coming available */
	transient private EndOfFailureAnnouncer endOfFailureAnnouncer;

	/** This determines if artificial failure feature is on/off */
	private boolean artificialFailuresActive = true;

	/**
	 * This object is responsible for announcing time outs when they happen.
	 */
	transient private TimeOutAnnouncer timeOutAnnouncer;

	/**
	 * This boolean determines if announcing time outs is active or not.
	 */
	private boolean timeOutActive = true;

	/**
	 * To declare a job as timed-out a time of n *
	 * timeEsitmatedForTheJobToBeCompleted must elapse. This timeOutFactor is n.
	 */
	private double timeOutFactor = 2;

	/**
	 * Please refer to Conclusion Chapter in the Thesis document.
	 */
	private int timeResolution = 10;

	/**
	 * This queue contains the ID of the current availableServers.
	 */
	private IDsQueue<Integer> availableServersQueue;

	/**
	 * This value determines the mean of the failure period  used to
	 * generate artificial traces for all the servers using fillAllTraces
	 * method.
	 */
	private double failurePeriodsMean = 1;

	/**
	 * This value determines the mean of the up time periods used to
	 * generate artificial traces for all the servers using fillAllTraces
	 * method.
	 */
	private double upTimePeriodsMean = 5;

	/**
	 * The start date of a test.
	 */
	private GregorianCalendar startTime;

	/**
	 * The stop date of a test.
	 */
	private GregorianCalendar stopTime;
	
	/**
	 * The flag telling the completion server if any jobs need to be canceled
	 */
	private boolean cancelFlag = false;
	
	/**
	 * The parameters of Job IDs and Servers for the cancel flag
	 */
	private int[] serverIDs;
	
	private long jobID;
	
	/**
	 * Default constructor
	 */
	public Mapper() {
		this.setJobsTable(new JobsTable(DEFAULT_JOBS_NUMBER));
		this.setExecuter(new MapperSideExecuter_BuiltIn());
		this.setErrorsLogger(new Logger("errors"));
		this.setFailuresLogger(new Logger("artificialFailures"));
		this.setTimeOutAnnouncer(new TimeOutAnnouncer(this));
		this.getTimeOutAnnouncer().startThread();
		this.setEndOfFailureAnnouncer(new EndOfFailureAnnouncer(this));
		this.getEndOfFailureAnnouncer().startThread();
	}

	/**
	 * 
	 * @return A string of the canonical host name of the Mapper machine
	 */
	public String getMapperHostName() {
		try {
			return InetAddress.getLocalHost().getCanonicalHostName();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	public boolean getcancelFlag()
	{
		return cancelFlag;
	}
	
	public int[] getserverIDs()
	{
		return serverIDs;
	}
	
	public long getjobID()
	{
		return jobID;
	}
	
	public void setcancelFlag(boolean CancelFlag, int[] serverids, long jobid)
	{
		cancelFlag = CancelFlag;
		serverids = serverIDs;
		jobid = jobID;
	}
	
	public void resetcancelFlag(){
		cancelFlag = false;
	}
	
	public MapperSideExecuter getExecuter() {
		return executer;
	}

	public void setExecuter(MapperSideExecuter executer) {
		this.executer = executer;
	}

	public ServersTable getServersTable() {
		return this.serversTable;
	}

	public void setServersTable(ServersTable serverstable) {
		this.serversTable = serverstable;
	}

	public JobClassesTable getClassesTable() {
		return this.classesTable;
	}

	public void setClassesTable(JobClassesTable classesTable) {
		this.classesTable = classesTable;
	}

	public JobsTable getJobsTable() {
		return jobsTable;
	}

	public void setJobsTable(JobsTable jobsTable) {
		this.jobsTable = jobsTable;
	}

	public MappingScheme getMappingScheme() {
		return mappingScheme;
	}

	public void setMappingScheme(MappingScheme mappingScheme) {
		this.mappingScheme = mappingScheme;
	}

	public double getTimeUnitInMinutes() {
		return timeUnitInMinutes;
	}

	public void setTimeUnitInMinutes(double timeUnitInMinutes) {
		this.timeUnitInMinutes = timeUnitInMinutes;
		this.fillActualProcessingRates();
	}

	public Logger getErrorsLogger() {
		return errorsLogger;
	}

	public void setErrorsLogger(Logger errorsLogger) {
		this.errorsLogger = errorsLogger;
	}

	public Logger getFailuresLogger() {
		return failuresLogger;
	}

	public void setFailuresLogger(Logger failuresLogger) {
		this.failuresLogger = failuresLogger;
	}

	public EndOfFailureAnnouncer getEndOfFailureAnnouncer() {
		return endOfFailureAnnouncer;
	}

	public void setEndOfFailureAnnouncer(EndOfFailureAnnouncer endOfFailureAnnouncer) {
		this.endOfFailureAnnouncer = endOfFailureAnnouncer;
	}

	public boolean isArtificialFailuresActive() {
		return artificialFailuresActive;
	}

	public void setArtificialFailuresActive(boolean artificialFailuresActive) {
		this.artificialFailuresActive = artificialFailuresActive;
	}

	public TimeOutAnnouncer getTimeOutAnnouncer() {
		return timeOutAnnouncer;
	}

	public void setTimeOutAnnouncer(TimeOutAnnouncer timeOutAnnouncer) {
		this.timeOutAnnouncer = timeOutAnnouncer;
	}

	public boolean isTimeOutActive() {
		return timeOutActive;
	}

	public void setTimeOutActive(boolean timeOutActive) {
		this.timeOutActive = timeOutActive;
	}

	public double getTimeOutFactor() {
		return timeOutFactor;
	}

	public void setTimeOutFactor(double timeOutFactor) {
		this.timeOutFactor = timeOutFactor;
	}

	public int getTimeResolution() {
		return timeResolution;
	}

	public void setTimeResolution(int timeResolution) {
		this.timeResolution = timeResolution;
	}

	public IDsQueue<Integer> getAvailableServersQueue() {
		return availableServersQueue;
	}

	public void setAvailableServersQueue(IDsQueue<Integer> availableServersQueue) {
		this.availableServersQueue = availableServersQueue;
	}

	public double getFailurePeriodsMean() {
		return failurePeriodsMean;
	}

	public void setFailurePeriodsMean(double failurePeriodsMean) {
		this.failurePeriodsMean = failurePeriodsMean;
	}

	public double getUpTimePeriodsMean() {
		return upTimePeriodsMean;
	}

	public void setUpTimePeriodsMean(double upTimePeriodsMean) {
		this.upTimePeriodsMean = upTimePeriodsMean;
	}

	public GregorianCalendar getStartTime() {
		return startTime;
	}

	private void setStartTime(GregorianCalendar startTime) {
		this.startTime = startTime;
	}

	public GregorianCalendar getStopTime() {
		return stopTime;
	}

	private void setStopTime(GregorianCalendar stopTime) {
		this.stopTime = stopTime;
	}

	public void printServers() {
		this.getServersTable().printAll();
	}

	public void printClasses() {
		this.getClassesTable().printAll();
	}

	/**
	 * This method is used by other modules to request jobs
	 * 
	 * @param job
	 *            Job requested
	 */
	public synchronized void submitJob(Job job) {
		// Registering the job in jobs table and giving it ID
		this.getJobsTable().addJob(job);
		// Notify the Mapping Scheme that a job has been requested
		if (this.getMappingScheme() != null)
			this.getMappingScheme().mapJob(job);
	}

	/**
	 * This method is used to resubmit timed out job
	 * 
	 * @param job
	 *            Job requested
	 */
	public synchronized void resubmitJob(Job job) {
		// Notify the Mapping Scheme that a job has been requested
		if (this.getMappingScheme() != null)
			this.getMappingScheme().mapJob(job);
	}

	/**
	 * This method is invoked by the active mapping scheme after the job and
	 * server are chosen
	 * 
	 * @param jobID_Received
	 *            a job ID sent by the mapping scheme to be send to a server
	 * @param serverID
	 *            the server chosen to send the job to
	 * @return ID that the Built-In Executer Layer gave to the job
	 * 
	 */
	protected long sendJob(long jobID_Received, int serverID) {

		Job job = this.getJobsTable().get(jobID_Received);
		if (job == null) {
			System.err.print("Job == NULL");
			return -3;
		}

		int jobClassID = (int) this.getJobsTable().getJobClassID(job.getIndex());
		JobClass jobClass = this.getClassesTable().get(jobClassID);

		Server server = this.getServersTable().get(serverID);

		// Determine adjustment
		double ratio = Adjuster.getRatio(jobClass, server);

		long jobSecondaryId = Job.NOT_ASSIGNED;

		GregorianCalendar currentTime = new GregorianCalendar();

		/* -------- Artificial Failures Management ------- */
		if (this.isArtificialFailuresActive()) {
			// Check if server is suffering from an artificial failure right now
			if (Adjuster.isServerSufferingFromAFailure(currentTime, server)) {
				// TODO sync issue
				this.getFailuresLogger().log(
						"Server " + server.getHostName() + " had an artifical failure.\t Job " + job.getIndex()
								+ "could not be sent. ");
				GregorianCalendar endOfFailure = Adjuster.getEndOFFailurePeriod(currentTime, server);
				long differenceInMilliSeconds = endOfFailure.getTimeInMillis() - currentTime.getTimeInMillis();
				this.getEndOfFailureAnnouncer().add(differenceInMilliSeconds / 1000.0 / 60.0,
						this.getServersTable().get(serverID));
				System.err.println("Mapper.send Job: " + this.getServersTable().get(serverID).getHostName()
						+ " is declared DOWN!!!");
				this.getServersTable().setServerDown(serverID, true);
				this.getMappingScheme().serverIsDown(serverID);

				/* -------- Availability Queue Management------- */
				this.getAvailableServersQueue().remove(serverID);

				return -2;
			}

		}

		// Send it to the suitable server
		jobSecondaryId = this.getExecuter().submitLoopJob(server.getHostName(),
				server.getIndex(), job.getIndex(), (long) (job.getexecutionTime()), ratio);

		this.getJobsTable().setJobSecondaryID(job.getIndex(), jobSecondaryId);
		if (jobSecondaryId != JOB_FAILED && jobSecondaryId != JOB_FAILED_DUE_ACTUAL_ERROR) { // In
			// case
			// of
			// success
			this.getJobsTable().setJobServer(job.getIndex(), server);
			this.getJobsTable().setJobTimeSent(job.getIndex(), new GregorianCalendar());
			server.incrementActiveJobsNumber();

			/* -------- Availability Queue Management------- */
			this.getAvailableServersQueue().remove(server.getIndex());

			/* -------- Time Out Announcer Management ------- */
			if (this.isTimeOutActive()) {
				double timeExpectedInTU = 1.0 / server.getAssumedRate(jobClassID);
				timeExpectedInTU /= server.getAvailability();

				if (timeExpectedInTU < 0.5) {
					timeExpectedInTU = 0.5;
				}

				this.getTimeOutAnnouncer().add((timeExpectedInTU) * this.getTimeOutFactor(), job.getIndex());
			}

		} else { // if Sending the Job failed
			this.getAvailableServersQueue().remove(server.getIndex());
			System.err.print(server.getHostName() + ": Communication error");

			this.getErrorsLogger().log("Mapper.sendJob(): Sending job failed.");
		}
		return jobSecondaryId;
	}
	
	/**
	 * This method is used to cancel a job which is currently in progress
	 * 
	 * @param jobID_received
	 * @param serverID
	 * @return
	 */
	public long cancelJob(long jobID_received, int serverID)
	
	{
		long returnval = Job.NOT_ASSIGNED;
		Job job = this.getJobsTable().get(jobID_received);
		if (job == null)
		{
			System.err.println("The job does not exist");
			return -3;
		}
		Server server = this.getServer(serverID);
		returnval = this.getExecuter().deleteJob(job.getIndex(), server.getHostName());
		return returnval;
	}
	
	public String toString() {
		return this.getClassesTable().toString() + "\n" + this.getServersTable().toString() + "\n"
				+ "Time out Factor: " + this.getTimeOutFactor() + "\nAF enabled: " + this.isArtificialFailuresActive()
				+ "\nT/O enabled: " + this.isTimeOutActive();
	}

	public void print() {
		System.out.print(this.toString());
	}

	public void printAvailabilityQueue() {
		if (this.getAvailableServersQueue() != null) {
			this.getAvailableServersQueue().printServers();
		}
	}

	/**
	 * This method is invoked by the Completion Server
	 * 
	 * @see CompletionServer
	 * 
	 * @param serverID
	 *            The ID of the server that executed the job.
	 * @param jobID
	 *            The ID of the job that was completed.
	 * @param startTime
	 *            The time when the job started.
	 * @param completionTime
	 *            The time when the job was completed.
	 * 
	 */
	protected void jobIsDone(int serverID, long jobID, GregorianCalendar startTime, GregorianCalendar completionTime) {
		/* -------- Availability Queue Management------- */
		this.getAvailableServersQueue().enqueue(serverID);

		this.getServersTable().get(serverID).decrementActiveJobsNumber();

		this.getJobsTable().setJobTimeStarted(jobID, startTime);

		this.getJobsTable().setJobTimeDone(jobID, completionTime);
		if (cancelFlag)
		{
			for (int i = 0; i < this.getserverIDs().length; i++)
			{
				if (this.getserverIDs()[i] != serverID)
				{
					this.cancelJob(jobID, this.getserverIDs()[i]);
				}
			}
		}
	}

	/**
	 * This method is invoked by the TimeAnnouncer object when a job times out.
	 * 
	 * @param jobID
	 *            The ID of the job that has timed-out.
	 */
	protected void jobTimedOut(long jobID) {
		this.getJobsTable().setTimedOut(jobID, true);
		this.getMappingScheme().handleJobTimeOut(jobID);
	}

	/**
	 * 
	 * @param i
	 * @return the i th server in the servers table.
	 */
	public Server getServer(int i) {
		return this.getServersTable().get(i);
	}

	public double getAssumedRate(int serverID, int jobClassID) {
		return this.getServersTable().get(serverID).getProcessingRate(jobClassID).getAssumedRate();
	}

	public double getRealRate(int serverID, int jobClassID) {
		return this.getServersTable().get(serverID).getProcessingRate(jobClassID).getRealRate();
	}

	public void setAssumedRate(int serverID, int jobClassID, double rate) {
		this.getServersTable().get(serverID).getProcessingRate(jobClassID).setAssumedRate(rate);
	}

	public void setRealRate(int serverID, int jobClassID, double rate) {
		this.getServersTable().get(serverID).getProcessingRate(jobClassID).setRealRate(rate);
	}

	public JobClass getJobClass(int i) {
		return this.getClassesTable().get(i);
	}

	public void initProcessingRates() {
		for (int h = 0; h < this.getServersTable().size(); h++) {
			for (int i = 0; i < this.getClassesTable().size(); i++) {
				this.getServer(h + 1).addProcessingRate(0, 0, i + 1);
			}
		}
	}

	/**
	 * Starts the test and the mapping operation.
	 */
	public void startMapper() {
		this.setAvailableServersQueue(new IDsQueue<Integer>(this));
		// inserting all the servers
		for (int i = 1; i <= this.getServersTable().size(); i++) {
			this.getAvailableServersQueue().enqueue(this.getServer(i).getIndex());
		}

		setStartTime(new GregorianCalendar());
		this.getMappingScheme().startMappingScheme();
	}

	/**
	 * Stops the test and the mapping operation.
	 */
	public void stopMapper() {
		this.setStopTime(new GregorianCalendar());
		this.getMappingScheme().stopMappingScheme();
	}

	/**
	 * @return The time elapsed since startTime.
	 */
	public double getTimeElapsedInTimeUnits() {
		GregorianCalendar current = new GregorianCalendar();
		if (this.getStopTime() == null) {
			return ((current.getTimeInMillis() - this.getStartTime().getTimeInMillis()) / 1000.0 / 60.0 / this
					.getTimeUnitInMinutes());
		} else
			return ((getStopTime().getTimeInMillis() - this.getStartTime().getTimeInMillis()) / 1000.0 / 60.0 / this
					.getTimeUnitInMinutes());
	}

	/**
	 * This method calculate the actual rates for all servers and set the
	 * assumed rates the same as the actual ones.
	 */
	public void fillProcessingRates() {
		if (this.getServersTable() == null)
			return;
		if (this.getServersTable().isEmpty())
			return;
		
		for (int h = 0; h < this.getServersTable().size(); h++) 
		{
			for (int i = 0; i < this.getClassesTable().size(); i++)
			{
				this.getServer(h + 1).addProcessingRate((this.getTimeUnitInMinutes() * 60 * 1000)/ this.getJobClass(i + 1).getexecutionTime(), (this.getTimeUnitInMinutes() * 60 * 1000)/ this.getJobClass(i + 1).getexecutionTime(), i + 1);
			}
		}

	}

	/**
	 * This method calculate the actual rates for all servers but does not
	 * modify the assumed rates.
	 */
	public void fillActualProcessingRates() {
		if (this.getServersTable() == null)
			return;
		if (this.getServersTable().isEmpty())
			return;

		for (int h = 0; h < this.getServersTable().size(); h++) {
			for (int i = 0; i < this.getClassesTable().size(); i++) {
				this.setRealRate(h + 1, i + 1, (this.getTimeUnitInMinutes() * 60 * 1000)/ this.getJobClass(i + 1).getexecutionTime());
			}
		}
	}

	/**
	 * This method saves the definitions on a file. The file can be restores
	 * using the readMapperAsObjects.
	 * 
	 * @param fileName
	 *            The name of the file to be saved.
	 */
	public void saveMapperAsObjects(String fileName) {
		ObjectOutputStream output = null;
		try {
			output = new ObjectOutputStream(new FileOutputStream(new File(fileName), true));
			output.writeObject(this.getClassesTable());
			output.writeObject(this.getServersTable());
			output.writeDouble(this.getTimeUnitInMinutes());
			output.writeDouble(this.getFailurePeriodsMean());
			output.writeDouble(this.getUpTimePeriodsMean());
			output.writeInt(this.getTimeResolution());
			output.writeBoolean(this.isTimeOutActive());
			output.writeBoolean(this.isArtificialFailuresActive());

			output.writeDouble(this.getTimeOutFactor());

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (output != null)
				try {
					output.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
	}

	/**
	 * Loads the definition of a mapper. Please see Appendix B from the thesis
	 * document.
	 * 
	 * @param fileName
	 */
	public void readMapperAsObjects(String fileName) {
		ObjectInputStream input = null;
		try {
			input = new ObjectInputStream(new FileInputStream(new File(fileName)));
			this.setClassesTable((JobClassesTable) input.readObject());
			this.setServersTable((ServersTable) input.readObject());
			this.setTimeUnitInMinutes((Double) input.readDouble());
			this.setFailurePeriodsMean((Double) input.readDouble());
			this.setUpTimePeriodsMean((Double) input.readDouble());
			this.setTimeResolution((Integer) input.readInt());
			this.setTimeOutActive((Boolean) input.readBoolean());
			this.setArtificialFailuresActive((Boolean) input.readBoolean());

			this.setTimeOutFactor((Double) input.readDouble());

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

			e.printStackTrace();
		} finally {
			if (input != null)
				try {
					input.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
		}

	}

	/**
	 * This method generates new FailureTrace objects for the servers 
	 * @param failurePeriodsMean The mean of all the failure periods.
	 * @param upTimeMean The mean of all the up-time periods.
	 */
	public void fillAllFailureTraces(double failurePeriodsMean, double upTimeMean) {
		this.setFailurePeriodsMean(failurePeriodsMean);
		this.setUpTimePeriodsMean(upTimeMean);
		for (int i = 1; i <= this.getServersTable().size(); i++) {
			GregorianCalendar startTime = new GregorianCalendar();
			GregorianCalendar endTime = new GregorianCalendar();
			endTime.add(GregorianCalendar.HOUR, 48);
			this.getServer(i).setFailureTrace(new FailureTrace());
			this.getServer(i).getFailureTrace().fillTrace(startTime, endTime, failurePeriodsMean,
					upTimeMean, this.getTimeUnitInMinutes());
		}
	}

	/**
	 * This method generates new FailureTrace objects for the servers.
	 */
	public void fillAllFailureTraces() {
		for (int i = 1; i <= this.getServersTable().size(); i++) {
			GregorianCalendar startTime = new GregorianCalendar();
			GregorianCalendar endTime = new GregorianCalendar();
			endTime.add(GregorianCalendar.HOUR, 7);
			this.getServer(i).toString();
			this.getServer(i).setFailureTrace(new FailureTrace());
			this.getServer(i).getFailureTrace().fillTrace(startTime, endTime, this.getFailurePeriodsMean(),
					this.getUpTimePeriodsMean(), this.getTimeUnitInMinutes());
		}
	}
	
	
	
	/**
	 * This method returns the mue matrix as a double array.
	 * 
	 * @return the mue matrix as a double array.
	 */
	public double[][] constructMueMatrix() {
		double[][] mue = new double[this.getClassesTable().size()][this.getServersTable().size()];

		for (int i = 1; i <= this.getClassesTable().size(); i++) {
			for (int j = 1; j <= this.getServersTable().size(); j++) {
				mue[i - 1][j - 1] = this.getAssumedRate(j, i);
			}
		}
		return mue;
	}

	/**
	 * 
	 * @return the actual mue matrix (actual processing rates).
	 */
	public double[][] constructActualMueMatrix() {
		double[][] timeInProcessing = new double[this.getClassesTable().size()][this.getServersTable().size()];
		double[][] jobsProcessed = new double[this.getClassesTable().size()][this.getServersTable().size()];

		for (long l = 1; l <= this.getJobsTable().size(); l++) {
			if (this.getJobsTable().getJobStatus(l).equals(Job.DONE)) {
				timeInProcessing[this.getJobsTable().getJobClassID(l) - 1][this.getJobsTable().getJobServer(l)
						.getIndex() - 1] += (this.getJobsTable().getJobTimeDone(l).getTimeInMillis() - this
						.getJobsTable().getJobTimeStarted(l).getTimeInMillis())
						/ 1000.0 / 60.0 / this.getTimeUnitInMinutes();
				jobsProcessed[this.getJobsTable().getJobClassID(l) - 1][this.getJobsTable().getJobServer(l).getIndex() - 1]++;
			}
		}

		double[][] actualMue = new double[this.getClassesTable().size()][this.getServersTable().size()];
		for (int i = 1; i <= this.getClassesTable().size(); i++) {
			for (int j = 1; j <= this.getServersTable().size(); j++) {
				actualMue[i - 1][j - 1] = jobsProcessed[i - 1][j - 1] / timeInProcessing[i - 1][j - 1];
			}
		}
		return actualMue;
	}

}
