package mapping;

import java.util.GregorianCalendar;

import mapping.data.DeltaStar;
import mapping.data.Job;
import mapping.data.Server;

/**
 * This is the implementation of the LPAS_DG scheduling policy.
 * 
 * @author Majd Kokaly
 * 
 */
public class LPAS_DG_MS extends MappingScheme {
	/**
	 * The serialVersionUID is used to universally identify this version of this
	 * class.
	 */
	private static final long serialVersionUID = -6903975517099008321L;

	/**
	 * An array of jobs queue. Every job class has its own queue.
	 */
	private JobsQueue[] classesQueues;

	/**
	 * The matrix that this policy using in making its decisions. Please refer
	 * to the thesis document (Section LPAS_DG).
	 */
	private DeltaStar deltaStar;

	/**
	 * The LPAS_DG_MS_ThreadRB object that helps this object.
	 */
	private LPAS_DG_MS_ThreadRB helperThread;

	/**
	 * The thread responsible for solving the LP allocation after every system
	 * tick.
	 */
	private LPAS_DG_MS_SOLVING_TICKER ticker;

	/**
	 * The thread that runs this Runnable class.
	 */
	private Thread thread;

	public LPAS_DG_MS(Mapper mapper) {
		super(mapper);
		this.setDescription("LPAS_DG");
	}

	public JobsQueue[] getClassesQueues() {
		return classesQueues;
	}

	public void setClassesQueues(JobsQueue[] classesQueues) {
		this.classesQueues = classesQueues;
	}

	public synchronized DeltaStar getDeltaStar() {
		return deltaStar;
	}

	public synchronized void setDeltaStar(DeltaStar deltaStar) {
		this.deltaStar = deltaStar;
	}

	private LPAS_DG_MS_ThreadRB getHelperThread() {
		return helperThread;
	}

	private void setHelperThread(LPAS_DG_MS_ThreadRB helperThread) {
		this.helperThread = helperThread;
	}

	public LPAS_DG_MS_SOLVING_TICKER getTicker() {
		return ticker;
	}

	private void setTicker(LPAS_DG_MS_SOLVING_TICKER ticker) {
		this.ticker = ticker;
	}

	protected Thread getThread() {
		return thread;
	}

	protected void setThread(Thread thread) {
		this.thread = thread;
	}

	/**
	 * The LP allocation is solved and other data members are initialized plus
	 * all the threads are started in this method.
	 * 
	 * @see MappingScheme#startMappingScheme()
	 */
	public void startMappingScheme() {
		try {
			DeltaStar delta = LP_Solver.solveWithAj(this.getMapper().getServersTable(), this.getMapper()
					.getClassesTable());
			this.setDeltaStar(delta);
			this.initQueues();
			this.setTicker(new LPAS_DG_MS_SOLVING_TICKER(this));
		} catch (Exception e) {
			e.printStackTrace();
		}

		// Creating the thread responsible from updating the queue in LPAS_DG_MS
		this.setHelperThread(new LPAS_DG_MS_ThreadRB(this));

		// Starting the threads
		this.getHelperThread().startThread();
		this.getTicker().startThread();
	}

	/**
	 * The threads are stopped.
	 * 
	 * @see MappingScheme#stopMappingScheme()
	 */
	public void stopMappingScheme() {
		// Stopping the threads
		this.getHelperThread().stopThread();
		this.getTicker().stopThread();
	}

	/**
	 * LPAS_DG policy maintains a queue for every class of jobs. This method
	 * stores a specific job in its own class queue.
	 * 
	 * @see MappingScheme#mapJob(Job)
	 */
	protected void mapJob(Job job) {
		// LPAS_DG maintains a queue for every class of jobs. This line stores a
		// specific job it its own class
		int jobClassID = (int) this.getMapper().getJobsTable().getJobClassID(job.getIndex());

		this.getClassesQueues()[jobClassID - 1].enqueue(job);
	}

	/**
	 * Resubmit timed out jobs.
	 * 
	 * @see MappingScheme#handleJobTimeOut(long)
	 */
	public void handleJobTimeOut(long jobID) {
		this.getMapper().getJobsTable().setTimedOut(jobID, true);
		this.getMapper().getJobsTable().setJobServer(jobID, null);
		this.getMapper().resubmitJob(this.getMapper().getJobsTable().get(jobID));
	}

	/**
	 * The LP allocation should be resolved when server changes its
	 * availability. That is what this method does.
	 * 
	 * @see MappingScheme#serverIsDown(int)
	 */
	public void serverIsDown(int serverID) {
		this.refreshDeltaStar();
		System.err.println("LPAS_DG knows that server:" + serverID + " is DOWN.\n--------");
	}

	/**
	 * The LP allocation should be resolved when server changes its
	 * availability. That is what this method does.
	 * 
	 * @see MappingScheme#serverIsUp(int)
	 */
	public void serverIsUp(int serverID) {
		this.refreshDeltaStar();
		System.err.println("LPAS_DG knows that server:" + serverID + " is UP.\n--------");
	}

