package mapping;

import java.util.GregorianCalendar;

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

/**
 * This is the implementation of the Gcu scheduling policy.
 * 
 * @author Majd Kokaly
 * 
 */
public class Gcu_MS extends MappingScheme {

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

	/**
	 * This integer is used to send messages to the Gcu_MS_Thread
	 */
	private static long QUEUE_IS_EMPTY = -1;

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

	/**
	 * The Gcu_MS_Thread object that will help this object.
	 */
	private Gcu_MS_Thread Gcu_MS_Thread;

	/**
	 * A thread object to run this runnable.
	 */
	private Thread thread;

	/**
	 * A default constructor.
	 * 
	 * @param mapper
	 *            To set the mapper field.
	 */
	public Gcu_MS(Mapper mapper) {
		super(mapper);
		this.setDescription("GCU");
	}

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

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

	protected Gcu_MS_Thread getGcu_MS_Thread() {
		return Gcu_MS_Thread;
	}

	protected void setGcu_MS_Thread(Gcu_MS_Thread gcu_MS_Thread) {
		Gcu_MS_Thread = gcu_MS_Thread;
	}

	protected Thread getThread() {
		return thread;
	}

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

	/**
	 * @see MappingScheme#startMappingScheme()
	 */
	public void startMappingScheme() {
		try {
			this.initQueues();
		} catch (Exception e) {
			e.printStackTrace();
		}
		// Creating the thread responsible from updating the queue in LPAS_DG_MS
		this.setGcu_MS_Thread(new Gcu_MS_Thread(this));
		// Starting the thread
		this.getGcu_MS_Thread().startThread();
	}

	/**
	 * @see MappingScheme#stopMappingScheme()
	 */
	public void stopMappingScheme() {
		// Stopping the thread
		this.getGcu_MS_Thread().stopThread();
	}

	/**
	 * Gcu maintains a queue for every class of jobs. This method store a
	 * specific job in its own class queue.
	 * 
	 * @see MappingScheme#mapJob(Job)
	 */
	protected void mapJob(Job job) {
		/*
		 * Gcu maintains a queue for every class of jobs. This next two lines
		 * store a specific job in its own class queue.
		 */
		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));
	}

	/**
	 * @see MappingScheme#serverIsDown(int)
	 */
	public void serverIsDown(int serverID) {

	}

	/**
	 * @see MappingScheme#serverIsUp(int)
	 */
	public void serverIsUp(int serverID) {

	}

	protected 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());
		}
		return buf.toString();
	}

	/**
	 * This method initializes a queue for every job class.
	 */
	public void initQueues() {
		classesQueues = new JobsQueue[this.getMapper().getClassesTable().size()];
		for (int i = 0; i < this.getMapper().getClassesTable().size(); i++) {
			classesQueues[i] = new JobsQueue();
		}
	}

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

		Server server = this.getMapper().getServersTable().get(readyServer);
		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

				long timeSubmittedInMillis = this.getMapper().getJobsTable().getJobTimeSubmitted(
						this.getClassesQueues()[i].peak().getIndex()).getTimeInMillis();
				DiOfT = nowInMillis - timeSubmittedInMillis; // Di(t);
			} else {
				DiOfT = QUEUE_IS_EMPTY;

			}

			if (Uij * DiOfT > max) { // Ui,j
				// .
				// Di(t)

				max = Uij * DiOfT;
				queueIndex = i;

			}
		}

		job = this.getClassesQueues()[queueIndex].peak();
		if (job != null) {
			long result = this.getMapper().sendJob(job.getIndex(), readyServer);
			if (result >= 0) { // Could be sent

				this.getClassesQueues()[queueIndex].dequeue();
			} else {

				job = null;
			}
		}
		return job;
	}
}