	/**
	 * This method resolves the LP allocation.
	 */
	protected void refreshDeltaStar() {
		try {
			this.setDeltaStar(LP_Solver.solveWithAj(this.getMapper().getServersTable(), this.getMapper()
					.getClassesTable()));
			this.getDeltaStar().printAll();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void printQueues() {
		for (int i = 0; i < this.getClassesQueues().length; i++) {
			System.out.println("Queue " + (i + 1) + ":");
			this.getClassesQueues()[i].print();
		}
		System.out.println("\n\n");
	}

	protected String getQueuesString() {
		StringBuffer buf = new StringBuffer();
		for (int i = 0; i < this.getClassesQueues().length; i++) {
			buf.append("Queue " + (i + 1) + ": \n");
			buf.append(this.getClassesQueues()[i].toString());
		}
		// System.out.println("\n\n");
		return buf.toString();
	}

	public void initQueues() {
		// classesQueues = new JobsQueue[m.getClassesTable().size()];
		classesQueues = new JobsQueue[this.getMapper().getClassesTable().size()];
		for (int i = 0; i < this.getMapper().getClassesTable().size(); i++) {
			classesQueues[i] = new JobsQueue();
		}
	}

	/**
	 * This integer is used to send messages to the LPAS_DG_MS_THREADS to tell
	 * it that all job queues are empty.
	 */
	final static int ALL_QUEUES_ARE_EMPTY = -5;

	/**
	 * This integer is used to send messages to the LPAS_DG_MS_THREADS to tell
	 * it that no jobs in the current queue are suitable for this server to
	 * execute.
	 */
	final static int NO_JOB_IN_QUEUE_IS_SUITABLE_FOR_SERVER = -4;
	
	/**
	 * This integer is used to send messages to the LPAS_DG_MS_THREADS to tell
	 * it that all SUTIABLE_JOB_FOUND_BUT_COULD_NOT_BE_SENT.
	 */
	final static int SUTIABLE_JOB_FOUND_BUT_COULD_NOT_BE_SENT = -3;

	/**
	 * This integer is used to send messages to the LPAS_DG_MS_THREADS to tell
	 * it that a FATAL_ERROR has happened.
	 */
	final static int FATAL_ERROR = -1;
	
	/**
	 * This integer is used to send messages to the LPAS_DG_MS_THREADS to tell
	 * it that the job was successfully sent.
	 */
	final static int SUCCESS = 1;
	
	
	final static long QUEUE_IS_EMPTY = -2;

	/**
	 * This method is invoked by the active LPAS_MS_Thread. This method has the
	 * logic of this scheduling policy. Please refer to the thesis document for
	 * details.
	 * 
	 * @param availableServer
	 *            the id of the available server.
	 */
	protected int sendJobForServer(int availableServer) {
		int queuesThatHasItLeastOnJob = 0;
		double max = -1;
		int queueIndex = 0;
		double Uij;
		long DiOfT = -1;
		long nowInMillis = (new GregorianCalendar()).getTimeInMillis();
		Job job = null;

		Server server = this.getMapper().getServersTable().get(availableServer);
		for (int i = 0; i < this.getClassesQueues().length; i++) {
			Uij = server.getAssumedRate(i + 1); // U i,j
			
			if (this.getClassesQueues()[i].size() > 0) {// if not Empty
				queuesThatHasItLeastOnJob++;
				
				long timeSubmittedInMillis = this.getMapper().getJobsTable().getJobTimeSubmitted(
						this.getClassesQueues()[i].peak().getIndex()).getTimeInMillis();
				DiOfT = nowInMillis - timeSubmittedInMillis; // Di(t);
				
			} else {
				DiOfT = QUEUE_IS_EMPTY;
			}

			if (!this.getDeltaStar().isZero(i, availableServer - 1) && Uij * DiOfT > max) { // Ui,j
				// .
				// Di(t)
				// TODO
				max = Uij * DiOfT;
				queueIndex = i;
			}
		}

		if (queuesThatHasItLeastOnJob == 0) {
			return ALL_QUEUES_ARE_EMPTY;
		}

		if (max == -1 && queuesThatHasItLeastOnJob > 0) {
			return NO_JOB_IN_QUEUE_IS_SUITABLE_FOR_SERVER; // No suitable job
			// for
			// availableServer
			// but there is
			// other jobs
			// waiting
		}

		job = this.getClassesQueues()[queueIndex].peak();
		if (job != null) {
			long result = this.getMapper().sendJob(job.getIndex(), availableServer);
			if (result >= 0) { // Could be sent
				this.getClassesQueues()[queueIndex].dequeue();
				return SUCCESS; // Success
			} else {
				return SUTIABLE_JOB_FOUND_BUT_COULD_NOT_BE_SENT;
			}

		} else {
			System.out.println("LPAS_DG_MS.sendJobForServer(): FATAL_ERROR");
			return FATAL_ERROR; // Should not reach here
		}

	}

}
